Jetpack Compose Retrofit ViewModel合规性排查及多问题咨询
问题解答与代码修正
一、ViewModel规范问题与遗漏内容
1. 协程使用不规范
- 禁止使用
GlobalScope:它不受ViewModel生命周期管理,易引发内存泄漏。应改用viewModelScope(Jetpack ViewModel内置的生命周期绑定协程作用域)。 - 无需将
getData声明为suspend函数:viewModelScope.launch会自行管理协程生命周期;若保留suspend,内部无需再嵌套启动协程,直接执行网络请求即可。
2. 数据可观察性缺失
当前data是普通变量,更新后无法触发Compose UI重组。需使用MutableStateFlow或LiveData存储数据,让UI能感知数据变化。
3. 错误处理不完善
仅打印错误信息未暴露给UI,用户无法感知请求失败。需添加错误状态的可观察对象,将异常信息传递到UI层。
4. 空值处理不安全
使用!!强制解包response.body()可能引发空指针异常,建议用安全调用结合空值判断。
二、数据类数组初始化错误
原代码ArrayOfDataClassExample(arrayOf(DataClassExample))错误在于:arrayOf(DataClassExample)试图创建包含类本身的数组,而非类的实例。正确初始化方式:
- 空数组:
ArrayOfDataClassExample(emptyArray()) - 默认实例数组:
ArrayOfDataClassExample(arrayOf(DataClassExample(0, 0)))
另外,建议将数据类中的Array改为List:List是不可变类型,Gson序列化/反序列化更友好,API操作更便捷:
data class ArrayOfDataClassExample( val data: List<DataClassExample> )
三、跨ViewModel数据访问问题
原代码val accountType by preferencesViewModel.accountType.observeAsState("")存在两个问题:
- 无法直接在ViewModel中获取另一个ViewModel:需通过依赖注入(如Hilt)或在UI层(Activity/Fragment)获取后传递。
observeAsState是Compose UI层函数:不能在ViewModel内部使用,应在ViewModel中收集Flow/LiveData,或在UI层观察。
解决方案(以Hilt为例)
- 为
PreferencesViewModel添加@HiltViewModel注解:
@HiltViewModel class PreferencesViewModel @Inject constructor(application: Application) : AndroidViewModel(application) { val accountType = MutableStateFlow("") }
- 在
ServerDataViewModel中通过构造函数注入PreferencesViewModel:
@HiltViewModel class ServerDataViewModel @Inject constructor( application: Application, private val preferencesViewModel: PreferencesViewModel ) : AndroidViewModel(application) { private val accountType = preferencesViewModel.accountType }
四、自定义请求头实现
若需将authKey作为请求头传递,应使用@Header注解:
interface ApiInterface { @GET("/data") suspend fun loadData(@Header("Authorization") authKey: String): Response<ArrayOfDataClassExample> // 替换为实际头字段名,如"Auth-Key" }
如果需要全局添加请求头(所有接口都携带),可通过OkHttp Interceptor实现:
private val client = OkHttpClient.Builder().apply { addInterceptor { chain -> val modifiedRequest = chain.request().newBuilder() .addHeader("Authorization", "固定值或从SharedPreferences获取") .build() chain.proceed(modifiedRequest) } }.build()
五、修正后的完整代码示例
数据类
data class DataClassExample( val id: Int, val accountID: Int ) data class ArrayOfDataClassExample( val data: List<DataClassExample> )
ApiInterface
interface ApiInterface { @GET("/data") suspend fun loadData(@Header("Authorization") authKey: String): Response<ArrayOfDataClassExample> }
RetrofitInstance
object RetrofitInstance { private val client = OkHttpClient.Builder().build() val api: ApiInterface by lazy { Retrofit.Builder() .baseUrl(Util.Base) .client(client) .addConverterFactory(GsonConverterFactory.create()) .build() .create(ApiInterface::class.java) } }
ViewModel(使用Hilt和StateFlow)
@HiltViewModel class ServerDataViewModel @Inject constructor( application: Application, private val preferencesViewModel: PreferencesViewModel ) : AndroidViewModel(application) { private val _uiState = MutableStateFlow<UiState>(UiState.Idle) val uiState: StateFlow<UiState> = _uiState sealed class UiState { object Idle : UiState() object Loading : UiState() data class Success(val data: ArrayOfDataClassExample) : UiState() data class Error(val message: String) : UiState() } fun getData() { viewModelScope.launch { _uiState.value = UiState.Loading val authKey = preferencesViewModel.accountType.value try { val response = RetrofitInstance.api.loadData(authKey) if (response.isSuccessful) { response.body()?.let { _uiState.value = UiState.Success(it) } ?: run { _uiState.value = UiState.Error("空响应体") } } else { _uiState.value = UiState.Error("请求失败:${response.code()}") } } catch (e: HttpException) { _uiState.value = UiState.Error("网络异常:${e.message ?: "未知错误"}") } catch (e: IOException) { _uiState.value = UiState.Error("连接失败:${e.message ?: "未知错误"}") } } } }
Compose UI使用示例
@Composable fun DataScreen(viewModel: ServerDataViewModel = hiltViewModel()) { val uiState by viewModel.uiState.collectAsState() when (uiState) { ServerDataViewModel.UiState.Idle -> Text("点击按钮加载数据") ServerDataViewModel.UiState.Loading -> CircularProgressIndicator() is ServerDataViewModel.UiState.Success -> { LazyColumn { items((uiState as ServerDataViewModel.UiState.Success).data.data) { item -> Text("ID: ${item.id}, AccountID: ${item.accountID}") } } } is ServerDataViewModel.UiState.Error -> Text("错误:${(uiState as ServerDataViewModel.UiState.Error).message}") } Button(onClick = { viewModel.getData() }) { Text("加载数据") } }
内容的提问来源于stack exchange,提问作者mfusco
相关产品推荐
相关产品推荐

