Issue
What would be the proper (best practice) way to get data from a Retrofit api, insert it into a Room database, and display the data from Room in the Viewmodel? Should the operation of inserting the data from Retrofit to Room happen in the repository or another class? How should the Room data be returned to the viewmodel?
As of right now my code fetches data using Retrofit <- repository <- ViewModel <- Fragment -- Using Hilt for di. Also using the same data class for Retrofit and Room entity
Any advice or implementation suggestions are appreciated
Repository:
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>
}
ViewModel:
@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:
@Entity(tableName = "item_table")
data class Item(
@PrimaryKey(autoGenerate = false)
@NonNull
val listing_id: Long,
val title: String,
val description: String
)
Dao:
@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>)
}
ItemDatabase:
@Database(entities = [Item::class],version = 1)
abstract class ItemDatabase : RoomDatabase() {
abstract fun itemDao() : ItemDao
}
AppModule:
@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()
}
EDIT Updated Repository:
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)
}
}
}
Updated ItemViewModel:
class ItemViewModel @Inject constructor(
itemRepository: ItemRepository
) : ViewModel() {
val items: LiveData<List<Item>> = itemRepository.loadItems()
}
I then call this code in my fragment
viewModel.items.observe(viewLifecycleOwner) { items ->
itemAdapter.submitList(items)
Solution
You have to do this in Repository and Repository is the "single source of truth" this is where you make the decision, when you get the data from network and save into localDB and load/expose from the localDB which is going to be the room persistence
You can use the utility class known as Network Bound Resource and there are alot implementation of network bound resource, this one is from official sample google's sample of network bound resource,
which is generic type abstract class use for this specific purpose but if you still wanna do it in the custom way, here is what you need to do.
1-> Make the network call and get the data from network and this data you are not going to expose to viewmodel
2-> once you receive the data insert into the localDB.
3-> Load or Expose your data from localDB to the viewmodel
Code Sample:
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)
}
}
}
You are gonna Inject
Repository in the viewmodel and from there data will observed by view, with that you are only gonna get the data from localDB.
Note: This is just a sample you can further reuse it according to your use case for instance error handling, network handling like when network is not available or when you encounter the error what you should do.. things like that.
Answered By - USMAN osman
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.