1 of 30

Using Navigation component in a large multi-module app

David Vávra

2 of 30

David Vávra

  • based in Prague, Czechia
  • Google Developer Expert for Android, Play, and Pay
  • Android Dev Lead at Air Bank Germany
  • CEO at Settle Up

Twitter: @vavradav

3 of 30

Why to use Jetpack Navigation?

  • All navigation at one place (XML)
  • No more Fragment transactions
  • Fragments have guaranteed arguments (SafeArgs)
  • Back and Up navigation handled correctly
  • Support for deep links
  • Android Studio support
  • Recommended, stable (2.1.0)

4 of 30

Air Bank Germany app architecture

  • 100% Kotlin
  • single activity
  • multi-module
  • Android ViewModels (MVVM)
  • Koin DI
  • RxJava

5 of 30

Where to put navigation XML?

6 of 30

7 of 30

8 of 30

9 of 30

10 of 30

How to navigate from ViewModels?

11 of 30

NavigationCommand.kt

sealed class NavigationCommand {

data class To(val directions: NavDirections): NavigationCommand()

object Back: NavigationCommand()

data class BackTo(val destinationId: Int): NavigationCommand()

object ToRoot: NavigationCommand()

}

12 of 30

BaseViewModel.kt

// Command is SingleLiveEvent: http://bit.ly/arch-blueprints

val navigationCommands = Command<NavigationCommand>()

fun navigate(directions: NavDirections) {

navigationCommands.postValue(NavigationCommand.To(directions))

}

13 of 30

BaseFragment.kt

override fun onActivityCreated(savedInstanceState: Bundle?) {

super.onActivityCreated(savedInstanceState)

vm.navigationCommands.observe { command ->

when (command) {

is NavigationCommand.To ->

findNavController().navigate(command.directions)

14 of 30

CardListViewModel.kt

navigate(CardListFragmentDirections.cardDetail(cardId))

15 of 30

How to use arguments in ViewModels?

16 of 30

BaseFragment.kt

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

if (savedInstanceState == null) {

vm.setArguments(arguments)

vm.loadData()

}

}

17 of 30

CardDetailViewModel.kt

override fun loadData() {

val cardId = CardDetailFragmentArgs.fromBundle(args).cardId

load(cardsRepository.getCard(cardId)) {

18 of 30

How to show a login screen with deep links?

19 of 30

Documentation on conditional navigation

  • ProfileFragment →LoginFragment → ProfileFragment
  • Disadvantages
    • LoginFragment doesn’t behave like a root destination
    • ProfileFragment starts initializing

20 of 30

MainActivity.kt

override fun onStart() {

super.onStart()

if (sessionRepository.isLoggedOut()) {

startActivity<LoginActivity>()

finish()

}

}

21 of 30

MainActivity.kt

val intent = Intent(this, LoginActivity::class.java)

intent.putExtra("deepLinkExtras", this.intent.extras)

startActivity(intent)

finish()

22 of 30

LoginActivity.kt

val intent = Intent(this, MainActivity::class.java)

intent.putExtras(this.intent.getBundleExtra("deepLinkExtras"))

startActivity(intent)

finish()

23 of 30

How to navigate back with a result?

24 of 30

NavigationResult.kt

interface NavigationResult {

fun onNavigationResult(result: Bundle)

}

Documentation: shared ViewModel

25 of 30

BaseActivity.kt

fun navigateBackWithResult(result: Bundle) {

val childFragmentManager = supportFragmentManager.findFragmentById(R.id.nav_host_fragment)?.childFragmentManager

var backStackListener: FragmentManager.OnBackStackChangedListener by Delegates.notNull()

backStackListener = FragmentManager.OnBackStackChangedListener {

(childFragmentManager?.fragments?.get(0) as NavigationResult).onNavigationResult(result)

childFragmentManager.removeOnBackStackChangedListener(backStackListener)

}

childFragmentManager?.addOnBackStackChangedListener(backStackListener)

navController().popBackStack()

}

http://bit.ly/nav-comp

26 of 30

How to plug in a complex screen with a lot of animations?

27 of 30

Our dashboard

  • Multi-account dashboard: ProductListFragment
  • Product detail: ProductDetailFragment
  • Single-account dashboard: ProductDetailFragment with all navigation as ProductListFragment
  • Very custom animations between those fragments
  • Custom account switcher

28 of 30

DashboardFragment

  • only this Fragment is in the nav graph
  • ProductListFragment and ProductDetailFragment are child fragments
  • We handle the fragment transactions �and animations ourselves

29 of 30

TL;DR

  • multi-module = put nav XML into common-android
  • navigate from ViewModels using SingleLiveEvents, put all boilerplate to base classes
  • pass SafeArgs arguments to ViewModel
  • different Activity for login screen, pass intent extras for deep link support
  • Use our solution for navigation with result
  • You can handle Fragment transactions yourself in some complex cases

30 of 30

Thank you

Medium post with all the code:

http://bit.ly/nav-comp

Twitter: @vavradav