Why is the navigation triggered from the Fragments?

Viewed 251

I'm trying to setup my project according to the MVVM pattern and I'm using Android Architecture Components by Jetpack.

I have an "splash" fragment, during which an HTTP request is made and depending on the response, the user will be navigated to either the settings view or to the main view.

FragmentSplash:

class FragmentSplash : Fragment()
{
    private lateinit var viewModelSplash: ViewModelSplash

    override fun onCreateView( inflater: LayoutInflater,
                               container: ViewGroup?,
                               savedInstanceState: Bundle? ): View?
    {
        viewModelSplash = ViewModelProviders.of( this ).get( ViewModelSplash::class.java )
        return inflater.inflate(com.host.myproject.R.layout.fragment_splash, container, false )
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?)
    {
        viewModelSplash.command.observe(this, Observer {
            when( it ) // I know strings are bad :)
            {
                "NAVIGATE_TO_MAIN" -> Navigation.findNavController( view ).navigate( R.id.action_showConfirm )
                "NAVIGATE_TO_"     -> Navigation.findNavController( view ).navigate( R.id.action_showBase )
            }
        });

        super.onViewCreated(view, savedInstanceState)
    }
}

ViewModelSplash:

class ViewModelSplash : ViewModel()
{
    private val _command = MutableLiveData<String>()
    val command: LiveData<String>
        get() = _command

    init
    {
        Timer("fakeHTTPRequest", false).schedule(3500) {
            onInitializationFinished( response )
        }
    }

    private fun onInitializationFinished( response : Response )
    {
        if( response.ok )
            _command.postValue( "NAVIGATE_TO_MAIN" )
        else
            _command.postValue( "NAVIGATE_TO_CONFIRM" )
    }
}

The problem that I see is this:

-The FragmentSplash contains logic pattern the "when" statement
-The FragmentSplash is aware of other fragments

I'm trying to understand what is the proper way to implement this as all of the examples I've seen so far call the navigator within the Fragment after ViewModel changes the data within the observable:

viewModelSplash.command.observe(this, Observer {
...
    Navigation.findNavController( view ).navigate
...

I'm completely lost.. doesn't this

contradict the "in-dependency of views/logic"?

I would think there should be some kind of Router class, which gets notification from ViewModel that it needs to navigate elsewhere, that way view would be completely clean. Any suggestions where to go from here please?

1 Answers

You could try to have navigation commands live data in the view model and observe it in BaseFragment. Lets say you want to navigate from Fragment A to Fragment B then:

Navigation commands

sealed class NavigationCommand {
  data class navigateTo(val directions: NavDirections): NavigationCommand()
  object Back: NavigationCommand()
}

In BaseFragment

viewModel.navigationCommands.observe { command ->
    when (command) {
      is NavigationCommand.navigateTo ->      
        findNavController().navigate(command.directions)
      NavigationCommand.Back ->      
        findNavController().popBackStack()
      }
    }

In ViewModel

fun navigate(directions: NavDirections) {
  navigationCommands.postValue(NavigationCommand.To(directions))
}

Navigation from ViewModel

navigate(FragmentADirections.actionToFragmentB())

This is just one approach and you can check this Using Navigation Architecture Component in a large banking app. It has a great explanation of navigating from view model and removing boiler plate code from fragment.

Related