I am using Room in a project and I have the following DAO interface:
#Dao
interface BalanceDao {
#Query("SELECT * FROM balance")
fun getAllBalances(): Flowable<List<BalanceDataModel>>
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertBalanceList(balanceDataModelList: List<BalanceDataModel>): List<Long>
}
Insert works fantastically, but the getAllBalances() method does not work, since it does not retrieve any row. I extracted the DB after the insertion and I can see all the rows there; SELECT * from balance works perfectly when locally executed to the extracted DB with a desktop app.
I also tried to change the return type of getAllBalances() from Flowable<List<BalanceDataModel>> to Single<List<BalanceDataModel>> but the same keeps happening: no results.
I have a PortfolioManager, from which I call the following method and I pass the observer and the owner from my Fragment.
fun getAllCoins(owner: LifecycleOwner, observer: Observer<List<Balance>>) {
portfolioViewModel
.balanceListLiveData
.observe(owner, observer)
return portfolioViewModel.getPortfolioCoins()
}
Then in the PortfolioManager, I have access to a ViewModel, from which I call the following method:
fun getPortfolioCoins() {
coinRepository
.getBalanceListPortfolio()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeBy(onSuccess = {
balanceListLiveData.postValue(it)
})
}
And in the repository class, I have this:
fun getBalanceListPortfolio(): Single<List<Balance>> {
val converter = BalanceDataModelConverter()
val balanceList = mutableListOf<Balance>()
coinDatabase.balanceDao()
.getAllBalances()
.toObservable()
.flatMapIterable { t: List<BalanceDataModel> -> t }
.map { t: BalanceDataModel ->
{
val a: Balance = converter.fromFirstToSecond(t)
balanceList.add(a)
}
}
return Single.just(balanceList.toList())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
}
Anybody knows what could be wrong? Thanks a lot in advance!
I just saw your recent edit. I think your problem is you are returning an empty list in getBalanceListPortfolio. This observable was not subscribed upon.
coinDatabase.balanceDao()
.getAllBalances()
.toObservable()
.flatMapIterable { t: List<BalanceDataModel> -> t }
.map { t: BalanceDataModel ->
{
val a: Balance = converter.fromFirstToSecond(t)
balanceList.add(a)
}
}
I suggest you convert this to list and return this observable (something like this, cant try to compile. I have no working env right now).
return coinDatabase.balanceDao()
.getAllBalances()
.toObservable()
.flatMapIterable { t: List<BalanceDataModel> -> t }
.map { t: BalanceDataModel -> converter.fromFirstToSecond(t) }
.toList()
.toObservable()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
This is wrapped as an observable to you might want to change your type to Observable (instead of single) first, just to test. Let me know the results.
Related
Let's say I have an Android app with the following Repository class to fetch objects from API:
override fun fetchOrderById(orderId: Long): Single<List<ItemRow>> {
return api.fetchOrderByIdObservable(orderId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.map { orderResponse ->
orderResponse.items.map { deliveryItem ->
deliveryItem.asItemRow()
}
}
}
override fun fetchOrders(): Single<OrdersResponse> {
return api.fetchOrdersObservable()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
}
override fun fetchAllOrders(): Single<List<ItemRow>> {
// TODO
}
With the following data class:
class OrdersResponse(#SerializedName("orders") val orders: List<Long>)
class OrderResponse(#SerializedName("items") val items: List<DeliveryItem>)
Right now, I can use fetchOrderById to get all delivery items (ItemRow) for a certain orderId as a domain object in my app to show in a list. How can I use fetchOrders, which returns a list of orderIds to get all delivery items for all orders? Which operators would be useful here? I played around with FlatMap, but could not get it to work. Link to articles would be ++ too. Thanks!
operators you need: switchMap(), flatMap(), fromArray(), toList(), map() it's gonna be something like this:
fun fetchAllOrders(): Observable<List<ItemRow>> {
return fetchOrders()
.toObservable()
.switchMap { ordersResponse ->
Observable.fromIterable(ordersResponse.orders)
.flatMap {orderId -> fetchOrderById(orderId).toObservable() }
.toList()
.toObservable()
}
.map { list -> list.flatten() }
}
This worked eventually:
public fun fetchAllItems(): Observable<List<ItemRow>> {
return networkService.api.fetchOrdersObservable()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.flatMap { orderResponse -> Observable.just(orderResponse.orders) }
.flatMapIterable { id -> id }
.flatMap { id -> fetchOrderById(id) }
.flatMapIterable { itemRow -> itemRow }
.toList()
}
I'm having a weird problem with my repository implementation. Every time I call my function that's supposed to get data from the database and update the database with a network call, I receive multiple results from my database observer.
override fun getApplianceControls(
serialNumber: SerialNumber
): Flowable<ApplianceControlState> {
val subject = BehaviorProcessor.create<ApplianceControlState>()
controlsDao.get(serialNumber.serial)
.map { controls ->
ApplianceControlState.Loaded(controls.toDomainModel())
}
.subscribe(subject)
controlApi.getApplianceControls(serialNumber.serial)
.flatMapObservable<ApplianceControlState> { response ->
val entities = response.toEntity(serialNumber)
// Store the fetched controls on the database.
controlsDao.insert(entities).andThen(
// Return an empty observable because the db will take care of emitting latest values.
Observable.create { }
)
}
.onErrorResumeNext { error: Throwable ->
Observable.create { emitter -> emitter.onNext(ApplianceControlState.Error(error)) }
}
.subscribeOn(backgroundScheduler)
.subscribe()
return subject.distinctUntilChanged()
}
#Dao
interface ApplianceControlsDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(controls: List<TemperatureControlEntity>): Completable
#Query("SELECT * FROM control_temperature WHERE serial = :serial")
fun get(serial: String): Flowable<List<TemperatureControlEntity>>
}
Basically, if I call getApplianceControls once, I get desired result. Then I call again, with another serial number, which is empty and I get the empty array. But then I call a third time, but with the same serial number as the first time and I get a mix of correct results and empty array after the insert call is made.
Like this:
1st call, to serial number "123" -> Loaded([control1, control2, control3])
2nd call, to serial number "000" -> Loaded([])
3rd call, to serial number "123" -> Loaded([control1, control2, control3]), Loaded([]), Loaded([control1, control2, control3])
If I remove the db insert from the api response, it works fine. Everything weird occurs after insert is called.
Edit: getApplianceControls() is called from the ViewModel.
fun loadApplianceControls(serialNumber: SerialNumber) {
Log.i("Loading appliance controls")
applianceControlRepository.getApplianceControls(serialNumber)
.subscribeOn(backgroundScheduler)
.observeOn(mainScheduler)
.subscribeBy(
onError = { error ->
Log.e("Error $error")
},
onNext = { controlState ->
_controlsLiveData.value = controlState
}
).addTo(disposeBag)
}
As i mention in comment you have 2 subscriptions that are not unsubscribed anywhere, it could cause memory leak (it doesn't dispose when subject is disposed), also with such implementation you ignore API errors.
i'd try to change it to:
override fun getApplianceControls(serialNumber: SerialNumber): Flowable<ApplianceControlState> {
val dbObservable = controlsDao.get(serialNumber.serial)
.map { controls ->
ApplianceControlState.Loaded(controls.toDomainModel())
}
val apiObservable = controlApi.getApplianceControls(serialNumber.serial)
.map { response ->
val entities = response.toEntity(serialNumber)
// Store the fetched controls on the database.
controlsDao.insert(entities).andThen( Unit )
}
.toObservable()
.startWith(Unit)
return Observables.combineLatest(dbObservable, apiObservable) { dbData, _ -> dbData }
// apiObservable emits are ignored, but it will by subscribed with dbObservable and Errors are not ignored
.onErrorResumeNext { error: Throwable ->
Observable.create { emitter -> emitter.onNext(ApplianceControlState.Error(error)) }
}
.subscribeOn(backgroundScheduler)
//observeOn main Thread
.distinctUntilChanged()
}
I'm not sure if it solves the original issue. But if so - the issue is in flatMapObservable
ALSO would be useful to see controlApi.getApplianceControls() implementation.
I have problems reading value from RoomDB.
Here is my RoomDBInterface -
#Dao
interface RoomDao {
#Query("SELECT * FROM accountTable")
fun getAccountData(): Flowable<AccountModel>
}
The Repo that reads from this interface looks like this -
override fun retrieveAccountData() {
Log.e("hi", "inside retrieveAccountData()")
disposable.add(
roomDao.getAccountData().subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ userData ->
Log.e("hi", "callback to upper layers")
callback?.onAccountDataRetrieved(userData)
},
{ throwable -> callback?.onAccountDataRetrieved(null) })
)
}
I just get the first log which is inside retrieveAccountData() but the disposable subscribe does not return the callback. What am I doing wrong 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 am not getting how to use the result of first observable in second observable.
-> My First Observable
var uploadImgObservable = Observable.create<File> {....}
-> My Second Observable
var thumbnailObservable = Observable.create<Task<UploadTask.TaskSnapshot>> {...}
Now i wanna use the result of uploadImgObservable in thumbnailObservable.
I also tried to use flatmap as suggested by stackoverflow but i didnt get it ..
This is how i used flatmap in my observable..
Observable.create<Task<UploadTask.TaskSnapshot>> { e ->
firebaseStorageReference.child("profile_images").child(current_user_uid+"_thumbnail"+ ".jpg").putFile(imageFile)
.addOnCompleteListener { task: Task<UploadTask.TaskSnapshot> ->
e.onNext(task)
e.onComplete()
}
}.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.unsubscribeOn(Schedulers.io())
.flatMap(object : Function<Task<UploadTask.TaskSnapshot>,Observable<File>>{
override fun apply(t: Task<UploadTask.TaskSnapshot>): Observable<File> {
var compressedImageBitmap = compress?.setMaxWidth(640)
?.setMaxHeight(480)
?.setQuality(70)
?.setCompressFormat(Bitmap.CompressFormat.WEBP)
?.setDestinationDirectoryPath(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES).getAbsolutePath())
?.compressToFile(actualImageFile)
return Observable.just(compressedImageBitmap)
}
})?.subscribe(object : Observer<File>{
override fun onNext(t: File) {
}
override fun onError(e: Throwable) {
}
override fun onComplete() {
}
override fun onSubscribe(d: Disposable) {
}
})
As you can see after using flatmap, Observable<Task<UploadTask.TaskSnapshot>> converts to Observable<File> but i dont wanna convert the type of observable after using the result of first observable.
What should i do to use the result of first observable in second observable?
The name of the flatMap has "map" in it which means it will map some value to another. But what you can do is
firstObservable.flatMap(firstObservableResult ->
secondObservable
.flatMap(secondObservableResult -> Observable.just(firstObservableResult)))
.subscribe(firstObservableResult-> {
// process request data
});
Hope you still understand Java code.