1 /*
<lambda>null2 * Copyright 2020 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 * http://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 androidx.lifecycle.viewmodel.compose.samples
18
19 import androidx.annotation.Sampled
20 import androidx.compose.runtime.Composable
21 import androidx.compose.runtime.getValue
22 import androidx.compose.runtime.mutableStateListOf
23 import androidx.compose.runtime.mutableStateMapOf
24 import androidx.compose.runtime.mutableStateOf
25 import androidx.compose.runtime.remember
26 import androidx.compose.runtime.saveable.listSaver
27 import androidx.compose.runtime.setValue
28 import androidx.compose.runtime.toMutableStateList
29 import androidx.compose.runtime.toMutableStateMap
30 import androidx.core.os.bundleOf
31 import androidx.lifecycle.DEFAULT_ARGS_KEY
32 import androidx.lifecycle.HasDefaultViewModelProviderFactory
33 import androidx.lifecycle.SavedStateHandle
34 import androidx.lifecycle.ViewModel
35 import androidx.lifecycle.ViewModelProvider
36 import androidx.lifecycle.createSavedStateHandle
37 import androidx.lifecycle.viewmodel.CreationExtras
38 import androidx.lifecycle.viewmodel.MutableCreationExtras
39 import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
40 import androidx.lifecycle.viewmodel.compose.SavedStateHandleSaveableApi
41 import androidx.lifecycle.viewmodel.compose.saveable
42 import androidx.lifecycle.viewmodel.compose.viewModel
43 import java.util.UUID
44
45 @Sampled
46 @Composable
47 fun CreationExtrasViewModel() {
48 val owner = LocalViewModelStoreOwner.current
49 val defaultExtras =
50 (owner as? HasDefaultViewModelProviderFactory)?.defaultViewModelCreationExtras
51 ?: CreationExtras.Empty
52 // Custom extras should always be added on top of the default extras
53 val extras = MutableCreationExtras(defaultExtras)
54 extras[DEFAULT_ARGS_KEY] = bundleOf("test" to "my_value")
55 // This factory is normally created separately and passed in
56 val customFactory = remember {
57 object : ViewModelProvider.Factory {
58 override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T {
59 val args = extras[DEFAULT_ARGS_KEY]?.getString("test")
60 @Suppress("UNCHECKED_CAST")
61 // TestViewModel is a basic ViewModel that sets a String variable
62 return TestViewModel(args) as T
63 }
64 }
65 }
66 // Create a ViewModel using the custom factory passing in the custom extras
67 val viewModel = customFactory.create(TestViewModel::class.java, extras)
68 // The value from the extras is now available in the ViewModel
69 viewModel.args
70 }
71
72 @Sampled
73 @Composable
CreationExtrasViewModelInitializernull74 fun CreationExtrasViewModelInitializer() {
75 // Just like any call to viewModel(), the default owner is the LocalViewModelStoreOwner.current.
76 // The lambda is only called the first time the ViewModel needs to be created.
77 val viewModel = viewModel {
78 // Within the lambda, you have direct access to the CreationExtras which allows you to call
79 // extension methods on CreationExtras such as createSavedStateHandle()
80 val handle = createSavedStateHandle()
81 // You can send any custom parameter, repository, etc. to your ViewModel.
82 SavedStateViewModel(handle, "custom_value")
83 }
84 // The handle and parameter are now available from the ViewModel
85 viewModel.handle
86 viewModel.value
87 }
88
89 class TestViewModel(val args: String?) : ViewModel()
90
91 class SavedStateViewModel(val handle: SavedStateHandle, val value: String) : ViewModel()
92
93 @Sampled
SnapshotStateViewModelnull94 fun SnapshotStateViewModel() {
95
96 /** A simple item that is not inherently [Parcelable] */
97 data class Item(val id: UUID, val value: String)
98
99 @OptIn(SavedStateHandleSaveableApi::class)
100 class SnapshotStateViewModel(handle: SavedStateHandle) : ViewModel() {
101
102 /**
103 * A snapshot-backed [MutableList] of a list of items, persisted by the [SavedStateHandle].
104 * The size of this set must remain small in expectation, since the maximum size of saved
105 * instance state space is limited.
106 */
107 private val items: MutableList<Item> =
108 handle.saveable(
109 key = "items",
110 saver =
111 listSaver(
112 save = { it.map { item -> listOf(item.id.toString(), item.value) } },
113 restore = {
114 it.map { saved ->
115 Item(id = UUID.fromString(saved[0]), value = saved[1])
116 }
117 .toMutableStateList()
118 }
119 )
120 ) {
121 mutableStateListOf()
122 }
123
124 /**
125 * A snapshot-backed [MutableMap] representing a set of selected item ids, persisted by the
126 * [SavedStateHandle]. A [MutableSet] is approximated by ignoring the keys. The size of this
127 * set must remain small in expectation, since the maximum size of saved instance state
128 * space is limited.
129 */
130 private val selectedItemIds: MutableMap<UUID, Unit> =
131 handle.saveable(
132 key = "selectedItemIds",
133 saver =
134 listSaver(
135 save = { it.keys.map(UUID::toString) },
136 restore = {
137 it.map(UUID::fromString).map { id -> id to Unit }.toMutableStateMap()
138 }
139 )
140 ) {
141 mutableStateMapOf()
142 }
143
144 /**
145 * A snapshot-backed flag representing where selections are enabled, persisted by the
146 * [SavedStateHandle].
147 */
148 var areSelectionsEnabled by handle.saveable("areSelectionsEnabled") { mutableStateOf(true) }
149
150 /** A list of items paired with a selection state. */
151 val selectedItems: List<Pair<Item, Boolean>>
152 get() = items.map { it to (it.id in selectedItemIds) }
153
154 /** Updates the selection state for the item with [id] to [selected]. */
155 fun selectItem(id: UUID, selected: Boolean) {
156 if (selected) {
157 selectedItemIds[id] = Unit
158 } else {
159 selectedItemIds.remove(id)
160 }
161 }
162
163 /** Adds an item with the given [value]. */
164 fun addItem(value: String) {
165 items.add(Item(UUID.randomUUID(), value))
166 }
167 }
168 }
169
170 @Sampled
SnapshotStateViewModelWithDelegatesnull171 fun SnapshotStateViewModelWithDelegates() {
172
173 /** A simple item that is not inherently [Parcelable] */
174 data class Item(val id: UUID, val value: String)
175
176 @OptIn(SavedStateHandleSaveableApi::class)
177 class SnapshotStateViewModel(handle: SavedStateHandle) : ViewModel() {
178
179 /**
180 * A snapshot-backed [MutableList] of a list of items, persisted by the [SavedStateHandle].
181 * The size of this set must remain small in expectation, since the maximum size of saved
182 * instance state space is limited.
183 */
184 private val items: MutableList<Item> by
185 handle.saveable(
186 saver =
187 listSaver(
188 save = { it.map { item -> listOf(item.id.toString(), item.value) } },
189 restore = {
190 it.map { saved ->
191 Item(id = UUID.fromString(saved[0]), value = saved[1])
192 }
193 .toMutableStateList()
194 }
195 )
196 ) {
197 mutableStateListOf()
198 }
199
200 /**
201 * A snapshot-backed [MutableMap] representing a set of selected item ids, persisted by the
202 * [SavedStateHandle]. A [MutableSet] is approximated by ignoring the keys. The size of this
203 * set must remain small in expectation, since the maximum size of saved instance state
204 * space is limited.
205 */
206 private val selectedItemIds: MutableMap<UUID, Unit> by
207 handle.saveable(
208 saver =
209 listSaver(
210 save = { it.keys.map(UUID::toString) },
211 restore = {
212 it.map(UUID::fromString).map { id -> id to Unit }.toMutableStateMap()
213 }
214 )
215 ) {
216 mutableStateMapOf()
217 }
218
219 /**
220 * A snapshot-backed flag representing where selections are enabled, persisted by the
221 * [SavedStateHandle].
222 */
223 var areSelectionsEnabled by handle.saveable { mutableStateOf(true) }
224
225 /** A list of items paired with a selection state. */
226 val selectedItems: List<Pair<Item, Boolean>>
227 get() = items.map { it to (it.id in selectedItemIds) }
228
229 /** Updates the selection state for the item with [id] to [selected]. */
230 fun selectItem(id: UUID, selected: Boolean) {
231 if (selected) {
232 selectedItemIds[id] = Unit
233 } else {
234 selectedItemIds.remove(id)
235 }
236 }
237
238 /** Adds an item with the given [value]. */
239 fun addItem(value: String) {
240 items.add(Item(UUID.randomUUID(), value))
241 }
242 }
243 }
244