I need to check if database is empty and if it is, then download data with retrofit2, select what I need and insert it to database and finally return inserted data from database. I have tried to do it with this example https://stackoverflow.com/a/48478847/5184417, but I can't figure out the part with inserting data to database. It gives an error about return of flatmap that insert to database doesn't return anything.
I have this piece of code
fun getEmployees(): Flowable<List<Employee>> {
return employeeDao.getEmployeeCount()
.take(1)
.flatMap { counts ->
if (counts.isEmpty() || counts[0] == 0) {
Api.getAPIService().getDepartments()
.flatMap{ response ->
employeeDao.deleteAll()
for (departments in response.Departments) {
if (departments.Name == "AR") {
for (employee in departments.employees) {
employeeDao.insert(employee)
}
}
}
state.postValue(RepositoryState.READY)
}
.ignoreElements()
.andThen(employeeDao.getAll())
}
employeeDao.getAll()
}
}
interface ApiService {
#GET("departments")
fun getDepartments() : Single<Departments>
}
#Dao
interface EmployeeDao {
#Query("SELECT * FROM employees")
fun getAll(): Flowable<List<Employee>>
#Query("SELECT count(1) FROM employees")
fun getEmployeeCount(): Flowable<List<Int>>
#Insert(onConflict = REPLACE)
fun insert(employee: Employee)
}
Thanks for any help!
flatmap is used to chain the observable. It's syntax is:
observable1
.flatmap(i-> {
return observable2;}
)
So the point is that you should return an Observable inside flatmap and that Observable will be propagated down(I mean observable 2 in above code). One possible solution is that make employeeDao.getAll() to return a flowable OR some how just wrap the output of employeeDao.getAll() inside Observable.just() or Observable.create() or whatever method you know.
EDIT: You must return an observable inside flatmap, you are not using any return statement.
Related
I can't figure out how to do a "simple" operation with Room and MVVM pattern.
I’m fetching some data with Retrofit. A “proper” response triggers an observer in the activity and a small part of the response itself is inserted in the database using Room library, wiping all previous values stored and inserting the fresh ones. Otherwise old values are retained on DB.
Just after that, I would like to check for a field in the database, but I’m not able to force this operation to wait until the previous one is completed.
Models
#Entity(tableName = "licence")
data class Licence(
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "licence_id")
var licenceId: Int = 0,
#Ignore
var config: List<LicenceConfig>? = null,
.......
//all the others attributes )
#Entity(foreignKeys = [
ForeignKey(
entity = Licence::class,
parentColumns = ["licence_id"],
childColumns = ["licence_reference"],
onDelete = ForeignKey.CASCADE
)],tableName = "licence_configurations")
data class LicenceConfig(
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "licence_config_id")
var licenceConfigId: Int,
#ColumnInfo(name="licence_reference")
var licenceReference: Int,
Observer in the activity
loginViewModel.apiResponse.observe(this, Observer { response ->
response?.let {
loginViewModel.insertLicences(response.licence)
}
//here I need to wait for the insertion to end
loginViewModel.methodToCheckForTheFieldOnDatabase()
})
ViewModel
fun insertLicences(licences: List<Licence>) = viewModelScope.launch {
roomRepository.deleteAllLicences()
licences.forEach { licence ->
roomRepository.insertLicence(licence).also { insertedLicenceId ->
licence.config?.forEach { licenceConfiguration ->
roomRepository.insertLicenceConfiguration(
licenceConfiguration.apply { licenceReference = insertedLicenceId.toInt() }
)
}
}
}
}
Room Repository
class RoomRepository(private val roomDao: RoomDao) {
val allLicences: LiveData<List<Licence>> = roomDao.getAllLicences()
suspend fun insertLicence(licence: Licence): Long {
return roomDao.insertLicence(licence)
}
suspend fun insertLicenceConfiguration(licenceConfiguration: LicenceConfig){
return roomDao.insertLicenceConfiguration(LicenceConfig)
}
}
RoomDao
#Dao
interface RoomDao {
#Query("select * from licence")
fun getAllLicences(): LiveData<List<Licence>>
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertLicence(licence: Licence): Long
#Insert
suspend fun insertLicenceConfiguration(licence: LicenceConfig)
#Query("DELETE FROM licence")
suspend fun deleteAllLicences()
}
Set an observer to the "allLicences" LiveData or directly on that field on DB is not an option because the operations will be performed just after the activity creation and I have to wait until the API response to perform them.
In another project, without Room, I have used async{} and .await() to perform sequential operations while working with coroutines but I can't really make it works here. When I pause the debugger just after the insertion method the value of "allLicences" it's always null but after resuming and exporting the DB the data are properly inserted. I also tried adding .invokeOnCompletion{} after the ViewModel method but with the same result.
Basically I would like to wait for this method to end to do another operation.
Any suggestions?
EDIT
I totally forgot to report the models! Each licence have a list of configurations. When I perform a licence insert I take the autogenerated id, I apply it to the licenceConfig and then I perform the insert for each licenceConfig object (the code in the nested forEach loop of the ViewModel method). The problem seems to be that performing this nested loop breaks the "synchronicity" of the operation
To wait until insertion is completed, you need to move the coroutine creation from insertLicences() to your observer and also make the insertLicences() a suspend function.
loginViewModel.apiResponse.observe(this, Observer { response ->
lifecycleScope.launch {
response?.let {
loginViewModel.insertLicences(response.licence)
}
//here I need to wait for the insertion to end
loginViewModel.methodToCheckForTheFieldOnDatabase()
}
})
and
suspend fun insertLicences(licences: List<Licence>) {
roomRepository.deleteAllLicences()
licences.forEach { licence ->
roomRepository.insertLicence(licence).also { insertedLicenceId ->
licence.config?.forEach { licenceConfiguration ->
roomRepository.insertLicenceConfiguration(
licenceConfiguration.apply { licenceReference = insertedLicenceId.toInt() }
)
}
}
}
}
Alternative Solution
You can shift all of the code present in the observer into ViewModel.
loginViewModel.apiResponse.observe(this, Observer { response ->
loginViewModel.refreshLicenses(response)
})
and in ViewModel
fun refreshLicenses(response:Response?){
viewModelScope.launch{
response?.let {
insertLicences(response.licence)
}
methodToCheckForTheFieldOnDatabase()
}
}
and also make insertLicences as suspend function
suspend fun insertLicences(licences: List<Licence>) {
roomRepository.deleteAllLicences()
licences.forEach { licence ->
roomRepository.insertLicence(licence).also { insertedLicenceId ->
licence.config?.forEach { licenceConfiguration ->
roomRepository.insertLicenceConfiguration(
licenceConfiguration.apply { licenceReference = insertedLicenceId.toInt() }
)
}
}
}
}
Edit: Didn't read your conclusion before I reply but, I still think that your answer lies in coroutines
Using callbacks or promises, won't your function be executed when the insert query is finished?
Callbacks
With callbacks, the idea is to pass one function as a parameter to
another function, and have this one invoked once the process has
completed.
fun postItem(item: Item) {
preparePostAsync { token ->
submitPostAsync(token, item) { post ->
processPost(post)
}
}
}
fun preparePostAsync(callback: (Token) -> Unit) {
// make request and return immediately
// arrange callback to be invoked later
}
I would prefer promises to be honest
Promises
The idea behind futures or promises (there are also other terms these
can be referred to depending on language/platform), is that when we
make a call, we're promised that at some point it will return with an
object called a Promise, which can then be operated on.
fun postItem(item: Item) {
preparePostAsync()
.thenCompose { token ->
submitPostAsync(token, item)
}
.thenAccept { post ->
processPost(post)
}
}
fun preparePostAsync(): Promise<Token> {
// makes request an returns a promise that is completed later
return promise
}
Do your work and when the promise is fullfilled, proceed to data validation.
You can read more about coroutines here
Not sure how to handle insert method's return type.
#Dao
interface ProductDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertAlll( product:List<Product>):List<Product>
}
override fun getFactoriProduct(): Observable<List<Product>> {
return Observable.create { emitter ->
api.getProductRemote()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
if (it != null) {
emitter.onNext(db.productDao().insertAlll(it))
Timber.e("request->$it")
}
}, {
emitter.onNext(db.productDao().getProduct())
it.printStackTrace()
Timber.e("ErrorRequest->$it")
})
}
}
activity.kt
fun init() {
mainViewmodel.getProduct().subscribe {
val adapter = ProductAdapter(it)
RecyclerView2.layoutManager = LinearLayoutManager(this, LinearLayout.HORIZONTAL, false)
RecyclerView2.adapter = adapter
adapter.update(it)
}.addTo(this.CompositeDisposable)
how to handle insert method's return type.
public abstract java.util.List insertAlll(#org.jetbrains.annotations.NotNull()
As per this documentation
A method annotated with the #Insert annotation can return:
long for single insert operation
long[] or Long[] or List for multiple insert operations
void if you don't care about the inserted id(s)
Generally when you use rxjava with room what you do is observe the changes of the database, so that whenever you insert or delete a data from database you get a new Flowable or observable of the updated data.
so firstly include this in your app gradle file
app/build.gradle
implementation 'androidx.room:room-rxjava2:2.1.0-alpha06'
This will help you to directly return a stream of data from room.
Now in your Daos you can make the following changes
Dao.kt
#Dao
interface Dao{
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertAlll(product:List<Product>):Single<List<Int>>
#Query("Select * from YOUR_TABLE_NAME")
fun getAll():Flowable<List<Product>> // return a flowable here because this will be triggered whenever your data changes
}
Now in your view model get the data
ViewModel.kt
val data = MutableLiveData<List<Product>>;
db.dao().getAll() // will fetch a new data after every insertion or change
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ data.postValue(it) },
{ e -> e.printstacktrace() }
))
// This is just to insert the list of produts
db.dao().insertAll(listProduct)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{// Do nothing },
{ e -> e.printstacktrace() }
))
Now in your Activity you can update your UI by observing data
Activity.kt
viewModel.data.observe(this, Observer {
//update your recycler view adapter here
})
I want a way to search and update an existing entry in Room with RxJava. If there's no record it should create a new one.
For example, lets say I have the following queries:
#Insert
Single<Long> createContent(Content content);
#Query("SELECT * FROM Content WHERE contentId = :contentId")
Single<Content> searchContent(String contentId);
My Goals:
Check if there's a previous data and return its value
If there's no record create a new one and return it's value
Problem with this approach:
Whenever there's no record the from #Query, the Single<Content> directly goes to error ignoring any map/flatMap operator
The #Insert query returns a Single<Long> but the #Query returns a Single<Content>
Is there any way to call and return a new Observable from the error? Something like this:
daoAccess.searchContent(contentId)
.subscribeOn(Schedulers.io())
.map(Resource::success)
.onErrorResumeNext(new Function<Throwable, Single<Content>>() {
#Override
public Single<Content> apply(Throwable throwable) throws Exception {
return daoAccess.createContent(contentId);
}
})
You could use Single.onErrorResumeNext():
daoAccess.searchContent(contentId)
.subscribeOn(Schedulers.io())
.map(Resource::success)
.onErrorResumeNext(throwable ->
daoAccess.createContent(content)
.map(id -> Resource.success(content))
)
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
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.