Reputation: 736
I'm new to Hilt. Trying to convert from Dagger to Hilt. But I'm stuck on injecting NavController
to the Activity
.
So to create NavController
instance I need to have access to the activity and the host fragment Id. In Dagger this was my approach to create that instance.
First, create ActivityModule
@Module
class ActivityModule {
@ActivityScope
@Provides
fun navigation(activity: MainActivity, hosFragment: Int) =
Navigation.findNavController(activity,hosFragment)
}
In the second step ActivitySubComponent
has the ability to take activity and host fragment id as parameters.
@ActivityScope
@Subcomponent(modules = [ActivityModule::class])
interface ActivitySubComponent {
@Subcomponent.Factory
interface Factory{
fun create(@BindsInstance activity: MainActivity,
@BindsInstance hostFragment: Int): ActivitySubComponent
}
fun inject(MainActivity: MainActivity)
}
After adding this subcomponent with other subcomponents to the ApplicationComponent
from you can get the instance of NavController
to the fragment like below.
class MainActivity : AppCompatActivity() {
@Inject
lateinit var navigationController: NavController
lateinit var viewBinding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(viewBinding.root)
injector.activityComponent().create(this, R.id.hostFragment).inject(this)
}
}
injector
coming from the following class.
interface InjectorProvider {
val component: ApplicationComponent
}
val Activity.injector get() = (application as InjectorProvider).component
val Fragment.injector get() = (requireActivity().application as InjectorProvider).component
When I was trying to transition into Hilt, the first issue I faced was how to provide parameters to the module. Not like in Dagger I could not find an alternative for Subcomponent Factory. Is it even possible to provide parameters to a module on Hilt?
Anyways, I decided to provide the FragmentContainerView
id to the NavController
manually since it's not changing. With that, I came to the below solution in Hilt.
@Module
@InstallIn(ActivityComponent::class)
object NavigationModule {
@Provides
fun navigationController(activity: Activity) =
activity.findNavController(R.id.hostFragment)
}
And tried to inject it into my MainActvity
like below.
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
@Inject
lateinit var navigationController: NavController
lateinit var viewBinding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(viewBinding.root)
initialization()
}
private fun initialization() {
setSupportActionBar(viewBinding.toolbar)
NavigationUI.setupActionBarWithNavController(this, navigationController)
}
}
Application built successfully but now I'm getting a run time error saying "ID does not reference a View inside this Activity" but my activity layout is constructed like below.
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:orientation="vertical">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/purple_700"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" />
<androidx.fragment.app.FragmentContainerView
android:id="@+id/hostFragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="@navigation/main_navigation" />
</LinearLayout>
Error pointing at super.onCreate(savedInstanceState)
line on the MainActivity
. Please let me know what am I missing here. Thanks.
Upvotes: 1
Views: 2069
Reputation: 736
I was able to figure it out. The above issue must be caused by the fact the UI is not available at the time it tries to inject the NavController
instance. So Hilt has a way of defining an interface and Injecting that interface into the Activity. And we can define the classes by that implementing that interface. This gives us a chance to provide the parameters necessary to build our instance when they are available.
First, create the interface like below.
interface AppNavigator {
fun getNaveHostFragment(hostFragmentId: Int): NavController
}
Secondly, create an implementation class.
class AppNavigatorImplementation @Inject constructor(private val activity: FragmentActivity): AppNavigator {
override fun getNaveHostFragment(hostFragmentId: Int): NavController {
val navHostFragment = activity.supportFragmentManager.findFragmentById(R.id.hostFragment) as NavHostFragment
return navHostFragment.navController
}
}
After that define NavigationModule
like below.
@InstallIn(ActivityComponent::class)
@Module
abstract class NavigationModule {
@Binds
abstract fun bindNavigator(impl: AppNavigatorImplementation): AppNavigator
}
Finally, you can inject that instance to the activity like below.
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
@Inject
lateinit var navigator: AppNavigator
lateinit var viewBinding: ActivityMainBinding
private lateinit var navigationController: NavController
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(viewBinding.root)
initialization()
}
private fun initialization() {
navigationController = navigator.getNaveHostFragment(R.id.hostFragment)
setSupportActionBar(viewBinding.toolbar)
NavigationUI.setupActionBarWithNavController(this, navigationController)
}
}
Upvotes: 1