Ajayi Olamide
04/03/2024, 7:16 AMtracks
state is not collected/displayed in AudioList
?
Please note:
1. MediaSelection.Songs
is the default mediaUiState.currentMediaSelection
, which means AudioList
is launched by default when we navigate to LocalMediaScreen
. Items are not displayed in AudioList
but if we switched ArtistList
tab, items are displayed. If I make mediaUiState.currentMediaSelection
default to MediaSelection.Artists
and ArtistList
initial tab, then artist items are not displayed but AudioList
items are displayed when tab is switched.
2. Both AudioList
and ArtistList
have similar codes in LaunchedEffect
to retrieve their respective items when launched, but items are not displayed in AudioList
although they're successfully displayed in ArtistList
.
3. I can confirm their respective localMediaMediaViewModel.loadMedia()
is correctly called as I successfully log those items before updating their respective StateFlow
values.
4. In AudioList
, I can call collect
on the tracks
StateFlow
of localMediaViewModel
to listen for items added and push them to local state tracks
. This is not a desired implementation (as I want to call loadMedia
just once, based on the condition that the respective items are yet to be loaded). However, this approach successfully works on LocalMediaScreen
launch. Once I switch to a different tab and switch back, tracks
are no longer visible.
This is a strange behaviour but I'm suspecting it's lifecycle/thread related. localMediaRepo.loadMedia
is a Sequence<T>
function that yields each item after construction. I'm not sure this is an issue as I get the same behaviour even if I returned List<T>
.
Kindly help🙏Zach Klippenstein (he/him) [MOD]
04/03/2024, 11:55 AMBox
in the else case? Just don’t emit anything.
2. Artist/AudioList: The keys to your LaunchedEffect
should be the non-snapshot-state dependencies of your effect. In this case, your view models.
3. Artist/AudioList: Your view layer is doing too much. It’s not the responsibility of the view layer to coordinate view models like this. I think some of this is encouraged by the ViewModel architecture, which is partly why it’s so bad. This is terrible for testability as well.
4. loadMedia: Why is this a suspend function if all it does is launch into the viewModelScope
?
5. loadMedia: It is a very bad idea to return a lazy Sequence
from a suspend function, because if the suspend function is executed in a different context from where the sequence is consumed (as is explicitly the case here), most of the sequence’s work will actually be performed in the consuming context. This probably isn’t what you want, since you’re creating the sequence on a different dispatcher.
6. loadMedia: Why are you returning a Sequence
anyway if you’re just consuming the entire thing immediately? Even if you weren’t hopping threads the laziness wouldn’t provide any value here, so you’re just introducing overhead and unnecessary concurrency.
a. A case where laziness might make sense would be if your consumer suspended while consuming, but Sequence
still wouldn’t be the right tool for that job because of (5). You’d likely want a Flow
instead with a flowOn
to keep the producer on the correct dispatcher.
7. loadMedia: Why are you storing `MutableList`s in states? It doesn’t make sense to mutate those lists anyway, so you can just use the read-only type (List
).
8. loadMedia: It’s cut off so I can’t see, but what are you doing with the sequence iterator? I hope that full code isn’t (_thing.value as Success).add(iter.next)
, because that won’t work and would likely explain the behavior you’re seeing. Please read this for an explanation of why.Ajayi Olamide
04/03/2024, 1:32 PMBox
here is just a temporary placeholder. I have several other arms I need to handle for currentSelection
— a number of components I need to display based on the current value of currentSelection
. I just want to ensure I got these two working first.
2. Thanks for this. I'll check the link to read more about "non-snapshot-state dependencies." I wasn't just sure if it's okay pass a ViewModel as dependency to an effect.
3. Could you please suggest how to make the ArtistList/AudioList view layer do less? I would appreciate if you're more specific about this. Is it wrong to pass multiple ViewModel
s to a single layer? Is it something that has to do with the function calls in LaunchedEffect
?
4. The goal is to create a function that runs in the background while updating the UI as data becomes available. localMediaRepo.loadMedia
would read all audio files on the device and construct a list of T
from the result. As this might be a long process for devices with lots of files, I thought suspending this task is (supposed to be) a good idea. What do you think?
5. I was returning List<T>
initially but I think creating a Sequence
that yields T
, once it's available, for the ViewModel to push to StateFlow
would be a good idea. This way, the view layer could collect the data and display in "real-time." What do you think? I'd like to see how I can address the potential issues here.
6. I think I would need to checkout Flow
and flowOn
.
7. I think I explained this in (5). I had a List
but somehow changed to MutableList
. I was just trying to emulate a real-time update.
8. Initially, loadMedia
was returning a List<T>
and I was updating like _thing.value += list
. This wasn't working as well. But like I've said, loadMedia
is just a function for reading audio files from the device and construct a sequence of T
from the result.
Thanks for your help and I hope I'm able to address some of the issues you raised.Zach Klippenstein (he/him) [MOD]
04/04/2024, 3:42 PMZach Klippenstein (he/him) [MOD]
04/04/2024, 3:48 PMfun loadMedia(stuff): Flow<Item> = flow {
// load files, call emit
}.flowOn(<http://Dispatchers.IO|Dispatchers.IO>)
class YourViewModel {
private val _items = mutableStateListOf<Item>()
val items: List<Item> get() = _items
suspend fun run() {
_items.clear()
loadMedia(…).collect {
_items += it
}
}
}
Zach Klippenstein (he/him) [MOD]
04/04/2024, 8:03 PMZach Klippenstein (he/him) [MOD]
04/04/2024, 8:05 PMAjayi Olamide
04/06/2024, 11:34 AMFlow
as you suggested. I think I should emit events from AudioList/ArtistList up the hierarchy, especially for handling states shared globally across the app, instead of passing multiple viewmodels down the tree. I think that should be more reasonable according to your suggestion.