I have this code:
compositeDisposable.add(RetrofitClient
.getApi()
.getData()
.flatMap(response -> {
Data data;
if (response.isSuccessful()) {
data = response.body();
//insert data to database
Database.getInstance(context)
.getDao()
.insert(data);
} else {
ResponseBody responseBody = response.errorBody();
if (responseBody != null) {
data = new Gson().fromJson(responseBody.charStream(), Data.class);
}
}
return Observable.just(data);
})
.onErrorResumeNext(throwable -> {
//get data from database
Data data = Database.getInstance(context).getDao().getData();
return Observable.just(data);
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.retryWhen(throwableObservable ->
throwableObservable.take(1).delay(1, TimeUnit.SECONDS))
.doOnTerminate(view::hideScreenLoader)
.subscribe(this::showData, throwable -> {
Toast.makeText(context,
throwable.getMessage(), Toast.LENGTH_LONG).show();
}));
I can't understand why sometimes users got this error: "Caused by java.lang.RuntimeException Can't toast on a thread that has not called Looper.prepare()".
Toast must called at main thread, thanks.
I found the decision in this topic: how to handle RxAndroid errors in the main thread;
Need to swap "retryWhen" and "observeOn", because retryWhen has "delay function", that switch to computation thread :)
Related
I'm using Single.fromCallable() to make a network call and I want to use the data from the response to make a second call.
How can I chain these two calls instead of nesting them?
private void queryForUser() {
Single.fromCallable(() -> remoteRepository.queryForUser()))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(response-> {
String username = response.getUsername();
//Perform second call
performSecondQuery(username);
}, err -> {
Log.e(TAG, "Failed to get user", err);
});
}
private void performSecondQuery(String username){
Single.fromCallable(() -> remoteRepository.performSecondQuery(username)))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(secondQueryResult -> {
Log.d(TAG, "performSecondQuery: " + secondQueryResult);
}, err -> {
Log.e(TAG, "Failed to perform second query", err);
});
}
My Java lambda syntax is rusty, but using flatMap() should give you something like this:
private Single<Whatever> performSecondQuery(String username){
return Single.fromCallable(() -> remoteRepository.performSecondQuery(username)))
}
private void queryForUser() {
Single.fromCallable(() -> remoteRepository.queryForUser()))
.flatMap { response -> performSecondQuery(response.getUsername()) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
// TODO something with the result
}, err -> {
Log.e(TAG, "Failed to get user", err);
});
}
(note: I don't know what the second query is doing, so I don't know what Whatever is — it would be the return type of remoteRepository.performSecondQuery())
I am new to doing asynchronous programming in Android Java. I am wondering if there is a way to run another Callback after an initial Callback function has completed. Right now, I think they are running in parallel even though the second relies on the first.
First Callback:
// GETTING USER
private interface FirestoreUserCallback {
void onCallback (User myUser);
}
private void getUser(final FirestoreUserCallback firestoreCallback) {
Task<DocumentSnapshot> task = fStore.collection("users").document(fAuth.getCurrentUser().getUid()).get();
task.addOnSuccessListener(new OnSuccessListener<DocumentSnapshot>() {
#Override
public void onSuccess(DocumentSnapshot documentSnapshot) {
user = documentSnapshot.toObject(User.class);
firestoreCallback.onCallback(user);
Log.d(TAG, "user created");
}
});
task.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.d(TAG, "user creation failed");
}
});
}
Second Callback:
// GETTING ALL DOCUMENTS
private interface FirestoreDocumentCallback {
void onCallback (List<TableEntries> myEntries);
}
private void getDocuments (final FirestoreDocumentCallback firestoreDocumentCallback) {
fStore.collection("result")
.document(Integer.toString(user.getCompanyNumber())) // need to use User object returned from the first Callback
.collection("SAM").get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
List<TableEntries> results = new ArrayList<>();
if (task.isSuccessful()) {
for (QueryDocumentSnapshot document : task.getResult()) {
// add objects to results ArrayList ...
Log.d(TAG, document.getId() + " => " + document.getData());
}
firestoreDocumentCallback.onCallback(results);
} else {
Log.d(TAG, "Error getting documents: ", task.getException());
}
}
});
}
onCreate:
getUser(new FirestoreUserCallback () {
#Override
public void onCallback(User myUser) {
user = myUser;
}
});
getDocuments(new FirestoreDocumentCallback() {
#Override
public void onCallback(List<TableEntries> myEntries) {
entries = myEntries;
}
});
getDocuments() relies on the user variable being given its value from the first Callback. I'm receiving this error:
java.lang.NullPointerException: Attempt to invoke virtual method 'double java.lang.Double.doubleValue()' on a null object reference
Callbacks are looking fine. You just need to check if your value is null or not before accessing it. Just add a null check
if(doubleValue!=null)
Using RxJava. First, we fetch the user and then fetch the documents. Rx-Java has an operator flatmap. flatmap is used to execute the sequential tasks, where the second task is dependent on the data from the first task.
final CompositeDisposable disposable = new CompositeDisposable();
//function to fetch user data
Single<User> getUser(){
return API.getUserData(...);
}
//function to fetch ducuments
Sinlge<UserDetail> getDocuments(int userId){
return API.getUserDetail(userId, ...);
}
//Subscribe
disposable.add(getUser()
.flatmap(user-> return getDocuments(...))
.subscribeOn(Scheduler.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableSingleObservable(){
#Override
public void onSuccess(UserDetail userDetail){
Log.v("Api result", "Successful";
//Do some work
}
#Override
public void onError(Throwable e)
Log.v("Api result", "Error Returned");
}
}));
If either of the API call fails, onError() is called. If first API fails, second API call is not executed and onError() is called.
The simplest solution for your use-case is to pass both queries to Tasks.whenAllSuccess() method, as explained in my answer from the following post:
Firestore - Merging two queries locally
So once the task is complete, you can use the elements from both queries. Another solution might be to use Android Jetpack with LiveData along with ViewModel, as the Android team recommends.
I'm noob in RxJava but someday... ; )
What I have:
Receiving json with Event Planner Model
inside this model the are Tasks
I want to save this tasks into local db but also want add to them parent_id in case of relationship
Can someone with more experience take a look at this code:
private fun saveEventPlanners(eventPlanners: ArrayList<EventPlanner>) {
LogMgr.d(TAG, "saveEventPlanners() events:$eventPlanners")
compositeDisposable.add(wfmStorageDomain.saveEventPlanners(eventPlanners)
.subscribeOn(rxSchedulers.computation())
.observeOn(rxSchedulers.computation())
.subscribe({ saved ->
LogMgr.v(TAG, "event planners saved successfully, value: $saved")
val taskList = ArrayList<Task>()
eventPlanners.forEach { eventPlanner ->
if (eventPlanner.status == EventPlanner.EventPlannerStatus.NEW) {
FS.get().wfmComponent.getEventPlannerStatusChanger().updateEventPlannersStatus(eventPlanner.event_id!!, EventPlanner.EventPlannerStatus.RECEIVED)
}
(eventPlanner.tasks as ArrayList<Task>).forEach {
val task = Task()
task.id = it.id
task.status_id = Task.STATUS.NEW
task.name = it.name
task.end_scenario_id = it.end_scenario_id
task.start_scenario_id = it.start_scenario_id
task.isBind = it.isBind
task.parent_event_planner_id = eventPlanner.event_planner_id
taskList.add(task)
}
compositeDisposable.add(wfmStorageDomain.saveTasks(taskList)
.subscribeOn(rxSchedulers.computation())
.observeOn(rxSchedulers.computation())
.subscribe({
LogMgr.d(TAG, "tasks saved successfully = $it")
}, {
LogMgr.e(TAG, "Error while saving tasks", it)
}))
}
}, {
LogMgr.e(TAG, "Error while saving event planners", it)
}))
notifyObservers(eventPlanners)
}
I'm getting error in this function:
java.util.ConcurrentModificationException
at java.util.ArrayList$ArrayListIterator.next(ArrayList.java:573)
at com.raizlabs.android.dbflow.sql.saveable.ListModelSaver.saveAll(ListModelSaver.java:32)
at com.raizlabs.android.dbflow.sql.saveable.ListModelSaver.saveAll(ListModelSaver.java:19)
at com.raizlabs.android.dbflow.structure.ModelAdapter.saveAll(ModelAdapter.java:196)
at com.raizlabs.android.dbflow.rx2.structure.RXModelAdapter$3.call(RXModelAdapter.java:61)
at com.raizlabs.android.dbflow.rx2.structure.RXModelAdapter$3.call(RXModelAdapter.java:58)
at io.reactivex.internal.operators.completable.CompletableFromCallable.subscribeActual(CompletableFromCallable.java:35)
at io.reactivex.Completable.subscribe(Completable.java:1919)
at io.reactivex.internal.operators.completable.CompletableSubscribeOn$SubscribeOnObserver.run(CompletableSubscribeOn.java:64)
at io.reactivex.Scheduler$DisposeTask.run(Scheduler.java:579)
at io.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:66)
at io.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:57)
at java.util.concurrent.FutureTask.run(FutureTask.java:234)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:153)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:267)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1080)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:573)
at java.lang.Thread.run(Thread.java:841)
And this is how looks saving into db:
override fun saveTasks(tasks: ArrayList<Task>): Single<Boolean> {
LogMgr.d(TAG, "saveTasks() : $tasks")
return Single.create({ emitter ->
RXModelAdapter.from(Task::class.java)
.saveAll(tasks)
.subscribeOn(getSubscriptionScheduler())
.subscribe({
LogMgr.d(TAG, "saveTasks() onComplete")
emitter.onSuccess(true)
}, {
LogMgr.e(TAG, "saveTasks() onError ", it)
emitter.onError(it)
})
})
}
private fun getSubscriptionScheduler(): Scheduler {
return FS.get().commonComponent.rxSchedulers.get().sqlite()
}
The problem is this:
val taskList = ArrayList<Task>()
eventPlanners.forEach { eventPlanner ->
// ...
compositeDisposable.add(wfmStorageDomain.saveTasks(taskList)
// ...
}
You have a list which you keep adding from different eventPlanner items and submitting that partial list to the save task. If there are more than one item in eventPlanners this will result in the ConcurrentModificationException error. Either make taskList local to the forEach loop or move the saveTasks out of forEach.
I am messing around some with the google awareness api and now my understanding of RxJava is limiting me.
What I want to achieve in the end:
I want to get a Weather and a Location from the Api, and merge them into one object that I can pass on to my view for update.
However, I'm not sure how I achieve the returning of an Observable from the api callback here since it has void return type, and how to achieve merging of the weather and location object from api.getWeather and api.getLocation
public void requestUserCurrentInfo() {
Subscription userInfo = getWeatherLocation().subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()).subscribe(userinfo ->
Log.d(TAG,userinfo.something()));
}
public Observable<UserInfo> getWeatherLocation () {
try {
Awareness.SnapshotApi.getWeather(client)
.setResultCallback(weather -> {
if (!weather.getStatus().isSuccess()) {
Log.d(TAG, "Could not get weather");
return;
}
//How do I do here?
return weather.getWeather();
});
Awareness.SnapshotApi.getLocation(mGoogleApiClient)
.setResultCallback(retrievedLocation -> {
if(!retrievedLocation.getStatus().isSuccess()) return;
Log.d("FRAG", retrievedLocation.getLocation().getLatitude() + "");
});
} catch (SecurityException exception) {
throw new SecurityException("No permission " + exception);
}
}
For my other things in my Project, I get some stuff through a REST api following the repository pattern, then I can get it like this because every step returns a Observable< SmhiResponse >
getWeatherSubscription = getWeatherUsecase.execute().subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()).subscribe(
smhiResponseModel -> {Log.d(TAG,"Retrieved weather"); locationView.hideLoading();},
err -> {Log.d(TAG,"Error fetching weather"); locationView.hideLoading();}
);
You don't return an observable from the callback but wrap your callbacks into observables to make them combinable (untested):
Observable<WeatherResult> weatherObservable = Observable.create(subscriber -> {
Awareness.SnapshotApi.getWeather(client)
.setResultCallback(weather -> {
if (!weather.getStatus().isSuccess()) {
subscriber.onError(new Exception("Could not get weather."));
Log.d(TAG, "Could not get weather");
} else {
//How do I do here?
subscriber.onNext(weather);
subscriber.onCompleted();
}
});
});
Observable<LocationResult> locationObservable = Observable.create(subscriber -> {
Awareness.SnapshotApi.getLocation(mGoogleApiClient)
.setResultCallback(retrievedLocation -> {
if(!retrievedLocation.getStatus().isSuccess()) {
subscriber.onError(new Exception("Could not get location."));
} else {
Log.d("FRAG", retrievedLocation.getLocation().getLatitude() + "");
subscriber.onNext(retrievedLocation);
subscriber.onCompleted();
}
});
});
now combine them via .combineLatest() or .zip():
Observable<CombinedResult> combinedResults = Observable.zip(weatherObservable, locationObservable,
(weather, location) -> {
/* somehow combine weather and location then return as type "CombinedResult" */
});
don't forget to subscribe, otherwise none of them gets executed:
combinedResults.subscribe(combinedResult -> {/*do something with that stuff...*/});
Observable.combineLatest(getWeather (), getLocation(), new Func2<List<Object_A>, List<Object_B>, Object>() {
#Override
public Object call(Object o, Object o2) {
combine both results and return the combine result to observer
}
})
getweather() and getlocation() return observables
i'm having trouble implementing rxJava in order to check if there is internet connection on android i'm doing it like this:
on my launcher activity i have this in onCreate:
AndroidObservable.bindActivity(this,
Observable.just(Utils.isActiveInternetConnection(Launcher.this)))
.subscribeOn(Schedulers.newThread())
.subscribe(new Action1<Boolean>() {
#Override
public void call(Boolean aBoolean) {
if (aBoolean) {
Toast.makeText(Launcher.this, "There is internet connection", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(Launcher.this, "There is no internet connection", Toast.LENGTH_SHORT).show();
}
}
});
i have an Utils class it is a final class with static methods the methods in that the observable is using are this ones:
public static boolean isActiveInternetConnection(Context context) {
if (isNetworkAvailable(context)) {
try {
HttpURLConnection urlc = (HttpURLConnection) (new URL("http://www.google.com").openConnection());
urlc.setRequestProperty("User-Agent", "Test");
urlc.setRequestProperty("Connection", "close");
urlc.setConnectTimeout(1500);
urlc.connect();
return (urlc.getResponseCode() == 200);
} catch (IOException e) {
Log.e("network", "Error checking internet connection", e);
}
} else {
Log.d("network", "No network available!");
}
return false;
}
private static boolean isNetworkAvailable(Context context){
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
if (null != activeNetwork) {
return true;
}
else {
return false;
}
}
i'm receiving android.os.NetworkOnMainThreadException and i can't find why, thanks in advance.
Observable.just(...) is called immediately on the calling thread (the main thread, in this case). Your code is effectively just an inlined version of this:
boolean activeConn = Utils.isActiveInternetConnection(Launcher.this);
AndroidObservable.bindActivity(this,
Observable.just(activeConn))
.subscribeOn(...)
...
You've tried to move it off the main thread by calling subscribeOn() - but the call has already happened.
The way we handle this (and I'm not sure that this is the best way, but it works) is to defer the network or blocking call until subscription happens, set up the observable to run on the correct threads, and then subscribe:
AndroidObservable.bindActivity(this,
Observable.defer(new Func0<Boolean>() {
#Override
public Observable<Observable<Boolean>> call() {
return Observable.just(Utils.isActiveInternetConnection(Launcher.this));
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<Boolean>() {
#Override
public void call(Boolean aBoolean) {
if (aBoolean) {
Toast.makeText(Launcher.this, "There is internet connection", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(Launcher.this, "There is no internet connection", Toast.LENGTH_SHORT).show();
}
}
});
AdamS is correct, however RxJava 2 now offers Observable.fromCallable() to defer an observable operation till subscription.
A good reference:
https://caster.io/lessons/fromcallable-converting-slow-methods-into-an-observable/
Some example code from my use-case:
Single.fromCallable(new Callable<Response>() {
#Override
public Response call() throws Exception {
return NetworkGateway.networkRequest();
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(observer);
I guess just just calls the method synchronously as it expects the boolean value and it tries to get it.
I am rather bad at RxJava but you may try something like this:
Observable<Boolean> onlineObservable = Observable.create(new Observable.OnSubscribe<Boolean>() {
#Override
public void call(Subscriber subscriber) {
subscriber.onNext(Utils.isActiveInternetConnection(context));
}
});
onlineObservable.subscribeOn(Schedulers.newThread()).subscribe(result -> {...});
this is my retrieve data from DataBase by RXAndroid code:
Observable.create(new Observable.OnSubscribe<List<GRO_VO>>() {
#Override
public void call(Subscriber<? super List<GRO_VO>> subscriber) {
String jsonIn;
jsonIn =retrieveDataFromDB();
Gson gson = new Gson();
Type listType = new TypeToken<List<GRO_VO>>() {
}.getType();
eventJoinList = gson.fromJson(jsonIn, listType);
Log.d("RX",jsonIn);
subscriber.onNext(eventJoinList);
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<List<GRO_VO>>() {
#Override
public void call(List<GRO_VO> eventJoinList) {
Log.d("RX", ".subscribe");
recyclerView.setAdapter(new EventJoinAdapter(eventJoinList));
}
});
I think the just operator will emit data immediately, so it's is not useful to retrieve data from a database via network. It is very easy to use, but it can only be used for data that's already in the ram of the device.
I also had that problem, like #Baniares had, but after I use the create operator, all the problem gone...
From the RXJava documentation:
static <T> Observable<T> create(Observable.OnSubscribe<T> f)
Returns an Observable that will execute the specified function when a Subscriber subscribes to it.
Using the create operator can establish the standard process:
1 .subscribe(...) Subscriber(the sub class of class Observer) start the connection to Observable.
2 .subscribeOn(Schedulers.io()) collect a backGround Thread from the RX-ThreadPool
3 .create(...) retrieve data from server...during some netWork..etc
4 .observeOn(AndroidSchedulers.mainThread()) this means data will be set by the UI-Thread
5 Once we get the data , we can set the data in the onNext( ) method in the .subscribe( ) , the data will be set on the UI by UI-Thread since we make UI-thread do the work on the .observerOn(AndroidSchedulers.mainThread())
And note that if you use .create( ) operator,you must finished your observable in the .create( ) , others operator such like map,flatMap will not be executed after the .create( ) operator.
A very important concept we must need to know before start to use RXJava/RXAndroid. RX is a callback-based library, you tell RX what it should to do in what condition, it invoke your pass-in function(or in JAVA I may should to call them anonymous inner class... ) to achieve what you want.