How to get data from Room database - android

I have a function written which listens to changes happening in my room database. if I just want to get the data at any other point without listening to changes what should I be doing.
Dao
#Dao
interface OfflineDataDao {
#Query("SELECT * FROM OfflineData")
fun getOfflineData(): Flow<List<OfflineData>>
class OfflineDatabaseManager private constructor(
private val dp: LibraryDatabase
) {
//LISTENS TO CHANGES IN THE DATABASE
fun getOfflineData(): Flow<List<OfflineData>> {
return dp.getOfflineDataDao().getOfflineData()
}
}
fun getOfflineData() {
launch {
OfflineDatabaseManager.getInstance(app.applicationContext).getOfflineData().collect {
Timber.d("OfflineDataLib - observing offline data" + it.toString())
}
}
}
Using the above functions I can listen to changes in the database but what if I just want to get the data from the database any other point.
How can I do that please
To be more precise, I have a function in which I listen to network changes, if have network or not and in there I want get the data in the offline data, How can I do this.
This is my function which listens to network changes
fun listenToConnectionChanges() {
launch {
OfflineDatabaseManager.getInstance(app.applicationContext).networkConnectionActivated
.collect { isNetworkConnectionActive ->
Timber.d("OfflineDataLib - getOfflineData() - isNetworkConnectionActive - " + isNetworkConnectionActive)
if (isNetworkConnectionActive) {
//I WANT TO GET THE DATA FROM THE DATABASE
}
}
}
}
Thanks
R

You can use a suspend function and directly return List<OfflineData> in your DAO.
OfflineDataDao:
#Query("SELECT * FROM OfflineData")
suspend fun getOfflineData(): <List<OfflineData>
OfflineDatabaseManager:
suspend fun getOfflineData(): <List<OfflineData> {
return dp.getOfflineDataDao().getOfflineData()
}
And then directly get the data in a coroutine scope:
fun getOfflineData() {
launch {
val items = OfflineDatabaseManager.getInstance(app.applicationContext).getOfflineData()
}
}

Related

Koltin Flow is repeating execution using flatMapMerge

I was trying to implement an approach to fetch products from two Data sources (Room & FirebaseFirestore) using Flows.
It was working fine until I noticed that the debugger was returning to the same break point infinitely. When the execution of "ViewmMdel.insertProducts(products)" ends, the debugger returns to Repository.getProducts(//) & repeats.
I changed the approach using only suspending functions & coroutines & works fine but I am curious about how I must to use Flows to implement this approach.
Maybe is only that flatMapMerge is in preview version.
Thanks in advance :D
This one is the implementation:
ViewModel:
fun getProductNames(companyName: String) {
viewModelScope.launch {
repository.getProducts(companyName).catch {
_event.value = AddSaleEvents.ShouldShowLoading(false)
_event.value = AddSaleEvents.ProductsFailureResponse(it.message.toString())
}.collect { products ->
productsList = products
if (products != emptyList<Product>()) {
_event.value = AddSaleEvents.ShouldShowLoading(false)
_event.value = AddSaleEvents.ProductsSuccessfulResponse(products)
insertProducts(products)
} else {
_event.value = AddSaleEvents.ShouldShowLoading(false)
_event.value = AddSaleEvents.ProductsSuccessfulResponse(products)
}
}
}
}
Repository:
#OptIn(FlowPreview::class)
override suspend fun getProducts(compnayName: String): Flow<List<Product>> {
return localDataSource.getProducts().flatMapMerge { list -> // LINE RUNNING INFINITELY
getProductsFromFirebase(list, compnayName)
}.flowOn(Dispatchers.IO).catch {
Log.d("Error", it.message.toString())
}
}
private fun getProductsFromFirebase(products: List<Product>, compnayName: String) = flow {
if (products.isEmpty()) {
remoteDataSource.getProducts(compnayName).collect {
emit(it)
}
} else {
emit(products)
}
}
LocalDataSource with Room:
override suspend fun getProducts(): Flow<List<Product>> = saleDao.getProducts()
Firebase Data Source:
override suspend fun getProducts(company: String): Flow<List<Product>> = flow {
val response = fireStore.collection("products").whereEqualTo("company", company).get()
response.await()
if (response.isSuccessful && !response.result.isEmpty) {
emit(response.result.toObjects(FirebaseProduct::class.java).toEntity())
}
}.catch {
Log.d("Error", it.message.toString())
}
How can I chain the response of a flow to trigger another one inside the MVVM Architecture + Clean Architecture?
6 if it is possible, I want to understand the reason the code is repeating infinitely.
Looks like insertProducts(products) triggers room's DAO.
So localDataSource.getProducts() is a observable read query
Observable queries are read operations that emit new values whenever there are changes to any of the tables that are referenced by the query.
Try to change LocalDataSource
interface SaleDao {
// fun getProducts(): Flow<List<Product>>
suspend fun getProducts(): List<Product>
}

Android observe repository with flow and Result sealed dataclass

I'm having trouble how to design the architecture of my Android App. I'm using the repository pattern, however, how can I observe a Room entity when the return type is a Dataclass with Success/Failure within a Flow and using a uiState Dataclass. Let me elaborate more with an image:
In my viewmodel, I have the repository injected with Hilt. So I can call _myRepo.getData().
The repository executes a network call (retrofit) and awaits the result.
The server returns a JSON with the data requested.
With a serializer, I convert the JSON to a list of DTOs. These DTO's are mapped to Entities and inserted in the Room Database.
Now comes the confusing part.
If my ResultType dataclass is:
sealed class ResultType<T> (val data : T? = null, val message : String? = null) {
class Success<T>(data: T?) : ResultType<T>(data)
class Error<T>(message: String?, data: T? = null) : ResultType<T>(data, message)
class Loading<T>(val isLoading: Boolean = true) : ResultType<T>(null)
}
In my repository, the function getData() looks like:
suspend fun getData() : Flow<ResultType<List<UserDataClass>>> = flow {
try {
emit(ResultType.Loading(true))
val mylist = _api.getUsers(mapparameters).map { it.toUserEntity() }
_db.userDao.delete()
_db.userDao.insertAll(mylist)
emit(ResultType.Success(_db.userDao.getAll().map { it.toUserDataClass() }))
} catch (ex: HttpException) {
emit(ResultType.Error(ex.localizedMessage))
} catch (ex: IOException) {
emit(ResultType.Error(ex.localizedMessage))
}
}
So, in my viewmodel, I'm collecting the flow like this:
private fun getUsers() {
viewModelScope.launch(Dispatchers.IO) {
_repository.getData(username, password).collect { result ->
when (result) {
is ResultType.Loading -> {
_uiState.update { prev ->
prev.copy(isLoading = true)
}
}
is ResultType.Success -> {
result.data?.let {
_myList = it.toMutableList()
_uiState.update { prev ->
prev.copy(
users = _myList,
isLoading = false
)
}
}
}
}
}
}
Finally, for the UI, the uiState is:
data class UsersState(
val users : List<UserDataClass> = emptyList(),
val isLoading : Boolean = true
)
This is where I'm currently stuck. Because I need to observe for any change in the Room database for those entities
Currently using MVVM for this project.
Let me know if you require more info
You can use flow directly for your Room DAO objects. Don't actually know, how your data classes look like, but will assume:
#Dao
abstract class UsersDAO {
#Query("SELECT * FROM Users")
abstract fun getUsers(): Flow<List<UserDataClass>>
}
Now, every update of the table will emit the new object in the flow. So, in your domain/presentation layer you should simply subscribe to the flow from your DAO.
At first, you need to provide access to dao from your repository
suspend fun getData(): Flow<<List<UserDataClass>> {
try {
val mylist = _api.getUsers(mapparameters).map { it.toUserEntity() }
_db.userDao.delete()
_db.userDao.insertAll(mylist)
} catch (e: Exception) {
// I let you implement catch blocks yourself,
// you probably don't want to emit an error if you want
// observe only actual updates of the table, not the errors
}
return _db.userDao.getUsers() // here you return your flow!
}
Then, in the presentation layer you can do:
private fun getUsers() {
viewModelScope.launch(Dispatchers.IO) {
_repository.getData().collect { usersList ->
// your update logic
}
}
}

Kotlin Flow collect takes a long time

I have a local database in my Android app. There is a function that takes some data from server and updates local database.
When this function is running, if I collect a list from local database by returning Flow, it takes unusual time to finish.
I don't have any problem with LiveData, it works well but Flow doesn't.
this is my dao :
#Transaction
#Query("SELECT * FROM tbl WHERE id=:id")
fun getData(id: String): Flow<Entity?>
repo :
fun getData(id: String): Flow<Entity?> {
return dao.getData(id).map { it?.toModel() }
}
fragment :
lifecycleScope.launch() {
repo.getData(args.id)
.flowWithLifecycle(viewLifecycleOwner.lifecycle)
.distinctUntilChanged()
.collect { data ->
data?.let {
setData(data)
}
}
}
Try to add .flowOn(Dispatchers.IO) in you repository function after map like this:
Repository
fun getData(id: String): Flow<Entity?> {
return dao.getData(id)
.map { it?.toModel() }
.flowOn(Dispatchers.IO)
}
I think the problem is that you delay the main thread with your heavy function operation (map) on the flow.

Cannot make the method retryWhen work(Kotlin, Rx)

I invoke retryNextTweet.onNext() method but it doesn't make observable retry.
My goal is to get the next item from the local storage. If there are no records in sqlite, then i'll fill the local storage using apiService and retry.
private val retryNextTweet: PublishSubject<Any> = PublishSubject.create()
override fun getNextTweet(cacheId: Long, tweetSearchParams: TweetSearchParams): Observable<Tweet> {
return tweetDao.getNextTweet(cacheId)
.toObservable()
.retryWhen {
it.flatMap { loadTweetsFromApi(tweetSearchParams).subscribe({
if(it.isNotEmpty())
retryNextTweet.onNext(Any())
}, {})
retryNextTweet }
}
}
#Dao
interface TweetDao {
#Query("SELECT * FROM tweet WHERE cacheId > :cacheId LIMIT 1")
fun getNextTweet(cacheId: Long): Single<Tweet>
}
I just replaced PublishSubject with a BehaviorSubject and it worked. Thank you akarnokd https://stackoverflow.com/users/61158/akarnokd

Room with Flowable: initialize database if it's empty

I have following #Dao, that provides Flowable<User> stream:
#Dao
interface UsersDao {
#Query("SELECT * FROM users")
fun loadUsers(): Flowable<List<User>>
}
I want the subscriber of the stream to receive updates of the database as soon as some change happens there. Subscribing to Room's Flowable I will get that feature out of the box.
What I want is following: if database is empty I want to perform a web request and save users into database. The subscriber will automatically receive new updates that had just happened.
Now I want the client of the repository not to be aware all of the initialization logics: all he does - he performs usersRepository.loadUsers(). And all of these magic should take place inside the repository class:
class UsersRepository #Inject constructor(
private val api: Api,
private val db: UsersDao
) {
fun loadUsers(): Flowable<List<User>> {
...
}
}
Of course I can use following approach:
fun loadUsers(): Flowable<List<User>> {
return db.loadTables()
.doOnSubscribe {
if (db.getCount() == 0) {
val list = api.getTables().blockingGet()
db.insert(list)
}
}
}
But I would like to construct the stream without using side-effects (doOn... operators). I've tried composing() but that didn't help much. Been stuck on this for a while.
You could apply some conditional flatMaps:
#Dao
interface UsersDao {
#Query("SELECT * FROM users")
fun loadUsers(): Flowable<List<User>>
#Query("SELECT COUNT(1) FROM users")
fun userCount() : Flowable<List<Integer>>
#Insert // I don't know Room btw.
fun insertUsers(List<User> users) : Flowable<Object>
}
interface RemoteUsers {
fun getUsers() : Flowable<List<User>>
}
fun getUsers() : Flowable<List<User>> {
return
db.userCount()
.take(1)
.flatMap({ counts ->
if (counts.isEmpty() || counts.get(0) == 0) {
return remote.getUsers()
.flatMap({ users -> db.insertUsers(users) })
.ignoreElements()
.andThen(db.loadUsers())
}
return db.loadUsers()
})
}
Disclaimer: I don't know Room so please adapt the example above as the features of it allow.
Assuming your insert() call is async and also handles updates, you could do something like this:
fun loadUsers(): Flowable<List<User>> = userDao.getAllUsers().switchIfEmpty { api.getAllUsers().doOnNext { userDao.insert(it) } }
You could also use some:
fun loadUsers(): Flowable<List<User>> = userDao.getAllUsers().flatMap { it-> if (it.isEmpty()) api.getAllUsers().doOnNext { userDao.insert(it) } else Flowable.just(it)}
Advice:
You should consider the case when the data is stale, therefore you need to go another way around, do a network request and database call at the same time. Whichever observable finish first, take the result and display it. Updating database should be right after network call is done.

Categories

Resources