Skip to content

Commit f9f26cb

Browse files
authored
Add snippets for new Compose State DAC guides (#741)
* Add snippets for new Compose State DAC guides Snippets for both "State Lifespans" and "State Callbacks" DAC guides * Remove not recommended RememberObserver snippets * Fix mismatched tag * Add missing copyright headers
1 parent aea6b59 commit f9f26cb

File tree

2 files changed

+301
-0
lines changed

2 files changed

+301
-0
lines changed
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
/*
2+
* Copyright 2025 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.example.compose.snippets.state
18+
19+
import androidx.compose.runtime.Composable
20+
import androidx.compose.runtime.RememberObserver
21+
import androidx.compose.runtime.remember
22+
import kotlinx.coroutines.CoroutineScope
23+
import kotlinx.coroutines.Dispatchers
24+
import kotlinx.coroutines.Job
25+
import kotlinx.coroutines.launch
26+
27+
private object RememberAndRetainObserverSnippets1 {
28+
interface RememberObserver : androidx.compose.runtime.RememberObserver {
29+
override fun onAbandoned() {}
30+
override fun onForgotten() {}
31+
override fun onRemembered() {}
32+
}
33+
34+
// [START android_compose_state_observers_initialize_in_remember]
35+
class MyComposeObject : RememberObserver {
36+
private val job = Job()
37+
private val coroutineScope = CoroutineScope(Dispatchers.Main + job)
38+
39+
init {
40+
// Not recommended: This will cause work to begin during composition instead of
41+
// with other effects. Move this into onRemembered().
42+
coroutineScope.launch { loadData() }
43+
}
44+
45+
override fun onRemembered() {
46+
// Recommended: Move any cancellable or effect-driven work into the onRemembered
47+
// callback. If implementing RetainObserver, this should go in onRetained.
48+
coroutineScope.launch { loadData() }
49+
}
50+
51+
private suspend fun loadData() { /* ... */ }
52+
53+
// ...
54+
}
55+
// [END android_compose_state_observers_initialize_in_remember]
56+
}
57+
58+
private object RememberAndRetainObserverSnippets2 {
59+
interface RememberObserver : androidx.compose.runtime.RememberObserver {
60+
override fun onAbandoned() {}
61+
override fun onForgotten() {}
62+
override fun onRemembered() {}
63+
}
64+
65+
// [START android_compose_state_observers_teardown_in_forget]
66+
class MyComposeObject : RememberObserver {
67+
private val job = Job()
68+
private val coroutineScope = CoroutineScope(Dispatchers.Main + job)
69+
70+
// ...
71+
72+
override fun onForgotten() {
73+
// Cancel work launched from onRemembered. If implementing RetainObserver, onRetired
74+
// should cancel work launched from onRetained.
75+
job.cancel()
76+
}
77+
78+
override fun onAbandoned() {
79+
// If any work was launched by the constructor as part of remembering the object,
80+
// you must cancel that work in this callback. For work done as part of the construction
81+
// during retain, this code should will appear in onUnused.
82+
job.cancel()
83+
}
84+
}
85+
// [END android_compose_state_observers_teardown_in_forget]
86+
}
87+
88+
private object RememberAndRetainObserverSnippets3 {
89+
interface RememberObserver : androidx.compose.runtime.RememberObserver {
90+
override fun onAbandoned() {}
91+
override fun onForgotten() {}
92+
override fun onRemembered() {}
93+
}
94+
95+
// [START android_compose_state_observers_private_implementation]
96+
abstract class MyManager
97+
98+
class MyComposeManager : MyManager() {
99+
// Callers that construct this object must manually call initialize and teardown
100+
fun initialize() { /*...*/ }
101+
fun teardown() { /*...*/ }
102+
}
103+
104+
@Composable
105+
fun rememberMyManager(): MyManager {
106+
// Protect the RememberObserver implementation by never exposing it outside the library
107+
return remember {
108+
object : RememberObserver {
109+
val manager = MyComposeManager()
110+
override fun onRemembered() = manager.initialize()
111+
override fun onForgotten() = manager.teardown()
112+
override fun onAbandoned() { /* Nothing to do if manager hasn't initialized */ }
113+
}
114+
}.manager
115+
}
116+
// [END android_compose_state_observers_private_implementation]
117+
}
118+
119+
private object RememberAndRetainObserverSnippets4 {
120+
class Foo : RememberObserver {
121+
override fun onRemembered() {}
122+
override fun onForgotten() {}
123+
override fun onAbandoned() {}
124+
}
125+
126+
class Bar(val foo: Foo)
127+
128+
@Composable fun rememberFoo() = remember { Foo() }
129+
130+
@Composable
131+
fun Snippet() {
132+
// [START android_compose_state_observers_transitive_remember]
133+
val foo: Foo = rememberFoo()
134+
135+
// Acceptable:
136+
val bar: Bar = remember { Bar(foo) }
137+
138+
// Recommended key usage:
139+
val barWithKey: Bar = remember(foo) { Bar(foo) }
140+
// [END android_compose_state_observers_transitive_remember]
141+
}
142+
}
143+
144+
private object RememberAndRetainObserverSnippets5 {
145+
class Foo : RememberObserver {
146+
override fun onRemembered() {}
147+
override fun onForgotten() {}
148+
override fun onAbandoned() {}
149+
}
150+
151+
class Bar(val foo: Foo)
152+
153+
@Composable fun rememberFoo() = remember { Foo() }
154+
155+
// [START android_compose_state_observers_transitive_remember_params]
156+
@Composable
157+
fun MyComposable(
158+
parameter: Foo
159+
) {
160+
// Acceptable:
161+
val derivedValue = remember { Bar(parameter) }
162+
163+
// Also Acceptable:
164+
val derivedValueWithKey = remember(parameter) { Bar(parameter) }
165+
}
166+
// [END android_compose_state_observers_transitive_remember_params]
167+
}
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
/*
2+
* Copyright 2025 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.example.compose.snippets.state
18+
19+
import androidx.compose.runtime.Composable
20+
import androidx.compose.runtime.getValue
21+
import androidx.compose.runtime.mutableStateOf
22+
import androidx.compose.runtime.remember
23+
import androidx.compose.runtime.retain.retain
24+
import androidx.compose.runtime.saveable.SaverScope
25+
import androidx.compose.runtime.saveable.listSaver
26+
import androidx.compose.runtime.saveable.rememberSaveable
27+
import androidx.compose.runtime.saveable.rememberSerializable
28+
import androidx.compose.ui.platform.LocalContext
29+
import androidx.media3.exoplayer.ExoPlayer
30+
import kotlinx.serialization.Serializable
31+
import kotlinx.serialization.serializer
32+
33+
private object StateLifespansSnippets1 {
34+
// [START android_compose_state_lifespans_saver]
35+
data class Size(val x: Int, val y: Int) {
36+
object Saver : androidx.compose.runtime.saveable.Saver<Size, Any> by listSaver(
37+
save = { listOf(it.x, it.y) },
38+
restore = { Size(it[0], it[1]) }
39+
)
40+
}
41+
42+
@Composable
43+
fun rememberSize(x: Int, y: Int) {
44+
rememberSaveable(x, y, saver = Size.Saver) {
45+
Size(x, y)
46+
}
47+
}
48+
// [END android_compose_state_lifespans_saver]
49+
}
50+
51+
private object StateLifespansSnippets2 {
52+
// [START android_compose_state_lifespans_retain_invocation]
53+
@Composable
54+
fun MediaPlayer() {
55+
// Use the application context to avoid a memory leak
56+
val applicationContext = LocalContext.current.applicationContext
57+
val exoPlayer = retain { ExoPlayer.Builder(applicationContext).apply { /* ... */ }.build() }
58+
// ...
59+
}
60+
// [END android_compose_state_lifespans_retain_invocation]
61+
}
62+
63+
private object StateLifespansSnippets3 {
64+
65+
@Serializable
66+
object AnotherSerializableType
67+
68+
private fun <T> defaultValue(): T = TODO()
69+
70+
// [START android_compose_state_lifespans_combined_retain_save]
71+
@Composable
72+
fun rememberAndRetain(): CombinedRememberRetained {
73+
val saveData = rememberSerializable(serializer = serializer<ExtractedSaveData>()) {
74+
ExtractedSaveData()
75+
}
76+
val retainData = retain { ExtractedRetainData() }
77+
return remember(saveData, retainData) {
78+
CombinedRememberRetained(saveData, retainData)
79+
}
80+
}
81+
82+
@Serializable
83+
data class ExtractedSaveData(
84+
// All values that should persist process death should be managed by this class.
85+
var savedData: AnotherSerializableType = defaultValue()
86+
)
87+
88+
class ExtractedRetainData {
89+
// All values that should be retained should appear in this class.
90+
// It's possible to manage a CoroutineScope using RetainObserver.
91+
// See the full sample for details.
92+
var retainedData = Any()
93+
}
94+
95+
class CombinedRememberRetained(
96+
private val saveData: ExtractedSaveData,
97+
private val retainData: ExtractedRetainData,
98+
) {
99+
fun doAction() {
100+
// Manipulate the retained and saved state as needed.
101+
}
102+
}
103+
// [END android_compose_state_lifespans_combined_retain_save]
104+
}
105+
106+
private object StateLifespansSnippets4 {
107+
// [START android_compose_state_lifespans_remember_factory_functions]
108+
@Composable
109+
fun rememberImageState(
110+
imageUri: String,
111+
initialZoom: Float = 1f,
112+
initialPanX: Int = 0,
113+
initialPanY: Int = 0
114+
): ImageState {
115+
return rememberSaveable(imageUri, saver = ImageState.Saver) {
116+
ImageState(
117+
imageUri, initialZoom, initialPanX, initialPanY
118+
)
119+
}
120+
}
121+
122+
data class ImageState(
123+
val imageUri: String,
124+
val zoom: Float,
125+
val panX: Int,
126+
val panY: Int
127+
) {
128+
object Saver : androidx.compose.runtime.saveable.Saver<ImageState, Any> by listSaver(
129+
save = { listOf(it.imageUri, it.zoom, it.panX, it.panY) },
130+
restore = { ImageState(it[0] as String, it[1] as Float, it[2] as Int, it[3] as Int) }
131+
)
132+
}
133+
// [END android_compose_state_lifespans_remember_factory_functions]
134+
}

0 commit comments

Comments
 (0)