Kotlin Room withTransaction in Repository or #Transaction in Dao - android

I have a question regarding Room and it’s withTransaction { } code block in combination with Koin.
I have a repository where I need to access a couple of DAOs at the same time. I wanted to work with a withTransaction { } so I wouldn’t clutter 1 DAO with references to other DAOs.
I’m not sure which object to inject in the constructor of my repository. The withTransaction{ } can only be accessed by getting the RoomDatabase. But having the RoomDatabase in my Repository means that I have access to all the DAOs connected to that RoomDatabase. I’m not sure what the best practice around this use-case would be.
Should I use the withTransaction { } and risk that all DAOs are accessible be that Repository or should I have the DAOs in my Repository's constructor and hand them to the `ReviewDao' to handle every insert?
An example would be something like this with withTransaction { }
class ReviewRepository(
private val roomDatabase: RoomDatabase
) {
private val reviewDao = roomDatabase.reviewDao()
private val userDao = roomDatabase.userDao()
suspend fun saveReview(reviewResponse: ReviewResponse) {
roomDatabase.withTransaction {
reviewDao.insert(reviewResponse.getAsEntity())
userDao.insert(reviewResponse.user.getAsEntity())
}
}
}
And an example without withTransaction { } would be this
class ReviewRepository(
private val reviewDao : ReviewDao,
private val userDao : UserDao
) {
suspend fun saveReview(reviewResponse: ReviewResponse) {
reviewDao.insertWithUser(reviewResponse.getAsEntity(), reviewResponse.user.getAsEntity(), userDao)
}
}
#Dao
interface ReviewDao {
#Transaction
suspend fun insertWithUser(review: Review, user: User, userDao: UserDao) {
insert(review)
userDao.insert(user)
}
}

The solution that worked for me was to use a separate class that takes in the database in the constructor and provides an extension to the withTransaction.
class TransactionProvider(
private val db: AppDatabase
) {
suspend fun <R> runAsTransaction(block: suspend () -> R): R {
return db.withTransaction(block)
}
}
I can then inject this class into the Repository I need without providing the complete database object to that Repository.

I would go with a variant of your Repository solution. In a project, we had UseCase and Handler structures. To simply put;
class SaveReviewHandler(private val db: RoomDatabase) {
private val reviewDao = db.reviewDao()
private val userDao = db.userDao()
suspend fun execute(useCase: SaveReview) {
db.withTransaction {
reviewDao.insert(useCase.review)
userDao.insert(useCase.user)
}
}
}
data class SaveReview(review: ReviewEntitry, user: UserEntity)
and we can call it as follows:
saveReviewHandler.execute(SaveReview(reviewResponse.getAsEntity(),reviewResponse.user.getAsEntity()))
One good side of this architecture is; your repository may have other complex transactions, but it does not have to have direct access to DAOs.

Related

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

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)
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.

Lazy initialization with repository

I checked out the Android Sunflower best practices app. And i kinda struggle to understand why the initialization of the PlantDetailsViewModel is working.
The class is defined as follows
class PlantDetailViewModel #AssistedInject constructor(
plantRepository: PlantRepository,
private val gardenPlantingRepository: GardenPlantingRepository,
#Assisted private val plantId: String
) : ViewModel() {
val isPlanted = gardenPlantingRepository.isPlanted(plantId)
val plant = plantRepository.getPlant(plantId)
....
#AssistedInject.Factory
interface AssistedFactory {
fun create(plantId: String): PlantDetailViewModel
}
companion object {
fun provideFactory(
assistedFactory: AssistedFactory,
plantId: String
): ViewModelProvider.Factory = object : ViewModelProvider.Factory {
#Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return assistedFactory.create(plantId) as T
}
}
}
}
here the gardenPlantingRepository and plantRepository give access to a Room Database.
I wonder that this works because normally it is not possible to access a room database on the main thread. The Viemodel is used in the PlantDetailFragment and lazy initialized.
#Inject
lateinit var plantDetailViewModelFactory: PlantDetailViewModel.AssistedFactory
private val plantDetailViewModel: PlantDetailViewModel by viewModels {
PlantDetailViewModel.provideFactory(
plantDetailViewModelFactory,
args.plantId
)
}
if i try something that is quite the same i always get the problem that it is not possible to access the database on the main thread. so i tried to init my variables in the init function with a coroutine and Dispatchers.IO but the problem with this is that when i access member variables of my viewmodel they are not initialized. so how is the behaviour of the sunflower app reproducible
Repository uses Dao methods that return LiveData
The Room persistence library supports observable queries, which return LiveData objects. Observable queries are written as part of a Database Access Object (DAO).
Room generates all the necessary code to update the LiveData object when a database is updated. The generated code runs the query asynchronously on a background thread when needed. This pattern is useful for keeping the data displayed in a UI in sync with the data stored in a database.
https://developer.android.com/topic/libraries/architecture/livedata#use_livedata_with_room
If you want to use coroutines then you should create and expose LiveData in your ViewModel
class ViewModel(
val repository: Repository
) : ViewModel() {
private val _liveData = MutableLiveData<SomeType>()
val liveData: LiveData<SomeType> get() = _liveData
init {
viewModelScope.launch(Dispatchers.IO) {
_liveData.postValue(repository.getSomeData())
}
}
}
Then you should observe this liveData in your activity / fragment

Correct flow between Kotlin, Realm and ViewModels using Coroutines

Disclaimer: This is not actual code from any app, but an example of the flow and my current understanding on how best to do this. I am looking for help improving upon or what I am doing wrong.
I am trying to figure out the best way to structure an android application using the new jetpack viewModels, realm, and coroutines. I put together a gist of the flow that I have so far, and would love some feedback on how I can improve, what I could change, or what I am doing wrong. Ideally with examples or direct changes to my code.
It works as is, I am just not sure if I am using coroutines correctly or efficiently, and if there is a better way to structure the DAO's so that Realm can be injected for better testability. Someone has already mentioned changing the DAO to extend the LiveData<>, and using onActive() and onInactive() for posting the object. Is that a good idea?
// About Model is the model used by Realm. These models contains realm specific types, like RealmList
open class AboutModel(
var name: String = "",
#PrimaryKey
var version: String = ""
): RealmObject() {
/**
* Conversion function, to convert the view model layer object to the data layer object
*/
companion object {
fun from(about: About): AboutModel = AboutModel(about.name, about.version)
}
fun toObject(): About =
About(
this.name,
this.version
)
}
// About class used everywhere outside of the data/realm layer.
// Lines up with the AboutModel class, but free of realm or any other database specific types.
// This way, realm objects are not being referenced anywhere else. In case I ever need to
// replace realm for something else.
class About (val name: String = "Test", val version: String = "1.0.0") {
override fun toString(): String {
return "author is : $name, version is: $version"
}
}
// Couldn't inject the realm instance because its thread would not match with a suspend function.
// Even if both where background threads. Would be better if I could inject it, but couldn't get
// that to work.
class AboutDao() {
private val _about = MutableLiveData<About>()
init {
val realm = Realm.getDefaultInstance()
val aboutModel = realm.where(AboutModel::class.java).findFirst()
_about.postValue(aboutModel?.toObject() ?: About())
realm.close()
}
suspend fun setAbout(about: About) = withContext(Dispatchers.IO) {
val realm: Realm = Realm.getDefaultInstance()
realm.executeTransaction {
realm.copyToRealmOrUpdate(AboutModel.from(about))
_about.postValue(about)
}
realm.close()
}
fun getAbout() = _about as LiveData<About>
}
// Database is a singleton instance, so there is only ever one instance of the DAO classes
class Database private constructor() {
var aboutDao = AboutDao()
private set
companion object {
// #Volatile - Writes to this property are immediately visible to other threads
#Volatile private var instance: Database? = null
suspend fun getInstance() = withContext(Dispatchers.IO) {
return#withContext instance ?: synchronized(this) {
instance ?: Database().also { instance = it }
}
}
}
}
// Repo maintains the dao access. Is also setup to run as a singleton
class AboutRepo private constructor(private val aboutDao: AboutDao){
// This may seem redundant.
// Imagine a code which also updates and checks the backend.
suspend fun set(about: About) {
aboutDao.setAbout(about)
}
suspend fun getAbout() = aboutDao.getAbout()
companion object {
// Singleton instantiation you already know and love
#Volatile private var instance: AboutRepo? = null
fun getInstance(aboutDao: AboutDao) =
instance ?: synchronized(this) {
instance ?: AboutRepo(aboutDao).also { instance = it }
}
}
}
// Injector is used to help keep the injection in a single place for the fragments and activities.
object Injector {
// This will be called from About Fragment
suspend fun provideAboutViewModelFactory(): AboutViewModelFactory = withContext(Dispatchers.Default) {
AboutViewModelFactory(getAboutRepo())
}
private suspend fun getAboutRepo() = withContext(Dispatchers.IO) {
AboutRepo.getInstance(Database.getInstance().aboutDao)
}
}
// AboutViewModel's Factory. I found this code online, as a helper for injecting into the viewModel's factory.
class AboutViewModelFactory (private val aboutRepo: AboutRepo)
: ViewModelProvider.NewInstanceFactory() {
#Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return AboutViewModel(aboutRepo) as T
}
}
// About Fragments ViewModel
class AboutViewModel(private val aboutRepo: AboutRepo) : ViewModel() {
suspend fun getAbout() = aboutRepo.getAbout()
suspend fun setAbout(about: About) = aboutRepo.set(about)
}
// Fragment's onActivityCreated, I set the viewModel and observe the model from the view model for changes
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
lifecycleScope.launch {
viewModel = ViewModelProviders.of(
this#AboutFragment,
Injector.provideAboutViewModelFactory()
).get(AboutViewModel::class.java)
withContext(Dispatchers.Main) {
viewModel.getAbout().observe(viewLifecycleOwner, Observer { about ->
version_number.text = about?.version
})
}
}
}

Repository Pattern in Android. Is there any way better than this?

I have an app based on MVVM architecture.
I have two modules that repository and datasource.
And using coroutines. I came across some projects on Github they applied different ways.
My implementation like this;
You can think naming as login, profile etc. instead of X letter
datasource interface
interface IXDatasource{
suspend fun fetchData(): LiveData<XResponse>
}
datasource implementation
class XDatasourceImp #Inject constructor(private val apiService: ApiService) : IXDatasource{
suspend fun fetchData(): LiveData<XResponse> {
// getting data
return xResponse
}
}
repository interface
interface XRepository {
suspend fun getXData(): LiveData<XResponse>
}
repository implementation
class XRepositoryImp #Inject constructor(private val iDatasource: IXDatasource): XRepository {
override suspend fun getXData(): LiveData<XResponse> {
return withContext(Dispatchers.IO) {
iDatasource.fetchData()
}
}
}
I called this in my ViewModel
class XViewModel #Inject constructor(xRepository: XRepository) : BaseViewModel() {
val xModel by lazyDeferred {
xRepository.getXData()
}
}
And I use in my activity/fragment
private fun init() = launch {
val xModel = viewModel.xModel.await()
xModel.observe(this#MyActivity, Observer {
if (it == null) {
return#Observer
}
// filling views or doing sth
})
}
It works but I wonder all of them is required or not? I can apply the room database to my datasource. Is there any way better than this? I know it can change by the case of the app. I try to find the best way. I will be happy if you suggest anything or share anything about repository pattern implementation.

Android: clean architecture with Room database and LiveData in DAO

I'm trying to apply clean-architecture approach to my project (Link: guide I'm currently referencing).
I'm using Room database for local storage and I want it to be the single source of data in the application - this means that all data gathered from network calls first is saved in database and only after is passed to the presenter. Room provides return of LiveData from its DAOs and this is exactly what suits my needs.
However I also want to use repositories as a single way to access data. Here's an example of repository interface in domain layer (the most abstract one):
interface Repository<T>{
fun findByUsername(username: String) : List<T>
fun add(entity: T): Long
fun remove(entity: T)
fun update(entity: T) : Int
}
And here I'm running into the problem - I need to get a LiveData from Room's DAO in my ViewModel and I'd like to get it using Repository implementation. But in order to achieve this I need either to:
Change Repository method findByUsername to return LiveData>
Or call Room's DAO directly from ViewModel skipping repository implementation completely
Both of these options have sufficient drawbacks:
If I import android.arch.lifecycle.LiveData into my Repository interface than it would break the abstraction in Domain layer, as it is now depending on android architecture libraries.
If I call Room's DAO directly in the ViewModel as val entities: LiveData<List<Entity>> = database.entityDao.findByUsername(username) then I'm breaking the rule that all data access must be made using Reposiotry and I will need to create some boilerplate code for synchronization with remote storage etc.
How is it possible to achieve single data source approach using LiveData, Room's DAO and Clean architecure patterns?
Technically you are running into trouble because you don't want synchronous data fetching.
fun findByUsername(username: String) : List<T>
You want a subscription that returns to you a new List<T> each time there is a change.
fun findByUsernameWithChanges(username: String) : Subscription<List<T>>
So now what you might want to do is make your own subscription wrapper that can handle LiveData or Flowable. Of course, LiveData is trickier because you must also give it a LifecycleOwner.
public interface Subscription<T> {
public interface Observer<T> {
void onChange(T t);
}
void observe(Observer<T> observer);
void clear();
}
And then something like
public class LiveDataSubscription<T> implements Subscription<T> {
private LiveData<T> liveData;
private LifecycleOwner lifecycleOwner;
private List<Observer<T>> foreverObservers = new ArrayList<>();
public LiveDataSubscription(LiveData<T> liveData) {
this.liveData = liveData;
}
#Override
public void observe(final Observer<T> observer) {
if(lifecycleOwner != null) {
liveData.observe(lifecycleOwner, new android.arch.lifecycle.Observer<T>() {
#Override
public void onChange(#Nullable T t) {
observer.onChange(t);
}
});
} else {
Observer<T> foreverObserver = new android.arch.lifecycle.Observer<T>() {
#Override
public void onChange(#Nullable T t) {
observer.onChange(t);
}
};
foreverObservers.add(foreverObserver);
liveData.observeForever(foreverObserver);
}
}
#Override
public void clear() {
if(lifecycleOwner != null) {
liveData.removeObservers(lifecycleOwner);
} else {
for(Observer<T> observer: foreverObservers) {
liveData.removeObserver(observer);
}
}
}
public void setLifecycleOwner(LifecycleOwner lifecycleOwner) {
this.lifecycleOwner = lifecycleOwner;
}
}
And now you can use your repository
val subscription = repository.findByUsernameWithChanges("blah")
if(subscription is LiveDataSubscription) {
subscription.lifecycleOwner = this
}
subscription.observe { data ->
// ...
}
When similar question is asked about using RxJava, developers usualy answer, that is ok, and RxJava now is a language part, so, you can use it in domain layer. In my opinion - you can do anything, if it helps you, so, if using LiveData don't create problems - use it, or you can use RxJava, or Kotlin coroutines instead.
Use Flow as return type in your domain
since flow is part of Kotlin language, it's fully acceptable to use this type in your domain.
here is an example
Repository.kt
package com.example.www.myawsomapp.domain
import com.example.www.myawsomapp.domain.model.Currency
import com.example.www.myawsomapp.domain.model.Result
import kotlinx.coroutines.flow.Flow
interface Repository {
fun getCurrencies(): Flow<List<Currency>>
suspend fun updateCurrencies(): Result<Unit>
}
then in your data package you can implement it
package com.example.www.myawsomapp.data
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
class RepositoryImpl #Inject constructor(
private val currencyDao: CurrencyDao,
private val api: CurrencyApi,
private val connectivity: Connectivity
) :
Repository {
override fun getCurrencies(): Flow<List<Currency>> =
currencyDao.getAll().map { list -> list.map { it.toDomain() } }
override suspend fun updateCurrencies(): Result<Unit> =
withContext(Dispatchers.IO) {
val rowsInDataBase = currencyDao.getCount()
if (rowsInDataBase <= 0) {
if (connectivity.hasNetworkAccess()) {
return#withContext updateDataBaseFromApi()
} else {
return#withContext Failure(HttpError(Throwable(NO_INTERNET_CONNECTION)))
}
} else {
return#withContext Success(Unit)
}
}
}
Note that
currencyDao.getAll().map { list -> list.map { it.toDomain() } }
from your dao you are receiving data class of data/model package, while ideally your viewmodel should receive data class of domain/model package so that you are mapping it to domain model
here is dao class
package com.example.www.myawsomapp.data.database.dao
import com.blogspot.soyamr.cft.data.database.model.Currency
import kotlinx.coroutines.flow.Flow
import com.blogspot.soyamr.cft.data.database.model.Currency
#Dao
interface CurrencyDao {
#Query("SELECT * FROM currency")
fun getAll(): Flow<List<Currency>>
}
then in your viewmodel you would convert flow to livedata
val currencies =
getCurrenciesUseCase()
.onStart { _isLoading.value = true }
.onCompletion { _isLoading.value = false }.asLiveData()

Categories

Resources