Android MVVM代码规范

包的划分

在包目录下应包含以下package

  • di(依赖注入相关的,XXXModule、XXXQualifier应该在此package下)
  • contract(现在startActivityForResult方法已经被弃用,如果需要启动activity获取结果,请写一个contacts类集成ActivityResultContract)
  • domain(UseCase所在的package)
  • repository
  • ui(例子如下)


    ui例子

app架构

mvvm架构,对应关系是

  • 一个Activity/Fragment持有一个或多个ViewModel
  • 一个ViewModel持有一个或多个UseCase
  • 一个UseCase持有一个或多个Repository

UseCase编写规范

UseCase类代码如下

abstract class UseCase(private val coroutineDispatcher: CoroutineDispatcher) {

    /** Executes the use case asynchronously and returns a [Result].
     *
     * @return a [Result].
     *
     * @param parameters the input parameters to run the use case with
     */
    suspend operator fun invoke(parameters: P): Result {
        return try {
            // Moving all use case's executions to the injected dispatcher
            // In production code, this is usually the Default dispatcher (background thread)
            // In tests, this becomes a TestCoroutineDispatcher
            withContext(coroutineDispatcher) {
                execute(parameters).let {
                    Result.Success(it)
                }
            }
        } catch (e: Exception) {
            Result.Error(e)
        }
    }

    /**
     * Override this to set the code to be executed.
     */
    @Throws(RuntimeException::class)
    protected abstract suspend fun execute(parameters: P): R
}

UseCase的作用是

  • 切线程(例如把网络请求切换到非UI线程)
  • Try-Catch处理(例如处理网络请求异常)
  • 业务逻辑处理(业务逻辑放在UseCase里面可以实现复用,放在ViewModel里面不好复用)

ViewModel编写规范

View无法决定自己跳转页面、展示Dialog、显示加载对话框、显示一个SncakBar,View只能告诉ViewModel我的某个按钮被点击了,如

override fun openEventDetail(id: SessionId) {
        analyticsHelper.logUiEvent(
            "Home to event detail",
            AnalyticsActions.HOME_TO_SESSION_DETAIL
        )
        _navigationActions.tryOffer(FeedNavigationAction.NavigateToSession(id))
    }

ViewModel类中应该包含一个NavigationAction密封类,通过这个类,告诉View应该跳转到哪个页面或者弹出一个对话框,如

sealed class FeedNavigationAction {
    class NavigateToSession(val sessionId: SessionId) : FeedNavigationAction()
    class NavigateAction(val directions: NavDirections) : FeedNavigationAction()
    object OpenSignInDialogAction : FeedNavigationAction()
    class OpenLiveStreamAction(val url: String) : FeedNavigationAction()
    object NavigateToScheduleAction : FeedNavigationAction()
}

ViewModel和View的交互方式

不同于MVP,在MVP中,View和Presenter相互持有,P层可以直接调用V层的方法。但是在MVVM中,View可以持有ViewModel,但ViewModel不能持有View,ViewModel需要把一个可观察的数据源给View去观察,在数据变化的时候View去更新数据。可观察的数据源可分为3大类

RxJava

天生的观察者模式,但存在两个问题

  • 如何在View不可见的时候不再发送数据给View,避免在View不可见的时候毫无意义的绘制
  • 如何在View可见的时候把最新的数据发送给View,让View展示最新的数据
LiveData

LiveData不同于RxJava,RxJava是一个数据流,你可以进行Map,Filter等数据流操作,但LiveData是一个DataHolder,帮你保存一个数据,当页面不可见的时候不会给View发送数据,当页面可见的时候把最新的数据给View。
但LiveData存在一个非常大的问题。比如我要让View去跳转到一个新的Activity要怎么做,如果用LiveData去发送这个消息的话,View收到消息后跳转页面,看起来没什么问题,但是当重新回到页面的时候,由于LiveData的机制,当页面可见的时候会发送最新的数据给View,然后我们又跳转了一次页面

Flow

kotlin才有的数据流处理方式,适合Android的MVVM项目。注意我们要暴露两种不同类型的Flow让View去观察

  • 展示型。比如显示用户名,私有化一个MutableStateFlow,以便我们可以更新数据,暴露StateFlow出来让View去观察。写法如下
private val _mapVariant = MutableStateFlow(null)
    val mapVariant: StateFlow = _mapVariant
  • 消费型。比如跳转一个新的页面。这种写法样式很固定,只需要把泛型类型换成别的就可以
private val _navigationActions = Channel(Channel.CONFLATED)
    val navigationActions = _navigationActions.receiveAsFlow()

NavigationAction的写法

  • 类名是XXXNavigationAction
  • 如果需要带参数就是Data Class,不需要带参数就是object
  • 跳转新的Activity以NavigateTo开头
  • 打开Dialog以Show开头
sealed class FeedNavigationAction {
    data class NavigateToSession(val sessionId: SessionId) : FeedNavigationAction()
    object ShowSignInDialog : FeedNavigationAction()
    object NavigateToSchedule : FeedNavigationAction()
}

ViewModel中获取view中的数据方式(Activity中的Intent或Fragment中的Argument)

@HiltViewModel
class AdvertisingViewModel @Inject constructor(
    savedStateHandle: SavedStateHandle
) : ViewModel() {

    val advertising: Advertising =
        savedStateHandle.get(ExtraKey.ADVERTISING) !!

注意事项

  • ViewModel暴露给View用的方法不允许有返回值

View编写规范

  • ui包下的view包应该包含一个View类(Activity/Fragment)和一个ViewModel类


    image.png
  • view类只能从ViewModel获取数据,禁止从ViewModel之外的地方获取数据
  • view不能决定自己跳转到哪里

你可能感兴趣的:(Android MVVM代码规范)