I'm new in rxjava or rxandroid, and looking for a better way dealing with multiple requests. I need to get the token from server and use the result as a parameter to do login verification and if it returns success then get the sessionId through getSessionId method.
I've considered about zip or merge, but I don't think it'll work. So can you give me an idea or I don know , train of thought?
Thank you.
Here's my code:
private void getToken(final String name , final String pwd){
api.newToken()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<TokenModel>() {
#Override public void call(TokenModel tokenModel) {
String token = tokenModel.request_token;
if (!"".equals(token)){
login(token, name, pwd);
}else {
Timber.e("got token failed");
}
}
});
}
private void login(String token, String name, String pwd){
api.validateToken(token, name, pwd)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<TokenModel>() {
#Override public void call(TokenModel tokenModel) {
String token = tokenModel.request_token;
if (!"".equals(token)){
getSessionId(token);
}else {
Timber.e("got token failed");
}
}
});
}
private void getSessionId(String token){
api.newSessionn(token)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<TokenModel>() {
#Override public void onCompleted() {
//go to home activity
}
#Override public void onError(Throwable e) {
//handle error
}
#Override public void onNext(TokenModel tokenModel) {
//store session id
}
});
}
Your first subscription call your second subscription, ...
You can avoid this using flapmap operator.
api.newToken(...)
.flapMap(token -> api.validateToken(token))
.flapMap(token -> api.newSession(token)).subscribe()
New observable in a subscription can offen be replaced by a flatMap call.
If you want to manage your error, in a flatMap, if the token is invalid, your can return an error observable instead of returning new api call observable.
.flatMap(token -> if(token.isValid){ return api.newCall(); } else { return Observable.error(...); ;)
Related
I am using mvvm architecture I would like to notify view when volley post request is successful, what i could do is to instantiate ViewModel in appRepository class and then post values to a liveData, but i guess that's not a good approach as I haven't seen a similar practice. Can anyone suggest me a good approach to return my response to ui, or at least notify that post request has been successful.
From fragment/View I trigger this method
// save data to api
checkInViewModel.updateEventPersonEntity(eventPersonsEntity);
ViewModel forwards it to apprespository
public void updateEventPersonEntity(EventPersonsEntity eventPersonsEntity) {
mRepository.updateEventPersonEntity(eventPersonsEntity);
}
AppRepository.Java class
public void updateEventPersonEntity(EventPersonsEntity eventPersonsEntity) {
executor.execute(() -> {
// mDb.eventPersonsDao().update(eventPersonsEntity);
if (isNetworkAvailable(context)) {
post_updateEventPersonEntity(eventPersonsEntity);
}
});
}
private void post_updateEventPersonEntity(EventPersonsEntity eventPersonsEntity) {
Map<String, Object> params = new HashMap<>();
params.put("EventPersonId", eventPersonsEntity.getEventPersonId());
params.put("EventId", eventPersonsEntity.getEventId());
params.put("PersonId", eventPersonsEntity.getPersonId());
params.put("CashStart", parseDoubleToGerman(eventPersonsEntity.getCashStart()));
params.put("CashEnd", parseDoubleToGerman(eventPersonsEntity.getCashEnd()));
params.put("StartingTime", String.valueOf(eventPersonsEntity.getStartingTime()));
params.put("EndingTime", String.valueOf(eventPersonsEntity.getEndingTime()));
params.put("isChekcedIn", eventPersonsEntity.getIsCheckedIn());
params.put("isChekcedOut", eventPersonsEntity.getIsCheckedOut());
JSONObject objRegData = new JSONObject(params);
String eventPersonApi = APP_URL.EVENT_PERSONS_API + eventPersonsEntity.getEventPersonId();
RequestQueueSingleton.getInstance(context).objectRequest(eventPersonApi, Request.Method.PUT, this::onSuccess_updateEventPersonEntity, this::onError, objRegData);
}
private void onError(VolleyError error) {
Log.d(APP_REPOSITORY_TAG, "requestError: " + error);
}
private void onSuccess_updateEventPersonEntity(JSONObject jsonObject) {
// notify ui
}
You can do this same as you did for your success response logic in repository. Simply create new callback interface:
interface OnEventUpdatedListener{
void eventUpdated();
}
Then, update your method to look like this, passing the listener to the actual method that does the work:
public void updateEventPersonEntity(EventPersonsEntity eventPersonsEntity, OnEventUpdatedListener listener) {
mRepository.updateEventPersonEntity(eventPersonsEntity, listener);
}
Pass this inside your:
if (isNetworkAvailable(context)) {
post_updateEventPersonEntity(eventPersonsEntity, listener);
}
After that, in your onSuccess() method simply call:
private void onSuccess_updateEventPersonEntity(JSONObject jsonObject) {
listener.eventUpdated();
}
Finally, you will have the info when the update happens, in the calling site, if you call your repository like this:
updateEventPersonEntity(null, new OnEventUpdatedListener() {
#Override
public void EventUpdated() {
// Do your logic here
}
});
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 want to perform tasks in RxJava one by one.
For Example:-
1. Fetch User Ids from Server
2. Fetch Users from server by thier Ids.
I have tried this method
public Observable<List> getUids(){
return Observable.create(emitter -> {
List<String> uids = new ArrayList<>();
//fetchData from server
emitter.onNext(uids);
});
}
public Observable<User> getUser(String uid){
return Observable.create(emitter -> {
User user = new User();
//fetchData user from server
emitter.onNext(user);
});
}
//Executing this code like
getUids().flatMapIterable(ids -> ids)
.flatMap(this::getUser)
.subscribe(new Observer<User>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(User user) {
print("next "+user.getName());
}
#Override
public void onError(Throwable e) {
print("error "+e.getMessage());
}
#Override
public void onComplete() {
print("complete");
}
});
There are some problems in it
1.this is not calling Subscriber's onComplete() method when all users are fetched.
2.if there is an error in getUser method, app is crashing. with io.reactivex.exceptions.UndeliverableException exception
Can you please tell me where I am mistaking?
Call emitter.onComplete() in your getUids() and getUser(...) Observables and then append .toList() after .flatMap(this::getUser).
Returns a Single that emits a single item, a list composed of all the items emitted by the finite source ObservableSource.
UndeliverableException is a wrapper for the exception that is happening in your .flatMap(this::getUser). I can not help you more with the information that you provided, what is it that you want to happen when an exception is thrown?
I have this code :
getLocationObservable() // ---> async operation that fetches the location.
// Once location is found(or failed to find) it sends it to this filter :
.filter(location -> { // ---> I want to use this location in the the onNext in the end
after finishing some calculation here, I either return 'true' and continue
to the next observable which is a Retrofit server call, or simply
return 'false' and quit.
})
.flatMap(location -> getRetrofitServerCallObservable( location )
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
new Observer<MyCustomResponse>() {
#Override
public void onSubscribe(Disposable d) {
_disposable = d;
}
#Override
public void onNext(MyCustomResponse response) {
// I want to be able to use the `location` object here
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
}
});
I want to be able to use the location object from line 3(first observable), in the "onNext" that is trigerred by the second observable.
I can't manage to work it out.. any help would be much appreciated.
Instead of
getRetrofitServerCallObservable( location )
you could map the result to be a Pair (from your favourite library) of the response and the location:
getRetrofitServerCallObservable( location ).map(response -> Pair.create(location, response))
Then, in your onNext, you'd be receiving Pair<Location,MyCustomResponse> instances.
If you don't want to use a Pair class, you could use Object[], but if you do, please don't tell me about it :P
I am trying to authenticate client token created by Firebase authentication library in Android in GCE endpoint.
The guide of how to do this can be found here
Basically I need to call this code snippet from the end point (i.e. server backend code not android code).
FirebaseAuth.getInstance().verifyIdToken(idToken)
.addOnSuccessListener(new OnSuccessListener<FirebaseToken>() {
#Override
public void onSuccess(FirebaseToken decodedToken) {
String uid = decodedToken.getUid();
// ...
}
});
Let say I want to execute that code and return the user to android client code. How should I do that?
This is my sample code that does not make sense. But it demonstrate what I want to do!
#ApiMethod(name = "serverAuth")
public MyUser serverAuth(#Named("token") String token) {
FirebaseAuth.getInstance().verifyIdToken(token)
.addOnSuccessListener(new OnSuccessListener<FirebaseToken>() {
#Override
public void onSuccess(FirebaseToken decodedToken) {
String uid = decodedToken.getUid();
String email = decodedToken.getEmail();
String name = decodedToken.getName();
Map<String, Object> claims = decodedToken.getClaims();
String claimString = "";
for (Object claim : claims.values()) {
claimString += claims.toString();
}
MyUser user = new MyUser(uid, email, name, claimString);
//How to return this user?
}
});
//This is compile error since user varriable does not exist here
return user;
}
I have google search how to execute async code in GCE endpoints. But getting nowhere with that. What I get is something about code execution that is blocking until done and then return the user. But how to code so that async code as above become blocking?
CountDownLatch is the magic class you need. It will let you wait till the OnSuccessListener is actually completed.
Adapt your method this way: (I removed the steps that lead to MyUser's creation in order to focus on important points.)
#ApiMethod(name = "serverAuth")
public MyUser serverAuth(#Named("token") String token) {
final List<MyUser> users = new ArrayList<>();
final CountDownLatch cdl = new CountDownLatch(1);
FirebaseAuth.getInstance().verifyIdToken(token)
.addOnSuccessListener(new OnSuccessListener<FirebaseToken>() {
#Override
public void onSuccess(FirebaseToken decodedToken) {
// ... init uid, email, name and claimString
users.add(new MyUser(uid, email, name, claimString));
cdl.countDown();
}
});
try {
cdl.await(); // This line blocks execution till count down latch is 0
} catch (InterruptedException ie) {
}
if (users.size() > 0) {
return users.get(0);
} else {
return null ;
}
}
This is the basic version of what you need. IMHO, it requires 2 more improvements :
You should also take the possibility of failure into account :
FirebaseAuth.getInstance().verifyIdToken(token)
.addOnSuccessListener(new OnSuccessListener<FirebaseToken>() {
#Override
public void onSuccess(FirebaseToken decodedToken) {
cdl.countDown();
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
// log error, ...
cdl.countDown();
}
});
You should also take the possibility that none of the listeners are called. In this situation your method will never return. To avoid that, you can set a timeout on the await() method :
try {
// This line blocks execution till count down latch is 0
// or after 30 seconds.
cdl.await(30l, TimeUnit.SECONDS);
} catch (InterruptedException ie) {
}
That's it. Hope this may help.