如何在具有多个片段的 Kotlin MVVM Android 应用程序中集中 HTTP 响应处理和导航?

问题描述 投票:0回答:1

假设我们有一个 Android-Kotlin 单活动应用程序,其中每个屏幕都是使用 MVVM 模式实现的片段。该应用程序具有以下层:

  1. 查看:片段
  2. ViewModel:位于模型层的片段(视图)和存储库之间。
  3. 模型:由数据源和与数据源交互的存储库组成。

在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)

此函数在存储库中以这种方式使用,例如:

fun checkOtpCode(phoneNumber: String, code: String) =
    apiCall { remoteDataSource.checkOtp(phoneNumber,code) }

在 apiCall 函数中,我需要通知视图层,如果响应代码在 5xx 范围内,则应用程序应导航到服务器错误片段,或者如果是 401 或 403 状态代码,则应导航到登录片段—无需在所有片段中重复导航和状态代码检查逻辑,同时仍然遵循 MVVM 架构模式并且不会使代码变得如此难看。

有人建议有一个全局的 SharedFlow 并将其收集在 MainActivity 中。例如,如果状态代码在 5xx 范围内,则将调用 globalFlow.emit(NavDest.ServerError),并在 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()
    }
}
android kotlin android-fragments mvvm repository
1个回答
0
投票
  • 如何在具有多个片段的 Kotlin MVVM Android 应用程序中集中 HTTP 响应处理和导航?

在具有单活动、多片段 Android 应用程序的 MVVM 架构中,可以通过利用 ViewModel 和导航事件的密封类来实现集中 HTTP 响应处理和导航,同时避免跨片段的冗余代码。下面是一种遵循 MVVM 原则的干净且可扩展的方法。

  • 定义导航事件:

创建一个密封类来表示导航事件,允许 ViewModel 将它们传达给 View 层

sealed class NavigationEvent {
    object NavigateToLogin : NavigationEvent()
    object NavigateToServerError : NavigationEvent()
}
  • ApiCall函数

    内联有趣的 apiCall( crossinline apiCall:挂起()->响应, crossinline onNavigationEvent: (NavigationEvent) -> 单位 ) = 流量 { 发出(状态.进度(0)) val 响应 = apiCall() val responseBody = response.body()

     when {
         response.isSuccessful && responseBody != null -> emit(State.Success(responseBody))
         response.code() in 500..599 -> {
             onNavigationEvent(NavigationEvent.NavigateToServerError)
             emit(State.Error(HttpException(response)))
         }
         response.code() == 401 || response.code() == 403 -> {
             onNavigationEvent(NavigationEvent.NavigateToLogin)
             emit(State.Error(HttpException(response)))
         }
         else -> emit(State.Error(HttpException(response)))
     }
    

    }.catch { 异常 -> 发出(状态。错误(异常)) }.flowOn(调度程序.IO)

  • 从存储库发出导航事件

在 API 调用期间将导航事件传递到 ViewModel:

   class ExampleRepository {
    fun checkOtpCode(
        phoneNumber: String,
        code: String,
        onNavigationEvent: (NavigationEvent) -> Unit
    ) = apiCall(
        { remoteDataSource.checkOtp(phoneNumber, code) },
        onNavigationEvent
    )
}
  • 处理 ViewModel 中的导航事件

将导航事件的 SharedFlow 或 LiveData 暴露给 Fragment:

    class ExampleViewModel(private val repository: ExampleRepository) : ViewModel() {

    private val _navigationEvent = MutableSharedFlow<NavigationEvent>()
    val navigationEvent: SharedFlow<NavigationEvent> = _navigationEvent

    fun checkOtpCode(phoneNumber: String, code: String) = repository.checkOtpCode(
        phoneNumber,
        code
    ) { event ->
        viewModelScope.launch {
            _navigationEvent.emit(event)
        }
    }
}
  • 观察片段中的导航事件

收集 Fragment 中的导航事件并执行导航:

class ExampleFragment : Fragment() {

    private val viewModel: ExampleViewModel by viewModels()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        lifecycleScope.launchWhenStarted {
            viewModel.navigationEvent.collect { event ->
                when (event) {
                    is NavigationEvent.NavigateToLogin -> findNavController().navigate(R.id.loginFragment)
                    is NavigationEvent.NavigateToServerError -> findNavController().navigate(R.id.serverErrorFragment)
                }
            }
        }

        // Api call here
        viewModel.checkOtpCode("1234567890", "1234")
    }
}

该解决方案遵循 MVVM,将导航逻辑保留在 ViewModel 中,使其集中、可重用且可测试。它通过将导航事件范围限定到特定的 ViewModel 实例来避免全局状态,从而确保干净的架构并减少紧密耦合。为了进一步改进,可以将共享导航逻辑移至 BaseViewModel,并且可以使用 SingleLiveEvent 与 LiveData 进行一次性导航。这种方法确保了代码的可维护性、模块化和可扩展性。

我希望这对您有帮助。谢谢你:)

© www.soinside.com 2019 - 2024. All rights reserved.