Jetpack系列(十) -- MVVM 框架demo实现(2)

133 阅读5分钟

前言

时间: 23/10/12

AndroidStudio版本: Giraffe 2022.3.1 JDK:17 开发语言: Kotlin

Gradle版本: 8.0 Gradle plugin Version: 8.1.1

概述

上一节,讲述了什么是 mvvm 代码框架,并且通过使用之前学习的 jetpack 组件编写了一个 mvvm 框架的小 demo。

但是上一节只完成了登录的功能,哪这节就来完善一下这个 demo。

功能

密码加密

涉及到网络请求和用户数据,我们一般都需要对用户的一些信息进行加密处理,这是一个软件基本的要求。

目前来讲,各种加密算法都很常用。总的来说,就分为对称和非对称加密。简单讲就是可解密和不可解的两种加密算法。

我这里使用的是使用AES+固定密钥对用户的密码进行加密。

private val SECRET_KEY = "Jensen0906xxm423"
private val AES = "AES"
private val CHARSET = Charset.forName("utf-8")
private val CIPHER_ALGORITHM = "AES/CBC/PKCS7Padding"fun String?.AESEncode(): String {
    if (isNullOrEmpty()) {
        return ""
    }
    val cipher = Cipher.getInstance(CIPHER_ALGORITHM)
    val byteArray = SECRET_KEY.toByteArray(CHARSET)
    val keySpec = SecretKeySpec(byteArray, AES)
    val iv = IvParameterSpec(byteArray)
    return try {
        cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv)
        val encrypted = cipher.doFinal(toByteArray(CHARSET))
        Base64.encodeToString(encrypted, Base64.NO_WRAP)
    } catch (e: Exception) {
        e.printStackTrace()
        ""
    }
}

加密之后会生成密文,密文也是一个字符串,只是直接看看不出任何意义。我们一般是用这个密文进行和服务器数据交互的,正常来讲存储到数据库中的也会是密文,我们在登录时直接通过对比密文,也就是字符串就能实现登录功能。

这里还用到了 Kotlin 的特性 —— 顶层函数 以及 扩展函数

  1. 顶层函数

    可以看出上面的代码,包括变量和方法,都不存在类包括它。我们在 Java 中正常写方法或函数,是需要编写在类中的,方便其它方法或类调用。但是 Kotlin 可以像上面的代码一样写函数,而这个就是 顶层函数。

  2. 扩展函数

    这是 Kotlin 的另一个特性,上面的 AESEncode() 方法前面有一个String?.,这个就是 扩展函数 的特点,表示这个函数是扩展给 String 用的。总所周知,Java 中的 String 类是 final 修饰的,也就是说我们并不能重写 String 类。如果我们需要给 String 添加一些自定义逻辑的方法,就需要另外写一个工具类型的方法。并且这个方法需要传入待改动的参数 string1,返回改动后的 string2。如下

    public String AESEncode(String resouse) {
        String result;
        result = "resouse do AESEncode";
        return result;
    }
    //调用
    String result = AESEncode(password);
    

    而使用了 Kotlin 的扩展函数后,我们只需要用以下方法调用即可。

    password.AESEncode()
    

    Java 的这种调用方式,看起来就不符合调用关系——调用者在方法里头。而 Kotlin 则是能清晰看出 password调用了AESEncode()这个方法。

使用

在之前编写好的代码的基础上,只需要修改 viewmodel 中请求的数据 user 即可。

    fun login(user: User?) {
        if (user == null || user.username.isNullOrEmpty() || user.password.isNullOrEmpty()) {
            Toast.warning(appContext, appContext.getString(R.string.username_or_password_empty))
            return
        }
        user.password = user.password.AESEncode() // 添加
        val requestBody =
            RequestBody.create(MediaType.parse("application/json;"), Gson().toJson(user))
        viewModelScope.launch {
            repository.login(_user, requestBody)
        }
    }
运行效果

mvvm_result2

可以看到,虽然登录成功了,但是在登录的时候密码变成了密文了。这是因为我们实现了 databinding 数据双向绑定,那么更改 user 的 username 或者 password 的值,都会实时更新到 UI 上。

修复bug也很简单,有两种方式。一是不使用双向绑定( 不推荐,因为代码可能涉及较多 );二是新建一个 User 对象,存放更改后的密码,并用作网络请求的数据。

同样的使用扩展函数。

fun User.makePassowrdEncode(): User {
    val user = User()
    val data = this
    return user.apply {
        id = data.id
        username = data.username
        password = data.password.AESEncode()
        userStatus = data.userStatus
    }
}
​
//UserViewModel.kt
        val requestBody =
            RequestBody.create(MediaType.parse("application/json;"), Gson().toJson(user.makePassowrdEncode()))
        viewModelScope.launch {
            repository.login(_user, requestBody)
        }

这样就不会出现 密文显示出来的情况了。

用户注册

其实功能实现和用户登录一样,从数据层往 UI 层一步一步走就行。直接贴代码,就不多作细说了,如果有疑问,可以看同系列的上一篇文章。

//interface NetWorkApi
    @POST("user/register")
    suspend fun register(@Body body: RequestBody): ApiResult<User?>
//class UserRepository
    suspend fun register(user: MutableLiveData<User?>, requestBody: RequestBody) {
        execute({ api.register(requestBody) }, user)
    }
//class UserViewModel
    fun register(user: User?, pass2: String?) {
        if (user == null || user.username.isNullOrEmpty() || user.password.isNullOrEmpty()) {
            Toast.warning(appContext, appContext.getString(R.string.username_or_password_empty))
            return
        } else if (user.password != pass2) {
            Toast.warning(appContext, appContext.getString(R.string.twice_password_not_same))
            return
        }
        val requestBody =
            RequestBody.create(MediaType.parse("application/json;"), Gson().toJson(user.makePassowrdEncode()))
        viewModelScope.launch {
            repository.register(_user, requestBody)
        }
    }
//class RegisterActivity
    private lateinit var userViewModel: UserViewModel
​
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
​
        userViewModel = ViewModelProvider(this)[UserViewModel::class.java]
        val user = User()
        binding.user = user
        binding.btnRegister.setOnClickListener {
            Log.d(TAG, user.toString())
            userViewModel.register(user, binding.etPasswordAgain.text?.toString())
        }
​
        userViewModel.userLiveData.observe(this) {
            Toast.success(applicationContext, "register success! --${user.username}--")
            val intent = Intent(this@RegisterActivity, LoginActivity::class.java)
            intent.putExtra(REGISTER_SUCCESS_NAME, it?.username)
            // registerForActivityResult的返回,startActivityForResult的替代方法
            setResult(TO_REGISTER_FOR_RESULT, intent) 
            finish()
        }
    }

布局界面就不贴了,文章末尾有源码,可以自己下载。

运行效果

mvvm_result3

其它

其实写到这里就已经没什么新鲜的东西可以说了,我相信举一反三不算难事。但是本篇涉及到了一些这个系列之前从未提及的东西。例如 在 MainActivity 中获取到 all books 的数据后使用 recycleview 来显示出来;又或者注册成功后跳转回登录界面,并且注册的用户名已经填充到 UI 等。这些其实和 mvvm 关系不大,都是平常开发经常用到的,所以我这里就不一一细说了。

总结

至此,jetpack 系列的学习就差不多落幕了,虽然还有一些东西还没有讲过,但是都无伤大雅( 除了 kotlin Flow ),因为 jetpack 出来这么久了,网上有很多相关的资料和书籍。后续有时间会补上一些没讲过的细节,可能是出一个 jetpack 进阶系列,也可能就直接整 compose 给大家了。不管怎样,希望这个 jetpack 学习系列能给你带来帮助。

最后我想说的是,开发本质就是 发现问题 - 调查问题 - 解决问题 三者的循环。

有想法、建议或者疑问的可以评论或私信。

万分感谢点赞、收藏 和 关注。

Demo地址(GitHub)