I try to make sample login page with two fields (username, password
) and save button with android architecture component, using android data binding, validating the data in viewmodel
and from view model I make call to repository for remote server call as mentioned in official doc, remote server return me userid with success so how can I start new fragment from view model using this success? I learn something about singleLiveEvent
and EventObserver
, but I'm not able to find there clear usage example:
LoginViewModel
private MutableLiveData<String> snackbarStringSingleLiveEvent= new MutableLiveData<>();
@Inject
public LoginViewModel(@NonNull AppDatabase appDatabase,
@NonNull JobPortalApplication application,
@NonNull MyApiEndpointInterface myApiEndpointInterface) {
super(application);
loginRepository = new LoginRepository(application, appDatabase, myApiEndpointInterface);
snackbarStringSingleLiveEvent = loginRepository.getLogin(username.get(), password.get(), type.get());
}
public MutableLiveData<String> getSnackbarStringSingleLiveEvent() {
return snackbarStringSingleLiveEvent;
}
Repository
public SingleLiveEvent<String> getLogin(String name, String password, String type) {
SingleLiveEvent<String> mutableLiveData = new SingleLiveEvent<>();
apiEndpointInterface.getlogin(name, password, type).enqueue(new Callback<GenericResponse>() {
@Override
public void onResponse(Call<GenericResponse> call, Response<GenericResponse> response) {
mutableLiveData.setValue(response.body().getMessage());
}
@Override
public void onFailure(Call<GenericResponse> responseCall, Throwable t) {
mutableLiveData.setValue(Constant.FAILED);
}
});
return mutableLiveData;
}
Login Fragment
private void observeViewModel(final LoginViewModel viewModel) {
// Observe project data
viewModel.getSnackbarStringSingleLiveEvent().observe(this, new Observer<String>() {
@Override
public void onChanged(String s) {
}
});
}
How can I use EventObserver
in above case? Any practical example?
SingleLiveEvent is a subclass of MutableLiveData with a single Observer Observing it at a time, hence it is aware of view's lifecycle.
StateFlow and LiveData have similarities. Both are observable data holder classes, and both follow a similar pattern when used in your app architecture. The StateFlow and LiveData do behave differently: StateFlow requires an initial state to be passed into the constructor, while LiveData does not.
SingleLiveData. It is a lifecycle-aware observable that sends only new updates after subscription, used for events like navigation and Snackbar messages. livedata-ktx has different implementation comparing to SingleLiveEvent from google samples android-architecture.
Check out below example about how you can create single LiveEvent to observe only one time as LiveData
:
Create a class called Event
as below that will provide our data once and acts as child of LiveData
wrapper :
public class Event<T> {
private boolean hasBeenHandled = false;
private T content;
public Event(T content) {
this.content = content;
}
public T getContentIfNotHandled() {
if (hasBeenHandled) {
return null;
} else {
hasBeenHandled = true;
return content;
}
}
public boolean isHandled() {
return hasBeenHandled;
}
}
Then declare this EventObserver
class like below so that we don't end up placing condition for checking about Event
handled every time, everywhere :
public class EventObserver<T> implements Observer<Event<T>> {
private OnEventChanged onEventChanged;
public EventObserver(OnEventChanged onEventChanged) {
this.onEventChanged = onEventChanged;
}
@Override
public void onChanged(@Nullable Event<T> tEvent) {
if (tEvent != null && tEvent.getContentIfNotHandled() != null && onEventChanged != null)
onEventChanged.onUnhandledContent(tEvent.getContentIfNotHandled());
}
interface OnEventChanged<T> {
void onUnhandledContent(T data);
}
}
And How you can implement it :
MutableLiveData<Event<String>> data = new MutableLiveData<>();
// And observe like below
data.observe(lifecycleOwner, new EventObserver<String>(data -> {
// your unhandled data would be here for one time.
}));
// And this is how you add data as event to LiveData
data.setValue(new Event(""));
Refer here for details.
Edit for O.P.:
Yes, data.setValue(new Event(""));
is meant for repository when you've got response from API (Remember to return same LiveData
type you've taken in VM instead of SingleLiveEvent
class though).
So, let's say you've created LiveData
in ViewModel
like below :
private MutableLiveData<Event<String>> snackbarStringSingleLiveEvent= new MutableLiveData<>();
You provide value to this livedata as Single Event from repository like below :
@Override
public void onResponse(Call<GenericResponse> call, Response<GenericResponse> response) {
mutableLiveData.setValue(new Event(response.body().getMessage())); // we set it as Event wrapper class.
}
And observe it on UI (Fragment) like below :
viewModel.getSnackbarStringSingleLiveEvent().observe(this, new EventObserver<String>(data -> {
// your unhandled data would be here for one time.
}));
Event.java
public class Event<T> {
private T content;
private boolean hasBeenHandled = false;
public Event(T content) {
this.content = content;
}
/**
* Returns the content and prevents its use again.
*/
public T getContentIfNotHandled() {
if (hasBeenHandled) {
return null;
} else {
hasBeenHandled = true;
return content;
}
}
/**
* Returns the content, even if it's already been handled.
*/
public T peekContent() {
return content;
}
}
EventObserver.java
public class EventObserver<T> implements Observer<Event<? extends T>> {
public interface EventUnhandledContent<T> {
void onEventUnhandledContent(T t);
}
private EventUnhandledContent<T> content;
public EventObserver(EventUnhandledContent<T> content) {
this.content = content;
}
@Override
public void onChanged(Event<? extends T> event) {
if (event != null) {
T result = event.getContentIfNotHandled();
if (result != null && content != null) {
content.onEventUnhandledContent(result);
}
}
}
}
Example, In ViewModel Class
public class LoginViewModel extends BaseViewModel {
private MutableLiveData<Event<Boolean>> _isProgressEnabled = new MutableLiveData<>();
LiveData<Event<Boolean>> isProgressEnabled = _isProgressEnabled;
private AppService appService;
private SchedulerProvider schedulerProvider;
private SharedPreferences preferences;
@Inject
LoginViewModel(
AppService appService,
SchedulerProvider schedulerProvider,
SharedPreferences preferences
) {
this.appService = appService;
this.schedulerProvider = schedulerProvider;
this.preferences = preferences;
}
public void login(){
appService.login("username", "password")
.subscribeOn(schedulerProvider.executorIo())
.observeOn(schedulerProvider.ui())
.subscribe(_userLoginDetails::setValue,
_userLoginDetailsError::setValue,
() -> _isProgressEnabled.setValue(new Event<>(false)),
d -> _isProgressEnabled.setValue(new Event<>(true))
)
}
}
In Login Fragment,
viewModel.isProgressEnabled.observe(this, new EventObserver<>(hasEnabled -> {
if (hasEnabled) {
// showProgress
} else {
// hideProgress
}
}));
Using Event and EventObserver class we can achieve the same like SingleLiveEvent class but if you are thinking a lot of boilerplate code just avoid this method. I hope it would help you and give some idea about why we are using SingleEvent in LiveData.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With