Mark
10/22/2020, 2:21 AMFlow
collection, `RecyclerView`s and MVVM. For a simple list to be displayed in a RecyclerView
, the ViewModel
exposes a Flow
of List<Item>
which is collected in the Fragment
which in turn sets the items on a `RecyclerView`’s adapter (and calls notifyDataSetChanged()
). I believe this is standard procedure.
However, suppose I want to ‘decorate’ certain items in List<Item>
(think of making a specific DB call for an item ID to obtain a Flow
specific to that item).
Where to collect such Flows?
1. ViewModel
- hide this complexity from the fragment/adapter, so that List<DecoratedItem>
contains an immutable snapshot of the data with the ‘decoration’ already applied
2. Fragment
- Item
could expose a ‘decoration’ Flow
which is collected in the Fragment
. With this level of granularity, the fragment would be better able to call the relevant notifyItemChanged()
function
3. RecyclerView.Adapter
- when binding the item we can start collecting the ‘decoration’ flow for that item. This fits well, because there is no point collecting that when the item is not bound.
My notes:
(1) Seems like the more correct, since it keeps non-UI logic out of the fragment/adapter, but then we have a potentially long list with some update caused by an item that’s long been scrolled past, and applying DiffUtil
etc
(2) I’m not particularly convinced by this approach, but I’ve included it because it’s where collection already occurs
(3) Seems neat because we are only collecting items that are currently bound
Maybe it depends on how active these ‘decoration’ flows are? Low activity would mean the concerns of (1) are not such a big deal.
Any thoughts, please?gildor
10/22/2020, 2:25 AMflow.map { it: List<T> ->
it.map(::MyItemViewModel)
}
.collect { it: List<MyItemViewModel> ->
DiffUtil.calculateDiff(MyDiffCallback(it))
}
Mark
10/22/2020, 2:29 AMgildor
10/22/2020, 2:49 AMsuspend fun MyItemViewModel(item: T) = withContext(Default) { SomeHeavyComputation }
gildor
10/22/2020, 2:50 AMgildor
10/22/2020, 2:51 AMval vmCache = mutableMapOf<T, MyItemViewModel>()
.map { it: List<T> ->
it.map(::MyItemViewModel)
}
vmCache.getOrPut { MyItemViewModel(item }
Mark
10/22/2020, 2:51 AMgildor
10/22/2020, 2:52 AMgildor
10/22/2020, 2:53 AMMark
10/22/2020, 2:53 AMgildor
10/22/2020, 2:54 AMitemId IN (item1, item2, item3, …)
gildor
10/22/2020, 2:56 AMMark
10/22/2020, 2:56 AMFlow<Item>
-> Flow<List<Item>>
(A, B, C) -> ([], [A], [A, B], [A, B, C]) so items are only gradually discovered, which means you would have to do that potentially expensive IN
query each time the Flow<List<Item>>
emits.gildor
10/22/2020, 3:01 AMgildor
10/22/2020, 3:02 AMMark
10/22/2020, 3:03 AMMark
10/22/2020, 4:11 AMflatMapLatest
for the decoration flow. When binding an item to the viewholder, we just set the view holder’s flow to use that item’s decoration flow. We could use MutableStateFlow (set up in ViewModel) to cache the latest decoration of each decoration flow.Mark
10/22/2020, 4:21 AMgildor
10/22/2020, 4:24 AMWe therefore need to launch a new coroutine for each view holderif it a part of viewmodel which represents this item, I think it’s fine
Mark
10/22/2020, 5:03 AMgildor
10/22/2020, 5:29 AMMark
10/22/2020, 5:34 AMgildor
10/22/2020, 6:47 AMI can’t see why I should provide a scopeBecause flows are cold, you can have only one subscriber, this operators creates StateFlow, which starts collecting original flow on provided state and use StateFlow to store last emmited valud, without scope it would violate strucutred concurrency
gildor
10/22/2020, 6:47 AMMark
10/22/2020, 6:48 AMgildor
10/22/2020, 7:01 AMgildor
10/22/2020, 7:02 AMMark
10/22/2020, 8:09 AMgildor
10/22/2020, 8:12 AMouter flow will cache the latest valuesExactly, it will be cached, but to get this cache, it should be saved somewhere, so next subscriber could collect it too, remember, that inner flow may never completem, so you will have situation when outer flow keeps inner flow open forever, and there is no way to cancel it, but if you pass scope, you can cancel scope
Mark
10/22/2020, 8:34 AM