lcj
lcj

Reputation: 1797

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:

  1. Take the component out of the viewmodel and call it directly from the fragment
  2. Leave the component in the viewmodel and provide the feedback to the fragment/user by utilizing livedata?
  3. 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?

Upvotes: 1

Views: 946

Answers (1)

Praveen
Praveen

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

Related Questions