So I am trying to build a dialog fragment screen (Android) which has an EditText where users can input their desired username. Using the RxBindings library's RxTextView.textChanges() I am observing the EditText and doing the following:
Resetting existing hints and disabling the "Confirm" button.
Filtering out invalid strings using a regex pattern.
Making a request to the server to check if the valid string is available.
Showing the appropriate response and, if applicable, enabling the "Confirm" button.
My repository's checkUsername() method returns a Single<Boolean> to denote whether the username was available or not. Here's the code:
Observable<String> usernameObservable = RxTextView.textChanges(usernameEditText)
.doOnNext(charSequence -> resetUsernameChecks())
.filter(charSequence -> !TextUtils.isEmpty(charSequence))
.debounce(400, TimeUnit.MILLISECONDS)
.map(CharSequence::toString)
.filter(s -> {
boolean match = s.matches("^(?=.{4,12}$)(?![_.])(?!.*[_.]{2})[a-zA-Z0-9._]+(?<![_.])$");
if (!match) showMessage(R.string.profile_dialog_username_hint_invalid, true);
return match;
});
Disposable usernameSubscription = usernameObservable
.subscribe(username -> {
Disposable d = viewModel.checkUsername(username)
.subscribe(available -> {
if (available) {
claimButton.setEnabled(true);
showMessage(R.string.profile_dialog_username_hint_available, false);
} else {
showMessage(R.string.profile_dialog_username_hint_unavailable, true);
}
});
disposables.add(d);
});
disposables.add(usernameSubscription);
With the following helper methods:
#UiThread
private void resetUsernameChecks() {
claimButton.setEnabled(false);
usernameInputLayout.setError(null);
}
#UiThread
private void showMessage(#StringRes int message, boolean error) {
showMessage(getString(message), error);
}
#UiThread
private void showMessage(String message, boolean error) {
if (error) {
usernameInputLayout.setErrorTextAppearance(R.style.AppTheme_RedMessage);
} else {
usernameInputLayout.setErrorTextAppearance(R.style.AppTheme_GreenMessage);
}
usernameInputLayout.setError(message);
}
However, the issue I run into is that either of the showMessage() methods cause a crash because the there may either be a background thread in the running (thus preventing changes to the UI) or that a previously called checkUsername() lingers back and messes everything up.
Essentially, how can I make sure I do work on the correct thread and cancel any lingering network calls before making a new one?
EDIT:
I fixed the thread issue by encapsulating the UI methods inside a Runnable. However, I still have issue with cancelling previous call. Lets say the user searches for an available username -- nick -- but then quickly hits backspace. The already fired network call now comes back and inaccurately shows that nic is available even though it may not be.
So, for you to update UI, you gotta do it in UI Thread. I guess,
usernameInputLayout.setErrorTextAppearance(R.style.AppTheme_GreenMessage);
is like the UI thread thing, so you need to use subscribeOn.
If you would show more from the log, we would know the error. But, right now, your best option is to use UI Thread to update UI.
usernameObservable.subscribeOn(AndroidSchedulers.mainThread())
.subscribe(v -> //whatever you want )
Edit: My bad about "Observable by default runs on background thread." It is wrong. The correct is: It runs in the thread it is defined in, unless we use subscribeOn()/observeOn() methods.
Related
I'm having an issue trying to understand, in a reactive way, how simultaneous operations to the same observable should work.
The scenario is the following:
I have a list of users and a remove button.
Every time I press remove I'm making a call to the API: UsersApi.removeUser. It is possible to remove multiple users at the same time. Which means that multiple UsersApi.removeUser are happening simultaneously.
After each UsersApi.removeUser I need to make a UsersApi.refreshUser call
So in terms of pseudo code what I am doing when clicking remove is the following:
Presenter:
public Observable<User> removeUser(int userId) {
return UsersApi.removeUser(userId)
.flatMap(user -> UsersApi.refreshUser(userId));
}
Fragment:
public void removeUser() {
presenter.removeUser(userId)
.subscribe(user -> {
//remove user from ui
// update number of total users
})
}
The problem with this approach is that because of the asynchronous nature of the remove (multiple removes allowed) I cannot guarantee that what is reaching the subscribe is the latest one. The subscribe will be reached twice, one for each remove, and the user info might not be updated or the latest. Does that make sense?
What I want to happen:
Parallel/Simultaneous remove calls using a reactive approach (triggered by multiple remove clicks from the user)
After a remove call finishes, start the next remove call
Edit: What I would like to know is how to do/if is possible to do the solution I did (see edit2) using Rx operators.
Edit2: My solution for this was to enqueue the user operations (in this case remove) and emit, using a PublishSubject, when the UsersApi.refreshUser(userId) call finishes.
So basically what I did was (pseudo code):
private final PublishSubject<UserOperation> userOperationObs;
private final ConcurrentLinkedQueue<UserOperation> pendingOperations;
private boolean executingOperation;
private void emitUserOperation(final UserOperation operation) {
if (!executingOperation) {
executingOperation = true;
userOperationObs.onNext(operation);
} else {
executingOperation.add(operation);
}
}
public Observable<User> removeUser(UserOperation operation) {
return UsersApi.removeUser(operation.getUserId)
.switchMap(user -> UsersApi.refreshUser(operation.getUserId))
.doOnNext(user -> {
executingOperation = false;
final UserOperation nextOperation = pendingOperations.poll();
if (nextOperation != null) {
userOperationObs.onNext(operation);
}
};
}
You could turn your UI click into Observable (eg. by using RxBinding). After that, you could use concatMap operator to perform api call so it will start next network call once current api call is finished.
// emit clicks as stream
Observable<?> clicks = RxView.clicks(removeView)
// listen clicks then perform network call in sequence
clicks.concatMap(ignored -> usersApi.refreshUser(userId))
I use Retrofit and RxJava for network calls. For the first time I ran into a weird problem. For one of the calls the following error message is displayed:
HTTP FAILED: android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
But the call is started from the main thread and it's the same result even if I remove any calls in onNext. So it must be something in the call, which is a solo call! This is the call in a presenter:
public void updateEmail(final String newEmail) {
disposables.add(AccountRepository.updateEmail(newEmail)
.retry(1)
.subscribeWith(new DisposableObserver<Reply>() {
#Override
public void onNext(Reply reply) {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
}
}));
}
And this is the Repository call:
public static Observable<Reply> updateEmail(String email) {
return getMyApiService().updateEmail(email)
.subscribeOn(Schedulers.from(AsyncTask.THREAD_POOL_EXECUTOR))
.observeOn(AndroidSchedulers.mainThread());
}
And the Retrofit interface called MyApiInterface:
#FormUrlEncoded
#POST(UrlMap.apiProfileUpdateEmailUrl)
Observable<Reply> updateEmail(#Field("email") String email);
Nothing unusual, I have more than 100 calls like this.
Now if I add retry(1) as above, the call goes through, but the first call seem to be leaked, because it's still in the threads in the Network Profiler.
Furthermore, The call happens in a Fragment in an Activity with bottom navigation. If I switch to another fragment, all the calls there finish in the background without error, but the UI is not updated, I see a blank screen. But if I navigate to another activity and back, the UI is updating again.
The disposables is a CompositeDisposable object and it's cleared when the presenter is unbound. Disposable.clear in onNext() doesn't help.
I added five interceptors to the client for logging, headers, etc. Maybe they cause this somehow? Or something else? I'm trying to fix this for more than a day, but couldn't get much closer to the solution.
There is a listener for user verification changes -- that are coming from all the response headers -- in the main feed, so we can obfuscate the images. I wrapped the listener in a runOnUiThread() method and the leak went away.
I'm learning RxJava so please be gentle. I've watched the tutorials, done the reading, searched SO, however, I'm still having some problems transforming my AsyncTaskLoader. For some reason, I can't find a pattern of operators to achieve my task (although I think it's a common one). What I'm trying to do is the following: return an Observable my fragment could subscribe to. The observable should do the following on subscribe:
1) Fetch data from the local database by doing 2 queries, running some logic and returning results;
2) Fetching data from API;
3) Synchronising the new API data with the database;
4) Repeating step one and returning results;
So far I've transformed my db calls and my API calls to return observables. I'm trying to understand how I can emit the cold results and continue with the chain. I could probably keep the two operations separately, and use the same subscriber to subscribe to both? But I'm not sure how that would work if my new loader-replacement class returns an observable... Also I don't really need to process the results from the second observable - I just need for the first one to replay when the second one finished.
So far I have the following:
public Observable<StuffFetchResult> getColdStuff() {
return Observable.zip(mDataSource.listStuff(), mDataSource.listOtherStuff(),
(stuff, moreStuff) -> {
List<Stuff> mergedList = new ArrayList<>();
// do some merging stuff
return new StuffFetchResult(mergedList);
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
Assume I also have getHotStuff() that will do the API call and the synchronisation with the database, if that's the right approach, and return the same Observable. However, I'm stuck on the next step - how can I restart the first observable to replay once hotStuff has completed, without adding another subscriber?
EDIT:
I've made some progress and I think all I need now is to join it all up. I have my two methods:
1) getColdStuff() is pretty much as described above
2) getHotStuff() will do call to the API, synchronise with the database, and return an Observable. The idea was to call getColdStuff() again after getHotStuff() has finished in order to refresh the UI, so actual result returned from getHotStuff() can be ignored. All it needs to do is to trigger getColdStuff() once done.
I've tried the suggestion in the answer to and created the following:
BehaviorRelay<Observable<StuffFetchResult>> callSequence = BehaviorRelay.create();
Observable<StuffFetchResult> valueSequence = Observable.switchOnNextDelayError(callSequence.toSerialized());
valueSequence.subscribe(new Subscriber<StuffFetchResult>() {
#Override
public void onCompleted() {}
#Override
public void onError(Throwable e) {}
#Override
public void onNext(StuffFetchResult result) {
// UI stuff
}
});
callSequence.call(loader.getColdStuff());
I can subscribe to valueSequence here and use callSequence.call(loader.getColdStuff());, which will run the first method and produce results in onNext() of my subscription, which I can use for my UI. However, I'm not sure how to run getHotStuff() in parallel and also do a different action on it when it returns. Also getHotStuff() returns a different type of Observable so I can't really use the same callSequence?
EDIT 2
Using two subscribers, I can achieve the required behaviour I think. Not really sure if that's the right way to go about it though.
loader.getHotStuff()
.subscribeOn(Schedulers.io())
.subscribe( new Subscriber<Object>() {
#Override
public void onCompleted() {}
#Override
public void onError(Throwable e) {}
#Override
public void onNext(Object stuffWeDontCareAbout) {
callSequence.call(loader.getColdStuff());
}
});
if i understand your scenario correctly, you may want something like that -
BehaviorSubject<Observable<T> callSequence = BehaviorSubject.create();
Observable<T> valueSequence = Observable.swithOnNextDelayError(callSequence.toSerialized());
your subscriber will be listening to the valueSequence, and whenever you need to "restart", you will call this -
callSequence.onNext(call.cache()); // *call* is Observable<T>
(i leave the .subscribeOn/.observeOn configuration to you)
I have some confusion on subscribers and when they react to observers. Lets say i have the following simple observer with a subscriber that does an action:
Observable.just(preferences.getBoolean(C"vibrate", false))
.subscribeOn(Schedulers.io())//observe on new thread
.observeOn(AndroidSchedulers.mainThread()) //subscribe(listen) on main thread
.subscribe(new Action1<Boolean>() {
#Override
public void call(Boolean shouldVibrate) {
if (shouldVibrate)
Toast.makeText(context,"i should vibrate now",Toast.SHORT).show();
}
});
I realize the observer gets called right away when this code is first seen. But what if the shared preference is changed again afterwards, will this code run again automatically or does it only run everytime i call subscribe ? What if i wanted it to run everytime the shared preference was altered (sort of like a watcher).
It really depends on the observable. I would suggest reading "Hot" and "Cold" Observables on the reactive Observable docs.
In your case, this is a Cold observable. It will resubscribe each time it is subscribed to. However, you only subscribe to it once. Your code snippet will actually block on the preferences fetch (probably not a huge problem), but it will only emit one preference.
I would suggest using the ContentObservable class in the RxAndroid extension lib for RxJava, which you are already using (because of AndroidSchedulers).
It would look something like this (This is back-of-napkin code, I have not compiled or ran this):
// Defer the observable so it gets a fresh preference value. Also, we'll
// be using it a few times.
final Observable<Boolean> vibratePreference = Observable.defer(
new Func0<Observable<Boolean>>() {
#Override
public Observable<Boolean> call() {
return Observable.just(preferences.getBoolean("vibrate", false));
}
});
vibratePreference
.concatWith(ContentObservable.fromSharedPreferencesChanges(preferences)
// Only consider changes to the vibrate preference.
.filter(new Func1<String, Boolean>() {
#Override
public Boolean call(final String key) {
return "vibrate".equals(key);
}
})
// Each time the preference changes, get the latest value.
.flatMap(new Func1<String, Observable<Boolean>>() {
#Override
public Observable<Boolean>(final String unusedKey) {
return vibratePreference;
}
}))
.scheduleOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe( /* ...and so on. */ );
Also, if you are doing this on an activity or a fragment, I would strongly suggest looking into bindActivity and bindFragment in AppObservable in RxAndroid to make sure you are binding this observable to the lifecycle. You also may want to store a CompositeSubscription that you can empty in onPause and restore subscriptions in onResume. Those are slightly off-topic but will most likely be useful very soon.
I am using RxJava to move network access to a separate thread in Android, but my UI still blocks.
I am not using the wrong observable as shown here: Android RxJava, Non Blocking?
The codepoints [A], [B] and [C] in below code are passed in the order [A] -> [C] -> [B] so the current thread is processed fine and RxJava calls [C] once it had a result. This is fine.
Also, blocking is much better compared to doing the network call on the UI thread, but I still have minor blocking. The UI stays fluent after the call is made, but if the server does not respond in a matter of milliseconds, it blocks.
private search; // search is an instance variable in the same class
// [A]
Observable.just(search.find("something")) // search.find calls the REST endpoint
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<Search>() {
#Override public void call(Search search) {
// further processing // [B]
}
}, new Action1<Throwable>() {
#Override public void call(Throwable throwable) {
// error handler
}
});
// [C]
Could it be a problem that search is an instance variable in the same class where the Observable uses it, but the endpoint call is performed from a separate library? It shouldn't matter, right?
Am I doing anything bad that I shouldn't be doing?
--
Find looks like this (removed exception handling for brevity):
public Search find(String searchtext) {
setSearchtext(searchtext);
SearchEndpoint.find(Session.getUser().getId(), searchtext);
return this;
}
SearchEndpoint like this:
public static Search find(final Long userId, final String searchtext) throws IOException {
return ApiService.api().searches().find(userId).setFind(searchtext).execute();
}
and makes a call to the generated Google cloud endpoint library.
Try this:
Observable.create(new Observable.OnSubscribe<Search>() {
#Override
// method signature is from memory - I hope I am correct...
public void call(Subscriber<? super Search> subscriber) {
try {
Search search = search.find("something");
subscriber.onNext(search);
subscriber.onCompleted();
} catch (SomeException e) {
subscriber.onError(e);
}
}
})
// and then continue with your .subscribeOn(...)
To clarify, maybe this makes the problem with your code more obvious:
Observable.just(search.find("something"))
is clearly equivalent to
Search search = search.find("something");
Observable.just(search)
And this makes it obvious that search.find is executed before we ever hand the control over to rxjava and it is executed on whatever thread you are currently on - then the construction of an Observable from the pre-computed value and the delivery of the value happen on another thread but that does not help you much...
I know this is a few months old-- but instead of createing an entirely new Observable (which is relatively error-prone), you can use the map operator to run the search:
String search_input = "something"; // this is where you can specify other search terms
Observable.just(search_input)
.map(s -> search.find(s)) // search.find calls the REST endpoint
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe( // your subscriber goes here
If not using lambdas, that map function should look like:
.map(new Func1<String, Search>() {
#Override
public Search call(String s) {
return search.find(s)
}
})