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.