如何在多Fragment的Kotlin MVVM安卓应用中集中处理HTTP响应与导航
问题背景
我们的Android-Kotlin应用采用单Activity+多Fragment架构,所有页面基于MVVM模式实现,层级划分如下:
- View层:负责UI展示的Fragments
- ViewModel层:作为View与Model的中间层,处理业务逻辑
- Model层:包含数据源(远程/本地)及封装数据交互的Repositories
目前Repository层有一个通用的apiCall函数,用于统一处理网络请求:
inline fun <T> apiCall(crossinline apiCall: suspend () -> Response<T>) = flow { emit(State.Progress(0)) val response = apiCall() val responseBody = response.body() if (response.isSuccessful && responseBody != null) emit(State.Success(responseBody)) else emit(State.Error(HttpException(response))) }.catch { emit(State.Error(it)) }.flowOn(Dispatchers.IO)
在Repository中的使用示例:
fun checkOtpCode(phoneNumber: String, code: String) = apiCall { remoteDataSource.checkOtp(phoneNumber,code) }
需求
需要在apiCall中统一处理响应码对应的导航逻辑:
- 响应码处于5xx范围时,导航至服务器错误Fragment
- 响应码为401或403时,导航至登录Fragment
要求:
- 避免重复的状态码检查与导航逻辑
- 严格遵循MVVM架构规范
- 代码简洁优雅
不满意的现有方案
有人提议使用全局SharedFlow事件总线,在MainActivity中收集事件处理导航,代码示例如下:
object SharedEventBus { private val _globalEvent = MutableSharedFlow<Event>() val globalEvent: SharedFlow<Event> = _globalEvent suspend fun sendEvent(event: Event) { _globalEvent.emit(event) } sealed class Event { object NavigateToLogin : Event() object NavigateToServerError : Event() } }
优化方案
以下方案既符合MVVM架构,又能统一逻辑、避免耦合:
1. 扩展State类,新增全局导航事件类型
在现有State密封类中添加GlobalNavigation子类,用于承载导航指令:
sealed class State<out T> { data class Progress(val value: Int) : State<Nothing>() data class Success<out T>(val data: T) : State<T>() data class Error(val throwable: Throwable) : State<Nothing>() // 新增全局导航事件类型 sealed class GlobalNavigation : State<Nothing>() { object NavigateToLogin : GlobalNavigation() object NavigateToServerError : GlobalNavigation() } }
2. 修改apiCall函数,统一处理状态码
在apiCall中解析响应码,根据情况发送对应的导航状态,无需额外事件总线:
inline fun <T> apiCall(crossinline apiCall: suspend () -> Response<T>) = flow { emit(State.Progress(0)) val response = apiCall() val responseBody = response.body() when { response.isSuccessful && responseBody != null -> { emit(State.Success(responseBody)) } response.code() in 500..599 -> { emit(State.GlobalNavigation.NavigateToServerError) } response.code() == 401 || response.code() == 403 -> { emit(State.GlobalNavigation.NavigateToLogin) } else -> { emit(State.Error(HttpException(response))) } } }.catch { throwable -> emit(State.Error(throwable)) }.flowOn(Dispatchers.IO)
3. 用BaseViewModel统一处理导航事件
创建BaseViewModel,所有业务ViewModel继承它,在这里统一处理全局导航事件,避免重复逻辑:
abstract class BaseViewModel : ViewModel() { // 暴露给View层观察的导航事件Flow private val _navigationEvents = MutableSharedFlow<State.GlobalNavigation>() val navigationEvents: SharedFlow<State.GlobalNavigation> = _navigationEvents protected fun handleState(state: State<*>) { when (state) { is State.GlobalNavigation -> { viewModelScope.launch { _navigationEvents.emit(state) } } // 可选:统一处理通用错误提示 is State.Error -> { // 比如弹Toast提示通用错误 } else -> {} } } }
4. 业务ViewModel中调用handleState
业务ViewModel在收集Repository返回的Flow时,先调用handleState处理全局事件,再处理业务专属逻辑:
class AuthViewModel(private val authRepo: AuthRepository) : BaseViewModel() { fun checkOtp(phone: String, code: String) = authRepo.checkOtpCode(phone, code) .onEach { state -> handleState(state) // 处理当前业务的Success/Progress状态 when (state) { is State.Success -> { // 验证码验证成功后的业务逻辑 } else -> {} } }.flowOn(Dispatchers.Main) }
5. 用BaseFragment统一订阅导航事件
创建BaseFragment,所有业务Fragment继承它,在这里订阅导航事件并处理跳转:
abstract class BaseFragment<VM : BaseViewModel> : Fragment() { protected abstract val viewModel: VM override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) collectNavigationEvents() } private fun collectNavigationEvents() { lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.navigationEvents.collect { event -> when (event) { State.GlobalNavigation.NavigateToLogin -> { findNavController().navigate(R.id.action_global_to_login) } State.GlobalNavigation.NavigateToServerError -> { findNavController().navigate(R.id.action_global_to_server_error) } } } } } } }
方案优势
- 符合MVVM规范:数据流从Model到ViewModel再到View,层级清晰,无跨层调用
- 无重复逻辑:状态码检查在
apiCall中统一处理,导航逻辑在BaseFragment中统一实现 - 解耦性强:无全局单例耦合,导航事件通过ViewModel的Flow传递,依赖关系明确
- 扩展性高:新增导航事件只需在
GlobalNavigation中添加类型,在BaseFragment中补充处理逻辑即可
内容的提问来源于stack exchange,提问作者Mohammad Hasan Mahdavi
相关产品推荐
相关产品推荐

