how to make callback in android + viewModel - android

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.

Related

How to use Room Database Viewmodel Observer in Android Foreground Service

I have following view model and observer in A Fragment(in onViewCreated) which populates the recyelerview using adapter
commonOwnDBViewModel =
new ViewModelProvider(requireActivity()).get(CommonOwnDBViewModel.class);
Observer<List<FolderEntity>> notesObserver =
FolderEntityList -> {
folderNameAdapter.setListManager(FolderEntityList);
recyclerView.getAdapter().notifyDataSetChanged();
Toast.makeText(mActivity, "" + FolderEntityList.size(), Toast.LENGTH_SHORT).show();
};
commonOwnDBViewModel.getAllFolderEntityList
().observe(requireActivity(), notesObserver);
it gives me a list and works fine in the fragment now I want to use the same list in A service using Viewmodel and Observer how can we do that how do we manage Lifecycle in service.
Instead of using normal service, use LifecycleService.
You can get the lifecycle of the service by calling getLifecycle() method and pass this lifecycle to your observer.
More details here: https://developer.android.com/reference/androidx/lifecycle/LifecycleService

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()
}
}
}

Callback is not Trigger when app is is background/Minimized

I have Implemented MVVM architecture and it is working good when app is in foreground but when i minimize the app, repository send the data to view model and view model to activity but the callback is not called until i resume the application.
Method is Repository:
// Method for new order and its process, and listening the connects collection
fun connects(driverId: String, connectsSnapshot: (QuerySnapshot?) -> Unit) {
if (connectsRef!=null){
connectsRef?.remove()
connectsRef=null
}
this.connectsSnapshot = connectsSnapshot
connectsRef = FirebaseFirestore.getInstance().collection("connects").whereEqualTo("driver_id", UserDto.getInstance().id).whereEqualTo("status", "new").orderBy("created", Query.Direction.DESCENDING).limit(1)
.addSnapshotListener { snapshots, e ->
if (e != null) {
connectsSnapshot (null)
return#addSnapshotListener
}
System.out.println("==>ListnerHit")
connectsSnapshot (snapshots)
}
}
this one send data to view model
val mutableLiveDataForConnect = MutableLiveData<QuerySnapshot>()
val mObserverForConnect: Observer<QuerySnapshot> = Observer {
getView().connectListener(it)
}
fun getConnectsData(driverId: String)
{
repository.connects(driverId){
mutableLiveDataForConnect.postValue(it)
System.out.println("==>ViewModel")
}
}
this one also get called but i am not able to receive the data on activity when app is minimized. it works fine when i move the code to acitivty but i need to move the code to repository.
LiveData doesn't get new values without an active Observer, and when you minimise your app the Observer gets paused, because it is lifecycle dependent. Once the app gets back to the foreground, the Observer returns to the active state, the latest data gets posted to it and your activity is updated. This is how LiveData and Observers are supposed to work.
It's not entirely clear to me what you are trying to do. Why is it necessary to update the activity while it's not visible to anyone? It sounds like you are trying to do something in the activity/Observer that's not supposed to be happening there with MVVM.

How to trigger LiveData SwitchMap even if there is no observer attached

Issue:
The switchMap transformation does not trigger until its result must have an active observer. I am trying to trigger a switchMap transformation even if there is no observer. Can anyone suggest how i can achieve this functionality? There is some code below with depicting the current scenario.
How to reproduce:
In ProfileViewModel, The UI can observe two different LiveData
profileLiveData : to observe Profile Data
profileApiStateLiveData : to observe the state of Http Call that fetches the profile data from backend
ProfileViewModel.java
public class ProfileViewModel extends AndroidViewModel {
private MutableLiveData<Boolean> getProfileCommand = new MutableLiveData<>();
public ProfileViewModel(#NonNull Application application) {
super(application);
profileLiveData = Transformations.switchMap(getProfileCommand, forceUpdate -> UserRepository.getInstance().getProfile(forceUpdate));
profileApiStateLiveData = UserRepository.getInstance().getProfileApiRequestStatus();
}
public void loadProfile(boolean forceUpdate) {
getProfileCommand.postValue(forceUpdate);
}
// Profile Data : Observable Field
private LiveData<Profile> profileLiveData;
public LiveData<Profile> getProfileLiveData() {
if(profileLiveData == null) profileLiveData = new MutableLiveData<>();
return profileLiveData;
}
// Profile Http Call's State : Observable Field
private LiveData<ApiRequest> profileApiStateLiveData;
public LiveData<ApiRequest> getProfileApiStateLiveData() {
if(profileApiStateLiveData == null) profileApiStateLiveData = new MutableLiveData<>();
return profileApiStateLiveData;
}
}
Now the UI can observe changes in both Profile Data and State of Http Call.
So if a UI want to download and display the Profile on UI, the UI is responsible to observe the profileLiveData and profileApiStateLiveData and then call the ViewModel's method loadProfile(true);
// Observes Profile Data
mProfileViewModel.getProfileLiveData().observe(this, profileData -> {
// Use profile data here
});
// Observes State of Profile Http Call
mProfileViewModel.getProfileApiStateLiveData().observe(this, profileHttpCallState -> {
// show/hide progress based on http call state
});
// start downloading profile data
mProfileViewModel.loadProfile(true);
We can see here that the method loadProfile will trigger the switchMap Transformation and will start the Http Call. Please note that switchMap trigger happens because the UI is observing the result LiveData which is profileLiveData.
This scenario works fine. But if a certain Activity only wants to initiate the Http Call and only want to observe the profileApiStateLiveData not the profileLiveData, then the switchMap Trigger never occur, this is because there is no active observer of result LiveData profileLiveData.
/**** Do not observe Profile Data, because here we only need to observe Http Call State ***/
/**** Only Observe State of Profile Http Call ***/
mProfileViewModel.getProfileApiStateLiveData().observe(this, profileHttpCallState -> {
// show/hide progress based on http call state
});
/**** start downloading profile data ***/
mProfileViewModel.loadProfile(true);
/**** The above line does not trigger the switchMap Transformation. ***/
There is an ugly solution i have in which i have to add an un-necessary blank observer for profileLiveData in the UI. But that is error-prone because the other developers can forget to add this un-necessary blank observer for profileLiveData and they would have no clue about why profile data is not being fetched even they are calling the method loadProfile(true).
The help from Rx experts is much appreciated here :-)
This question has been answered very well and thanks to #arka-prava-basu . The profileLiveData has been detached from Transformation and it is now being fetched directly from Room Database. And the action
(UserRepository.getInstance().getProfile(forceUpdate) )
that was being performed by this transformation has been now transferred to loadProfile method. Below is the refactored code.
ProfileViewModel.java
public class ProfileViewModel extends AndroidViewModel {
public ProfileViewModel(#NonNull Application application) {
super(application);
profileLiveData = UserRepository.getInstance().getMemberInfo();
profileApiStateLiveData = UserRepository.getInstance().getProfileApiRequestStatus();
}
public void loadProfile(boolean forceDownload) {
if(forceDownload) {
UserRepository.getInstance().initiateProfileDownloading();
}
}
// Profile Data : Observable Field
private LiveData<Profile> profileLiveData;
public LiveData<Profile> getProfileLiveData() {
return profileLiveData;
}
// Profile Http Call's State : Observable Field
private LiveData<ApiRequest> profileApiStateLiveData;
public LiveData<ApiRequest> getProfileApiStateLiveData() {
return profileApiStateLiveData;
}
}

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

Categories

Resources