使用Dagger初始化ViewModel时遇未初始化异常,求解决方案
Dagger框架中Fragment未初始化ViewModel导致运行时异常
核心问题
运行时抛出kotlin.UninitializedPropertyAccessException,提示lateinit属性stringViewModel未初始化,进而引发Fragment加载失败,最终导致Activity启动异常。
完整报错日志
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>
问题原因及解决方案
问题根源
- StringFragment未执行注入逻辑:Fragment的
onAttach方法中注入代码被注释,导致stringViewModel从未被初始化。 - Activity初始化顺序错误:MainActivity中先调用
setContentView加载布局(此时Fragment会被实例化并执行onCreateView),之后才执行自身的注入,即使Fragment有注入逻辑,也会因为Activity还未完成注入而无法获取Component。
修复步骤
1. 修复StringFragment的注入逻辑
取消onAttach方法中的注释,调用AppComponent注入Fragment:
override fun onAttach(context: Context) { super.onAttach(context) // 从Context获取AppComponent并注入当前Fragment context.appComponent.inject(this) }
2. 调整MainActivity的初始化顺序
先执行注入,再加载布局,确保Component在Fragment实例化前已准备好:
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // 先注入,再加载布局 appComponent.inject(this) binding = DataBindingUtil.setContentView(this, R.layout.activity_main) }
3. 额外优化(可选)
ViewModel通常建议通过ViewModelProvider结合Dagger来注入,避免直接依赖Dagger注入ViewModel(因为ViewModel有自己的生命周期管理)。可以修改AppModule提供ViewModel的方式,或者使用ViewModelFactory:
// 示例:创建ViewModelFactory class StringViewModelFactory @Inject constructor( private val stringUseCase: StringUseCase ) : ViewModelProvider.Factory { override fun <T : ViewModel> create(modelClass: Class<T>): T { if (modelClass.isAssignableFrom(StringViewModel::class.java)) { @Suppress("UNCHECKED_CAST") return StringViewModel(stringUseCase) as T } throw IllegalArgumentException("Unknown ViewModel class") } }
然后在Fragment中通过ViewModelProvider获取:
private val stringViewModel: StringViewModel by viewModels { (requireContext().appComponent as AppComponent).provideStringViewModelFactory() }
(需要在AppComponent中添加fun provideStringViewModelFactory(): StringViewModelFactory方法)
内容的提问来源于stack exchange,提问作者Semenov Nilolay
相关产品推荐
相关产品推荐

