基于Kotlin协程+MVVM构建食谱App遇协程异常求助
协程异常排查与修复(Kotlin+MVVM食谱App)
问题背景
基于recipesdb开发食谱App,采用Kotlin+协程+MVVM架构,运行时出现协程异常,报错定位到CategoryViewModel.kt:26行。
核心错误原因
- Repository依赖未初始化:ViewModel中创建Repository时未传入
getDataService、application等必要参数,导致getDataService为null,调用API时实际执行null?.getCategoryList(),后续对result!!的非空断言直接触发空指针异常。 - 协程嵌套冗余:
getMainCategory()中已在viewModelScope.launch(Dispatchers.IO)协程内,又嵌套withContext(Dispatchers.IO),属于无效操作。 - 非空断言滥用:
mainCategoryList!!和result!!的非空断言在对象为null时直接崩溃,未做安全处理。 - Activity协程使用不当:用
GlobalScope启动协程(不推荐在Android中使用),且suspend fun getCategories()无需挂起,ViewModel内部已处理协程逻辑。 - Repository逻辑错误:
return res_rep后的Log语句永远无法执行,属于无效代码。
修复后的代码
1. Repository代码修复
class Repository( private val getDataService: GetDataServices, private val dao: RecipeDao?, private val application: Application ) { // 获取主分类列表,添加异常捕获 suspend fun getMainCategory(): Category? { return runCatching { getDataService.getCategoryList() }.onSuccess { Log.v("res_rep_get", it.toString()) }.getOrNull() } }
2. ViewModel代码修复
class CategoryViewModel( private val repository: Repository ) : ViewModel() { // 直接初始化LiveData,避免可空性问题 val mainCategoryList = MutableLiveData<ArrayList<CategoryItems>?>() // 去掉suspend修饰符,内部用viewModelScope处理协程 fun getMainCategory() { viewModelScope.launch(Dispatchers.IO) { val result = repository.getMainCategory() // 切换到主线程更新LiveData withContext(Dispatchers.Main) { mainCategoryList.value = result?.categoriesitems as? ArrayList<CategoryItems> } } } }
3. ViewModel工厂(用于依赖注入)
class CategoryViewModelFactory( private val repository: Repository ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun <T : ViewModel> create(modelClass: Class<T>): T { if (modelClass.isAssignableFrom(CategoryViewModel::class.java)) { return CategoryViewModel(repository) as T } throw IllegalArgumentException("Unknown ViewModel class") } }
4. Activity代码修复
@OptIn(DelicateCoroutinesApi::class) class SplashActivity : BaseActivity(), EasyPermissions.RationaleCallbacks, EasyPermissions.PermissionCallbacks { private var READ_STORAGE_PERM = 123 private lateinit var categoryViewModel: CategoryViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_splash) // 初始化Retrofit服务实例(替换为你的API基础地址) val getDataService = Retrofit.Builder() .baseUrl("https://www.themealdb.com/api/json/v1/1/") .addConverterFactory(GsonConverterFactory.create()) .build() .create(GetDataServices::class.java) // 初始化Repository和ViewModel val repository = Repository(getDataService, null, application) val viewModelFactory = CategoryViewModelFactory(repository) categoryViewModel = ViewModelProvider(this, viewModelFactory)[CategoryViewModel::class.java] // 触发数据请求并观察LiveData categoryViewModel.getMainCategory() categoryViewModel.mainCategoryList.observe(this) { value -> value?.let { Log.d("cor_get", it.toString()) } } } // 权限相关方法保持不变... }
5. 依赖优化(使用稳定版)
def lifecycle_version = "2.6.2" implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version") implementation("androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version") kapt("androidx.lifecycle:lifecycle-compiler:$lifecycle_version") // 若未使用Jetpack Compose,移除该依赖 // implementation("androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_version")
内容的提问来源于stack exchange,提问作者Abdulrahman
相关产品推荐
相关产品推荐

