I'm trying to create a form, send-only view that has a SignaturePad which once the user clicks on the save button, a save intent gets fired and do some background processing.
I have the following:
Presenter:
#Override
protected void bindIntents() {
Observable<SignatureViewState> observable =
intent(SignatureView::saveSignature)
.switchMap(intent -> Observable.fromCallable(() ->
storage.createFile(intent.getFullPath(), intent.getName(), intent.getImage()))
.subscribeOn(Schedulers.from(threadExecutor)))
.map(SignatureViewState.SuccessState::new)
.cast(SignatureViewState.class)
.startWith(new SignatureViewState.LoadingState())
.onErrorReturn(SignatureViewState.ErrorState::new)
.observeOn(postExecutionThread.getScheduler());
subscribeViewState(observable, SignatureView::render);
}
SignatureFragment:
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
saveButtonClickObservable = RxView.clicks(saveBtn)
.share()
.map(bla -> true);
compositeDisposable.add(saveButtonClickObservable.subscribe());
...
#Override
public Observable<SaveSignatureIntent> saveSignature() {
Observable<SaveSignatureIntent> saveSignatureIntentObservable =
Observable.just(new SaveSignatureIntent(savePath, bookingId + ".png", null));
Observable<SaveSignatureIntent> saveSignatureIntent =
Observable.zip(signatureBitmapObservable, saveSignatureIntentObservable,
(signature, intent) -> new SaveSignatureIntent(intent.fullPath, intent.name, signature));
return saveButtonClickObservable
.flatMap(bla -> saveSignatureIntent);
}
#Override
public void render(SignatureViewState state) {
if(state instanceof SignatureViewState.LoadingState)
renderLoading();
else if (state instanceof SignatureViewState.SuccessState)
renderSuccess();
if(state instanceof SignatureViewState.ErrorState)
renderError();
}
Lastly my view:
public interface SignatureView extends MvpView {
Observable<SignatureFragment.SaveSignatureIntent> saveSignature();
void render(SignatureViewState state);
}
The problem is, once my frag gets created, the .startWith gets fired, without the user clicking the button.
After that, if the user clicks on the button, the loading state never gets called (.startwith again) but only the success (or error).
What am I missing here ?
Thanks again !
Edit:
signatureBitmapObservable = Observable.fromCallable(() -> signaturePad.getTransparentSignatureBitmap(true))
.subscribeOn(Schedulers.io())
.startWith(bla -> renderLoading());
Another process is getting a transparent Bitmap, but after adding startWith, my callable never gets called. If I take it out, it works like a charm.
This is just a RxJava mistake, not really Mosby related.
put .startWith(new SignatureViewState.LoadingState()) (and maybe .onErrorReturn(SignatureViewState.ErrorState::new)) in the observable returned from switchMap(). Like this:
intent(SignatureView::saveSignature)
.switchMap(intent -> Observable.fromCallable(() ->
storage.createFile(intent.getFullPath(), intent.getName(), intent.getImage()))
.subscribeOn(Schedulers.from(threadExecutor))
.map(SignatureViewState.SuccessState::new)
.cast(SignatureViewState.class)
.startWith(new SignatureViewState.LoadingState())
.onErrorReturn(SignatureViewState.ErrorState::new)
) // end of switchMap
.observeOn(postExecutionThread.getScheduler());
The observable returned from switchMap only start once the user clicks on your button (because switchMap only triggers after intent has been fired).
startWith() means "before you do the real work, emit this first". If you apply startWith() as you did in your original code snipped, obviously it the observable start with loading, but what you really want is "before saving the signature, start with loading state". Therefore startWith() must be part of the "save signature" observable, and not of the "main" observable per se.
Related
So say in my app I have a SignInUserActivity. I have an API that allows the user to sign in, which returns a Single (RxJava 2) from signInUser(). I use the MVVM pattern so the following onClick method gets called each time I click the button with id button_sign_in_with_email.
I'm using CompositeDisposable to add and dispose of disposables.
I think the following code is wrong because it's adding a new Disposable each time I click the sign in button, when really I just want ONE disposable Single that returns a response each time I click sign in button, either "sign in FAILED" (in which a Toast will be shown and the Disposable will continue to observe the sign in button for future clicks) and "sign in SUCCESS" (in which the SignInUserActivity will have an Intent to MainActivity, thus calling onDestroy on SignInUserActivity, thus clearing disposables from CompositeDisposable.
protected void onDestroy() {
mViewModel.clearDisposables();
super.onDestroy();
}
And here is the onClick method:
public void onClick(View view) {
switch (view.getId()) {
case R.id.button_sign_in_with_email:
Disposable disposable = signInUser() // get response observable
.subscribe((Response<SignInUserResponseBody> response) -> { // subscribe to observable as disposable
if (response.body() != null && response.isSuccessful()) { // verify that response was successful
String signedInUserId = response.body().getUserId();
if (signedInUserId != null) { // verify that user id exists in SignInUserResponseBody
boolean signInSuccessful = handleSignIn(signedInUserId, response.headers()); // save new user's credentials in realm
if (signInSuccessful) { // successfully save new user's credentials
((SignInUserActivity) view.getContext()).onSignInSuccessful();
} else {
((SignInUserActivity) view.getContext()).onSignInFailed(view.getContext().getString(R.string.error_network_connectivity)); // error with saving to realm
}
} else {
((SignInUserActivity) view.getContext()).onSignInFailed(response.body().getErrorMessage()); // error with user input
}
} else {
((SignInUserActivity) view.getContext()).onSignInFailed(view.getContext().getString(R.string.error_network_connectivity)); // bad response from SkoolarService
}
}, (Throwable ex) -> {
onSignInFailed(view.getContext(), ex.getMessage());
});
addDisposable(disposable);
break;
So how do I fix it so I don't always add a disposable when I click?
Since your using a Single observable you don't have to dispose them manually. They get automatically disposed on Success or on Error. So you can remove the CompositeDisposable and call just subscribe on the single.
Refer RxJava reference
Usually, adding Disposables to CompositeDisposable over time should not cause any trouble, however, if you want to avoid the chance of leaking completely, you have to remove the Disposable when the flow terminates. This can get complicated so there is an extensions project consumer that removes the Disposable automatically upon termination:
// compile "com.github.akarnokd:rxjava2-extensions:0.18.5"
CompositeDisposable composite = new CompositeDisposable();
Disposable d = SingleConsumers.subscribeAutoRelease(
Single.just(1), composite,
System.out::println, Throwable::printStackTrace, () -> System.out.println("Done")
);
assertEquals(0, composite.size());
I got the following code using Mosby.
Fragment:
#Override
public Observable<CardInfo> loadCardIntent() {
return Observable.just(new CardInfo(cardselected, PreferenceManager.getDefaultSharedPreferences(getContext())
.getBoolean(PreferencesVariables.SHOW_BACK_CARD.toString(), false)))
//.delay(500, TimeUnit.MILLISECONDS)
.doOnNext(showBack -> Log.d(TAG, "Show card back: " + showBack));
}
#Override
public Observable<CardInfo> loadFrontIntent() {
return RxView.clicks(cardBackImageView)
.map(showFront -> new CardInfo(cardselected, false))
.doOnNext(showFront -> Log.d(TAG, "Show card front"));
}
#Override
public Observable<Boolean> hideCardIntent() {
return clicks(cardFrontImageView)
.map(ignored -> true)
.doOnNext(close -> Log.d(TAG, "Close card activity"));
}
Presenter:
#Override
protected void bindIntents() {
Observable<CardViewState> showSelectedCard = intent(CardView::loadCardIntent)
.switchMap(cardInfo -> interactor.getCard(cardInfo))
.doOnError(error -> System.out.print(error.getMessage()));
Observable<CardViewState> showFront = intent(CardView::loadFrontIntent)
.switchMap(cardInfo -> interactor.getCard(cardInfo))
.doOnError(error -> System.out.print(error.getMessage()));
Observable<CardViewState> hideCard = intent(CardView::hideCardIntent)
.switchMap(ignored -> interactor.hideCard());
Observable<CardViewState> intents = Observable.merge(showSelectedCard, showFront, hideCard)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
subscribeViewState(intents, CardView::render);
}
Fragment:
#Override
public void render(CardViewState viewState) {
if (viewState instanceof CardViewState.CardBackState) {
renderCard(R.raw.planningpoker_rueckseite, cardBackImageView);
renderCard(((CardViewState.CardBackState) viewState).card, cardFrontImageView);
showCardBack();
} else if (viewState instanceof CardViewState.CardFrontState) {
renderCard(R.raw.planningpoker_rueckseite, cardBackImageView);
renderCard(((CardViewState.CardFrontState) viewState).card, cardFrontImageView);
showCardFront();
} else if (viewState instanceof CardViewState.CardCloseState) {
getActivity().finish();
}
}
Interactor:
Observable<CardViewState> getCard(CardInfo cardInfo) {
return cardInfo.showBack ? Observable.just(new CardViewState.CardBackState(CARDS[cardInfo.card])) :
Observable.just(new CardViewState.CardFrontState(CARDS[cardInfo.card]));
}
Observable<CardViewState> hideCard() {
return Observable.just(new CardViewState.CardCloseState());
}
Without the delay in loadCardIntent() the render()-method does not get triggered with the CardBackState. But I don't want to use a arbitrary delay to ensure the right methods get triggered.
Is there any other way to ensure that all events get emitted?
Thanks for the help.
Hm, is your code available on github somewhere? So far everything seems to be ok. Maybe it is an internal mosby bug. Is it working if you add subscribeOn(schdulers.io()) to loadCardIntent() in presenters bind() method.
The only difference I see with or without delay() is that your code runs sync (on main UI thread) whereas delay() switches the execution of your code to a background thread. Are you sure your interactor.getCardInfo() is meant to run on androids main UI thread? I.e. if it runs on main thread, but you are doing a http request (on main UI thread) an exception is thrown. Do you catch exceptions in interactor?
This was a mosby internal issue and has been fixed now.
See https://github.com/sockeqwe/mosby/issues/242
Please use latest snapshot:
com.hannesdorfmann.mosby3:mvi:3.0.4-SNAPSHOT
(see README) to verify everything works now as intended.
Please comment on the linked github issue if it fixes your problem or not.
Thanks.
My solution for now is to use Schedulers.trampoline(). It is not ideal and in no way sufficient but it allows me to get rid of the delay that is more of a hassle.
The problem that Schedulers.trampoline() seem to be solving is that the change onto another thread takes a short amount of time. And that causes the event to get lost. So staying on the same thread fixes this.
I am new to Rxjava and exploring the possibilities and need help in below described scenario.
Step 1 : Swipe the View
Step 2 : Make an API Call to fetch Data
The above steps are repeated for the number of Views.
Problem :
API_CALL_1 fetching the View_1 Results
User swipes a View_2
API_CALL_2 fetching the View_2 Results
View_1 results are returned and populated in View_2 along with View_2 results
I need to cancel the API_CALL_1 request when the API_CALL_2 request.
Thanks.
Class member:
Subscription mSubscription;
Your API call:
if(subscription != null && !subscription.isUnsubscribed()){
subscription.unsubscribe();
subscription = null;
}
subscription = doApiCall("some_id")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<Object>() {
#Override
public void call(Object o) {
}
}, new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
}
});
So, the main idea of it, that you have to unsubscribe from previous call:
if(subscription != null && !subscription.isUnsubscribed()){
subscription.unsubscribe();
subscription = null;
}
Before starting new call
a better way is to use switchMap to cancel previous request
You are able to provide 'cancel' logic by using 'switchMap' operator. For example, you want to start/stop loading (Code written in Kotlin).
val loadingObservable = Observable.timer(10, TimeUnit.SECONDS)
val load = PublishSubject.create<Boolean>()
val resultObservable = load.switchMap { if(it) loadingObservable else Observable.never() }
Explanation:
'loadingObservable' is a timer that simulate long running operation
'load' is a command from user to start or stop loading. If you want
to start loading - send True else False
'switchMap' operator is
designed to dispose previous observable. So it you send True it will
start loading. If you send False it will dispose (cancel) previous
observable.
My app has a SearchView. When the user types in the SearchView the onQueryTextChange passes the query to the presenter and then it calls the API. I am using Retrofit and RxJava for the calls. The calls return a json file with the words containing what the user typed so far. The problem is that, if the user is fast to type letters and the network is slow sometimes the SearchView doesn't show the results based on all the typed letters but maybe up to the second last because the last call was quicker to get the results compared to the second last.
Example:
the user start typing:
"cou" -> make a call to the API (first call after 3 letters) -> start returnin values
"n" -> make a call -> start returning values
"t" -> make a call -> start returning values
"r" -> make a call (the connection is slow)
"y" -> make a call -> start returning values
-> "r" get the results finally and the returns them
public Observable<List<MyModel>> getValues(String query) {
return Observable.defer(() -> mNetworkService.getAPI()
.getValues(query)
.retry(2)
.onErrorReturn(e -> new ArrayList<>()));
}
The call is very simple and whenever I get an error I don't want to display anything.
Is there a way to solve that? Or maybe this is not the case to use reactive programming?
EDIT:
Just to make more clear, the flow is the following:
Activity that uses a custom search view (https://github.com/Mauker1/MaterialSearchView)
the custom searchview has a listener when the user starts typing. Once the user starts typing the activity calls the Presenter.
the presenter will subscribe an observable returned by the interactor:
presenter:
addSubscription(mInteractor.getValues(query)
.observeOn(mMainScheduler)
.subscribeOn(mIoScheduler)
.subscribe(data -> {
getMvpView().showValues(data);
}, e -> {
Log.e(TAG, e.getMessage());
}));
interactor:
public Observable<List<MyModel>> getValues(String query) {
return Observable.defer(() -> mNetworkService.getAPI()
.getValues(query)
.debounce(2, TimeUnit.SECONDS)
.retry(2)
.onErrorReturn(e -> new ArrayList<>()));
So now either I change the custom search view in a 'normal' searchview and then use RxBinding or maybe I should use an handler or something like that (but still struggling how to fit it in my architecture)
Firstly make your Searchview as Observable so that you can apply Rx operators.
To convert searchview into Observable
public static Observable<String> fromview(SearchView searchView) {
final PublishSubject<String> subject = PublishSubject.create();
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
#Override
public boolean onQueryTextSubmit(String s) {
subject.onComplete();
searchView.clearFocus(); //if you want to close keyboard
return false;
}
#Override
public boolean onQueryTextChange(String text) {
subject.onNext(text);
return false;
}
});
return subject;
}
private void observeSearchView() {
disposable = RxSearchObservable.fromview(binding.svTweet)
.debounce(300, TimeUnit.MILLISECONDS)
.filter(text -> !text.isEmpty() && text.length() >= 3)
.map(text -> text.toLowerCase().trim())
.distinctUntilChanged()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe()
}
You can apply filter, condition
RxJava debounce() operator to delay taking any action until the user pauses briefly.
Use of distinctUntilChanged() ensures that the user can search for the same thing twice, but not immediately back to back
The filter operator is used to filter the unwanted string like the empty string in this case to avoid the unnecessary network call.
Handling searchview withRXJava
You're in luck there's an operator for that called debounce
Observable.defer(() -> mNetworkService.getAPI()
.getValues(query)
.debounce(3, TimeUnit.SECONDS)
.retry(2)
.onErrorReturn(e -> new ArrayList<>()));
What debounce does is wait N time units for more results prior to continuing. Say for example the network takes 2 seconds to return and you flood it with request after request, debounce will wait for 3 seconds of no results and then return the last result. Think of it as dropping everything but the one before N time of inactivity.
This solve your problem but will still flood the network, ideally you would use the excellent RxBinding library do the defer prior to making the request something like:
RxTextView.textChanges(searchView)
.debounce(3, TimeUnit.SECONDS)
.map(input->mNetworkService.getAPI().getValues(input.queryText().toString()))
.retry(2)
.onErrorReturn(e -> new ArrayList<>()))
With the current setup it will wait 3 seconds after a user types something and only then make the network call. If instead they start typing something new, the first pending search request gets dropped.
Edit: changed to RxTextView.textChanges(textview) based on OP not using an android SearchView widget
Extending on what #MikeN said, if you want to only use the results of the LAST input, you should use switchMap() (which is flatMapLatest() in some other Rx implementations).
I solved the flooding issue without using RxBinding and I want to post my solution just in case someone else needs it.
So whenever the onTextChanged is called I check, first of all, if the size is > 2 and if it is connected to the network (boolean updated by a BroadcastReceiver). Then I create message to be sent has delayed and I delete all the other messages in the queue. This means that I will execute only the queries that are not within the specified delay:
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (TextUtils.getTrimmedLength(s) > 2 && isConnected) {
mHandler.removeMessages(QUERY_MESSAGE);
Message message = Message.obtain(mHandler, QUERY_MESSAGE, s.toString().trim());
mHandler.sendMessageDelayed(message, MESSAGE_DELAY_MILLIS);
}
}
Then the Handler:
private Handler mHandler = new Handler() {
#Override
public void handleMessage(Message msg) {
if (msg.what == QUERY_MESSAGE) {
String query = (String)msg.obj;
mPresenter.getValues(query);
}
}
};
Add rxbinding dependency to gradle implementation "com.jakewharton.rxbinding2:rxbinding-kotlin:2.1.1"
Use debounce and distinct for ignoring frequent key input and duplicate input
Dispose previous API call for getting only latest search result
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.toolbar_menu, menu)
// Associate searchable configuration with the SearchView
val searchManager = requireContext().getSystemService(Context.SEARCH_SERVICE) as SearchManager
searchView = menu.findItem(R.id.action_search).actionView as SearchView
searchView.setSearchableInfo(
searchManager.getSearchableInfo(requireActivity().componentName)
)
searchView.maxWidth = Integer.MAX_VALUE
// listening to search query text change
disposable = RxSearchView.queryTextChangeEvents(searchView)
.debounce(750, TimeUnit.MILLISECONDS)
.distinctUntilChanged()
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
callApi(it.queryText().toString())
}, {
Timber.e(it)
})
super.onCreateOptionsMenu(menu, inflater)
}
private fun callApi(query: String){
if(!apiDisposable.isDisposed){
apiDisposable.dispose()
}
apiDisposable = mNetworkService.getAPI(query)
}
I'm new to RxJava, here's my case,
send request A and will get List<A> back
for each A, send request AA and will get AA back, bind A and AA then
there is B & BB with similar logic
do something only after all requests complete
Example:
request(url1, callback(List<A> listA) {
for (A a : listA) {
request(url2, callback(AA aa) {
a.set(aa);
}
}
}
A and B are independent
How to structure the code? I also used Retrofit as network client.
OK, I think this should solve the first part of your problem:
Notice that the second call to flatMap is given 2 arguments - there is a version of flatMap that not only produces an Observable for each input item but that also take a second function which in turn will combine each item from the resulting Observable with the corresponding input item.
Have a look at the third graphic under this heading to get an intuitive understanding:
https://github.com/ReactiveX/RxJava/wiki/Transforming-Observables#flatmap-concatmap-and-flatmapiterable
Observable<A> obeservableOfAs = retrofitClient.getListOfAs()
.flatMap(new Func1<List<A>, Observable<A>>() {
#Override
public Observable<A> call(List<A> listOfAs) {
return Observable.from(listOfAs);
}
)}
.flatMap(new Func1<A, Observable<AA>>() {
#Override
public Observable<AA> call(A someA) {
return retrofitClient.getTheAaForMyA(someA);
}
},
new Func2<A, AA, A>() {
#Override
public A call(A someA, AA theAaforMyA) {
return someA.set(theAaforMyA);
}
})
...
From here on I am still not sure how you want to continue: Are you ready to just subscribe to the resulting Observable of As? That way you could handle each of the As (onNext) or just wait until all are done (onCompleted).
ADDENDUM: To collect all Items into a single List at the end, that is turn your Observable<A> into an Observable<List<A>> use toList().
https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators#tolist
So you have:
Observable<List<A>> observableOfListOfAs = observableOfAs.toList();
If you need more fine grained control over the construction of your list, you can also use reduce.
https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators#reduce
For the Bs, simply duplicate the whole flow you used for the As.
You can then use zip to wait for both flows to complete:
Observable.zip(
observableOfListOfAs,
observableOfListOfBs,
new Func2<List<A>, List<B>, MyPairOfLists>() {
#Override
public MyPairOfLists call(List<A> as, List<B> bs) {
return new MyPairOfLists(as, bs);
}
}
)
.subscribe(new Subscriber<MyPairOfLists>() {
// onError() and onCompleted() are omitted here
#Override
public void onNext(MyPairOfLists pair) {
// now both the as and the bs are ready to use:
List<A> as = pair.getAs();
List<B> bs = pair.getBs();
// do something here!
}
});
I suppose you can guess the definition of MyPairOfLists.