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?
Related
In my projet I have a method taht subscribe each on my observable the same way. I'm trying to enhanced it by puttin the retryWhen option on it.
To avoid a big retrywhen to handle different error I have design this logique
A RetryFunction class that is generic
abstract class RxStreamLimitedRetryFunction(private val nbOfAttempts: Int, val streamId: String) : Function<Observable<Throwable>, Observable<*>> {
override fun apply(t: Observable<Throwable>): Observable<*> {
return t.flatMap {
if (shouldRetry(it)) Observable.just(it)
else Observable.empty()
}.zipWith(Observable.range(0, nbOfAttempts + 1), BiFunction<Throwable, Int, Int> { throwable, attempts ->
if (attempts == nbOfAttempts) {
throw RetryMaxAttemptsException(nbOfAttempts)
} else {
Log.d("Retry nb ${attempts + 1} out of $nbOfAttempts for stream with id : $streamId with error ${throwable.message} ")
attempts
}
}).flatMap { onRetry(it) }
}
abstract fun onRetry(attempsNb: Int): Observable<*>
abstract fun shouldRetry(throwable: Throwable): Boolean
}
two child class each with different retry attemps following the error
class RxStream404Retry(streamId: String) : RxStreamLimitedRetryFunction(4, streamId) {
override fun onRetry(attempsNb: Int): Observable<*> {
return Observable.timer(500, TimeUnit.MILLISECONDS)
}
override fun shouldRetry(throwable: Throwable): Boolean {
return true
} }
class RxStream500Retry(streamId: String) : RxStreamLimitedRetryFunction(2, streamId) {
override fun onRetry(attempsNb: Int): Observable<*> {
return Observable.timer(500, TimeUnit.MILLISECONDS)
}
override fun shouldRetry(throwable: Throwable): Boolean {
return false
}}
The shouldRetry method is simplified in this exemple
All of this retry function find they way in a list of retryfunction that is set using an ObservableTransformer to the observable via a retryWhen per function
class RetryComposer : ObservableTransformer<RxStreamSuccess, RxStreamSuccess> {
val retryFunctionList = arrayListOf(RxStream404Retry("Test1"),
RxStream500Retry("Test2")
)
override fun apply(upstream: Observable<RxStreamSuccess>): ObservableSource<RxStreamSuccess> {
retryFunctionList.forEach {
upstream.retryWhen(it)
}
return upstream
}}
My subscribing chain looks like this :
streamCache[stremId] = observable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe { listener.onLoading() }
.compose(RetryComposer())
.doOnComplete {
Log.d(" Retry onComplete")
streamCache.remove(stremId) }
.subscribe(
{ result -> listener.onSuccess(result) },
{ throwable ->
streamCache.remove(stremId)
}
)
When I test with an observable that goes into error nothing happen my RxStream404Retry is not trigger. Can you not put more thant one retryWhen per observable ?
Thank a lot
I think the issue comes from:
retryFunctionList.forEach {
upstream.retryWhen(it) <- this returns a new Observable that is not attached to any subscriber
}
This code is equivalent to:
Observable obs1 = upstream.retryWhen(RxStream404Retry("Test1"))
Observable obs2 = upstream.retryWhen(RxStream500Retry("Test2"))
return upstream
So, these observables are not subscribed by the subscriber of the main Rx chain.
You may have look at the amb() operators for that (http://reactivex.io/RxJava/2.x/javadoc/io/reactivex/Observable.html#amb-java.lang.Iterable-)
You may try something like:
return upstream.retryWhen(amb(retryFunctionList)) // pseudo code
That would be the rough idea.
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.
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 new to Kotlin and I am making a method that makes a call to an interface of Endpoints and uses one of the methods present there. I am using Observable<> instead of Call<> into the response. I wanted to know how to obtain the response body() in the "result" above. This is my method
private fun refreshUser(userLogin: String) {
executor.execute {
// Check if user was fetched recently
val userExists = userDao.hasUser(userLogin, getMaxRefreshTime(Date())) != null
// If user have to be updated
if (!userExists) {
disposable = endpoints.getUser(userLogin)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ result -> /*Get the response body() HERE*/},
{ error -> Log.e("ERROR", error.message) }
)
}
}
}
It all depends on how you have defined the Retrofit interface. In order to get the Response you need to return something from the interface that looks like:
fun getUsers() : Observable<Response<User>>
Then inside { result -> /*Get the response body() HERE*/}, you will get something of the form Response<User>, which has the response's body.
Also to note, you do not need to enclosing executor if you leverage Room for the dao interactions; it has RxJava support. You can use RxJava operators to combine the dao lookup with the server call.
See this tutorial
https://medium.freecodecamp.org/rxandroid-and-kotlin-part-1-f0382dc26ed8
//Kotlin
Observable.just("Hello World")
.subscribeOn(Schedulers.newThread())
//each subscription is going to be on a new thread.
.observeOn(AndroidSchedulers.mainThread()))
//observation on the main thread
//Now our subscriber!
.subscribe(object:Subscriber<String>(){
override fun onCompleted() {
//Completed
}
override fun onError(e: Throwable?) {
//TODO : Handle error here
}
override fun onNext(t: String?) {
Log.e("Output",t);
}
})
if you wanna use retrofit 2 and rxjava 2
https://medium.com/#elye.project/kotlin-and-retrofit-2-tutorial-with-working-codes-333a4422a890
interface WikiApiService {
#GET("api.php")
fun hitCountCheck(#Query("action") action: String,
#Query("format") format: String,
#Query("list") list: String,
#Query("srsearch") srsearch: String):
Observable<Model.Result>
}
Observable is the class response.
private fun beginSearch(srsearch: String) {
disposable =
wikiApiServe.hitCountCheck("query", "json", "search", srsearch)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ result -> showResult(result.query.searchinfo.totalhits) },
{ error -> showError(error.message) }
)
}
If, as you mentioned to #Emmanuel, the return type of your getUser() method is Observable<Response<User>> then calling result.body() will yield the resulting User.
{ result ->
val user: User = result.body()
}
If however, you are looking for the the raw response, you can instead call result.raw().body(); which will return an okhttp3.ResponseBody type.
{ result ->
val body: ResponseBody = result.raw().body()
val text: String = body.string()
}
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.