2

I have an activity with the navigation drawer and a lot of fragments in it. I use Navigation library from the Architecture Components. In every fragment I have to write navController = Navigation.findNavController(activity!!, R.id.fragment_container). I want to get rid of this boilerplate code. So I decide to inject it with Dagger.

@Module
class MainActivityModule {
    @ActivityScope
    @Provides
    fun provideNavController(activity: MainActivity): NavController {
        return Navigation.findNavController(activity, R.id.fragment_container)
    }
}

@Suppress("unused")
@Module
abstract class ActivityBuildersModule {
    @ActivityScope
    @ContributesAndroidInjector(
            modules = [MainActivityModule::class, MainActivityFragmentBuildersModule::class])
    abstract fun contributeMainActivity(): MainActivity
}

Injection into fragments works fine, but when I try to inject into activity application crashes:

09-19 19:22:37.152 22342-22342/com.dmitrysimakov.gymlab
    E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.dmitrysimakov.gymlab, PID: 22342
java.lang.RuntimeException: Unable to start activity
    ComponentInfo{com.dmitrysimakov.gymlab/com.dmitrysimakov.gymlab.MainActivity}:
    java.lang.IllegalArgumentException: ID does not reference a View inside this Activity
    .....

So I have to duplicate navController = Navigation.findNavController(activity!!, R.id.fragment_container) in the activity. Is there a more elegant way to do this?

Dmitry Simakov
  • 660
  • 1
  • 8
  • 15
  • Can you share what you did for this? Currently, I've initialized navController in my BaseFragment & using it in all fragments. but I want to use it in ViewModels also using Injection. Is it ok to use navigation in ViewModels? How can I achieve that? – akshay bhange Apr 23 '19 at 06:59
  • 1
    @akshaybhange Don’t let ViewModels know about Android framework classes. Read [this article](https://medium.com/androiddevelopers/viewmodels-and-livedata-patterns-antipatterns-21efaef74a54) for more details. – Dmitry Simakov Apr 23 '19 at 16:19
  • Thanks Dmitry, Can you share how can I set TextWatcher to my EditText? It is a part of the android framework and I was using Textwatcher from ViewModel itself but I'm not able to find any good tutorial on it. – akshay bhange Apr 24 '19 at 06:42
  • @akshaybhange Set up TextWatcher inside your fragment and handle viewModel's data inside TextWatcher's methods. – Dmitry Simakov Apr 25 '19 at 06:46
  • 1
    @akshaybhange I have [something](https://github.com/DmitriySimakov/Kilogram/blob/master/app/src/main/java/com/dmitrysimakov/kilogram/ui/common/choose_exercise/ChooseExerciseFragment.kt) similar, but with OnQueryTextListener `searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { //... override fun onQueryTextChange(newText: String?): Boolean{ viewModel.setSearchText(newText) return true } })` – Dmitry Simakov Apr 25 '19 at 06:50

2 Answers2

2

App crash from activity because when provideNavController is called you haven't set your content view yet using setContentView

step 1: If your extending DaggerAppCompatActivity copy its code and extends AppCompatActivity instead otherwise just do step 2.

step 2: move AndroidInjection.inject(this) line from onCreate method aftersetContentView(...)

But take care in my experience this may lead to other problems like this so for getting you nav controller from your activity the best choise may be just use an extension, and actually there is a built in one if you have 'android.arch.navigation:navigation-ui-ktx:1.0.0-alpha05' dependency in your build.gradle:

findNavController(R.id.fragment_container)

So for your activity you may use this extension and anyway if you're using single activity recommanded way, in most case you'll get navigation from a fragment and there you may still use dagger injection

Samuel Eminet
  • 4,647
  • 2
  • 18
  • 32
  • Thank you @SamuelEminet. I didn't know about these extension. The same one is for fragments. So I no longer need to use Dagger for this. – Dmitry Simakov Sep 19 '18 at 18:07
0

The best solution to me is to inject the Fragments in onAttach and provide the NavController lazily.

I think the accepted solution is not quite well, as delaying the injection to setContentView is dangerous: when navigating back and forth that Fragment, setContentView will be called again to recreate the view hierarchy. If the Fragment needs to observe ViewModels or repositories, it will need to be injected. Hence, we will need to to delay observing the ViewModel until setContentView, which may end up to the Fragment observing the ViewModel with multiple subscriptions, which may cause bugs.

Indeed, it is recommended to inject the Fragments in onAttach : https://dagger.dev/android#when-to-inject

DaggerActivity calls AndroidInjection.inject() immediately in onCreate(), before calling super.onCreate(), and DaggerFragment does the same in onAttach().

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

GaRRaPeTa
  • 5,459
  • 4
  • 37
  • 61