本文探讨基于Kotlin语言实现Redux架构,结合Jetpack Compose构建可预测的状态管理。
传统Android开发常面临以下痛点:
Redux是单向数据流和单一数据源原则。
组件 | 职责 | Kotlin实现形式 |
---|---|---|
Store | 全局状态容器 | StateFlow管理对象 |
Action | 状态变更意图描述 | Sealed Class体系 |
Reducer | 纯函数处理状态转换 | 高阶函数 |
Redux 有几个核心概念,需要先了解它们在做什么:
1. Store(仓库)
• 整个应用只有一个Store。Store是一个数据仓库,保存着当前应用的所有状态 (State)。
• 你不能直接修改Store,所有想修改状态的地方,都要通过派发(dispatch)一个Action。
2. State(状态) • 你应用中所需维护的所有数据都放在这里,比如登录状态、用户信息、界面上的各种选项状态等。
• State 只是一个普通的 Kotlin 数据类或多个数据类的组合,里面没有任何业务逻辑,只有数据。
3. Action(动作)
• Reducer 是一个纯函数(输入 + 输出),定义了“当有 Action 派发进来时,如何根据 Action 来生成新的 State”。
• 由于Redux强调“不可变数据”,因此Reducer不会直接修改旧的State,而是创建返回一个新的 State 对象。
UI -> dispatch(Action) -> Reducer -> 新 State -> UI 自动更新
展示如何创建 Store、定义 State、Action、 Reducer,模拟一个登录。
// AppState.kt
data class AppState(
val loginState: LoginState = LoginState()
)
data class LoginState(
val isLoading: Boolean = false,
val isLoggedIn: Boolean = false,
val username: String = "",
val errorMsg: String? = null
)
// LoginAction.kt
sealed class LoginAction {
data class Login(val username: String, val password: String) : LoginAction()
object LoginSuccess : LoginAction()
data class LoginFailure(val errorMsg: String) : LoginAction()
object Logout : LoginAction()
}
reducer 最终会输入当前的状态 AppState 和一个 Action,然后返回新的 AppState。
// AppReducer.kt
fun appReducer(state: AppState, action: Any): AppState {
return state.copy(
loginState = loginReducer(state.loginState, action)
)
}
fun loginReducer(state: LoginState, action: Any): LoginState {
when (action) {
is LoginAction.Login -> {
return state.copy(
isLoading = true,
errorMsg = null,
username = action.username
) }
is LoginAction.LoginSuccess -> {
return state.copy(
isLoading = false,
isLoggedIn = true
)
}
is LoginAction.LoginFailure -> {
return state.copy(
isLoading = false,
errorMsg = action.errorMsg
) }
is LoginAction.Logout -> {
return state.copy(
isLoggedIn = false,
username = "",
errorMsg = null
) }
else -> return state
}
}
class Store(
initialState: AppState,
private val reducer: (AppState, Any) -> AppState
){
private val _state = MutableStateFlow(initialState) val state: StateFlow<AppState> = _state.asStateFlow()
fun dispatch(action: Any) {
val currentState = _state.value
val newState = reducer(currentState, action)
_state.value = newState
}
}
initialState = AppState(),
reducer = ::appReducer
)
class LoginViewModel : ViewModel() {
// Store 通过依赖注入或直接引用全局单例
private val store = Store(
initialState = AppState(),
reducer = ::appReducer
)
// 将状态暴露为 StateFlow 给 UI层
val loginState: StateFlow<LoginState> = store.state.map { it.loginState }
.stateIn(viewModelScope, SharingStarted.Eagerly, store.state.value.loginState)
fun login(username: String, password: String) {
// 先dispatch:告诉redux正在登陆
store.dispatch(LoginAction.Login(username, password))
viewModelScope.launch {
// 模拟网络延迟
delay(2000)
// 简单判断下密码:假设密码是 "123456" 则成功,否则失败
if (password == "123456") {
store.dispatch(LoginAction.LoginSuccess)
} else {
store.dispatch(LoginAction.LoginFailure("密码错误"))
}
}
}
fun logout() {
store.dispatch(LoginAction.Logout)
}
}
五、在 Jetpack Compose(Material 3)中使用
有了LoginViewModel,就能在 Composable 中去订阅 loginState 根据最新状态动态渲染界面描述为
1. 显示两个文本输入框(用户名、密码)
2. 显示一个登录按钮
3. 根据状态显示加载中、错误信息、或登录成功
@Composable
fun LoginScreen(
viewModel: LoginViewModel = viewModel() // 通过DI传入
) {
val loginState by viewModel.loginState.collectAsState()
// 登录表单本地State(也可以放到Redux管理,但通常只放必要的全局数据到Redux)
var username by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
// 注意:登陆后的 username 也可能和本地输入框不一致
// 如果希望登录前和登录成功后都绑定同一个变量,可以只用 Redux State 中的 username。
Scaffold(
topBar = {
TopAppBar(title = { Text("Redux + Nim Login") })
}
) { innerPadding ->
Column(
modifier = Modifier
.padding(innerPadding)
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.Center
) {
// 显示登录状态
Text(text = "Logged In: ${loginState.isLoggedIn}")
OutlinedTextField(
value = username,
onValueChange = { username = it },
label = { Text("用户名") },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(8.dp))
OutlinedTextField(
value = password,
onValueChange = { password = it },
label = { Text("密码") },
modifier = Modifier.fillMaxWidth(),
visualTransformation = PasswordVisualTransformation()
)
Spacer(modifier = Modifier.height(16.dp))
// 显示错误
loginState.errorMsg?.let { error ->
Text(
text = error,
color = Color.Red,
modifier = Modifier.padding(bottom = 8.dp)
)
}
// 显示加载进度
if (loginState.isLoading) {
CircularProgressIndicator()
} else {
// 如果没有在加载,就显示登录按钮或退出按钮
if (!loginState.isLoggedIn) {
Button(
onClick = {
viewModel.login(username, password)
},
modifier = Modifier.fillMaxWidth()
) {
Text("登录")
}
} else {
// 已经登录了
Button(
onClick = {
viewModel.logout()
},
modifier = Modifier.fillMaxWidth()
) {
Text("退出登录")
}
}
}
}
}
}
运行流程梳理
1. 用户输入用户名和密码;
2. 点击“登录”后,会调用 viewModel.login(username, password);
3. viewModel.login() 里先 dispatch(LoginAction.Login(username, password)),Redux State 立刻变更:isLoading = true;UI 立即显示“加载中”状态;
4. delay(2秒) 后,根据密码是否为 "123456" 来决定派发 LoginSuccess 或 LoginFailure("密码错误");
5. UI 收到新 State,再次重新组合:
• 成功则显示“已登录”以及“退出登录”按钮;
• 失败则显示错误信息。
优点:
适用大型项目实施
• 所有状态都在可控范围内,修改 State 的途径统一且可追溯。
• 对复杂场景非常有用,便于日志记录、调试、或者做时间回溯。
缺点:
不适用小型app实施
• Redux 会增加代码量和复杂度。
• Action、Reducer、Store 的模板化代码较多。
祝你学习上手 Redux + Compose 顺利,新年快乐)
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。