Skip to content

Conversation

@philips77
Copy link
Member

@philips77 philips77 commented Oct 11, 2022

This PR contains redesigned implementation of Navigation module and improvements in Permissions module.

Navigation

The navigation module has been redesigned with the same principles in mind:

  1. Navigation implemented as NavigationView.
  2. Each navigation destination is identified by DestinationId.
  3. Each destination defines how it should be rendered. These definitions can be in separate modules.
  4. Destinations can navigate between passing arguments (->) and results (<-).

Issues in the old implementation

  1. Before, each destination could navigate to another one by giving its DestinationId.
    The issue was, that sometimes destinations were defined in separate modules and were independent from each other.
  2. Arguments and results were passed using a ViewModel.
    The issue was, that although the route (ID) of the destination was saved by NavHostController in SavedStateHandle and recreated after the app was killed, the arguments and results were not.

New implementation

  1. As before, each destination is identified by DestinationId. A new helper method is added to make it easier:
val Scanner = createDestination("scanner")
  1. A module may define a destination:
private val ScannerDestination = defineDestination(Scanner) {
   ScannerScreen {
      onDeviceSelected = { /* ... */ },
   }
}
  1. Instead of giving the next destination id when navigating, the routing is delegated to a Router, which takes the ID of the current destination and an optional hint, and returns the next DestinationId:
typealias Router = (from: DestinationId, hint: Any?) -> DestinationId?
  1. Each module may define a local router when defining local destinations, or may rely on global router set in the NavigationView:
// Defining local router in a module together with destinations.
val ScannerDestinations = listOf(Scanner, Rationale).asDestinationsWithRouter { from, hint ->
   when (from) {
      Scanner -> Rationale
      else -> null
   }
}
// Global router can navigate between destinations from different modules.
NavigationView(ScannerDestinations + WelcomeDestinations) { from, _ ->
   when (from) {
      Welcome -> Scanner
      else -> null
   }
}
  1. The arguments passed using navigator.navigate(hint, args) are saved in SavedStateHandle of the target destination and are preserved in configuration changes, or when the app is killed by the system to release resources. They are also available in ViewModels created in the target destination when Hilt dependency injection is used, using SavedStateHandle.
  2. Each destination may inject Navigator object in the view models. This object allows to navigate to another destination (which is determined by the Router) passing an argument. It also allows to suspend for result.
@HiltViewModel
class FirstViewModel @Inject constructor(
   navigator: Navigator,
): ViewModel() {
   fun chooseColor() {
      viewModelScope.launch {
         val color = navigator.navigateForResult()
         // ...
      }
   }
}

@HiltViewModel
class SecondViewModel @Inject constructor(
   navigator: Navigator,
): ViewModel() {
   fun returnColor(color: Int) {
      navigator.navigateUpWithResult(color)
   }
}

Permissions

These are the main changes:

  1. The permissions module was refactored. RequireBluetooth(scanning: Bool, ...) was split into 2:
    • RequireBluetooth
    • RequireLocation - this is required for scanning for Bluetooth LE devices. On Android 12+ it always assumes the location is granted, as it is no longer required for scanning. At least for most of the devices (it is needed for iBeacons and Eddystone).
  2. It is now possible to call RequireBluetooth and disabling displaying default "No Bluetooth" views by setting contentWithoutBluetooth parameter to an empty composable. by default it will show NoBluetoothView.
  3. The same is true for RequireLocation and RequireInternet.
  4. The files in the module were moved around, the internal views were made internal.

@philips77
Copy link
Member Author

TODO:

  • Add support for iBeacons and Eddystone to RequireLocation.

@philips77
Copy link
Member Author

Latest changes:

  1. suspend fun navigateForResult(..) added.
  2. Navigator available in ViewModels using Hilt injection.
  3. open(Uri) method added, almost like it was before.

Questions:

  1. Should I leave routing in hands of a Router, or move the DestinationId param as a param to navigate and navigateForResult?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants