You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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("")存在两个问题:

  1. 无法直接在ViewModel中获取另一个ViewModel:需通过依赖注入(如Hilt)或在UI层(Activity/Fragment)获取后传递。
  2. observeAsState是Compose UI层函数:不能在ViewModel内部使用,应在ViewModel中收集Flow/LiveData,或在UI层观察。

解决方案(以Hilt为例)

  1. 为PreferencesViewModel添加@HiltViewModel注解:
@HiltViewModel
class PreferencesViewModel @Inject constructor(application: Application) : AndroidViewModel(application) {
    val accountType = MutableStateFlow("")
}
  1. 在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 13:35:56