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

如何在多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
    要求:
  1. 避免重复的状态码检查与导航逻辑
  2. 严格遵循MVVM架构规范
  3. 代码简洁优雅

不满意的现有方案

有人提议使用全局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)
                        }
                    }
                }
            }
        }
    }
}

方案优势

  1. 符合MVVM规范:数据流从Model到ViewModel再到View,层级清晰,无跨层调用
  2. 无重复逻辑:状态码检查在apiCall中统一处理,导航逻辑在BaseFragment中统一实现
  3. 解耦性强:无全局单例耦合,导航事件通过ViewModel的Flow传递,依赖关系明确
  4. 扩展性高:新增导航事件只需在GlobalNavigation中添加类型,在BaseFragment中补充处理逻辑即可

内容的提问来源于stack exchange,提问作者Mohammad Hasan Mahdavi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 02:57:15