'Field injection in viewModels with Hilt

I have the following situation:

UML Diagram

The MainActivity has a fragment, and they each have a viewModel, MainViewModel and FragmentViewModel respectively. I want to inject an instance of class A in both the viewmodels. My understanding from this post is that you can not use the injected class inside the init block of the viewmodel.

Hence, I attached a clickListener to a view inside the Fragment to read a property of class A. However, when I click on the view, I get a kotlin.UninitializedPropertyAccessException. Note, I do not have the same issue when I access a property of instance inside the MainViewModel (i.e. Hilt is injecting an instance of A into MainViewModel for me).

Does anyone have any ideas as to why Hilt is unable to inject an instance of A inside the FragmentViewModel? I have been careful as to annotate the FragmentViewModel in the same way as I have annotated the MainViewModel. Any tips on how I can share a single instance of A with dependency injection in both viewModels would also be appreciated.

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    private val mainViewModel: MainViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        findViewById<Button>(R.id.main_button).setOnClickListener {
            Log.e("Test", "a.initCount ${mainViewModel.getInitCount()}")
        }
    }
}

@HiltViewModel
class MainViewModel @Inject constructor() : ViewModel() {

    @Inject lateinit var instance: A

    fun getInitCount(): Int {
        return instance.initCount
    }
}

@AndroidEntryPoint
class Fragment : Fragment() {

    private val viewModel: FragmentViewModel by viewModels()

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        view.findViewById<Button>(R.id.fragment_button).setOnClickListener {
            Log.e("Test", "viewModel.a: ${viewModel.getInitCount()}")
        }
    }
}

@HiltViewModel
class FragmentViewModel @Inject constructor() : ViewModel() {

    @Inject lateinit var instance: A

    fun getInitCount(): Int {
        return instance.initCount
    }
}

@HiltAndroidApp
class App : Application()

class A @Inject constructor() {

    var initCount = 0

    init {
        Log.e("Test", "I have been initialised: ${++initCount}")
    }
}

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <Button
        android:id="@+id/main_button"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:text="main button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        />

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/fragment"
        android:name="com.example.hilttest.Fragment"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        android:layout_width="match_parent"
        android:layout_height="400dp"
        />

</androidx.constraintlayout.widget.ConstraintLayout>

fragment.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <Button
        android:id="@+id/fragment_button"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:text="button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        />

</androidx.constraintlayout.widget.ConstraintLayout>


Solution 1:[1]

I think there may have been a bug in the androidx.fragment:fragment-ktx library. Changing from v1.1.0 -> v1.2.0 seems to have resolved the issue, but there is no mention of the bug in the docs.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 220284