4

So, the title of this reflects the question. to obtain a link on a navigation controller (androidx.navigation.NavController) usually we use following code:

NavController navController = Navigation.findNavController(this, R.id.nav_host_frag);

Is it possible to inject a NavController using Dagger2 framework? (findNavController requires an activity or a view reference) Maybe it's a silly question and nobody injects the androidx.navigation.NavController, but nevertheless I decided to ask this question to be certain in my assumptions. Thanks ahead

Jack Jones
  • 117
  • 1
  • 6

3 Answers3

2

I don't see why you would want to inject the NavController when there are methods for you to find it, also I would be concerned with using dependency injection due to holding a reference to an Activity.

Given you are working with an Activity you would normally find the controller by using the following method:

private val navController: NavController by lazy { findNavController(R.id.main_container) }

Now if we take a look at the source code for the method findNavController() you will notice that it uses an extension function and Navigation.findNavController(this, viewId).

/**
 * Find a [NavController] given the id of a View and its containing
 * [Activity].
 *
 * Calling this on a View that is not a [NavHost] or within a [NavHost]
 * will result in an [IllegalStateException]
 */
fun Activity.findNavController(@IdRes viewId: Int): NavController =
        Navigation.findNavController(this, viewId)

The only thing I would do to complement the above is to create another extension function to facilitate navigation from a Fragment.

fun Fragment.navigate(resId: Int, bundle: Bundle? = null) {
    NavHostFragment.findNavController(this).navigate(resId, bundle)
}

Then you could simply use within a Fragment:

navigate(
    R.id.action_fragmentA_to_FragmentB,
    bundleOf(Global.CAN_NAVIGATE_BACK to false)
)
Rodrigo Queiroz
  • 2,674
  • 24
  • 30
  • I guess the reason why Jack Jones asks is because he want's to initiate navigation from his viewmodel. I have a similar need wanting to control navigation from inside of a state machine... – Ove Stoerholt Sep 06 '19 at 05:40
  • @OveStoerholt, In that case, you can use the command design pattern to encapsulate the action, you need to have a reference to the view to native therefore you have to delegate the responsibility to the Fragment or Activity. – Rodrigo Queiroz Sep 07 '19 at 01:42
  • You are never supposed to hold reference to a view in your viewmodel. It beats the whole point separation of concerns...You should think about navigation in a different way. You should have an observable on your viewmodel that controls your UI. You just need to update your observable and listen to it inside your views thus control the UI – David Innocent May 12 '20 at 06:23
0

Why shouldn't this work? You can add it like any other object to a component

  • through the Component.Builder via @BindsInstance or a module with an argument
  • by returning it from an @Provides annotated method

Using a @Provides annotated method you need to have the Activity or View available in the component as well. Depending on how you use Dagger you would usually have the specific Activity available, so you can just use that, e.g. for a MyActivityComponent with a MyActivity you could simply return it in a module

@Provides
NavController bindController(MyActivity activity) {
  Navigation.findNavController(this, R.id.nav_host_frag)
}
David Medenjak
  • 33,993
  • 14
  • 106
  • 134
  • This may lead to crashes when the Activity is recreated. See https://stackoverflow.com/a/60061872/789110 – GaRRaPeTa Feb 04 '20 at 16:50
0

I've answered this in https://stackoverflow.com/a/60061872/789110

In short,

  • Provide the NavController via usual dagger means, like:
   @Provides
    fun providesNavController(activity: MainActivity): NavController {
        return activity.supportFragmentManager.findFragmentById(R.id.main_content).findNavController()
    }
  • Inject the NavController from onAttach
  • Inject the NavController lazily to avoid race conditions between Android recreating the Activity and when the NavController can be retrieved:
    @Inject
    lateinit var navController: Provider<NavController>
GaRRaPeTa
  • 5,459
  • 4
  • 37
  • 61