Glimpse: Use SharedFlow in ViewModels

Since we collect data using the Lifecycle State as
a trigger using a SharedFlow is (one of) the optimal
way to emit updates.

Change-Id: Ib69b6790e52fa5e05181ebce078c72a5a7314598
This commit is contained in:
Luca Stefani 2023-09-05 11:11:30 +02:00
parent 065618e18b
commit 3addfcb9a2
7 changed files with 76 additions and 48 deletions

View File

@ -44,7 +44,9 @@ import org.lineageos.glimpse.viewmodels.MediaViewModel
*/
class AlbumFragment : Fragment(R.layout.fragment_album) {
// View models
private val mediaViewModel: MediaViewModel by viewModels { MediaViewModel.Factory }
private val mediaViewModel: MediaViewModel by viewModels {
MediaViewModel.factory(lifecycleScope, album.id)
}
// Views
private val albumRecyclerView by getViewProperty<RecyclerView>(R.id.albumRecyclerView)
@ -54,10 +56,9 @@ class AlbumFragment : Fragment(R.layout.fragment_album) {
// Permissions
private val permissionsGatedCallback = PermissionsGatedCallback(this) {
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
mediaViewModel.setBucketId(album.id)
mediaViewModel.mediaForAlbum.collectLatest { data ->
thumbnailAdapter.data = data.toTypedArray()
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
mediaViewModel.media.collectLatest {
thumbnailAdapter.data = it.toTypedArray()
}
}
}

View File

@ -40,7 +40,9 @@ import org.lineageos.glimpse.viewmodels.AlbumsViewModel
*/
class AlbumsFragment : Fragment() {
// View models
private val albumsViewModel: AlbumsViewModel by viewModels { AlbumsViewModel.Factory }
private val albumsViewModel: AlbumsViewModel by viewModels {
AlbumsViewModel.factory(lifecycleScope)
}
// Views
private val albumsRecyclerView by getViewProperty<RecyclerView>(R.id.albumsRecyclerView)
@ -54,7 +56,7 @@ class AlbumsFragment : Fragment() {
// Permissions
private val permissionsGatedCallback = PermissionsGatedCallback(this) {
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
albumsViewModel.albums.collectLatest {
albumThumbnailAdapter.data = it.toTypedArray()
}

View File

@ -56,7 +56,11 @@ import java.text.SimpleDateFormat
*/
class MediaViewerFragment : Fragment(R.layout.fragment_media_viewer) {
// View models
private val mediaViewModel: MediaViewerViewModel by viewModels { MediaViewerViewModel.Factory }
private val mediaViewModel: MediaViewerViewModel by viewModels {
albumId?.let {
MediaViewerViewModel.factory(lifecycleScope, it)
} ?: MediaViewerViewModel.factory(lifecycleScope)
}
// Views
private val adjustButton by getViewProperty<ImageButton>(R.id.adjustButton)
@ -85,9 +89,8 @@ class MediaViewerFragment : Fragment(R.layout.fragment_media_viewer) {
initData(medias.toSet().sortedByDescending { it.dateAdded })
} ?: albumId?.also {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
mediaViewModel.setBucketId(it)
mediaViewModel.mediaForAlbum.collectLatest(::initData)
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
mediaViewModel.media.collectLatest(::initData)
}
} ?: media?.also {
initData(listOf(it))

View File

@ -40,7 +40,9 @@ import org.lineageos.glimpse.viewmodels.MediaViewModel
*/
class ReelsFragment : Fragment(R.layout.fragment_reels) {
// View models
private val mediaViewModel: MediaViewModel by viewModels { MediaViewModel.Factory }
private val mediaViewModel: MediaViewModel by viewModels {
MediaViewModel.factory(lifecycleScope)
}
// Views
private val appBarLayout by getViewProperty<AppBarLayout>(R.id.appBarLayout)
@ -55,9 +57,9 @@ class ReelsFragment : Fragment(R.layout.fragment_reels) {
private val permissionsUtils by lazy { PermissionsUtils(requireContext()) }
private val permissionsGatedCallback = PermissionsGatedCallback(this) {
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
mediaViewModel.media.collectLatest { data ->
thumbnailAdapter.data = data.toTypedArray()
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
mediaViewModel.media.collectLatest {
thumbnailAdapter.data = it.toTypedArray()
}
}
}

View File

@ -6,23 +6,31 @@
package org.lineageos.glimpse.viewmodels
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.shareIn
import org.lineageos.glimpse.GlimpseApplication
import org.lineageos.glimpse.repository.MediaRepository
open class AlbumsViewModel(
private val mediaRepository: MediaRepository
private val mediaRepository: MediaRepository,
private val externalScope: CoroutineScope,
) : ViewModel() {
val albums = mediaRepository.albums()
val albums = mediaRepository.albums().shareIn(
externalScope,
replay = 1,
started = SharingStarted.WhileSubscribed()
)
companion object {
val Factory: ViewModelProvider.Factory = viewModelFactory {
fun factory(externalScope: CoroutineScope) = viewModelFactory {
initializer {
AlbumsViewModel(
mediaRepository = (this[APPLICATION_KEY] as GlimpseApplication).mediaRepository,
externalScope = externalScope,
)
}
}

View File

@ -6,37 +6,40 @@
package org.lineageos.glimpse.viewmodels
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.shareIn
import org.lineageos.glimpse.GlimpseApplication
import org.lineageos.glimpse.repository.MediaRepository
import org.lineageos.glimpse.utils.MediaStoreBuckets
open class MediaViewModel(
private val mediaRepository: MediaRepository
private val mediaRepository: MediaRepository,
private val externalScope: CoroutineScope,
private val bucketId: Int
) : ViewModel() {
val media = mediaRepository.media(MediaStoreBuckets.MEDIA_STORE_BUCKET_REELS.id)
private val bucketId = MutableStateFlow(MediaStoreBuckets.MEDIA_STORE_BUCKET_REELS.id)
fun setBucketId(bucketId: Int) {
this.bucketId.value = bucketId
}
@OptIn(ExperimentalCoroutinesApi::class)
val mediaForAlbum = bucketId.flatMapLatest { mediaRepository.media(it) }
val media = mediaRepository.media(bucketId).shareIn(
externalScope,
replay = 1,
started = SharingStarted.WhileSubscribed()
)
companion object {
val Factory: ViewModelProvider.Factory = viewModelFactory {
initializer {
MediaViewModel(
mediaRepository = (this[APPLICATION_KEY] as GlimpseApplication).mediaRepository,
)
fun factory(
externalScope: CoroutineScope,
bucketId: Int = MediaStoreBuckets.MEDIA_STORE_BUCKET_REELS.id
) =
viewModelFactory {
initializer {
MediaViewModel(
mediaRepository = (this[APPLICATION_KEY] as GlimpseApplication).mediaRepository,
externalScope = externalScope,
bucketId = bucketId,
)
}
}
}
}
}

View File

@ -8,18 +8,21 @@ package org.lineageos.glimpse.viewmodels
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
import androidx.lifecycle.createSavedStateHandle
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import kotlinx.coroutines.CoroutineScope
import org.lineageos.glimpse.GlimpseApplication
import org.lineageos.glimpse.repository.MediaRepository
import org.lineageos.glimpse.utils.MediaStoreBuckets
class MediaViewerViewModel(
savedStateHandle: SavedStateHandle,
mediaRepository: MediaRepository,
) : MediaViewModel(mediaRepository) {
externalScope: CoroutineScope,
bucketId: Int,
) : MediaViewModel(mediaRepository, externalScope, bucketId) {
private val mediaPositionInternal = savedStateHandle.getLiveData<Int>(MEDIA_POSITION_KEY)
val mediaPositionLiveData: LiveData<Int> = mediaPositionInternal
var mediaPosition: Int
@ -51,13 +54,19 @@ class MediaViewerViewModel(
companion object {
private const val MEDIA_POSITION_KEY = "position"
val Factory: ViewModelProvider.Factory = viewModelFactory {
initializer {
MediaViewerViewModel(
savedStateHandle = createSavedStateHandle(),
mediaRepository = (this[APPLICATION_KEY] as GlimpseApplication).mediaRepository,
)
fun factory(
externalScope: CoroutineScope,
bucketId: Int = MediaStoreBuckets.MEDIA_STORE_BUCKET_REELS.id
) =
viewModelFactory {
initializer {
MediaViewerViewModel(
savedStateHandle = createSavedStateHandle(),
mediaRepository = (this[APPLICATION_KEY] as GlimpseApplication).mediaRepository,
externalScope = externalScope,
bucketId = bucketId,
)
}
}
}
}
}