How to initialize ViewModel when using Dagger?

Viewed 40

I'm starting to learn dagger. I try to get a string from the ViewModel, an error occurs:

           FATAL EXCEPTION: main
        Process: com.sem.daggersimple, PID: 7882
        java.lang.RuntimeException: Unable to start activity ComponentInfo{com.sem.daggersimple/com.sem.daggersimple.presentation.MainActivity}: android.view.InflateException: Binary XML file line #26 in com.sem.daggersimple:layout/activity_main: Binary XML file line #26 in com.sem.daggersimple:layout/activity_main: Error inflating class fragment
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3450)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3602)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
            at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
            at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2067)
            at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:223)
            at android.app.ActivityThread.main(ActivityThread.java:7697)
            
            ...
    
    Caused by: android.view.InflateException: Binary XML file line #26 in com.sem.daggersimple:layout/activity_main: Binary XML file line #26 in com.sem.daggersimple:layout/activity_main: Error inflating class fragment
Caused by: android.view.InflateException: Binary XML file line #26 in com.sem.daggersimple:layout/activity_main: Error inflating class fragment
Caused by: kotlin.UninitializedPropertyAccessException: lateinit property stringViewModel has not been initialized
at com.sem.daggersimple.presentation.StringFragment.getStringViewModel(StringFragment.kt:24)
at com.sem.daggersimple.presentation.StringFragment.onCreateView(StringFragment.kt:43)
at androidx.fragment.app.Fragment.performCreateView(Fragment.java:2963)
            
            ...
            
at com.sem.daggersimple.presentation.MainActivity.onCreate(MainActivity.kt:20)

MainActivity:

    class MainActivity : AppCompatActivity() {
    
        @Inject
        lateinit var stringViewModel: StringViewModel
    
        private var binding: ActivityMainBinding? = null
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
    
            appComponent.inject(this)
    
        }

}

StringFragment:

class StringFragment : Fragment() {

    private var binding : FragmentStringBinding? = null

    @Inject
    lateinit var stringViewModel: StringViewModel

    override fun onAttach(context: Context) {
        super.onAttach(context)

        // Grabs the registrationComponent from the Activity and injects this Fragment
       // (activity as MainActivity).
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {

        binding = DataBindingUtil.inflate(inflater, R.layout.fragment_string, container, false)

       // stringViewModel.getString.

        Log.d("stringViewModel","stringViewModel = " + stringViewModel.getString.get(0))

        binding?.textView?.text = stringViewModel.getString

        return binding?.root
    }

}

StringRepository:

class StringRepository @Inject constructor() {

    override fun toString() : String = "какой-то текст"

}

StringUseCase:

class StringUseCase @Inject constructor(private val repository: StringRepository) {

    fun getString() : String {
        return repository.toString()
    }

}

StringViewModel:

class StringViewModel @Inject constructor(private val stringUseCase: StringUseCase) : ViewModel() {

    val getString = stringUseCase.getString()

}

App:

class App : Application() {

    lateinit var appComponent: AppComponent

    override fun onCreate() {
        super.onCreate()
        appComponent = DaggerAppComponent.create()
    }

}

val Context.appComponent: AppComponent
    get() = when(this) {
        is App -> appComponent
        else -> this.applicationContext.appComponent
    }

AppComponent:

@Component(modules = [AppModule::class])
interface AppComponent {

    fun inject(mainActivity: MainActivity)
    fun inject(stringFragment: StringFragment)

}

AppModule:

@Module
object AppModule {


    @Provides
    fun provideStringViewModel(stringUseCase: StringUseCase) : StringViewModel {

        return StringViewModel(stringUseCase)

    }

    @Provides
    fun provideStringUseCase(repository: StringRepository) : StringUseCase {
        return StringUseCase(repository)
    }

    @Provides
    fun provideStringRepository() : StringRepository {
        return StringRepository()
    }

}

activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<layout 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"
    tools:context=".presentation.MainActivity">

        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">

        <androidx.appcompat.widget.LinearLayoutCompat
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">

        <FrameLayout
            android:id="@+id/framelayout"
            xmlns:android="http://schemas.android.com/apk/res/android"
            android:layout_width="match_parent"
            android:layout_height="match_parent" >

        <fragment
            android:id="@+id/rd_fragment"
            android:name="com.sem.daggersimple.presentation.StringFragment"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

        </FrameLayout>

        </androidx.appcompat.widget.LinearLayoutCompat>

        </androidx.constraintlayout.widget.ConstraintLayout>

</layout>

fragment_string.xml:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    tools:context=".presentation.StringFragment">

    <!-- TODO: Update blank fragment layout -->
    <TextView
        android:id="@+id/textView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="@string/hello_blank_fragment" />

</layout>

P.S. I don't use Hilt

1 Answers

You cannot use var for lazy initialization delegated properties. You should use val. You can use any of these to initialize view model.

val stringViewModel by viewModels<StringViewModel>() 

val stringViewModel :StringViewModel by viewModels()
Related