Enabling Button when EditText has text (RxAndroid) - android

New to RxJava/RxAndroid and am finding the lack of examples disturbing. As a way to jump into using Rx, id like to try to get something small working. Basically, if an EditText has text entered into it, then enable a Button below it.
I came across this answer, but the authors edit doesnt really show how to completely implement something like this.
From what I've gathered, I can use RxBindings to create an Observable like:
Observable<CharSequence> observable = RxTextView.textChanges(mEditText);
Supposedly I would now need to .subcribe() an Observer to watch for changes in observable, but am unsure how this would be done.
Also, how would you create the EditTexts Observable without using RxBindings if needed?
Edit: Although Retrolambda exists, answers showing how to implement this without lambdas (or both) would be helpful.

Observable<CharSequence> observable = RxTextView.textChanges(mEditText);
observable.map(new Func1<CharSequence, Boolean>() {
#Override
public Boolean call(CharSequence charSequence) {
return charSequence.length() > 0;
}
}).subscribe(new Subscriber<Boolean>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(Boolean aBoolean) {
mButton.setEnabled(aBoolean);
}
});
Don't forget to keep a reference to the subscription and unsubscribe when you no longer need it (eg. in onDestroy).
RxJava-Android-Samples contains RxJava examples for Android. Check it out. You might wanna check out the Form Validation example.
Also, how would you create the EditTexts Observable without using
RxBindings if needed?
You can check out the implementation. It's open source. Internally it uses a TextWatcher to monitor the changes and emits items when the text changes.

In order to subscribe to Observable<CharSequence>, you would do something like this.
Observable<CharSequence> observable = RxTextView.textChanges(mEditText).skip(1);
mButton.setEnabled(false)
observable.subscribe(mButton -> mButton.setEnabled(true));
If you're not using retrolambda, you could do something like:
Observable<CharSequence> observable = RxTextView.textChanges(mEditText).skip(1);
mButton.setEnabled(false);
observable.subscribe(new Action1<CharSequence>(){
#Override
public void call(CharSequence c) {
mButton.setEnabled(true);
}
});
As for the second part of your question: to be honest, I'm not sure but I would guess that you would add a TextWatcher on the EditText and fire an event each time the text changes (using Observable.just(charSequenceAfterEdit)).

Related

Previous task(s) waiting on Firebase Realtime Database need to complete first before new one starts

I'm using the Task API in my app to retrieve data from Firebase Database, which is usually from different nodes. I have a helper class for Firebase Database like so:
public class FirebaseDbHelper {
public Task<DataSnapshot> getData() {
TaskCompletionSource<DataSnapshot> source = new TaskCompletionSource<>();
DatabaseReference dbRef = FirebaseDatabase.getInstance().getReference(FIRST_NODE).child(SUB_NODE);
dbRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
source.setResult(dataSnapshot);
}
#Override
public void onCancelled(DatabaseError databaseError) {
source.setException(databaseError.toException());
}
});
return source.getTask();
}
}
As you can see, getData() returns a Task object, which I use on my interactor class (I'm using the MVP architecture for my app) like so:
public class TestDbInteractor {
private FirebaseDbHelper mDbHelper;
private Listener mListener;
public TestDbInteractor(#NonNull Listener listener) {
mDbHelper = new FirebaseDbHelper();
mListener = listener;
}
void getData() {
mDbHelper.getData().addOnCompleteListener(task -> {
if (task.isSuccessful()) {
mListener.onGetDataSuccess(new MyObject(task.getResult()));
} else {
mListener.onGetDataFailed(task.getException());
}
});
}
public interface Listener {
void onGetDataSuccess(MyObject object);
void onGetDataFailed(Exception exception);
}
}
This works as expected. However, we noticed a behavior that when retrieving a lot of data, even if the activity that started the task is already finish()ed, the task still proceeds and attempts to complete. This I believe, is something that could be considered as a memory leak, since a process is still going even though it's supposed to be stopped/destroyed already.
What's worse is that when I try to get a different data (using a different Task in a different activity to a different node in Firebase), we noticed that it waits for the previous task to complete first before proceeding with this new one.
To give more context, we're developing a chat app similar to Telegram, where users could have multiple rooms and the behavior we saw is happening when a user enters a room. This is the flow:
User enters room, I request data for the room details.
Upon getting the room details, I display it, then request for the messages. I only retrieve the most recent 10. During this time, I just show a progress bar on the activity.
In order for the message details to be complete, I get data from different nodes on Firebase, this is where I use Tasks mainly.
After getting the messages, I pass it on to the View, to display the messages, then I attach a listener for new messages. Everything works as expected.
The behavior I mentioned at the beginning is noticeable when the user does something like this:
User enters a room with messages, room details are retrieved instantly, messages are still loading.
User leaves the room (presses the back button), this gets the user back to the room list, and enters a different one.
At this point, the retrieval of the room details takes such a long time - which we thought was odd, since the data isn't really that big to begin with.
After a few more testing, we concluded that the long retrieval time was caused by the current task (get room details) is still waiting for the previous task (get messages) started in a different activity, to finish first before starting.
I attempted to implement my answer here, trying to use a CancellableTask, but I am at a loss on how to use it with my current implementation, where I use a TaskCompletionSource, where you could only set a result or an exception.
I was thinking this could work if I move the task completion source to the interactor class level instead of the helper -- I haven't tried it yet. I think it's possible, but would take a lot of time to refactor the classes I already have.
So I figure why not try Doug's answer, using activity-scoped listeners. So I tested it like below.
In my activity, I added a getActivity() method, which can be called in the presenter:
public class TestPresenter
implements TestDbInteractor.Listener {
private View mView;
private TestDbInteractor mDbInteractor;
#Override
void bindView(View view) {
mView = view;
mDbInteractor = new TestDbInteractor(this);
}
#Override
void requestMessages() {
mDbInteractor.getData(mView.getActivity());
}
// Listener stuff below
}
and updated my getData() like so:
void getData(#NonNull Activity activity) {
mDbHelper.getData().addOnCompleteListener(activity, task -> {
if (task.isSuccessful()) {
mListener.onGetDataSuccess(new MyObject(task.getResult()));
} else {
mListener.onGetDataFailed(task.getException());
}
});
}
Unfortunately, this doesn't seem to work though, exiting the activity still waits for the tasks to complete, before the new task initiated in a different activity starts.
If you kick off a query to Realtime Database, it will always run to completion, whether or not there are any listeners attached to the Task that was returned. There is no way to cancel that work, neither by removing the last listener manually, nor by using activity-scoped listeners that are removed automatically. Queries in motion stay in motion. Also, all traffic to and from RTDB is pipelined over a single socket, which implies that the results of subsequent queries after one that's incomplete will have to wait for the everything ahead of it in the queue to complete first. This is likely the root cause for your observation - you have an incomplete query that other queries are waiting on, regardless of your use of the Task API.
Fortunately, if you have persistence enabled, the second query should be served by the cache of the first query, and not require another round trip to the server.
If you need to make sure that you retain the results of the first query across configuration changes that destroy the activity, then you should use something like LiveData from the Android architecture components to manage this, so that you can pick up the query where it left off after a configuration change. If you do this, don't use activity-scoped listeners.
I've written a three-part blog post about using architecture components with Firebase, which may also be of interest.
Hey You can use childEventListener. use dataSnapshot.getChildrenCount().
dbFriend=FirebaseDatabase.getInstance().getReference("Friend");
dbFriend=dbFriend.child(mPreferences.getString("username","")).child("already");
dbFriend.addChildEventListener(new ChildEventListener() {
int already=0;
#Override
public void onChildAdded(#NonNull DataSnapshot dataSnapshot, #Nullable String s) {
Username u=dataSnapshot.getValue(Username.class);
already=alread+1;
if(already >= dataSnapshot.getChildrenCount()){
//get to know when data fetching got completed
}
}
#Override
public void onChildChanged(#NonNull DataSnapshot dataSnapshot, #Nullable String s) {
}
#Override
public void onChildRemoved(#NonNull DataSnapshot dataSnapshot) {
}
#Override
public void onChildMoved(#NonNull DataSnapshot dataSnapshot, #Nullable String s) {
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});

Handling errors with RxJava and MVVM pattern in Android

I'm developing application wherein I want to use MVVM pattern. Currently, all events from xml are handled by the activity which pass them next to ViewModel. E.g. user clicks login button, the event is handled by activity; now the activity call view model's method, inside this method I'm calling RxFirebase (Rx wrapper on Firebase) method which returns Observable, subscribe to them and return it; in view I'm again subscribe to this observable for doing UI update. This situation is presented below.
My question is if this approach is correct? In my opinion, the better solution is to handle the error in ViewModel, but how then I can update UI? One of the solutions is to create interface, e.g. ShowMessageListener, next pass it to ViewModel and use to show message, but I prefer harness RxJava to this.
View method:
public void onLoginClick(View view) {
mBinding.clProgress.setVisibility(View.VISIBLE);
mViewModel.onLoginClick().subscribe(authResult -> {
mBinding.clProgress.setVisibility(View.GONE);
startAnotherActivity();
}, throwable -> {
mBinding.clProgress.setVisibility(View.GONE);
if (throwable instanceof FirebaseApiNotAvailableException) {
Snackbar.make(mBinding.getRoot(), R.string.google_play_services_unavilable, Snackbar.LENGTH_LONG).show();
} else {
Snackbar.make(mBinding.getRoot(), throwable.getMessage(), Snackbar.LENGTH_LONG).show();
}
});
}
ViewModel method:
public Observable<AuthResult> onLoginClick() {
Observable<AuthResult> observable = RxFirebaseAuth.signInWithEmailAndPassword(mAuth, mEmail.get(), mPassword.get());
observable.subscribe(authResult -> {
//save user
}, throwable -> {
//handle error
});
return observable;
}
Your answer is almost correct except that you should really seperate View and (Business)-Logic. This would be the attempt if you use databinding which is highly recommend when using Architecture Components.
That means that everything which updates the UI should be in your View, everything which is not relevant for the view should be in the ViewModel.
That means that you can pass your ViewModel to your Layout, which has a onClick and call the Method in the ViewModel. Example:
<?xml version="1.0" encoding="utf-8"?>
<layout ..>
<data><variable name="viewModel" type="YourVm" /></data>
<Button onClick="#{viewModel::onButtonClick}
</layout>
Now you can handle the onClick inside your ViewModel like
public void onClick(View view) {
Log.d("Click", "My Button was clicked");
}
If you "really" want to observe for errors from your View you could either Create an ObservableBoolean which is set to True onec there's an error and subscribe for changes. You can put it inside the ViewModel like:
public final ObservableBoolean observableError = new ObservableBoolean();
public void onClick(...) { observableError.set(true); }
Now you can observe the Boolean inside your View
yourViewModel.obserableError.observe(this, result -> {
// do your error stuff
});
If you don't use Databinding it's almost the same except that you pass a ClickListener to the Button.
Means that you listen for the OnClick in your View, call the "processing"-method in your ViewModel and update the ObservableBoolean if an error occured. Since your a Listening for changes you can process the SnackBar stuff inside your View.
Snackbar and everything which involves the view should really be seperated from the ViewModel except a navigator. In this case you should create WeakReferences to avoid leaks.
Take care that the ObservableBoolean is NOT part of RxJava. It's part of Architecture Components.
If you want to solve it using RxJava you could create a PublishSubject in your ViewModel like:
Viewmodel.java
public final PublishSubject<String> ps = PublishSubject.create<>()
public void onClick(...) { ps.next("my evil error string"); }
And finally Observe it in your view
myViewModel.ps.subscribe( data -> {...}, error -> { ... } )
Take care that you dispose your RxJava Subscriptions in onCleared() which is in your ViewModel interface.
Edit: I haven't tested the code since i have only Kotlin Projects at the moment but should work in java.
Found an issue in your code that you didnt validate if mBinding is null. This may be null since you subscribe for changes and try to create the SnackBar in the View which may be disposed already. Always use if (mBinding != null) Snackbar.snackysnacky(..)

RxJava and MVP in Android app

I am trying to implement a screen in Android app using MVP architecture and using RxJava and RxBinding on the View side.
Basically I have 2 Spinners, 1 TextEdit and a button that's disabled by default. I want to enable the button when Spinners have items selected and text field is not empty. Here is the code:
Observable.combineLatest(
RxAdapterView.itemSelections(mFirstSpinner),
RxAdapterView.itemSelections(mSecondSpinner),
RxTextView.textChanges(mEditText),
new Func3<Integer, Integer, CharSequence, Boolean>() {
#Override
public Boolean call(Integer first, Integer second, CharSequence value) {
return !TextUtils.isEmpty(value);
}
}).subscribe(new Action1<Boolean>() {
#Override
public void call(Boolean enable) {
mButton.setEnabled(enable);
}
});
The question now is how to integrate that into MVP pattern. Ideally the "business logic" of enabling the button should be in the presenter. What's the best way to achieve this? I am thinking of passing the original observers into the presenter somehow (side question is how?), and the presenter would combine those observers and it would have the logic of enabling the button. In the end, it would just call View to modify button state.
Are there any better options? Are there any good examples of MVP with RxJava on View side?
My proposition:
You are on the right track. However RxBinding logic should still be in the view. I would move the logic connected with deciding whether to enable button or not into presenter.
Define a model holding value from all fields you would like to check:
private class ViewValuesModel {
public Integer adapter1Value;
public Integer adapter2Value;
public CharSequence textValue;
public ViewValuesModel(Integer adapter1Value, Integer adapter2Value, CharSequence textValue) {
this.adapter1Value = adapter1Value;
this.adapter2Value = adapter2Value;
this.textValue = textValue;
}
}
The inside a view create an Observable:
Observable observable = Observable.combineLatest(
RxAdapterView.itemSelections(mFirstSpinner),
RxAdapterView.itemSelections(mSecondSpinner),
RxTextView.textChanges(mEditText),
new Func3<Integer, Integer, CharSequence, ViewValuesModel>() {
#Override
public ViewValuesModel call(Integer first, Integer second, CharSequence value) {
return new ViewValuesModel(first, second, value);
}
}
)
Then pass this Observable to presenter:
mPresenter.observeChoosableFieldChanges(observable).
Inside presenter do the rest:
observable
.map(new Func1<ViewValuesModel, Boolean>() {
#Override
public Booleancall(ViewValuesModel viewStates) {
return !TextUtils.isEmpty(viewStates.textValue);
}
})
.subscribe(new Action1<Boolean>() {
#Override
public void call(Boolean enable) {
if (enable) {
view.enableButton();
}
}
});
you can enumerate your sources and pass Pair value events to a Subject/Processor where you can do the logic whether or not to enable the button and post boolean events. The guy which updates the button from the presenter will subscribe to this Subject/Processor.
Like this you have the flexibility to change sources and logic without changing the Presenter-View contract.
Essentially you can have 2 absolutely decoupled components in the Presenter:
1) component which listens to the incoming view events and produces a stream of action to enable or disable the button
2) a component which listens to enable/disable actions and updates the view respectively (this you can also achieve with Google's Binding Library)
This way you can enable multiple decoupled chains of interaction and yet easy to maintain due components triviality and clarity of the flow connections.
You can also use smth like RHub library. You can find components example here

Chaining requests in Retrofit + RxJava

I have 2 APIs that I want to make request to in sequence and store their data in SQLite.
First I want to make request to API A and store its data in SQL table a. Then make request to API B and store its data in table b and some data in table a_b. The data stored in a_b is from request B alone.
How can I do this using RxJava. I read somewhere about using flatMap for this, something like this
apiService.A()
// store in DB here? How? maybe use map()?
.flatMap(modelA -> {
// or maybe store modelA in DB here?
return apiService.B().map(modelB -> {
storeInDB()l // store B here ?
return modelB;
});
});
If I wasn't using lambda functions, this would look as ugly as normal nested calls. Is this a better way to do it?
I don't think using map operator is the best way to go with things like storing the result of the api call.
What I like to do is to separate those things inside doOnNext operators. So your example would be something like this:
apiService.A()
.doOnNext(modelA -> db.store(modelA))
.flatMap(modelA -> apiService.B())
.doOnNext(modelB -> db.store(modelB));
(add necessary observeOn and subscribeOn yourself, exactly like you need them)
Yes, you can use flatmap for this exact purpose. See the below example (Assuming your service A returns Observable<FooA> and service B returns Observable<FooB>)
api.serviceA()
.flatMap(new Func1<FooA, Observable<FooB>>() {
#Override
public Observable<FooB> call(FooA fooA) {
// code to save data from service A to db
// call service B
return api.serviceB();
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<FooB>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(FooB fooB) {
// code to save data from service B to db
}
});

Passing parameter to Observable.create

I am using RXJava on Android for asynchronously access the database.
I want to save an object in my database.
In this way, I created a method which take a final parameter (the object I want to save) and returns an Observable.
At this point I don't care to emit anything so I will call subscriber.onComplete() at the end.
Here is my code:
public Observable saveEventLog(#NonNull final EventLog eventLog) {
return Observable.create(new Observable.OnSubscribe<Object>() {
#Override
public void call(Subscriber<? super Object> subscriber) {
DBEventLog log = new DBEventLog(eventLog);
log.save();
subscriber.onCompleted();
}
});
}
The thing is, I saw many answer using the final keyword for the parameter, but I would like to do this without it.
The reason is I don't really like the approach of declare a final variable in order to use it in another thread.
Is there any alternative? Thanks.
We usually suggest avoiding the use of create because it may seem simple to use it but they usually violate the advanced requirements of RxJava. Instead, you should use one of the factory methods of Observable. In your case, the just factory method will get what you wanted: no final parameter:
public Observable<?> saveEventLog(#NonNull EventLog eventLog) {
return Observable
.just(eventLog)
.doOnNext(e -> {
DBEventLog log = new DBEventLog(e);
log.save();
})
.ignoreElements();
}

Categories

Resources