Skip to content

Commit 7270cff

Browse files
committed
[1.7.0-feature] Compose State状态
1 parent d2edaea commit 7270cff

File tree

8 files changed

+249
-27
lines changed

8 files changed

+249
-27
lines changed

app/build.gradle

+11-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
apply plugin: 'com.android.application'
22
apply plugin: 'kotlin-android'
33
apply plugin: 'kotlin-kapt'
4+
apply plugin: 'kotlin-parcelize'
45
//apply plugin: 'com.performance.optimize'
56

67
//获取时间戳
@@ -144,15 +145,24 @@ dependencies {
144145
* Compose 相关配置
145146
*/
146147
implementation 'androidx.activity:activity-compose:1.3.1'
148+
//Compose Runtime: Compose 编程模型和状态管理的基本构建块,以及 Compose 编译器插件的目标核心运行时。
149+
//https://developer.android.com/jetpack/androidx/releases/compose-runtime?hl=zh-cn
147150
implementation "androidx.compose.runtime:runtime:$compose_version"
151+
implementation "androidx.compose.runtime:runtime-livedata:$compose_version"
152+
//Compose中使用ViewModel
153+
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.6.0-alpha01"
154+
//StateFlow<T>.collectAsStateWithLifecycle()
155+
implementation "androidx.lifecycle:lifecycle-runtime-compose:2.6.0-alpha01"
156+
148157
implementation "androidx.compose.ui:ui:$compose_version"
149158
implementation "androidx.compose.foundation:foundation:$compose_version"
150159
implementation "androidx.compose.foundation:foundation-layout:$compose_version"
151160
implementation "androidx.compose.material:material:$compose_version"
152-
implementation "androidx.compose.runtime:runtime-livedata:$compose_version"
161+
153162
implementation "androidx.compose.ui:ui-tooling:$compose_version"
154163
implementation "com.google.android.material:compose-theme-adapter:$compose_version"
155164

165+
156166
// Import the Compose BOM
157167
// implementation platform('androidx.compose:compose-bom:2022.10.00')
158168
// // Import other Compose libraries without version numbers
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
package org.ninetripods.mq.study.jetpack.compose
2+
3+
import android.content.res.Resources
4+
import android.graphics.BitmapShader
5+
import android.graphics.Shader
6+
import android.os.Parcelable
7+
import androidx.activity.compose.setContent
8+
import androidx.activity.viewModels
9+
import androidx.annotation.DrawableRes
10+
import androidx.compose.foundation.background
11+
import androidx.compose.foundation.layout.Column
12+
import androidx.compose.foundation.layout.fillMaxWidth
13+
import androidx.compose.material.Button
14+
import androidx.compose.material.Text
15+
import androidx.compose.runtime.Composable
16+
import androidx.compose.runtime.getValue
17+
import androidx.compose.runtime.livedata.observeAsState
18+
import androidx.compose.runtime.mutableStateOf
19+
import androidx.compose.runtime.produceState
20+
import androidx.compose.runtime.remember
21+
import androidx.compose.runtime.saveable.listSaver
22+
import androidx.compose.runtime.saveable.mapSaver
23+
import androidx.compose.runtime.saveable.rememberSaveable
24+
import androidx.compose.runtime.setValue
25+
import androidx.compose.ui.Modifier
26+
import androidx.compose.ui.graphics.Color
27+
import androidx.compose.ui.graphics.ImageBitmap
28+
import androidx.compose.ui.graphics.ShaderBrush
29+
import androidx.compose.ui.graphics.asAndroidBitmap
30+
import androidx.compose.ui.platform.LocalContext
31+
import androidx.compose.ui.res.imageResource
32+
import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi
33+
import androidx.lifecycle.compose.collectAsStateWithLifecycle
34+
import kotlinx.coroutines.flow.map
35+
import kotlinx.parcelize.Parcelize
36+
import org.ninetripods.mq.study.R
37+
import org.ninetripods.mq.study.jetpack.mvvm.base.BaseMvvmActivity
38+
import org.ninetripods.mq.study.jetpack.mvvm.base.BaseViewModel
39+
import org.ninetripods.mq.study.jetpack.mvvm.model.WanModel
40+
import org.ninetripods.mq.study.kotlin.ktx.log
41+
42+
/**
43+
* Created by mq on 2023/10/23
44+
*/
45+
class ComposeExampleActivity : BaseMvvmActivity() {
46+
47+
private val mVModel: ComposeVModel by viewModels()
48+
49+
override fun getViewModel(): BaseViewModel = mVModel
50+
51+
override fun init() {}
52+
53+
override fun setContentView() {
54+
setContent { GreetingScreen() }
55+
}
56+
57+
@Composable
58+
fun GreetingScreen() {
59+
var resId = remember { mutableStateOf(R.drawable.icon_flower) }
60+
log("resId: ${resId.value}")
61+
62+
Greeting(resId = resId.value,
63+
onClickAction = {
64+
resId.value = R.drawable.icon_qrcode_we_chat
65+
mVModel.getWanInfo()
66+
})
67+
}
68+
69+
@OptIn(ExperimentalLifecycleComposeApi::class)
70+
@Composable
71+
fun Greeting(
72+
res: Resources = LocalContext.current.resources,
73+
@DrawableRes resId: Int,
74+
onClickAction: () -> Unit,
75+
) {
76+
//1、remember的三种用法 ,remember会存储值,直到退出组合。
77+
val mutableState = remember { mutableStateOf("init0") } //返回MutableState<T>类型
78+
var value1 by remember { mutableStateOf("init1") } //返回T类型
79+
val (value2, setValue) = remember { mutableStateOf("init") } //返回两个值分别为:T,Function1<T, kotlin.Unit>
80+
81+
//2、remember还可以接受key参数,当key发生变化,缓存值会失效并再次对 lambda 块进行计算。这种机制可控制组合中对象的生命周期。在key发生变化之前(而不是在记住的值退出组合之前),计算会一直有效。
82+
//示例:下面这个代码段会创建一个 ShaderBrush 并将其用作 Box 可组合项的背景绘制。remember 则会存储 ShaderBrush 实例,因为其重建成本高昂(如前所述)。此外,remember 也会接受 avatarRes 作为 key1 参数,即选定的背景图片。如果 avatarRes 发生变化,笔刷会根据新图片进行重组,然后重新应用于 Box。当用户从选择器中选择另一张图片作为背景时,可能就会发生这种情况。
83+
val bitmap = remember(key1 = resId) {
84+
ShaderBrush(
85+
BitmapShader(
86+
ImageBitmap.imageResource(res, resId).asAndroidBitmap(),
87+
Shader.TileMode.REPEAT,
88+
Shader.TileMode.REPEAT
89+
)
90+
)
91+
}
92+
93+
// 1、如果涉及到配置更改后的状态恢复,直接使用rememberSaveable,会将值存储到Bundle中
94+
// 2、如果存储的值不支持Bundle,可以将Model声明为@Parcelable 或者使用MapSaver、ListSaver自定义存储规则
95+
var parcelCity by rememberSaveable {
96+
mutableStateOf(CityParcel("Beijing", "China"))
97+
}
98+
99+
var mapSaverCity by rememberSaveable(stateSaver = CityMapSaver) {
100+
mutableStateOf(City("Beijing", "China"))
101+
}
102+
103+
var listSaverCity by rememberSaveable(stateSaver = CityListSaver) {
104+
mutableStateOf(City("Beijing", "China"))
105+
}
106+
log("parcelCity: $parcelCity")
107+
log("mapSaverCity: $mapSaverCity")
108+
log("listSaverCity: $listSaverCity")
109+
110+
//Flow转State
111+
val state by mVModel.mWanFlow.collectAsStateWithLifecycle()
112+
//LiveData转State
113+
val liveDataState by mVModel.mWanLiveData.observeAsState()
114+
log("state:${state}")
115+
116+
val uiState by produceState<WanUiState>(WanUiState.Loading, mVModel) {
117+
//在协程中
118+
mVModel.mWanFlow
119+
.map { WanUiState.SUC(it) }
120+
.collect { value = it }
121+
}
122+
when (val finalUiState = uiState) {
123+
is WanUiState.Loading -> Text("Loading...")
124+
is WanUiState.SUC -> Column {
125+
for (model in finalUiState.models) {
126+
Text("Hello, ${model.desc}")
127+
}
128+
}
129+
}
130+
131+
Column(modifier = Modifier.background(bitmap)) {
132+
Text(
133+
text = "$state",
134+
color = Color.Red,
135+
modifier = Modifier.fillMaxWidth()
136+
)
137+
Text(
138+
text = "$liveDataState",
139+
color = Color.Red,
140+
modifier = Modifier.fillMaxWidth()
141+
)
142+
Button(onClick = onClickAction) {
143+
Text(text = "点击更改文本")
144+
}
145+
}
146+
}
147+
}
148+
149+
sealed class WanUiState {
150+
object Loading : WanUiState()
151+
data class SUC(val models: List<WanModel>) : WanUiState()
152+
}
153+
154+
@Parcelize
155+
data class CityParcel(val name: String, val country: String) : Parcelable
156+
157+
data class City(val name: String, val country: String)
158+
159+
//MapSaver自定义存储规则,将对象转换为系统可保存到 Bundle 的一组值。
160+
val CityMapSaver = run {
161+
val nameKey = "Beijing"
162+
val countryKey = "China"
163+
mapSaver(
164+
save = { mapOf(nameKey to it.name, countryKey to it.country) },
165+
restore = { City(it[nameKey] as String, it[countryKey] as String) }
166+
)
167+
}
168+
169+
//ListSaver
170+
val CityListSaver = listSaver<City, Any>(
171+
save = { listOf(it.name, it.country) },
172+
restore = { City(it[0] as String, it[1] as String) }
173+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package org.ninetripods.mq.study.jetpack.compose
2+
3+
import androidx.lifecycle.MutableLiveData
4+
import kotlinx.coroutines.channels.Channel
5+
import kotlinx.coroutines.flow.MutableStateFlow
6+
import kotlinx.coroutines.flow.StateFlow
7+
import kotlinx.coroutines.flow.receiveAsFlow
8+
import org.ninetripods.mq.study.jetpack.mvvm.base.BaseViewModel
9+
import org.ninetripods.mq.study.jetpack.mvvm.model.WanModel
10+
import org.ninetripods.mq.study.jetpack.mvvm.repo.WanRepository
11+
12+
class ComposeVModel : BaseViewModel(){
13+
//StateFlow UI层通过该引用观察数据变化
14+
private val _wanFlow = MutableStateFlow<List<WanModel>>(ArrayList())
15+
val mWanFlow: StateFlow<List<WanModel>> = _wanFlow
16+
17+
/**
18+
* 使用场景:一次性消费场景,比如弹窗,需求是在UI层只弹一次,即使App切到后台再切回来,也不会重复订阅(不会多次弹窗);
19+
* 如果使用SharedFlow/StateFlow,UI层使用的lifecycle.repeatOnLifecycle、Flow.flowWithLifecycle,则在App切换前后台时,UI层会重复订阅
20+
* Channel使用特点:
21+
* 1、每个消息只有一个订阅者可以收到,用于一对一的通信
22+
* 2、第一个订阅者可以收到collect之前的事件,即粘性事件
23+
*/
24+
private val _channel = Channel<List<WanModel>>()
25+
val channelFlow = _channel.receiveAsFlow()
26+
27+
//LiveData UI层通过该引用观察数据变化
28+
val mWanLiveData = MutableLiveData<List<WanModel>>()
29+
30+
//Repository中间层 管理所有数据来源 包括本地的及网络的
31+
private val mWanRepo = WanRepository()
32+
33+
/**
34+
* Flow方式
35+
*/
36+
fun getWanInfoByFlow(wanId: String = "") = requestDataWithFlow(modelFlow = _wanFlow) {
37+
mWanRepo.requestWanData(wanId)
38+
}
39+
40+
/**
41+
* Channel方式 一对一
42+
*/
43+
fun getWanInfoByChannel(wanId: String = "") = requestDataWithSingleFlow(channel = _channel) {
44+
mWanRepo.requestWanData(wanId)
45+
}
46+
47+
/**
48+
* LiveData方式
49+
*/
50+
fun getWanInfo(wanId: String = "") {
51+
launchRequest(liveData = mWanLiveData) { mWanRepo.requestWanData(wanId) }
52+
}
53+
}
54+
55+

app/src/main/java/org/ninetripods/mq/study/jetpack/datastore/BookRepo.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,14 @@ import kotlinx.coroutines.flow.MutableStateFlow
1010
import kotlinx.coroutines.flow.catch
1111
import kotlinx.coroutines.flow.map
1212
import org.ninetripods.mq.study.BookProto
13+
import org.ninetripods.mq.study.MyApplication
1314
import org.ninetripods.mq.study.jetpack.datastore.ktx.*
1415
import org.ninetripods.mq.study.jetpack.datastore.preferences.PreferenceKeys
1516
import org.ninetripods.mq.study.jetpack.datastore.sharedPreferences.BookModel
1617
import org.ninetripods.mq.study.jetpack.datastore.sharedPreferences.Type
1718
import java.io.IOException
1819

19-
class BookRepo private constructor(val context: Context) {
20+
class BookRepo(val context: Context = MyApplication.getApplication()) {
2021

2122
//获取SharedPreference
2223
private val mBookSp =

app/src/main/java/org/ninetripods/mq/study/jetpack/datastore/BookViewModel.kt

+1-11
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
package org.ninetripods.mq.study.jetpack.datastore
22

33
import androidx.lifecycle.ViewModel
4-
import androidx.lifecycle.ViewModelProvider
54
import androidx.lifecycle.viewModelScope
65
import kotlinx.coroutines.launch
76
import org.ninetripods.mq.study.BookProto
87
import org.ninetripods.mq.study.jetpack.datastore.sharedPreferences.BookModel
98

10-
class BookViewModel(private val bookRepo: BookRepo) : ViewModel() {
9+
class BookViewModel(private val bookRepo: BookRepo = BookRepo()) : ViewModel() {
1110

1211
val bookSpFlow = bookRepo.bookFlow
1312

@@ -46,13 +45,4 @@ class BookViewModel(private val bookRepo: BookRepo) : ViewModel() {
4645
*/
4746
val bookPtFlow = bookRepo.bookProtoFlow
4847

49-
}
50-
51-
class BookRepoFactory(private val bookRepo: BookRepo) : ViewModelProvider.Factory {
52-
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
53-
if (modelClass.isAssignableFrom(BookViewModel::class.java)) {
54-
return BookViewModel(bookRepo) as T
55-
}
56-
throw IllegalArgumentException("Unknown ViewModel class")
57-
}
5848
}

app/src/main/java/org/ninetripods/mq/study/jetpack/datastore/DataStoreActivity.kt

+2-9
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,9 @@ package org.ninetripods.mq.study.jetpack.datastore
55
*/
66
import android.widget.Button
77
import android.widget.TextView
8+
import androidx.activity.viewModels
89
import androidx.appcompat.widget.Toolbar
9-
import androidx.lifecycle.ViewModelProvider
1010
import androidx.lifecycle.lifecycleScope
11-
import kotlinx.coroutines.flow.collect
1211
import kotlinx.coroutines.launch
1312
import org.ninetripods.mq.study.BaseActivity
1413
import org.ninetripods.mq.study.BookProto
@@ -34,20 +33,14 @@ class DataStoreActivity : BaseActivity() {
3433
private val mBtnGetPt: Button by id(R.id.btn_pt_get)
3534
private val mTvContentPt: TextView by id(R.id.tv_pt_content)
3635

37-
private lateinit var mBookViewModel: BookViewModel
36+
private val mBookViewModel: BookViewModel by viewModels()
3837

3938
override fun setContentView() {
4039
setContentView(R.layout.activity_data_store)
4140
}
4241

4342
override fun initViews() {
4443
initToolBar(mToolBar, "Jetpack DataStore", true, true, TYPE_BLOG)
45-
46-
//创建ViewModel
47-
mBookViewModel = ViewModelProvider(
48-
this,
49-
BookRepoFactory(BookRepo.getInstance(this))
50-
).get(BookViewModel::class.java)
5144
}
5245

5346
override fun initEvents() {

app/src/main/java/org/ninetripods/mq/study/util/webview/Html5Webview.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,10 @@ private void initWebSettings() {
7373
//开启 DOM storage API 功能 较大存储空间,使用简单
7474
settings.setDomStorageEnabled(true);
7575
//开启 Application Caches 功能 方便构建离线APP 不推荐使用
76-
settings.setAppCacheEnabled(true);
77-
final String cachePath = context.getApplicationContext().getDir("cache", Context.MODE_PRIVATE).getPath();
78-
settings.setAppCachePath(cachePath);
79-
settings.setAppCacheMaxSize(5 * 1024 * 1024);
76+
// settings.setAppCacheEnabled(true);
77+
// final String cachePath = context.getApplicationContext().getDir("cache", Context.MODE_PRIVATE).getPath();
78+
// settings.setAppCachePath(cachePath);
79+
// settings.setAppCacheMaxSize(5 * 1024 * 1024);
8080
//设置数据库缓存路径 存储管理复杂数据 方便对数据进行增加、删除、修改、查询 不推荐使用
8181
settings.setDatabaseEnabled(true);
8282
final String dbPath = context.getApplicationContext().getDir("db", Context.MODE_PRIVATE).getPath();

buildSrc/src/main/java/Deps.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import LibsVersion.Companion.retrofit_version
2121
*/
2222
object App {
2323
const val targetSdkVersion = 31
24-
const val compileSdkVersion = 31
24+
const val compileSdkVersion = 32
2525
const val minSdkVersion = 21
2626
const val appId = "org.ninetripods.mq.study"
2727
const val versionCode = 20220930

0 commit comments

Comments
 (0)