美文网首页
Android MVVM代码规范

Android MVVM代码规范

作者: 克罗克达尔 | 来源:发表于2022-03-02 10:29 被阅读0次

    包的划分

    在包目录下应包含以下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<in P, R>(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<R> {
            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<MapVariant?>(null)
        val mapVariant: StateFlow<MapVariant?> = _mapVariant
    
    • 消费型。比如跳转一个新的页面。这种写法样式很固定,只需要把泛型类型换成别的就可以
    private val _navigationActions = Channel<EventInfoNavigationAction>(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<Advertising>(ExtraKey.ADVERTISING) !!
    

    注意事项

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

    View编写规范

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


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

    相关文章

      网友评论

          本文标题:Android MVVM代码规范

          本文链接:https://www.haomeiwen.com/subject/ocssrrtx.html