如何应对 Android 面试官 -> 运用 Jetpack 写一个音乐播放器(四)登录注册

前期回顾


如何应对 Android 面试官 -> 运用 Jetpack 写一个音乐播放器(一)基础搭建

如何应对 Android 面试官 -> 运用 Jetpack 写一个音乐播放器(二)音乐列表

如何应对 Android 面试官 -> 运用 Jetpack 写一个音乐播放器(三)播放能力

前言


本章继续前面的章节来完善音乐播放器;

本章我们使用 Jetpack 全家桶 + RxJava 来完善下『注册』+『登录』的能力;

注册


我们依然使用 DataBinding 来布局 xml

activity_user_register.xml

ini 复制代码
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

        <!-- ViewModel 的绑定操作 -->
        <variable
            name="vm"
            type="com.llc.puremusic.bridge.state.RegisterViewModel" />

        <!-- 布局的点击事件,全部交给 DataBinding 管理 -->
        <variable
            name="click"
            type="com.llc.puremusic.RegisterActivity.ClickClass" />

    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/root_bg_color"
        android:orientation="vertical">


        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="@dimen/title_bar_height"
            android:background="@color/top_bg_color">

            <TextView
                android:id="@+id/title_tv"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerInParent="true"
                android:text="注册"
                android:textColor="@color/white"
                android:textSize="@dimen/title_main_text_size" />

        </RelativeLayout>

        <!-- 面向 ViewModel 开发 -->
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="#f00"
            android:textSize="20dp"
            android:text="@{vm.registerState}"
            android:layout_gravity="center"/>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="15dp"
            android:layout_marginTop="30dp"
            android:layout_marginRight="15dp"
            android:background="@drawable/corner_login_bg"
            android:gravity="center_vertical"
            android:padding="10dp">

            <EditText
                android:id="@+id/user_phone_et"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@null"
                android:hint="账户"
                android:inputType="text"
                android:padding="10dp"
                android:textSize="12sp"
                android:text="@={vm.userName}"/>
        </LinearLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="15dp"
            android:layout_marginTop="15dp"
            android:layout_marginRight="15dp"
            android:background="@drawable/corner_login_bg"
            android:gravity="center_vertical"
            android:padding="10dp">

            <EditText
                android:id="@+id/user_password_et"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:background="@null"
                android:hint="密码"
                android:inputType="textPassword"
                android:padding="10dp"
                android:textSize="12sp"
                android:text="@={vm.userPwd}"
                />

            <CheckBox
                android:id="@+id/check_password_cb"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:button="@drawable/check_user_password_selector" />
        </LinearLayout>

        <Button
            android:id="@+id/user_register_bt"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="15dp"
            android:background="@drawable/corners_login_bg"
            android:text="注 册"
            android:textColor="@color/white"
            android:onClick="@{()->click.registerAction()}"/>

        <TextView
            android:id="@+id/user_agreement_tv"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="15dp"
            android:drawableLeft="@drawable/consent_icon"
            android:drawablePadding="5dp"
            android:text="建议:把注册的信息牢牢记住,以免忘记!" />

    </LinearLayout>
</layout>

布局的创建比较简单,就是两个输入框,一个按钮,进行用户名,密码的注册填入;

RegisterViewModel

接下来我们来完善注册唯一的 ViewModel

kotlin 复制代码
class RegisterViewModel : ViewModel() {

    // 用户名
    @JvmField
    val userName = MutableLiveData<String>()
    // 密码
    @JvmField
    val userPwd = MutableLiveData<String>()
    // 注册成功,注册失败 等等
    val registerState = MutableLiveData<String>()

    // 默认初始化
    init {
        userName.value = ""
        userPwd.value = ""
        registerState.value = ""
    }
}

注册的 ViewModel 还是比较简单的,用来管理用户名和密码,以及注册后的状态;

RequestRegisterViewModel

接下来,我们来晚上发起『注册』请求的 ViewModel,用来发起网络请求,并返回注册结果;

kotlin 复制代码
class RequestRegisterViewModel : ViewModel() {

    // 注册成功的状态 LiveData
    var registerSuccessData : MutableLiveData<LoginRegisterResponse>? = null
        get() {
            if (field == null) {
                field = MutableLiveData()
            }
            return field
        }
        private set

    // 注册失败的状态 LiveData
    var registerFailData : MutableLiveData<String>? = null
        get() {
            if (field == null) {
                field = MutableLiveData()
            }
            return field
        }
        private set
    
    // 发起注册的网络请求,并通过 LiveData 观察注册结果
    fun requestRegister(context: Context, username: String, userpwd: String, reuserpwd: String) {
        HttpRequestManager.instance.register(context, username, userpwd, reuserpwd, registerSuccessData, registerFailData)
    }
}

LoginRegisterResponse

注册返回结果 bean,登录和注册这两个能力可以使用同一个;

kotlin 复制代码
data class LoginRegisterResponse(val admin: Boolean,
                                 val chapterTops: List<*>,
                                 val collectIds: List<*>,
                                 val email: String ?,
                                 val icon: String?,
                                 val id: String?,
                                 val nickname: String?,
                                 val password: String?,
                                 val publicName: String?,
                                 val token: String?,
                                 val type: Int,
                                 val username: String?
                         )

register


接下来,我们来完善注册的网络请求能力;

我们借助 Retrofit + RxJava 来完成网络请求;

API

先来声明创建 Retrofit 的 interface

less 复制代码
interface WanAndroidAPI {

    /**
     * 登录API
     * username=Derry-vip&password=123456
     */
    @POST("/user/login")
    @FormUrlEncoded
    fun loginAction(@Field("username") username: String,
                    @Field("password") password: String)
    : Observable<LoginRegisterResponseWrapper<LoginRegisterResponse>> // 返回值

    /**
     * 注册的API
     */
    @POST("/user/register")
    @FormUrlEncoded
    fun registerAction(@Field("username") username: String,
                       @Field("password") password: String,
                       @Field("repassword") repassword: String)
            : Observable<LoginRegisterResponseWrapper<LoginRegisterResponse>> // 返回值
}

LoginRegisterResponseWrapper

这里对返回结果进行了一层包装,是为了兼容服务端返回的异常不规范信息;

kotlin 复制代码
data class LoginRegisterResponseWrapper<T>(val data: T, val errorCode: Int, val errorMsg: String)

APIResponse

接下来,我们对 Retrofit 返回的 RxJava 数据进行处理和解析;

kotlin 复制代码
abstract class APIResponse<T>(val context: Context) : Observer<LoginRegisterResponseWrapper<T>> {

    private var isShow: Boolean = true

    // 次构造
    constructor(context: Context, isShow: Boolean = false) : this(context) {
        this.isShow = isShow
    }

    // 成功 怎么办
    abstract fun success(data: T?)
    // 失败 怎么办
    abstract fun failure(errorMsg: String?)


    // 启点分发的时候
    override fun onSubscribe(d: Disposable) {
        // 弹出 加载框
        if (isShow) {
            LoadingDialog.show(context)
        }
    }

    // 上游流下了的数据   我当前层 获取到了 上一层 流下来的 包装Bena == t: LoginRegisterResponseWrapper<T>
    override fun onNext(t: LoginRegisterResponseWrapper<T>) {
        if (t.data == null) {
            // 失败
            failure("登录失败了,请检查原因:msg:${t.errorMsg}")
        } else {
            // 成功
            success(t.data)
        }
    }

    // 上游流下了的错误
    override fun onError(e: Throwable) {
        // 取消加载
        LoadingDialog.cancel()
        failure(e.message)
    }

    // 停止
    override fun onComplete() {
        // 取消加载
        LoadingDialog.cancel()
    }
}

Retrofit 封装

kotlin 复制代码
class APIClient {

    private object Holder {
        val INSTANCE = APIClient()
    }

    // 派生
    companion object {
        val instance = Holder.INSTANCE
    }
    
    fun <T> instanceRetrofit(apiInterface: Class<T>) : T {
        // OKHttpClient请求服务器
        val mOkHttpClient = OkHttpClient().newBuilder().myApply {
            // 添加读取超时时间
            readTimeout(10000, TimeUnit.SECONDS)
            // 添加连接超时时间
            connectTimeout(10000, TimeUnit.SECONDS)
            // 添加写出超时时间
            writeTimeout(10000, TimeUnit.SECONDS)
        }.build()

        val retrofit: Retrofit = Retrofit.Builder()
            .baseUrl("https://www.wanandroid.com")
            // 请求方 ←
            .client(mOkHttpClient)
            // 响应方 →
            // Response 回来
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) // RxJava来处理
            .addConverterFactory(GsonConverterFactory.create()) // Gson 来解析 --- JavaBean
            .build()
        return retrofit.create(apiInterface)
    }
}

// 默认 无参数的
fun <T> T.myApply(mm: T.() -> Unit) : T {
    // T == this
    mm()
    return this
}

很基础的封装,接下来我们来调用这个发起网络请求,并处理响应结果;

register 发起请求

kotlin 复制代码
fun register(
    context: Context,
    username: String,
    password: String,
    repassword: String,
    dataLiveData1: MutableLiveData<LoginRegisterResponse>,
    dataLiveData2: MutableLiveData<String>) {

    // RxJava封装网络模型
    APIClient.instance.instanceRetrofit(WanAndroidAPI::class.java)
        .registerAction(username, password, repassword)
        .subscribeOn(Schedulers.io()) // 给上面的代码分配异步线程
        .observeOn(AndroidSchedulers.mainThread()) // 给下面的代码分配 安卓的主线程
        .subscribe(object : APIResponse<LoginRegisterResponse>(context) {
            override fun success(data: LoginRegisterResponse?) { // RxJava自定义操作符过滤后的
                dataLiveData1.value = data // MVVM
            }

            override fun failure(errorMsg: String?) {  // RxJava自定义操作符过滤后的
                dataLiveData2.value = errorMsg // MVVM
            }
        })
}

RegisterActivity


我们接下来初始化 ViewModel,以及观察 LiveData 的变化并做出相应的处理;

kotlin 复制代码
class RegisterActivity : BaseActivity() {

    var mainBinding : ActivityUserRegisterBinding? = null // 当前Register的布局
    var registerViewModel: RegisterViewModel? = null // ViewModel
    // Reqeust ViewModel
    var requestRegisterViewModel : RequestRegisterViewModel? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        hideActionBar()

        registerViewModel = getActivityViewModelProvider(this).get(RegisterViewModel::class.java) // 状态VM
        requestRegisterViewModel = getActivityViewModelProvider(this).get(RequestRegisterViewModel::class.java) // 请求VM
        mainBinding = DataBindingUtil.setContentView(this, R.layout.activity_user_register) // 初始化DB
        mainBinding?.lifecycleOwner = this
        mainBinding?.vm = registerViewModel // DataBinding绑定 ViewModel
        mainBinding?.click = ClickClass() // 布局建立点击事件

        // 盯着 requestRegisterViewModel 监听成功了吗
        requestRegisterViewModel?.registerData1?.observe(this, {
            registerSuccess(it)
        })

        // 盯着 requestRegisterViewModel 监听失败了吗
        requestRegisterViewModel?.registerData2?.observe(this, {
            registerFailed(it)
        })
    }

    private fun registerSuccess(registerBean: LoginRegisterResponse?) {
        Toast.makeText(this, "注册成功😀", Toast.LENGTH_SHORT).show()
        registerViewModel?.registerState ?.value = "恭喜 ${registerBean?.username} 用户,注册成功"
        // 注册成功,直接进入登录界面
        startActivity(Intent(this, LoginActivity::class.java))
    }

    private fun registerFailed(errorMsg: String?) {
        Toast.makeText(this, "注册失败~ 呜呜呜", Toast.LENGTH_SHORT).show()
        registerViewModel?.registerState?.value = "骚瑞 注册失败,错误信息是:${errorMsg}"
    }

    inner class ClickClass {

        // 点击事件,注册的函数
        fun registerAction() {
            if (registerViewModel?.userName?.value.isNullOrBlank() || registerViewModel?.userPwd?.value.isNullOrBlank()) {
                registerViewModel?.registerState ?.value = "用户名或密码为空,请你好好检查"
                return
            }

            requestRegisterViewModel?.requestRegister(
                this@RegisterActivity,
                        registerViewModel?.userName?.value,
                        registerViewModel?.userPwd?.value,
                        registerViewModel?.userPwd?.value
            )
        }
    }
}

Activity 中只需要进行 ViewModel 的初始化,以及 LiveData 的 observe 监听,并做出 UI 上的改变;

登录


登录和注册流程其实是一样,只是调用的 api 不一样;

activity_user_login.xml

ini 复制代码
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        <!-- ViewModel 的绑定操作 -->
        <variable
            name="vm"
            type="com.llc.puremusic.bridge.state.LoginViewModel" />

        <!-- 布局的点击事件集 -->
        <variable
            name="click"
            type="com.llc.puremusic.LoginActivity.ClickClass" />

    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/root_bg_color"
        android:orientation="vertical">

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="@dimen/title_bar_height"
            android:background="@color/top_bg_color">

            <TextView
                android:id="@+id/title_tv"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerInParent="true"
                android:text="登录"
                android:textColor="@color/white"
                android:textSize="@dimen/title_main_text_size" />

            <TextView
                android:id="@+id/user_register_tv"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentRight="true"
                android:layout_centerVertical="true"
                android:padding="10dp"
                android:text="注册"
                android:onClick="@{()->click.startToRegister()}"
                android:textColor="@color/white"
                android:textSize="14dp" />

        </RelativeLayout>

        <!-- 登录成功 登录失败 的 各种状态 -->
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="#f00"
            android:textSize="20dp"
            android:text="@{vm.loginState}"
            android:layout_gravity="center"
            />

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="15dp"
            android:layout_marginTop="30dp"
            android:layout_marginRight="15dp"
            android:background="@drawable/corner_login_bg"
            android:gravity="center_vertical"
            android:padding="10dp">

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/login_user_name_icon" />

            <EditText
                android:id="@+id/user_phone_et"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@null"
                android:hint="账户"
                android:inputType="text"
                android:padding="10dp"
                android:textSize="12sp"
                android:text="@={vm.userName}"
                />
        </LinearLayout>


        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="15dp"
            android:layout_marginTop="15dp"
            android:layout_marginRight="15dp"
            android:background="@drawable/corner_login_bg"
            android:gravity="center_vertical"
            android:padding="10dp">

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/login_user_password_icon" />

            <EditText
                android:id="@+id/user_password_et"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:background="@null"
                android:hint="密码"
                android:inputType="textPassword"
                android:padding="10dp"
                android:textSize="12sp"
                android:text="@={vm.userPwd}"
                />

            <CheckBox
                android:id="@+id/check_password_cb"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:button="@drawable/check_user_password_selector" />
        </LinearLayout>


        <Button
            android:id="@+id/user_login_bt"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="15dp"
            android:background="@drawable/corners_login_bg"
            android:text="登 录"
            android:onClick="@{()->click.loginAction()}"
            android:textColor="@color/white" />


        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="15dp">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="忘记密码?"
                android:textColor="@color/main_color" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentRight="true"
                android:paddingRight="15dp"
                android:text="手机快捷登录"
                android:textColor="@color/main_color" />
        </RelativeLayout>

    </LinearLayout>
</layout>

区别就是这里需要一个『注册』的点击事件;

LoginViewModel

ini 复制代码
class LoginViewModel : ViewModel() {

    @JvmField
    val userName = MutableLiveData<String>()
    val userPwd = MutableLiveData<String>()
    val loginState = MutableLiveData<String>()

    init {
        userName.value = ""
        userPwd.value = ""
        loginState.value = ""
    }
}

RequestLoginViewModel

kotlin 复制代码
class RequestLoginViewModel : ViewModel() {

    var registerData1: MutableLiveData<LoginRegisterResponse>? = null
        get() {
            if (field == null) {
                field = MutableLiveData<LoginRegisterResponse>()
            }
            return field
        }
        private set

    var registerData2: MutableLiveData<String>? = null
        get() {
            if (field == null) {
                field = MutableLiveData<String>()
            }
            return field
        }
        private set

    fun requestLogin(context: Context, username: String, userpwd: String, reuserpwd: String) {
        HttpRequestManager.instance.login(context, username, userpwd, registerData1, registerData2)
    }
}

Login

kotlin 复制代码
fun login(
    context: Context,
    username: String,
    password: String,
    dataLiveData1: MutableLiveData<LoginRegisterResponse>,
    dataLiveData2: MutableLiveData<String>) {

    // RxJava封装网络模型
    APIClient.instance.instanceRetrofit(WanAndroidAPI::class.java)
        .loginAction(username, password)
        .subscribeOn(Schedulers.io()) // 给上面的代码分配异步线程
        .observeOn(AndroidSchedulers.mainThread()) // 给下面的代码分配 安卓的主线程
        .subscribe(object : APIResponse<LoginRegisterResponse>(context) {
            override fun success(data: LoginRegisterResponse?) {
                dataLiveData1.value = data // MVVM
            }

            override fun failure(errorMsg: String?) {
                dataLiveData2.value = errorMsg // MVVM
            }
        })
}

接下来依然是在 Activity 中初始化 ViewModel 以及 LiveData 的 observe 监听;

LoginActivity

kotlin 复制代码
class LoginActivity : BaseActivity() {

    private var mainBinding: ActivityUserLoginBinding? = null // 当前Register的布局
    private var loginViewModel: LoginViewModel? = null // ViewModel

    var requestLoginViewModel : RequestLoginViewModel? = null // TODO Reqeust ViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        hideActionBar()

        loginViewModel = getActivityViewModelProvider(this).get(LoginViewModel::class.java) // State ViewModel初始化
        requestLoginViewModel = getActivityViewModelProvider(this).get(RequestLoginViewModel::class.java) // Request ViewModel初始化
        mainBinding = DataBindingUtil.setContentView(this, R.layout.activity_user_login) // DataBinding初始化
        mainBinding?.lifecycleOwner = this
        mainBinding?.vm = loginViewModel // 绑定ViewModel与DataBinding关联
        mainBinding?.click = ClickClass() // DataBinding关联 的点击事件

        // 登录成功 眼睛监听 成功
        requestLoginViewModel?.registerData1?.observe(this, {
            loginSuccess(it)
        })

        // 登录失败 眼睛监听 失败
        requestLoginViewModel?.registerData2?.observe(this, {
            loginFailure(it)
        })
    }

    // 响应的两个函数
    private fun loginSuccess(registerBean: LoginRegisterResponse?) {
        Toast.makeText(this@LoginActivity, "登录成功😀", Toast.LENGTH_SHORT).show()
        loginViewModel?.loginState?.value = "恭喜 ${registerBean?.username} 用户,登录成功"
        // 登录成功 在跳转首页之前,需要 保存 登录的会话
        // 保存登录的临时会话信息
        mSharedViewModel.session.value = Session(true, registerBean)
        // 跳转到首页
        startActivity(Intent(this@LoginActivity,  MainActivity::class.java))
    }

    private fun loginFailure(errorMsg: String?) {
        Toast.makeText(this@LoginActivity, "登录失败 ~ 呜呜呜", Toast.LENGTH_SHORT).show()
        loginViewModel?.loginState?.value = "骚瑞 登录失败,错误信息是:${errorMsg}"
    }

    inner class ClickClass {

        // 点击事件,登录的函数
        fun loginAction() {
            if (loginViewModel?.userName?.value.isNullOrBlank() || loginViewModel?.userPwd?.value.isNullOrBlank()) {
                loginViewModel ?.loginState ?.value = "用户名或密码为空,请你好好检查"
                return
            }

            requestLoginViewModel?.requestLogin(
                this@LoginActivity,
                loginViewModel !!.userName.value!!,
                loginViewModel !!.userPwd.value!!,
                loginViewModel !!.userPwd.value!!
            )
        }

        // 跳转到 注册界面
        fun startToRegister() = startActivity(Intent(this@LoginActivity, RegisterActivity::class.java))
    }
}

保存&获取登录会话

接下来,我们就需要保存登录成功的信息,以及贯穿整个场景,不需要每个页面都触发登录,所以我们需要在 SharedViewModel 中更新会话信息;

接下来构建 Session 信息;

Session

kotlin 复制代码
data class Session constructor(val isLogin: Boolean, val loginRegisterResponse: LoginRegisterResponse?)

SharedViewModel

然后 SharedViewModel 中创建 Session 的 LiveData;

kotlin 复制代码
class SharedViewModel : ViewModel() {
    ... 省略部分代码
    
    // 保存登录信息的临时会话需要贯穿整个项目,所以是共享的,这里不能剔除粘性
    val session = MutableLiveData<Session>()
}

loginSuccess

接下来,LoginActivityloginSuccess 方法中,更新 Session 信息;

kotlin 复制代码
private fun loginSuccess(registerBean: LoginRegisterResponse?) {
    ....省略部分代码
    
    // 登录成功 在跳转首页之前,需要 保存 登录的会话
    // 保存登录的临时会话信息
    mSharedViewModel.session.value = Session(true, registerBean)
    
    ....省略部分代码
}

MainViewModel

然后,我们可以在 MainFragment 中获取登录信息,并展示,修改我们的 framgent_main.xmlMainViewModel 来展示信息;

kotlin 复制代码
class MainViewModel : ViewModel() {

    ....省略部分代码

    // 登录信息的临时数据
    @JvmField
    val loginSessionInfo = ObservableField<String>()

}

framgent_main.xml

xml 中增加一个 textView 用来展示信息;

ini 复制代码
<TextView
    android:id="@+id/textView"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@{vm.loginSessionInfo}"
    android:textColor="#E91E63"
    android:layout_marginStart="12dp"
    android:layout_gravity="center"
    />

MainFragment

然后,在 MainFragment 中监听 SharedViewModel 中的 session 的变化;

kotlin 复制代码
sharedViewModel.session.observe(viewLifecycleOwner, {
    Log.d("Mars", "登录Session的变化,只要发生改变,就需要更新到界面UI中")
    // 更新UI,面向 ViewModel 实例修改,UI就变了
    mainViewModel?.loginSessionInfo?.set(
        if (it.isLogin) "登录成功,欢迎${it.loginRegisterResponse ?.username}来到此系统" 
        else "你未登录")
})

好了,登录注册就写到这里吧~

欢迎三连


来都来了,点个关注,点个赞吧,你的支持是我最大的动力~

相关推荐
QING6189 小时前
Kotlin协程:Job.cancel() 和 Scope.cancel() 的区别详解!!!
android·kotlin·android jetpack
alexhilton1 天前
Jetpack ViewModel内幕:内部机制与跨平台设计
android·kotlin·android jetpack
QING6181 天前
Kotlin Flow 的 emit 和 tryEmit 有什么区别 ?
android·kotlin·android jetpack
勤劳打代码1 天前
水到渠成 —— 从项目出发的 Claude SKILL 实践
ai编程·claude·android jetpack
shenshizhong4 天前
Compose + Mvi 架构的玩android 项目,请尝鲜
android·架构·android jetpack
alexhilton8 天前
学会在Jetpack Compose中加载Lottie动画资源
android·kotlin·android jetpack
ljt272496066111 天前
Compose笔记(六十一)--SelectionContainer
android·笔记·android jetpack
QING61811 天前
Jetpack Compose 中的 ViewModel 作用域管理 —— 新手指南
android·kotlin·android jetpack
惟恋惜12 天前
Jetpack Compose 的状态使用之“界面状态”
android·android jetpack