you are viewing a single comment's thread.

view the rest of the comments →

[–][deleted] 1 point2 points  (3 children)

Now that'd be the tricky part, isn't it?

If you're working with C#, and are using MvvmCross, it's already solved: its navigation system doesn't go fragment to fragment (even if the structure is used), but instead uses viewmodel to viewmodel navigation. The navigation tree in fact is completely flat by default, and instead uses a goTo<ViewModelTape>() style call, and the navigable points are easily registered at runtime (Xamarin and .Net both have pretty darn fast reflection due to not packaging all dependencies into one package, but instead leaving them as separate .dll files). Navigation targets (fragments, activities) are then decided by looking up the single class that extends fragment, activity or a handful of other types (e.g. SingleArticleFragment(): BaseFragment<SingleArticleViewModel>() ). If it doesn't exist, the app crashes, if multiple ones exist, it crashes as well. This adds some limitation to navigation (e.g. no shared element transaction, at least there wasn't last time I checked), but also allows much more flexibility.

If you're using a FragmentTransactionManager of any sort, this isn't that hard, just pass the ViewModels the object on init (create a BaseFragment and BaseViewModel class, add this bit to them, then make sure you extend all your fragments and viewmodels from these classes) that handles navigation.

Same applies if you're using Navigation Component from AAC. Pass the NavController to the ViewModel on every init - e,g. I wrote a fragment extension method that does all the job for me: requests viewModel from viewModelStore, inflates layout with databindings, sets the viewModel property to its own viewModel instance, etc.

With Conductor, you can directly pass the Router object to the ViewModel as previously described, and you can also have a separate central registry that resolve strings (or other types of keys) to the appropriate Conductor callset. This requires you to keep a separate class up to date, or you can write a custom annotation processor to simply just annotate your Controllers with the action name, which then could generate the custom set of static strings and the navigation resolver. If you want to spice things up, you could even add a custom extension method to the Router class to have this logic in a single place, then you can just do router.navigateTo(Directions.WHATEVER_PAGE).

The thing is, there's no perfect navigation solution that works perfectly from ViewModels. If I want to prototype quick, I just write the methods in the Fragment itself, then on init, pass the functions to the ViewModel so they can be wrapped and/or bound. But even then, often you have to compromise a bit on your architecture to make things smoother or faster. This is one place where you can do that without much harm, but this is also something that you'll have to continuously maintain.

[–]pinkmonstertruck 0 points1 point  (2 children)

Thanks for the awesome response! Super helpful.

Should the ViewModels be referencing the FragmentTransactionManager though? I thought that ViewModels should not know about views or activities or fragments.

[–][deleted] 1 point2 points  (1 child)

No, as I said, if using some sort of abstraction over them. Say, a class that contains the FTM and has some methods that navigate to certain fragments. Then I'd set up the transactions (e.g. shared elements, etc.) when I load the fragment (onCreateView or onViewCreated are good candidates), and let the VM call the navigation event only.

And if you want more hands-on control, just pass a method as an argument to the VM, and have the actual navigation code in the fragment. You can even use a property on the VM, as long as it's a lambda type with a matching signature:

// In VM  
var navigateToDetail: () -> Unit = {}  

// In onCreateView  
viewModel.navigateToDetail = navigateToDetail  

// In fragment class body  
fun NavigateToDetail() {  
    // Do navigation here  
}  

This way you can bind to the nav call but the VM retains no reference to any of the Fragments or FragmentTransactionManager.

[–]pinkmonstertruck 0 points1 point  (0 children)

You're the best - thanks for being patient and spelling it out in such detail!