RxJava Fetch from Api if cache expired - android

I need to query data from Api then save it to Realm object. I need to get the data(observable) to the Presenter from the realm object unless 5 minutes from the last Api query elapsed, in other case I need to fetch from Api again. I`m new to RxJava. Any suggestions?

You could create a class hosting a ReplaySubject and some update logic:
class TimedCache<T> {
final Subject<T> cache =
ReplaySubject.createWithTime(5, TimeUnit.MINUTES).toSerialized();
final Single<T> valueProvider;
TimedCache(Single<T> valueProvider) {
this.valueProvider = valueProvider;
}
public Observable<T> valueObservable() {
return cache.take(1)
.switchIfEmpty(
valueProvider
.doOnSuccess(v -> {
cache.onNext(v);
// update realm here
})
.toObservable()
);
}
}

How about setting up two different observables:
1.) for observing the Realm data:
realm.where(MyData.class)
.findAllAsync()
.asFlowable()
.filter(RealmResults::isLoaded)
.subscribe(...);
2.) for fetching the data every 5 minutes
Observable.interval(5, TimeUnit.MINUTES)
.subscribeOn(Schedulers.io())
.switchMap((ignored) -> {
return apiService.getData();
})
.subscribe((data) -> {
try(Realm realm = Realm.getDefaultInstance()) {
realm.executeTransaction((r) -> {
r.insertOrUpdate(data);
});
}
});
EDIT: then just call a method like
flowable = realm.where(MyData.class)
.findAllAsync()
.asFlowable()
.filter(RealmResults::isLoaded)
.subscribe(...);
if(cache.isOlderThanFiveMinutes()) {
startRefreshTaskOnBackgroundThread();
}

Related

RxJava2 - execute call synchronously

I've a TestService, where I do an async task to get my data. I would like to wait for the response before I continue.
public List<Data> getData() {
List<Data> data = new ArrayList<>();
Disposable disposable = repository.getDataFromApi(false)
.observeOn(AndroidSchedulers.mainThread())
.subscribe( newData -> {
data.addAll(newData);
}, __ -> { });
mCompositeDisposable.add(disposable);
//Here I want to stop till "Data" arraylist is filled with data
... do something with data
}
In Volley I could just call req.executeSynchronously(); to make it happen. As getData() have to return data already, I've to somehow make it wait till I get response. How to do it? I'm using Single.
My approach using getBlocking();
public List<Data> getData() {
List<Data> data = new ArrayList<>();
Disposable disposable = repository.getDataFromApi(false)
.observeOn(AndroidSchedulers.mainThread())
.blockingGet();
.subscribe( newData -> {
data.addAll(newData);
}, __ -> { });
mCompositeDisposable.add(disposable);
//Here I want to stop till "Data" arraylist is filled with data
... do something with data
}
It says cannot resolve method subscribe, so I'm probably calling it wrong..
fun getDataFromApi(): Single<List<Data>> {
return service.getData()
.map { jsonApiObject ->
...
return#map data
}
}
Hopefully you are aware that blocking is a strong antipattern in RxJava and you should avoid blocking whenever you can.
Saying that, if you really need to block, you have two options:
use blockingGet() which - as the name indicates - blocks current thread and directly returns value of publisher (Single in your case). This is probably what you were looking for. In your case:
newData = repository.getDataFromApi(false).blockingGet();
data.addAll(newData);
synchronize with Java classes, like CountDownLatch - more complicated and I would use blockingGet() because it's more straightforward. But it's a possibility.

Rxjava + Realm access from incorrect thread

I'm getting this exception reading/writing from Realm
06-19 09:49:26.352 11404-11404/****** E/ContentValues: loadData: OnError Realm access from incorrect thread. Realm objects can only be accessed on the thread they were created.
java.lang.IllegalStateException: Realm access from incorrect thread. Realm objects can only be accessed on the thread they were created.
at io.realm.BaseRealm.checkIfValid(BaseRealm.java:385)
at io.realm.RealmResults.isLoaded(RealmResults.java:115)
at io.realm.OrderedRealmCollectionImpl.size(OrderedRealmCollectionImpl.java:307)
at io.realm.RealmResults.size(RealmResults.java:60)
at java.util.AbstractCollection.isEmpty(AbstractCollection.java:86)
at /****** .lambda$loadData$0(SplashPresenter.java:42)
at /****** $$Lambda$1.test(Unknown Source)
at io.reactivex.internal.operators.observable.ObservableFilter$FilterObserver.onNext(ObservableFilter.java:45)
at io.reactivex.observers.SerializedObserver.onNext(SerializedObserver.java:111)
at io.reactivex.internal.operators.observable.ObservableDelay$DelayObserver$1.run(ObservableDelay.java:84)
at io.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:59)
at io.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:51)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:272)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
at java.lang.Thread.run(Thread.java:761)
This is the code:
mSubscribe = Observable.just(readData())
.delay(DELAY, TimeUnit.SECONDS)
.filter(value -> !value.isEmpty())
.switchIfEmpty(createRequest())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()).subscribe(data -> {
getView().hideLoading();
writeData(data);
},
(throwable -> {
}));
Read data
private List<CategoryModel> readData() {
Realm defaultInstance = Realm.getDefaultInstance();
List<CategoryModel> title = defaultInstance.where(CategoryModel.class).findAllSorted("title");
defaultInstance.close();
return title;
}
Write data
private void writeData(List<CategoryModel> categoryModels) {
try {
Realm defaultInstance = Realm.getDefaultInstance();
defaultInstance.executeTransactionAsync(realm -> realm.insertOrUpdate(categoryModels));
defaultInstance.close();
} finally {
getView().notifyActivity(categoryModels);
}
}
How can I follow this logic using the proper threads?
The only rule to using Realm across threads is to remember that Realm, RealmObject or RealmResults instances cannot be passed across threads.
When you want to access the same data from a different thread, you should simply obtain a new Realm instance (i.e. Realm.getDefaultInstance()) and get your objects through a query (then close Realm at the end of the thread).
The objects will map to the same data on disk, and will be readable & writeable from any thread! You can also run your code on a background thread using realm.executeTransactionAsync() like this
.
How can i follow this logic using the proper threads?
By not trying to read on Schedulers.io() for your UI thread (Realm gives auto-updating lazy-loaded proxy views that provide change notifications for your data on the UI thread, after all).
So instead of this
mSubscribe = Observable.just(readData())
.delay(DELAY, TimeUnit.SECONDS)
.filter(value -> !value.isEmpty())
.switchIfEmpty(createRequest())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()).subscribe(data -> {
getView().hideLoading();
writeData(data);
},
(throwable -> {
}));
private List<CategoryModel> readData() {
Realm defaultInstance = Realm.getDefaultInstance();
List<CategoryModel> title = defaultInstance.where(CategoryModel.class).findAllSorted("title");
defaultInstance.close();
return title;
}
private void writeData(List<CategoryModel> categoryModels) {
try {
Realm defaultInstance = Realm.getDefaultInstance();
defaultInstance.executeTransactionAsync(realm -> realm.insertOrUpdate(categoryModels));
defaultInstance.close();
} finally {
getView().notifyActivity(categoryModels);
}
}
You're supposed to have something like
private Observable<List<CategoryModel>> readData() { // Flowable with LATEST might be better.
return io.reactivex.Observable.create(new ObservableOnSubscribe<List<CategoryModel>>() {
#Override
public void subscribe(ObservableEmitter<List<CategoryModel>> emitter)
throws Exception {
final Realm observableRealm = Realm.getDefaultInstance();
final RealmResults<CategoryModel> results = observableRealm.where(CategoryModel.class).findAllSortedAsync("title");
final RealmChangeListener<RealmResults<CategoryModel>> listener = results -> {
if(!emitter.isDisposed() && results.isLoaded()) {
emitter.onNext(results);
}
};
emitter.setDisposable(Disposables.fromRunnable(() -> {
if(results.isValid()) {
results.removeChangeListener(listener);
}
observableRealm.close();
}));
results.addChangeListener(listener);
}
}).subscribeOn(AndroidSchedulers.mainThread())
.unsubscribeOn(AndroidSchedulers.mainThread());
}
private void setSubscription() {
mSubscribe = readData()
.doOnNext((list) -> {
if(list.isEmpty()) {
Single.fromCallable(() -> this::createRequest)
.subscribeOn(Schedulers.io())
.subscribe((data) -> {
writeData(data);
});
}
}).subscribe(data -> {
if(!data.isEmpty()) {
getView().hideLoading();
getView().notifyActivity(data);
}
}, throwable -> {
throwable.printStackTrace();
});
}
private void writeData(List<CategoryModel> categoryModels) {
try(Realm r = Realm.getDefaultInstance()) {
r.executeTransaction(realm -> realm.insertOrUpdate(categoryModels));
}
}
void unsubscribe() {
mSubscribe.dispose();
mSubscribe = null;
}
This way (if I didn't mess anything up), you end up with the reactive data layer described here and here, except without the additional overhead of mapping out the entire results.
EDIT:
Since Realm 4.0, it is possible to expose a RealmResults directly as a Flowable (on the UI thread, or background looper thread).
public Flowable<List<MyObject>> getLiveResults() {
try(Realm realm = Realm.getDefaultInstance()) {
return realm.where(MyObject.class)
.findAllAsync()
.asFlowable()
.filter(RealmResults::isLoaded);
}
}
You need to extract required data from realm objects into POJO and emit POJOs using map operator, so that view objects can updated with data from realm using pojo on android main thread.
You can only manipulate Realm objects in a transaction or only in the thread you read/write these objects. In your case you are getting a RealmResult from the readData method and using RxJava you are switching threads which caused the exception. Use copyFromRealm to get the data from realm which will return them as plain objects rather than realm objects.

Realm Proper way of Copy Object In Transaction with RxJava

Currently, when copying a value to realm, i do the following:
public void addToRealm(Home item, RealmChangeListener<E> listener) {
realm.executeTransaction((Realm realm1) ->
realm1.copyToRealm(item).addChangeListener<Home>(listener));
}
And then i can access the newly added object inside the listener. What is the proper RxJava way of accomplishing the same? The observable must return
Observable<Home>, which is the realmCopy not the original object. Can any1 please provide a sample?
Managed to get it working by doing this, altought im not sure it is the best approach... What is the recommended approach?
return Observable.just(homeItem)
.map { (HomeItem homeItem) ->
return AnotherHomeItem(homeItem.xxx, homeItem.yyy)
}
.flatMap { (AnotherHomeItem anotherItem) ->
realm.beginTransaction()
val newItem = realm.copyToRealm(anotherItem).asObservable< AnotherHomeItem >()
realm.commitTransaction()
return newItem
}
.filter {
return it.isLoaded
}
You should write to the Realm on a background thread, and observe with a different subscription on the UI thread.
You persist with one subscription on the background thread:
public Subscription downloadObjectsFromNetwork() {
return objectApi.getObjects()
.subscribeOn(Schedulers.io())
.subscribe(response -> {
try(Realm realmInstance = Realm.getDefaultInstance()) {
realmInstance.executeTransaction(realm -> realm.insertOrUpdate(response.objects));
}
});
}
And you read with asObservable() on the UI thread:
public Subscription readFromRealm() {
return realm.where(SomeObject.class)
.findAllAsync()
.asObservable()
.subscribeOn(AndroidSchedulers.mainThread())
.observeOn(AndroidSchedulers.mainThread())
.filter(RealmResults::isLoaded)
.subscribe(objects -> adapter.updateData(objects));
}
Using Realm with RxJava
For queries, Realm provides the realmResults.asObservable() method. Observing results is only possible on looper threads (typically the UI thread).
For this to work, your configuration must contain the following
realmConfiguration = new RealmConfiguration.Builder(context) //
.rxFactory(new RealmObservableFactory()) //
//...
.build();
Afterwards, you can use your results as an observable.
Observable<RealmResults<SomeObject>> observable = results.asObservable();
For asynchronous queries, you should filter the results by isLoaded(), so that you receive an event only when the query has been executed. This filter() is not needed for synchronous queries (isLoaded() always returns true on sync queries).
Subscription subscription = RxTextView.textChanges(editText).switchMap(charSequence ->
realm.where(SomeObject.class)
.contains("searchField", charSequence.toString(), Case.INSENSITIVE)
.findAllAsync()
.asObservable())
.filter(RealmResults::isLoaded) //
.subscribe(objects -> adapter.updateData(objects));
For writes, you should either use the executeTransactionAsync() method, or open a Realm instance on the background thread, execute the transaction synchronously, then close the Realm instance.
public Subscription loadObjectsFromNetwork() {
return objectApi.getObjects()
.subscribeOn(Schedulers.io())
.subscribe(response -> {
try(Realm realmInstance = Realm.getDefaultInstance()) {
realmInstance.executeTransaction(realm -> realm.insertOrUpdate(response.objects));
}
});
}

Emit Observable only after second Observable is received

I'm using Rx for calling our API with Retrofit. At some point I need to call our API, wait for response 1, extract some metadata from it and then call API again waiting for response 2. After I have response 2 I can emit my Observable. My problem is, I don't know how to:
Make a call 2 and emit only after I have response 2
Here are my functions from the class that should emit Model Observable. Method get2 doesn't have to be visible for outside world.
public Observable<Model> get1(String slug) {
return api1
.getInfo(slug)
.subscribeOn(Schedulers.io())
.map(resonse1 -> {
String metadata = response1.getMetadata();
//Make call2 with metadata
//call(2)
Model model = response1.getModel();
model.setInfo(/*Info from call2*/)
return model;
})
.observeOn(AndroidSchedulers.mainThread());
}
private Observable<Info> get2(String metadata) {
return api2.getInfo(new InfoAsset(metadata))
.subscribeOn(Schedulers.io())
.map(response2 -> {
return response2.getInfo;
})
.observeOn(AndroidSchedulers.mainThread());
}
Instead of map use flatMap:
.flatMap(response1 -> {
String metadata = response1.getMetadata();
return get2(metadata)
.map(info -> {
Model model = response1.getModel();
model.setInfo(info);
return model;
});
})
...
Be careful though because you are using mutable objects across threads so you may have visibility problems. Consider using immutable objects or ensure changes are synchronized.
Use nested flatMaps, and don't use observeOn unless you want to do thread hopping:
private Observable<Info> get2(String metadata) {
return api2.getInfo(new InfoAsset(metadata))
.subscribeOn(Schedulers.io())
.map(response2 -> {
return response2.getInfo;
});
// no ObserveOn here.
}
public Observable<Model> get1(String slug) {
return api1
.getInfo(slug)
.subscribeOn(Schedulers.io())
.flatMap (response1 -> {
Model model = response1.getModel();
return get2(response1.getMetadata())
.map(response2 -> {
model.setInfo(response2);
return model;
});
);
});
}

Correct flow in RxJava with Retrofit and Realm

I'm implementing network API with the combination of RxJava and Retrofit, and I use Realm as my database. I got it pretty much working but I'm wondering if it is the correct approach and flow of events. So, here is the RetrofitApiManager.
public class RetrofitApiManager {
private static final String BASE_URL = "***";
private final ShopApi shopApi;
public RetrofitApiManager(OkHttpClient okHttpClient) {
// GSON INITIALIZATION
Retrofit retrofit = new Retrofit.Builder()
.client(okHttpClient)
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.baseUrl(BASE_URL)
.build();
shopApi = retrofit.create(ShopApi.class);
}
public Observable<RealmResults<Shop>> getShops() {
return shopApi.getShops()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext(response -> {
Realm realm = Realm.getDefaultInstance();
realm.executeTransaction(realm1 ->
realm1.copyToRealmOrUpdate(response.shops));
realm.close();
})
.flatMap(response -> {
Realm realm = Realm.getDefaultInstance();
Observable<RealmResults<Shop>> results = realm.where(Shop.class)
.findAllAsync()
.asObservable()
.filter(RealmResults::isLoaded);
realm.close();
return results;
});
}
}
And here is the call to get RealmResults<Shop> inside a Fragment.
realm.where(Shop.class)
.findAllAsync()
.asObservable()
.filter(RealmResults::isLoaded)
.first()
.flatMap(shops ->
shops.isEmpty() ? retrofitApiManager.getShops() : Observable.just(shops))
.subscribe(
shops -> initRecyclerView(),
throwable -> processError(throwable));
Here are my questions:
Is it a correct approach to chain events like in the example above or should I manage them in a different way?
Is it OK to useRealm instance in getShops() method and close i there or would it be better to pass it as an argument and then manage it somehow? Although, this idea seems to be a bit problematic with threads and calling Realm.close() always at the right time.
1) I would try to do as much as possible on the background thread, right now you are doing a lot of the work on the UI thread.
2)
public Observable<RealmResults<Shop>> getShops() {
return shopApi.getShops()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext(response -> {
try(Realm realm = Realm.getDefaultInstance()) {
realm.executeTransaction(realm1 ->
realm1.insertOrUpdate(response.shops));
} // auto-close
})
.flatMap(response -> {
try(Realm realm = Realm.getDefaultInstance()) {
Observable<RealmResults<Shop>> results = realm.where(Shop.class)
.findAllAsync()
.asObservable()
.filter(RealmResults::isLoaded);
} // auto-close
return results;
});
}
All Realm data is lazy-loaded, so it is only available while the Realm instance is open, so closing it after retrieving it has a high chance of not working. In your case though you are flat-mapping on the main thread, so most likely there is already an open instance there.
If you want you can use copyFromRealm() to get unmanaged data out that can be moved across threads and are not connected to Realm anymore, but they will also loose their live update features and take up more memory.
It would probably do this instead:
public Observable<RealmResults<Shop>> getShops() {
return shopApi.getShops()
.subscribeOn(Schedulers.io())
.doOnNext(response -> {
try(Realm realm = Realm.getDefaultInstance()) {
realm.executeTransaction(realm1 ->
realm1.copyToRealmOrUpdate(response.shops));
} // auto-close
})
.observeOn(AndroidSchedulers.mainThread())
.flatMap(response -> {
Observable<RealmResults<Shop>> results = realm.where(Shop.class)
.findAllAsync()
.asObservable()
.filter(RealmResults::isLoaded);
return results;
});
Alternatively you can treat the network request as a side-effect and just depend on Realm notifying you when there is changes (better approach IMO as you separate network from DB access which is e.g. what the Repository pattern is about)
public Observable<RealmResults<Shop>> getShops() {
// Realm will automatically notify this observable whenever data is saved from the network
return realm.where(Shop.class).findAllAsync().asObservable()
.filter(RealmResults::isLoaded)
.doOnNext(results -> {
if (results.size() == 0) {
loadShopsFromNetwork();
}
});
}
private void loadShopsFromNetwork() {
shopApi.getShops()
.subscribeOn(Schedulers.io())
.subscribe(response -> {
try(Realm realm = Realm.getDefaultInstance()) {
realm.executeTransaction(r -> r.insertOrUpdate(response.shops));
} // auto-close
});
}
What Christian Melchior mentioned in his answer, makes perfect sense, and should solve the problem you are having at your hand, but down the line this approach may introduce other issue(s).
In a good architecture, all the major modules(or libraries) should be isolated from rest of the code. Since Realm, RealmObject or RealmResult can not be passed across threads it is even more important to make Realm & Realm related operations isolated from rest of the code.
For each of your jsonModel class, you should have a realmModel class and a DAO (Data Access Object). Idea here is that other than DAO class none of the class must know or access realmModel or Realm. DAO class takes jsonModel, converts to realmModel, performs read/write/edit/remove operations, for read operations DAO converts realmModel to jsonModel and returns with it.
This way it is easy to maintain Realm, avoid all Thread related issues, easy to test and debug.
Here is an article about Realm best practices with a good architechture https://medium.com/#Viraj.Tank/realm-integration-in-android-best-practices-449919d25f2f
Also a sample project demonstrating Integration of Realm on Android with MVP(Model View Presenter), RxJava, Retrofit, Dagger, Annotations & Testing. https://github.com/viraj49/Realm_android-injection-rx-test
In my case, I seem to have defined a query for the RealmRecyclerViewAdapter like this:
recyclerView.setAdapter(new CatAdapter(getContext(),
realm.where(Cat.class).findAllSortedAsync(CatFields.RANK, Sort.ASCENDING)));
And otherwise defined a condition for Retrofit with RxJava to download more stuff when the condition is met:
Subscription downloadCats = Observable.create(new RecyclerViewScrollBottomOnSubscribe(recyclerView))
.filter(isScrollEvent -> isScrollEvent || realm.where(Cat.class).count() <= 0)
.switchMap(isScrollEvent -> catService.getCats().subscribeOn(Schedulers.io())) // RETROFIT
.retry()
.subscribe(catsBO -> {
try(Realm outRealm = Realm.getDefaultInstance()) {
outRealm.executeTransaction((realm) -> {
Cat defaultCat = new Cat();
long rank;
if(realm.where(Cat.class).count() > 0) {
rank = realm.where(Cat.class).max(Cat.Fields.RANK.getField()).longValue();
} else {
rank = 0;
}
for(CatBO catBO : catsBO.getCats()) {
defaultCat.setId(catBO.getId());
defaultCat.setRank(++rank);
defaultCat.setSourceUrl(catBO.getSourceUrl());
defaultCat.setUrl(catBO.getUrl());
realm.insertOrUpdate(defaultCat);
}
});
}
}, throwable -> {
Log.e(TAG, "An error occurred", throwable);
});
And this is for example a search based on an edit text's input:
Subscription filterDogs = RxTextView.textChanges(editText)
.switchMap((charSequence) ->
realm.where(Dog.class)
.contains(DogFields.NAME, charSequence.toString())
.findAllAsyncSorted(DogFields.NAME, Sort.ASCENDING)
.asObservable())
.filter(RealmResults::isLoaded)
.subscribe(dogs -> realmRecyclerAdapter.updateData(dogs));

Categories

Resources