如何应对 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

接下来,LoginActivity 的 loginSuccess 方法中,更新 Session 信息;

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

MainViewModel

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

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 "你未登录")
})

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

欢迎三连


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

相关推荐
alexhilton7 小时前
为什么你的 LazyColumn 仍然卡顿
android·kotlin·android jetpack
hai_android12 小时前
Android 嵌套滑动冲突:内部拦截 vs 外部拦截实战
android·android studio·android jetpack
alexhilton4 天前
模块化Jetpack Compose架构
android·kotlin·android jetpack
hai_android4 天前
InputStage 责任链核心执行流程
android·android studio·android jetpack
传奇开心果编程6 天前
【现代声明式UI学与练】第4课 列表渲染与 key——如何高效渲染列表、key 的作用、列表重排时的状态保持
学习·flutter·react native·ui·swiftui·android jetpack
传奇开心果编程6 天前
【现代声明式UI学与练】第7课 组件通信——父传子、子传父、跨层级通信、状态提升与 Context
学习·flutter·react native·ui·swiftui·android jetpack
传奇开心果编程7 天前
【现代声明式UI学与练】第9课 性能优化——渲染优化、列表优化、内存优化、启动优化
学习·flutter·react native·ui·性能优化·swiftui·android jetpack
天空之城--9 天前
Android一周动态:Android 18首次官宣、Compose Material3 1.4转正(5趋势+5资讯)
android·人工智能·flutter·架构·android jetpack
传奇开心果编程10 天前
【现代声明式UI学与练】第1课 从命令式UI到声明式UI
学习·flutter·ui·swiftui·react·android jetpack
传奇开心果编程13 天前
【用案例学Material 3 Expressive】第1课:从一封邮件开始,感受安卓设计新语言的表现力
android·学习·ui·kotlin·android jetpack