Handling errors with RxJava and MVVM pattern in Android - 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(..)

Related

Android call component w/listener or have the viewmodel call the component communicate withe the fragment

I am calling a signin method from a fragment using a viewmodel. I have been using a lot of callbacks in other areas but read that using MVVM I should not be communicating between the fragment and the viewmodel in this way. The Android documentation seems to use LiveData as well. Why is it ok to have listeners for components like adapters for recyclerview and not other components which are called from a view model.
The signin component is in the Splash fragment. Should I call it as a component outside the viewmodel and take advantage of the listeners?
I'm running into an error and want to give feedback to the user. Do I:
Take the component out of the viewmodel and call it directly from the fragment
Leave the component in the viewmodel and provide the feedback to the fragment/user by utilizing livedata?
Leave the signin component in the viewmodel and just use a callback/listener
UPDATE
Thank you for the feedback. I will provide more detail. I'm using Java, FYI. I am focused on the first run procedure, which is different from displaying a list or detail data. So, I need to have a lot of things happen to get the app ready for first use, and I need a lot of feedback in case things go wrong. I created a splash screen and have a way to record the steps in the process, so I can tell when something goes wrong, where it goes wrong. The user ends up seeing the last message, but the complete message is saved.
I have been adding a listener to the call and an interface to the component. If you haven't guessed, I'm somewhat of a novice, but this seemed to really be a good pattern, but I have been reading this is not the right way to communicate between the Fragment and the ViewModel.
Example, from the SplashFragment.java:
viewModel.signIn(getActivity(), getAuthHelperSignInCallback());
In the SplashViewModel.java:
public void signIn (Activity activity, AuthHelper.AuthHelperSignInListener listener) {
AuthHelper authHelper = new AuthHelper(activity.getApplication());
authHelper.signIn(activity,listener);
}
In the AuthHelper.java I have an interface:
public interface AuthHelperSignInListener {
void onSuccess(IAuthenticationResult iAuthenticationResult);
void onCancel();
void onError(MsalException e);
}
Using this method I can get information back that I need, so if I'm not supposed to use a callback/listener in the fragment like this, what is the alternative?
You can use channel to send these events to your activity or fragment, and trigger UI operation accordingly. Channel belongs to kotlinx.coroutines.channels.Channel package.
First, create these events in your viewModel class using a sealed class.
sealed class SignInEvent{
data class ShowError(val message: String) : SignInEvent()
data class ShowLoginSuccess(val message: String) : SignInEvent()
}
Define a channel variable inside viewModel.
private val signInEventChannel = Channel<SignInEvent>()
// below flow will be used to collect these events inside activity/fragment
val signInEvent = signInEventChannel.receiveAsFlow()
Now you can send any error or success event from viewModel, using the defined event channel
fun onSignIn() {
try {
//your sign in logic
// on success
signInEventChannel.send(SignInEvent.ShowLoginSuccess("Login successful"))
} catch(e: Exception){
//on getting an error.
signInEventChannel.send(SignInEvent.ShowError("There is an error logging in"))
}
}
Now you can listen to these events and trigger any UI operation accordingly, like showing a toast or a snackbar
In activity
lifecycleScope.launchWhenStarted {
activityViewModel.signInEvent.collect { event ->
when (event) {
//ActivityViewModel is your viewmodel's class name
is ActivityViewModel.SignInEvent.ShowError-> {
Snackbar.make(binding.root, event.message, Snackbar.LENGTH_SHORT)
.show()
}
is ActivityViewModel.SignInEvent.ShowLoginSuccess-> {
Snackbar.make(binding.root, event.message, Snackbar.LENGTH_SHORT)
.show()
}
}
}
In fragment
viewLifecycleOwner.lifecycleScope.launchWhenStarted {
fragmentViewModel.signInEvent.collect { event ->
when (event) {
is FragmentViewModel.SignInEvent.ShowError-> {
Snackbar.make(requireView(), event.message, Snackbar.LENGTH_SHORT)
.show()
}
is FragmentViewModel.SignInEvent.ShowLoginSuccess-> {
Snackbar.make(requireView(), event.message, Snackbar.LENGTH_SHORT)
.show()
}
}
}

how to make callback in android + viewModel

Hello I'm trying to use the new architecture components by jetpack.
So how the AsyncTask will be deprecated, how could I do a callback in android to get the result from a background thread. without my app lag
public void btnConfigurarClick(View v) {
btnConfigurar.setEnabled(false);
myViewModel.configurar(); // do in background resulting true or false
// how to get the result of it with a callback to set enable(true)
...
The concept of Callback gets converted to Subscribe/Publish in terms of ViewModels.
From Acvitity/Fragment, you would need to subscribe to a LiveData that exists inside the ViewModel.
The changes would be notified as you are observing.
Ex :
Class SomeActivity : Activity{
fun startObservingDataChange(){
yourViewModel.someLiveData.observe(viewLifecycleOwner) { data ->
// Whenever data changes in view model, you'll be notified here
// Update to UI can be done here
}
}
}
Class SomeViewModel{
// Observe to this live data in the View
val LiveData<String> someLiveData;
// Update happens to live data in view model
}
You can learn more about Architecture Components in this app.

Listening for button click and linking fragment with view model

Im trying to learn view models and implement them in my app. I have been following a tutorial on getting me started but, I have a couple questions.
How do i listen for a button click? Since all the business logic is suppose to be stored in the view model would I put an OnClick listener there? Or would i put it with my onChange method in the activity that launches the fragment?
How to tell the fragment to use the view model?
Update was looking at this guys tutorial Location of click event in MVVM architecture . Isn't the whole point of mvvm to eliminate the need of interfaces?
Update 2: Found where you can use data binding to shove OnClick listener into button here: Handle onClick event with Databinding and MVVM and Using DataBinding library for binding events
Live data observe code from activity launching fragment
//private BattleRhythmViewModel battleModel;
battleModel = ViewModelProviders.of(this).get(BattleRhythmViewModel.class);
battleModel.getEvents().observe(this, new Observer<ArrayList<Event>>() {
#Override
public void onChanged(#Nullable ArrayList<Event> events) {
// Add newly created events to array/recycler view
// Another one for pushing new platform/content to database
}
});
}
View model for fragment
public class BattleRhythmViewModel extends ViewModel {
private MutableLiveData<ArrayList<Event>> battleRhythmEvents;
private MutableLiveData<ArrayList<TableData>> battleRhythmExtra;
public LiveData<ArrayList<Event>> getEvents()
{
return battleRhythmEvents;
}
public LiveData<ArrayList<TableData>> getExtras()
{
return battleRhythmExtra;
}
}

What is the use of return statement when using with LiveData

I have followed this example, an integrated ViewModel and LiveData.
I used LiveData for ViewModel to Repository communication, and activity to ViewModel communication
I have few confusions that I want to clear this question.
This is working fine, and display Toast Message after 5 seconds on MainActivity.
MainActivity
MainViewModel homeViewModel = ViewModelProviders.of(this).get(MainViewModel.class);
homeViewModel.getResponseval().observe(this, new Observer<String>() {
#Override
public void onChanged(#Nullable String s) {
Toast.makeText(getApplicationContext(), "MainActivityObserverCalled", Toast.LENGTH_LONG).show();
}
});
MainViewModel
public class MainViewModel extends ViewModel {
public MutableLiveData<String> responseval;
private LoginRepositry loginRepositry;
public MainViewModel(){
loginRepositry = new LoginRepositry();
responseval = loginRepositry.getData("username","password");
}
public MutableLiveData<String> getResponseval() {
return responseval;
}
LoginRepositry
public class LoginRepositry {
private MutableLiveData<String> data = new MutableLiveData<>();
public MutableLiveData<String> getData(String username , String userpass) {
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
data.setValue("Login Repo Set Value");
}
}, 5000);
return data;
}
These are my 2 questions.
Now with each method, I am returning some data of type LiveData, but
at the time of returning the data, the value is not set. I set it after 5
seconds data.setValue("SomeValue"), So what is the use of return
here, is it only because of method return type, and does nothing in
case of LiveData
In MainActivity, i used homeViewModel.getResponseval().observe
to observer data, but in ViewModel, I didn't use observe, but
ViewModel is still observing the Repository and after 5 seconds
MainActivity gets a result. I am unable to understand whats happening here
.
So let's do this by parts:
Now with each method i am returning some data of type LiveData, but at
the time of returning the data, the value is not set. I set it after 5
seconds data.setValue("SomeValue"), So what is the use of return here,
is it only because of method return type, and does nothing in case of
LiveData
If you check the docs you'll see that LiveData "is an observable data holder class", so it holds data and you can observer it. This is very important to understand why you're returning this LiveData object here. By returning it you're telling the next layer of your architecture (the ViewModel) "hey, here is this holder, I will put some data here at some point, so observer it if you want to receive the data".
The ViewModel doesn't observe it, but simply pass it to the next entity interested in this holder of data, the UI (LifeCycleOwner). So in the owner you start to observe this "holder of data" and will be warned when new data arrives.
In MainActivity, i used homeViewModel.getResponseval().observe to
observer data, but in ViewModel, I didn't use observe, but ViewModel
is still observing the Repository, and after 5 seconds MainActivity
gets result. I am unable to understand whats happening here.
You need a LifeCycleOwner in order to observe updates from a LiveData, also from the docs: "LiveData considers an observer, which is represented by the Observer class, to be in an active state if its lifecycle is in the STARTED or RESUMED state. LiveData only notifies active observers about updates." In order to detect the state, it needs a LifeCycleOwner, that's why when you have the observe method you pass this as a parameter. And that's why you didn't use observe in the ViewModel.

Enabling Button when EditText has text (RxAndroid)

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)).

Categories

Resources