Android系统模板——LoginActivity解析

题记

最近在研究MVVM,起因是以前的一个类应用商城的项目,我用MVP写的框架,当我准备引入Jetpack组件的时候,结果发现JetPack跟MVP想结合的话有点不伦不类。并这些组件之间的联系很深,甚至可以说是耦合了,我不准备强行融合了,索性就拉上MVVM重构一把吧,当然个人精力有限,组内的健神给了我很大帮助。我也索性把自己作为MVVM小白研究架构的经验分享一波。
凡事都讲究方法,我认为最快的途径就是站在巨人的肩膀上。在结束了Jetpack组件基础知识的浏览后,我把眼光瞄向了Android Studio自带的模板类。
在这里插入图片描述

一. 整体结构

当我们新建一个Activity的时候,发现在仅有一个页面的情况下,产生了居多的文件,一个复杂的工程结构。
在这里插入图片描述
如上图所示,主要分为ui和data,这个整体分包我还是十分认可的。但是个人认为根据分包的清晰度而言,ui下的login分包本身没有问题,但是viewmodel和factory文件,以我个人理解而言,我会选择把它单独放在一个包内。暂且就按google工程师的思维解析吧,希望大佬的光辉能够庇护我有个更好的代码思路。

二. ui/login

按照个人习惯,还是先从页面开始分析功能,比较容易入手。

1. LoginActivity.kt

故名思议,这个界面就是最终的展示界面,从这个文件作为突破口。从代码
加注释开始解析。

  1. private lateinit var loginViewModel: LoginViewModel
    这个首先在view层引入了一个ViewModel,主要是作为数据更新和View刷新关联起来。和注意其中的lateinit关键字,这个是作为懒加载,目的在与代码编译阶段避开编译检查(不用添加!! / ?)。
    相关文档:1.Kotlin类和加载 2.ViewModel 概览
  2. loginViewModel = ViewModelProviders.of(this, LoginViewModelFactory()) .get(LoginViewModel::class.java)
    这个就是简单的ViewModel的构造方法,注意第二个参数的LoginViewModelFactory这个是一个重写的构造工厂,主要在这里传入默认的构造工程,只能创建空构造函数的ViewModel,重写之后,可以使ViewModel具有有参数的构造函数,并且在工厂中添加参数。
  3. loginViewModel.loginFormState.observe(this@LoginActivity, Observer {...}
    包括下面的那个observer,等同于一个观察 viewmodel的变量LoginFormState/loginResult对象的回调,当被观察对象发生变化的时候,触发这里的操作。
    val loginState = it ?: return@Observer这是一个简单的语法糖,判空并返回。
  4. password.apply {...} 这里有一个很有意思的语法糖applyfun T.apply(block: T.() -> Unit): T { block(); return this }。this指代当前对象或者省略,返回this。一般用于多个扩展函数链式调用,这里操作对象属性,并最终返回这个对象。

2. LoginViewModelFactory

看看自动生成的官方解释: * ViewModel provider factory to instantiate LoginViewModel.Required given LoginViewModel has a non-empty constructor. 理解为LoginViewModel具有非空构造函数需要定制工厂。
因为继承了ViewModelProvider.Factory,所以我们需要重写fun <T : ViewModel> create(modelClass: Class<T>): T {}。在return关键字之后new出这个带参数的实例。

3. LoginViewModel

  1. private val _loginForm = MutableLiveData<LoginFormState>() val loginFormState: LiveData<LoginFormState> = _loginForm
    注意前面4行定义的课观察数据容器。为什么一个LoginFormState会放在几乎相同的MutableLiveData/LiveData中呢?这个操作看似很骚,实则很妙。因为LiveData没有公开的方法可以更新存储的数据。MutableLiveData暴露了setValue(T)和postValue(T)方法,如果想要编辑LiveData对象中存储的数据就必须使用MutableLiveData。而加上private也符合数据封装的安全原则,使ViewModel只对观察者暴露不可修改的LiveData对象,而下面的loginDataChanged(...)方法正是暴露到外面的可操作存储数据的方法。
    PS:其实我个人感觉只用MutableLiveData就好,多一个LiveData有点画蛇添足,可能我的境界尚浅吧。

4. LoggedInUserView、LoginResult、LoginFormState

这三个类为什么归类为一起呢?因为都是date关键字修饰的数据类,我也比较喜欢成为数据bean。简单来说就是存储一些组合数据,作为一个组合起来的信息类进行数据传递。data类可以自动生成一些方法。
相关资料:Kotlin 数据类与密封类
6. LoggedInUserView
User details post authentication that is exposed to the UI → 用户界面展示信息
7. LoginFormState
Data validation state of the login form. → 登陆信息请求体
8. LoginResult
Authentication result : success (user details) or error message. → 登陆返回结果体

三. data

顾名思义,这个包下的文件均是存储一些过程/结果数据。

1. LoggedInUser

LoginRepository检索到的,捕获到的已登录用户的用户信息的数据类。简单说就是当前存储当前登陆用户信息。
下面就说

2. LoginDataSource

处理带有登录凭据的身份验证,并检索用户信息。简单说就是检索用户登陆信息,做出登陆动作,并返回结果。

3. LoginRepository

Class that requests authentication and user information from the remote data source andmaintains an in-memory cache of login status and user credentials information.
该类从远程数据源(LoginDataSource.login)请求身份验证和用户信息,并维护登录状态和用户凭据信息的内存缓存。
先说说缓存了什么。
9. 首先缓存的是当前登陆用户信息LoggedInUser,之所以用private修饰,为的是防止无故的修改,只有再做出登陆动作&登陆成功才会初始化登陆信息。
kotlin var user: LoggedInUser? = null private set //注意私有
10. 接下来缓存的是登陆状态boolean。重写了get方法,目的在与,如果没有当前用户登陆信息,则证明未登录。正好呼应了上面的private关键字。
kotlin val isLoggedIn: Boolean get() = user != null
11. 注意init{...}.关于init代码块的运行顺序,无非是是类的加载首先执行init代码块,然后再执行构造方法。所以我可以在init代码块中做各种初始化操作,比如声明属性。这里的注释,其实告诉我们,如果我们的用户信息在本地缓存(数据库…),依据安全性,在初始化这里做一些加密操作。

```kotlin
init {
    // If user credentials will be cached in local storage, it is recommended it be encrypted
    // @see https://developer.android.com/training/articles/keystore
    user = null
}
```
  1. 这个类里面同样也有login方法,调用的就是入参传进来的LoginDataSource的同名方法,根据返回值来判断,是否登陆成功(给LoggedInUser赋值来判断isLoggedIn)。
fun login(username: String, password: String): Result<LoggedInUser> {
       // handle login
       val result = dataSource.login(username, password)

       if (result is Result.Success) {
           setLoggedInUser(result.data)
       }
       return result
   }

四. Result

这个是我看着最难受,但是感觉很抓人的一个类。目前我用的很少或者说基本不用,但是我是觉得珍惜奇妙无比,以后一定会在我的架构中加入这个多样化的一个糖果。sealed关键字修饰的class真的是骚气无比。

基本用法如下 ↓ ↓ ↓
class 基类名称
sealed class 基类名称 {
class 子类1
class 子类2
fun 123()
}

怎么说呢?就是说sealed class命名的最外面的类是一个父类(也称为密封类),其类中跟属性一样命名的类是其子类。注意这个子类只能在同一个.kt文件内。并且无法通过本体的实例化对象,直接调用其中的方法,比如在这个工程中,result.tostring是直接报错的,但是 Result.Success()是可以的。
其实我现在无法用一些浅显的语言来说明这个玩意的好处,我就是感觉他很精妙,是一个枚举的加强版甚至说是完美版本。
我们现在用它可以做很多事情。

  1. 我们可以定义继承同一个类的一组类。也就是说同一个类可以有我们指定数量的实现。
  2. 这些子类的类型可以确定,或者说约束起来了。
    这对函数式编程有了很大的推动。看看最精妙的一段代码:
   override fun toString(): String {
        return when (this) {
            is Success<*> -> "Success[data=$data]"
            is Error -> "Error[exception=$exception]"
        }
    }

同一个类的同一个方法执行了不同的操作。比枚举骚气的是,toString()的调用是一个实例化后的类,或者说是一个object,类经过了实例化就可以保存当前状态。这不就我一个类对应不同状态吗?不管别人怎么认为,我就是感觉特别酷!!!

总结

整体架构分析下来,感觉思路清晰异常,但是还是感觉这个结构/分包过于臃肿甚至无聊。其中绝妙的语法糖,可以省去很多代码量。但是,福兮祸所依,过多语法糖的代码,对后来的观看者包括自己可能说是一种灾难,可阅读性在代码的高潮期过后,可能会降到冰点。
最后补一句,Kotlin语法的学习任重道远,架构的分析与攀登长路漫漫。愿我们在这条路上一飞冲天,与君共勉。
2020年6月18日23:36:13

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值