Vodet
Vodet

Reputation: 1519

Good practice for communicate between viewModel and fragment

I'm implementing the viewModel and for communicate between the viewModel and fragment I'm doing this :

public class SplashViewModel extends AndroidViewModel {

private LiveData<Boolean> actions;

public SplashViewModel(@NonNull Application application) {
    super(application);
    actions= new MutableLiveData<>();
}


public void aViewModelMethod() {
    //doing some stuff
    if (stuff == X){
          //I need to hide a view for exemple, I'm doing this
          actions.postValue(true);
    }
} 

Now inside my Fragment I have an observable who is trigger when actions.postValue(true) is reached

viewModel.actions.observe(getViewLifecycleOwner(), new Observer<Boolean>() {
            @Override
            public void onChanged(Boolean action) {
                if (action){
                    databinding.myView.setVisibility(View.VISIBLE);
                }
            }
        });

This work fine but if I have a lot of communication I need to implement each time a new variable, and observe it ? It's ok when they are 4 or 5 but what I am suppose to do when they are hundreds ?

I try to change boolean by an integer with a switch and a list of actions, but when the viewModel is initialize it's possible that several postValue are trigger and when I created the observable I'm only get the last one, that make sense.

Upvotes: 2

Views: 1395

Answers (2)

Sergei Zarochentsev
Sergei Zarochentsev

Reputation: 827

Usually, I have two observable live data in my view model. First is represent the state of the whole screen. Second I use for "single-shot" events like toasts, navigation, showing dialogs.

My view model:

class PinCreateViewModel(...) : ViewModel() {

    val event = MutableLiveData<BaseEvent<String>>()
    val state = MutableLiveData<PinCreateViewState>()
}

I have a single state object for the whole screen:

sealed class PinCreateViewState {

    object FirstInput : PinCreateViewState()

    data class SecondInput(val firstEnteredPin: String) : PinCreateViewState()

    object Error : PinCreateViewState()

    object Loading : PinCreateViewState()
}

I think with this approach it's easy to think about my screen states, easy to design my screen as a finite state machine, and easy to debug. Especially, I like this approach to very complex screens. In this case, I have a single source of truth for my whole screen state.

But sometimes I want to show dialogs, toast or open new screens. These things are not part of my screen state. And this is why I want to handle them separately. And in this case, I use Events:

sealed class BaseEvent(private val content: String) {

    var hasBeenHandled = false
        private set

    fun getContentIfNotHandled(): String? {
        return if (hasBeenHandled) {
            null
        } else {
            hasBeenHandled = true
            content
        }
    }

    fun peekContent(): String = content
}

class ErrorEvent(content: String) : BaseEvent(content)

class MessageEvent(content: String) : BaseEvent(content)

And my Fragment interaction with ViewModel looks like this:

override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)    
        observe(viewModel.event, this::onEvent)
        observe(viewModel.state, this::render)
}

private fun render(state: PinCreateViewState) {
        when (state) {
            PinCreateViewState.FirstInput -> setFirstInputState()
            is PinCreateViewState.SecondInput -> setSecondInputState()
            PinCreateViewState.Error -> setErrorState()
            PinCreateViewState.Loading -> setLoadingState()
        }
}

fun onEvent(event: BaseEvent<String>) {
        event.getContentIfNotHandled()?.let { text ->
            when (event) {
                is MessageEvent -> showMessage(text)
                is ErrorEvent -> showError(text)
            }
        }
    }

I really like Kotlin Sealed classes because it forces me to handle all possible cases. And I can find unhandled states even before compilation.

Upvotes: 2

VishalJha
VishalJha

Reputation: 141

PostValue method Posts a task to the main thread to set the given value. If you called this method multiple times before a main thread executed a posted task, only the last value would be dispatched.

If you will have hundreds of communication between your fragment and viewModel so you just have to deduce your fragment logic like if you have to show view on some conditions then just observe one non mutable live data in your fragment and use two live data's mutable and another non mutable .... use non mutable to set that boolean on every sort of stuff and checks in your viewModel and in the beginning assign that live data to your non mutable one.

private val _liveData = MutableLiveData<Boolean>()
internal val liveData: LiveData<Boolean> = _liveData

It's the better approach ,i hope i understand you question better if not please elaborate it more so that i can help .

Upvotes: 1

Related Questions