architecture-components-samples
architecture-components-samples copied to clipboard
BasicRxJavaSample updateUserName and configuration changes
On a real application, a method like updateUserName() might perform a long running operation (network call / db query)
This example is clearing the compositeDisposable on onStop(), so this long operation would interrupted upon config changes.
https://github.com/googlesamples/android-architecture-components/blob/master/BasicRxJavaSample/app/src/main/java/com/example/android/observability/ui/UserActivity.java
How can I work around this issue? I'm looking into the Completable::cache() operator and the possibility of using subjects but I'd like to hear your take on this.
Thanks :)
Also, the state of the button modified when updateUserName() is executed (mUpdateButton.setEnabled(false)) should be saved automatically in the Android Bundle and after rotation the button would be disabled and the user can't click on it anymore.
How would you solve this updateUserName and configuration changes problem?
See: https://github.com/googlesamples/android-architecture-components/blob/master/BasicRxJavaSample/app/src/main/java/com/example/android/observability/ui/UserActivity.java#L109
@feresr @sebaslogen A subject based solution would fix this problem.
Changes needed in UserActivity.java
@Override
protected void onStart() {
super.onStart();
mDisposable.add(mViewModel.getUserSubscription()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<String>() {
@Override
public void accept(String userName) throws Exception {
mUserName.setText(userName);
}
}));
mDisposable.add(mViewModel.getSaveButtonStateSubscription()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<Boolean>() {
@Override
public void accept(Boolean saveButtonStatus) throws Exception {
mUpdateButton.setEnabled(saveButtonStatus);
}
}));
mDisposable.add(mViewModel.getErrorSubscription()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<String>() {
@Override
public void accept(String errorMessage) throws Exception {
Log.e(TAG, errorMessage);
}
}));
}
@Override
protected void onStop() {
mDisposable.clear();
super.onStop();
}
private void updateUserName() {
String userName = mUserNameInput.getText().toString();
mViewModel.updateUserName(userName);
}
Changes need in UserViewModel.java
private CompositeDisposable compositeDisposable;
private BehaviorSubject<String> userSubject = BehaviorSubject.create();
private BehaviorSubject<String> errorSubject = BehaviorSubject.create();
private BehaviorSubject<Boolean> saveButtonStateSubject = BehaviorSubject.create();
public Observable<String> getUserSubscription() {
return userSubject.serialize();
}
public Observable<String> getErrorSubscription() {
return errorSubject.serialize();
}
public Observable<Boolean> getSaveButtonStateSubscription() {
return saveButtonStateSubject.serialize();
}
public UserViewModel(UserDataSource dataSource) {
mDataSource = dataSource;
compositeDisposable = new CompositeDisposable();
userSubject = BehaviorSubject.create();
saveButtonStateSubject = BehaviorSubject.create();
getUserName();
}
public void getUserName() {
compositeDisposable.add(mDataSource.getUser()
.map(new Function<User, String>() {
@Override
public String apply(User user) throws Exception {
mUser = user;
return user.getUserName();
}
})
.delay(3, TimeUnit.SECONDS)
.subscribeOn(Schedulers.computation())
.observeOn(Schedulers.computation())
.subscribe(new Consumer<String>() {
@Override
public void accept(String userName) throws Exception {
userSubject.onNext(userName);
}
}, new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
errorSubject.onNext("Unable to update username");
}
}));
}
public void updateUserName(final String userName) {
compositeDisposable.add(new CompletableFromAction(new Action() {
@Override
public void run() throws Exception {
mUser = mUser == null
? new User(userName)
: new User(mUser.getId(), userName);
mDataSource.insertOrUpdateUser(mUser);
}
})
.doOnSubscribe(new Consumer<Disposable>() {
@Override
public void accept(@NonNull Disposable disposable) throws Exception {
saveButtonStateSubject.onNext(false);
}
})
.delay(3, TimeUnit.SECONDS)
.subscribeOn(Schedulers.computation())
.observeOn(Schedulers.computation())
.subscribe(new Action() {
@Override
public void run() throws Exception {
saveButtonStateSubject.onNext(true);
}
}, new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
errorSubject.onNext("Unable to update username");
saveButtonStateSubject.onNext(true);
}
}));
}
@Override
protected void onCleared() {
compositeDisposable.dispose();
super.onCleared();
}
@viraj49 instead of Subjects I would recommend to use Relays https://github.com/JakeWharton/RxRelay/
@sebaslogen Sure, RxRelay would be a good choice here, I just wanted to share an implementation with minimal changes in the existing BasicRxJavaSample code, so that it's easy to relate.
For production apps, based on your needs, you can choose the library of your choice even subject of your choice, or a completely different implementation, the key concept here is that dataSubscriptions are detached from viewSubscriptions.
Another implementation using LiveData would look like this. Which can be improvised by using a "state model/data class". Was playing with different options and this one seems safer & shorter solution in comparison with subject based implementation.
UserActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_user);
mUserName = (TextView) findViewById(R.id.user_name);
mUserNameInput = (EditText) findViewById(R.id.user_name_input);
mUpdateButton = (Button) findViewById(R.id.update_user);
ViewModelFactory mViewModelFactory = Injection.provideViewModelFactory(this);
mViewModel = ViewModelProviders.of(this, mViewModelFactory).get(UserViewModel.class);
mUpdateButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
updateUserName();
}
});
mViewModel.getUserLiveData().observe(this, new Observer<String>() {
@Override
public void onChanged(String userName) {
mUserName.setText(userName);
}
});
mViewModel.getErrorLiveData().observe(this, new Observer<String>() {
@Override
public void onChanged(String errorMessage) {
Log.e(TAG, errorMessage);
}
});
mViewModel.getSaveButtonStateLiveData().observe(this, new Observer<Boolean>() {
@Override
public void onChanged(Boolean saveButtonStatus) {
mUpdateButton.setEnabled(saveButtonStatus);
}
});
}
UserViewModel.java
private CompositeDisposable compositeDisposable;
private MutableLiveData<String> userLiveData = new MutableLiveData<>();
private MutableLiveData<String> errorLiveData = new MutableLiveData<>();
private MutableLiveData<Boolean> saveButtonStateLiveData = new MutableLiveData<>();
public UserViewModel(UserDataSource dataSource) {
mDataSource = dataSource;
compositeDisposable = new CompositeDisposable();
getUserName();
}
public LiveData<String> getUserLiveData() {
return userLiveData;
}
public LiveData<String> getErrorLiveData() {
return errorLiveData;
}
public LiveData<Boolean> getSaveButtonStateLiveData() {
return saveButtonStateLiveData;
}
public void getUserName() {
compositeDisposable.add(mDataSource.getUser()
.map(new Function<User, String>() {
@Override
public String apply(User user) throws Exception {
mUser = user;
return user.getUserName();
}
})
.delay(3, TimeUnit.SECONDS)
.subscribeOn(Schedulers.computation())
.observeOn(Schedulers.computation())
.subscribe(new Consumer<String>() {
@Override
public void accept(String userName) throws Exception {
userLiveData.postValue(userName);
}
}, new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
errorLiveData.postValue("Unable to update username");
}
}));
}
public void updateUserName(final String userName) {
compositeDisposable.add(new CompletableFromAction(new Action() {
@Override
public void run() throws Exception {
mUser = mUser == null
? new User(userName)
: new User(mUser.getId(), userName);
mDataSource.insertOrUpdateUser(mUser);
}
})
.delay(3, TimeUnit.SECONDS)
.subscribeOn(Schedulers.computation())
.observeOn(Schedulers.computation())
.doOnSubscribe(new Consumer<Disposable>() {
@Override
public void accept(@NonNull Disposable disposable) throws Exception {
saveButtonStateLiveData.postValue(false);
}
})
.subscribe(new Action() {
@Override
public void run() throws Exception {
saveButtonStateLiveData.postValue(true);
}
}, new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
errorLiveData.postValue("Unable to update username");
saveButtonStateLiveData.postValue(true);
}
}));
}
@Override
protected void onCleared() {
compositeDisposable.dispose();
super.onCleared();
}