美文网首页
Android MVVM ——Using Kotlin and

Android MVVM ——Using Kotlin and

作者: Jotyy | 来源:发表于2019-05-17 15:25 被阅读0次

    随着对公司现有MVP架构模式代码的逐渐不满,每每新增一个小功能,总要在一个又一个的接口中新增方法,代码变得越来越不清晰,迭代变得越来越困难。作为一个爱搞事的程序猿,免不了要考虑一波新的架构模式,MVVM也就映入眼帘。首先对比一下我们曾经的MVP和即将要使用的MVVM吧(MVC就离开历史舞台吧)。

    为什么要淘汰MVP?

    • 太多的接口,一个功能处处接口
    • Presenter层与View层耦合严重
    • View层的一个改变往往牵动Presenter层,增加一个新方法往往要修改好几处地方
    • 每个Presenter无法重用,通常耦合到特定的View层

    MVVM有什么优势?

    • 不再有过多的接口
    • ViewModel和DataModel支持单元测试
    • ViewModel不再与特定的View耦合
    • ViewModel可以在多个View中组合或使用

    MVVM到底长什么样?

    MVVM模型.png

    View:

    Activity/Fragment/View

    从ViewModel层获取UI数据

    请求ViewModel对数据进行操作

    ViewModel:

    作为View和Model之间的桥梁

    请求Model层的数据并为View层转换

    使View层更新数据

    Model:

    作为DataModel/Repository

    持久化业务逻辑

    从多种数据源获取数据(DataBase,REST Api,cache)


    实际使用

    这一篇中,我不打算使用LiveData,LiveData在接下来的学习中我们会使用,而且将会相当的给力。先试试RX来实现数据Observe吧。

    基本实现

    下面这个例子,将会简单的从API请求用户信息列表并显示。

    /** ViewModel */
    class UserListViewModel(val userRepository: UserRepository){
        
        fun getUsers(): Observable<UserList> {
            //获取用户数据
            return userRepository.getUser()
                .map { UserList(it, "Uses loaded success.") }
        }
        
    }
    
    /** Model */
    class UserRepository(val userApi: UserApi){
        
        fun getUsers(): Observable<List<User>> = 
            userApi.getUsers()
        
    }
    
    interface UserApi{
        
        @GET("users")
        fun getUsers(): Observabel<List<User>>
        
    }
    

    UserListViewModle: ViewModel层,从Repository中获取数据,提供给View

    UserRepository: 用户数据仓库,从数据库、网路Api获取数据

    UserApi: 网路接口

    改进Model层

    我们可以修改UserRepository使它可以存储从API请求到的用户列表,这样每次我们调用getUser()方法中,我们会立即先返回缓存的用户列表,然后再返回API中的新数据。这可以使用RX中的mergeWith()方法来实现

    class UserRepository(val userApi: UserApi){
        
        var cacheUsers = emptyList<User>()
        
        fun getUsers(): Observale<List<User>> = 
            if (cachedUsers.isEmpty())
                userApi.getUsers().doOnNext{ cachedUsers = it }
            else 
                Observable.just(cachedUsers)
                    .mergeWith(userApi.getUsers())
                    .doOnNext{ cachedUsers = it }
    
    }
    

    (PS: 通常在项目中,我们都会使用Dagger来提供UserRepository、UserApi的单例)

    好处

    这种方法中(将ViewModel与DataModel / Repository分开),ViewModel层不知道,也不关心数据的来源。它也不负责缓存或存储数据。我们能够在不对ViewModel进行任何更改的情况下引入API数据的缓存。

    对UserRepository单元测试

    我们可以使用TestObserver对UserRepository进行单元测试

    class UserRepositoryTest{
        
        lateinit var userRepository: UserRepository
        lateinit var userApi: UserApi
        
        @Before
        fun setUp(){
            userApi = mock()
            userRepository = UserRepository(userApi)
        }
        
        @Test
        fun test_emptyCache_noDataOnApi_returnEmptyList(){
            `when`(userApi.getUsers()).thenReturn(Observable.just(emptyList<User>()))
            
            userRepository.getUsers().test()
                .assertValue{ it.isEmpty() }
    
        }
        
        @Test
        fun test_emptyCache_hasDataOnApi_returnsApiData() {
            `when`(userApi.getUsers()).thenReturn(Observable.just(listOf(aRandomUser())))
    
            userRepository.getUsers().test()
                    .assertValueCount(1)
                    .assertValue { it.size == 1 }
        }
    
        @Test
        fun test_hasCacheData_hasApiData_returnsBothData() {
            val cachedData = listOf(aRandomUser())
            val apiData = listOf(aRandomUser(), aRandomUser())
            `when`(userApi.getUsers()).thenReturn(Observable.just(apiData))
            userRepository.cachedUsers = cachedData
    
            userRepository.getUsers().test()
                    //Both cached & API data delivered
                    .assertValueCount(2)
                    //First cache data delivered
                    .assertValueAt(0, { it == cachedData })
                    //Secondly api data delivered
                    .assertValueAt(1, { it == apiData })
        }
    
        @Test
        fun test_cache_updatedWithApiData() {
            val apiData = listOf(aRandomUser(), aRandomUser())
            `when`(userApi.getUsers()).thenReturn(Observable.just(apiData))
    
            userRepository.getUsers().test()
    
            assertEquals(userRepository.cachedUsers, apiData)
        }
    
        fun aRandomUser() = User("mail@test.com", "John", UUID.randomUUID().toString().take(5))
        }
    }
    

    总结

    这只是我个人对Android中MVVM当前状态和新架构组件的看法。选择适合自己需求的架构,适合自己的团队结构,保持代码清洁,帮助您轻松测试代码,最重要的是允许您轻松添加新功能。

    相关文章

      网友评论

          本文标题:Android MVVM ——Using Kotlin and

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