将 Retrofit 数据插入 Room 并使用 ViewModel 显示的正确方法

Proper way to insert Retrofit data to Room and display it using ViewModel

从 Retrofit api 获取数据、将其插入 Room 数据库并在 Viewmodel 中显示来自 Room 的数据的正确(最佳实践)方法是什么?将数据从 Retrofit 插入 Room 的操作应该发生在存储库还是另一个 class 中? Room数据应该如何返回给viewmodel?

截至目前,我的代码使用 Retrofit <- repository <- ViewModel <- Fragment 获取数据 -- 使用 Hilt 进行 di。还对 Retrofit 和 Room 实体

使用相同的数据 class

如有任何建议或实施建议,我们将不胜感激

存储库:

class ItemRepository @Inject constructor(
    private val api: ShopraApi
) {
    suspend fun getItems() = api.getUserItemFeed()
}

Api:

interface ShopraApi {

companion object {
    const val BASE_URL = "-"
}
    @GET("getUserItemFeed.php?user_id=1")
    suspend fun getUserItemFeed() : List<Item>
}

视图模型:

@HiltViewModel
class ItemViewModel @Inject constructor(
    itemRepository: ItemRepository
) : ViewModel() {

    private val itemsLiveData = MutableLiveData<List<Item>>()
    val items: LiveData<List<Item>> = itemsLiveData

    init {
        viewModelScope.launch {
            itemsLiveData.value = itemRepository.getItems()
        }
    }
}

实体:

@Entity(tableName = "item_table")
data class Item(
    @PrimaryKey(autoGenerate = false)
    @NonNull
    val listing_id: Long,
    val title: String,
    val description: String
)

道:

@Dao
interface ItemDao {

    @Query("SELECT * FROM item_table")
    fun getItemsFromRoom(): LiveData<List<Item>>

    @Insert(onConflict = IGNORE)
    suspend fun insert(item: Item)

    @Insert(onConflict = IGNORE)
    suspend fun insertAllItems(itemRoomList: List<Item>)
}

物品数据库:

@Database(entities = [Item::class],version = 1)
abstract class ItemDatabase : RoomDatabase() {
    abstract fun itemDao() : ItemDao
}

应用模块:

@Module
@InstallIn(SingletonComponent::class)
object AppModule {
    @Provides
    @Singleton
    fun provideRetrofit(): Retrofit {
        return Retrofit.Builder()
            .baseUrl(ShopraApi.BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    }

    @Provides
    @Singleton
    fun provideShopraApi(retrofit: Retrofit): ShopraApi {
        return retrofit.create(ShopraApi::class.java)
    }

    @Provides
    @Singleton
    fun provideDatabase(app: Application) =
        Room.databaseBuilder(app, ItemDatabase::class.java, "item_database")
            .fallbackToDestructiveMigration()
            .build()

    @Provides
    fun provideTaskDao(db: ItemDatabase) = db.itemDao()
}

编辑更新存储库:

class ItemRepository @Inject constructor(
    private val api: ShopraApi,
    private val itemDao: ItemDao
) {
    fun loadItems(): LiveData<List<Item>> {
        return liveData{
            val getItems = api.getUserItemFeed()

            Log.e("ItemRepository","The size of the item list is 
${getItems.size}")

            getItems.forEach {
                item -> itemDao.insert(item)
            }
            val loadFromRoom = itemDao.getItemsFromRoom()

            emitSource(loadFromRoom)
       }
    }
}

已更新 ItemViewModel:

class ItemViewModel @Inject constructor(
     itemRepository: ItemRepository
) : ViewModel() {
    val items: LiveData<List<Item>> = itemRepository.loadItems()
}

然后我在我的片段中调用这段代码

viewModel.items.observe(viewLifecycleOwner) { items ->
        itemAdapter.submitList(items)

您必须在 Repository 中执行此操作,而 Repository 是“单一事实来源”,当您从网络获取数据并保存到其中时,这是您做出决定的地方localDB 和来自 localDB 的 load/expose 将成为 room persistence
您可以使用名为 Network Bound Resource 的实用程序 class 并且有很多网络绑定资源的实现,这个来自官方示例 google's sample of network bound resource, 这是通用类型抽象 class 用于此特定目的,但如果您仍想以自定义方式进行,这就是您需要做的。
1-> 进行网络调用并从网络获取数据,这些数据你不会暴露给 viewmodel
2-> 收到数据后插入 localDB。
3-> 将您的数据从 localDB 加载或公开到视图模型

代码示例:

class ItemRepo(
    val dao: Dao,
    val api: Api
) {
    fun loadItems(): LiveData<List<Item>> {
        return liveData {

            // get from network
            val getFromNetwork = api.getList()

            // save into local
            getFromNetwork.forEach{
                item ->
                dao.insertItem(item)
            }

            //load from local
            val loadFromLocal = dao.getAllItem()
            
            emitSource(loadFromLocal)
        }
    }
}

您将 Inject 视图模型中的存储库,视图将从那里观察数据,这样您只能从 localDB 获取数据。
注意:这只是一个示例,您可以根据您的用例进一步重复使用它,例如错误处理、网络处理,例如当网络不可用时或遇到错误时您应该做什么......诸如此类.