Reputation: 1797
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:
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?
Upvotes: 1
Views: 946
Reputation: 3486
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()
}
}
}
Upvotes: 2