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