Store references to items in DetailViewModel

Store references to the artist & album inside DetailViewModel to prevent an artist/album search on every re-creation.
This commit is contained in:
OxygenCobalt 2020-09-23 15:42:06 -06:00
parent f5d007267d
commit b80a3596b8
12 changed files with 115 additions and 68 deletions

View file

@ -0,0 +1,4 @@
package org.oxycblt.auxio
// RecyclerView click listener
class ClickListener<T>(val onClick: (T) -> Unit)

View file

@ -23,6 +23,3 @@ class MainActivity : AppCompatActivity() {
setContentView(R.layout.activity_main) setContentView(R.layout.activity_main)
} }
} }
// RecyclerView click listener
class ClickListener<T>(val onClick: (T) -> Unit)

View file

@ -28,17 +28,22 @@ class AlbumDetailFragment : Fragment() {
): View? { ): View? {
val binding = FragmentAlbumDetailBinding.inflate(inflater) val binding = FragmentAlbumDetailBinding.inflate(inflater)
// I honestly don't want to turn of the any data classes into parcelables due to how // If DetailViewModel isn't already storing the album, get it from MusicViewModel
// many lists they store, so just pick up the artist id and find it from musicModel. // using the ID given by the navigation arguments.
if (detailModel.currentAlbum == null) {
val musicModel: MusicViewModel by activityViewModels() val musicModel: MusicViewModel by activityViewModels()
val album = musicModel.albums.value?.find { it.id == args.albumId }!!
detailModel.currentAlbum = musicModel.albums.value!!.find {
it.id == args.albumId
}!!
}
binding.lifecycleOwner = this binding.lifecycleOwner = this
binding.detailModel = detailModel binding.detailModel = detailModel
binding.album = album binding.album = detailModel.currentAlbum
binding.songRecycler.adapter = DetailSongAdapter( binding.songRecycler.adapter = DetailSongAdapter(
album.songs, detailModel.currentAlbum!!.songs,
ClickListener { ClickListener {
Log.d(this::class.simpleName, it.name) Log.d(this::class.simpleName, it.name)
} }
@ -46,16 +51,17 @@ class AlbumDetailFragment : Fragment() {
binding.songRecycler.applyDivider() binding.songRecycler.applyDivider()
binding.songRecycler.setHasFixedSize(true) binding.songRecycler.setHasFixedSize(true)
// If the album was shown directly from LibraryFragment, then enable the ability // If the album was shown directly from LibraryFragment [No parent artist stored],
// to navigate to the artist from the album. Don't do this if the album was shown // then enable the ability to navigate upwards to the album's parent artist.
// from ArtistDetailFragment, as you can just navigate up to see the parent artist. if (detailModel.currentArtist == null) {
if (args.isFromLibrary) {
detailModel.doneWithNavToParent() detailModel.doneWithNavToParent()
detailModel.navToParentArtist.observe(viewLifecycleOwner) { detailModel.navToParentArtist.observe(viewLifecycleOwner) {
if (it) { if (it) {
findNavController().navigate( findNavController().navigate(
AlbumDetailFragmentDirections.actionShowParentArtist(album.artist.id) AlbumDetailFragmentDirections.actionShowParentArtist(
detailModel.currentAlbum!!.artist.id
)
) )
detailModel.doneWithNavToParent() detailModel.doneWithNavToParent()

View file

@ -28,20 +28,26 @@ class ArtistDetailFragment : Fragment() {
): View? { ): View? {
val binding = FragmentArtistDetailBinding.inflate(inflater) val binding = FragmentArtistDetailBinding.inflate(inflater)
// I honestly don't want to turn of the any data classes into parcelables due to how // If DetailViewModel isn't already storing the artist, get it from MusicViewModel
// many lists they store, so just pick up the artist id and find it from musicModel. // using the ID given by the navigation arguments
if (detailModel.currentArtist == null) {
val musicModel: MusicViewModel by activityViewModels() val musicModel: MusicViewModel by activityViewModels()
val artist = musicModel.artists.value?.find { it.id == args.artistId }!! detailModel.currentArtist = musicModel.artists.value!!.find {
it.id == args.artistId
}!!
}
binding.lifecycleOwner = this val artistAdapter = DetailAlbumAdapter(
binding.artist = artist detailModel.currentArtist!!.albums,
binding.albumRecycler.adapter = DetailAlbumAdapter(
artist.albums,
ClickListener { ClickListener {
navToAlbum(it) navToAlbum(it)
} }
) )
binding.lifecycleOwner = this
binding.artist = detailModel.currentArtist!!
binding.albumRecycler.adapter = artistAdapter
binding.albumRecycler.applyDivider() binding.albumRecycler.applyDivider()
binding.albumRecycler.setHasFixedSize(true) binding.albumRecycler.setHasFixedSize(true)
@ -56,13 +62,21 @@ class ArtistDetailFragment : Fragment() {
detailModel.isAlreadyNavigating = false detailModel.isAlreadyNavigating = false
} }
override fun onDestroy() {
super.onDestroy()
// Reset the stored artist so that the next instance of ArtistDetailFragment
// will not read it.
detailModel.currentArtist = null
}
private fun navToAlbum(album: Album) { private fun navToAlbum(album: Album) {
// Don't navigate if an item already has been selected. // Don't navigate if an item already has been selected.
if (!detailModel.isAlreadyNavigating) { if (!detailModel.isAlreadyNavigating) {
detailModel.isAlreadyNavigating = true detailModel.isAlreadyNavigating = true
findNavController().navigate( findNavController().navigate(
ArtistDetailFragmentDirections.actionShowAlbum(album.id, false) ArtistDetailFragmentDirections.actionShowAlbum(album.id)
) )
} }
} }

View file

@ -3,6 +3,8 @@ package org.oxycblt.auxio.detail
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import org.oxycblt.auxio.music.models.Album
import org.oxycblt.auxio.music.models.Artist
class DetailViewModel : ViewModel() { class DetailViewModel : ViewModel() {
var isAlreadyNavigating = false var isAlreadyNavigating = false
@ -10,6 +12,9 @@ class DetailViewModel : ViewModel() {
private val mNavToParentArtist = MutableLiveData<Boolean>() private val mNavToParentArtist = MutableLiveData<Boolean>()
val navToParentArtist: LiveData<Boolean> get() = mNavToParentArtist val navToParentArtist: LiveData<Boolean> get() = mNavToParentArtist
var currentArtist: Artist? = null
var currentAlbum: Album? = null
fun navToParent() { fun navToParent() {
mNavToParentArtist.value = true mNavToParentArtist.value = true
} }

View file

@ -19,8 +19,7 @@ data class Artist(
} }
fun finalizeGenre() { fun finalizeGenre() {
// If the artist has more than one genre, pick the most "prominent" one. // If the artist has more than one genre, pick the most "Prominent" one.
// [Really just eliminate duplicates created from my hacky way of getting genres loaded but shhhh]
genre = if (genres.size > 1) { genre = if (genres.size > 1) {
val groupGenres = genres.groupBy { it.name } val groupGenres = genres.groupBy { it.name }

View file

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?><!-- <?xml version="1.0" encoding="utf-8"?>
<!--
Divider used by recyclerview header items Divider used by recyclerview header items
https://stackoverflow.com/a/61157571/14143986 https://stackoverflow.com/a/61157571/14143986
--> -->
@ -12,4 +13,11 @@ https://stackoverflow.com/a/61157571/14143986
android:color="@color/divider_color" /> android:color="@color/divider_color" />
</shape> </shape>
</item> </item>
<item>
<ripple
android:color="@color/selection_color"
android:radius="@dimen/divider_ripple_size">
</ripple>
</item>
</layer-list> </layer-list>

View file

@ -26,8 +26,7 @@
android:background="?android:attr/windowBackground" android:background="?android:attr/windowBackground"
android:elevation="@dimen/elevation_normal" android:elevation="@dimen/elevation_normal"
app:titleTextAppearance="@style/TextAppearance.Toolbar.Bold" app:titleTextAppearance="@style/TextAppearance.Toolbar.Bold"
app:title="@string/title_library_fragment" app:title="@string/title_library_fragment" />
tools:titleTextColor="@color/blue" />
<androidx.core.widget.NestedScrollView <androidx.core.widget.NestedScrollView
android:layout_width="match_parent" android:layout_width="match_parent"
@ -36,8 +35,7 @@
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:animateLayoutChanges="true" android:animateLayoutChanges="true">
android:orientation="vertical">
<ImageView <ImageView
android:id="@+id/cover" android:id="@+id/cover"
@ -56,11 +54,11 @@
style="@style/DetailHeader" style="@style/DetailHeader"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_medium"
android:layout_marginTop="@dimen/margin_medium" android:layout_marginTop="@dimen/margin_medium"
android:layout_marginEnd="@dimen/margin_medium" android:layout_marginEnd="@dimen/margin_medium"
android:layout_marginStart="@dimen/margin_medium"
android:text="@{album.name}"
android:maxLines="1" android:maxLines="1"
android:text="@{album.name}"
app:autoSizeMaxTextSize="@dimen/detail_header_size_max" app:autoSizeMaxTextSize="@dimen/detail_header_size_max"
app:autoSizeMinTextSize="@dimen/generic_size_min" app:autoSizeMinTextSize="@dimen/generic_size_min"
app:autoSizeStepGranularity="@dimen/generic_size_increment" app:autoSizeStepGranularity="@dimen/generic_size_increment"
@ -73,15 +71,15 @@
<TextView <TextView
android:id="@+id/artist_name" android:id="@+id/artist_name"
android:clickable="true"
android:focusable="true"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_medium"
android:clickable="true"
android:focusable="true"
android:onClick="@{() -> detailModel.navToParent()}"
android:text="@{album.artist.name}"
android:textAppearance="?android:attr/textAppearanceListItem" android:textAppearance="?android:attr/textAppearanceListItem"
android:textColor="?android:attr/textColorSecondary" android:textColor="?android:attr/textColorSecondary"
android:onClick="@{() -> detailModel.navToParent()}"
android:layout_marginStart="@dimen/margin_medium"
android:text="@{album.artist.name}"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/album_name" app:layout_constraintTop_toBottomOf="@+id/album_name"
tools:text="Artist Name" /> tools:text="Artist Name" />
@ -90,30 +88,29 @@
android:id="@+id/album_year" android:id="@+id/album_year"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceListItem"
android:textColor="?android:attr/textColorSecondary"
android:layout_marginStart="@dimen/margin_medium" android:layout_marginStart="@dimen/margin_medium"
android:text="@{album.year != 0 ? String.valueOf(album.year) : @string/placeholder_no_date}" android:text="@{album.year != 0 ? String.valueOf(album.year) : @string/placeholder_no_date}"
android:textAppearance="?android:attr/textAppearanceListItem"
android:textColor="?android:attr/textColorSecondary"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/artist_name" app:layout_constraintTop_toBottomOf="@+id/artist_name"
tools:text="2020" /> tools:text="2020" />
<TextView <TextView
android:id="@+id/header" android:id="@+id/header_title"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_marginTop="@dimen/padding_medium"
android:background="@drawable/header_dividers"
android:fontFamily="@font/inter_bold"
android:paddingStart="@dimen/padding_medium"
android:paddingTop="@dimen/padding_small"
android:paddingEnd="@dimen/padding_small"
android:paddingBottom="@dimen/padding_small"
android:text="@string/label_songs" android:text="@string/label_songs"
android:textAppearance="@style/TextAppearance.MaterialComponents.Overline" android:textAppearance="@style/TextAppearance.MaterialComponents.Overline"
android:textSize="16sp" android:textSize="16sp"
android:fontFamily="@font/inter_bold" app:layout_constraintTop_toBottomOf="@+id/album_year" />
android:layout_marginTop="@dimen/padding_medium"
android:paddingStart="@dimen/padding_medium"
android:paddingEnd="@dimen/padding_small"
android:paddingTop="@dimen/padding_small"
android:paddingBottom="@dimen/padding_small"
android:background="@drawable/header_dividers"
app:layout_constraintTop_toBottomOf="@+id/album_year"
tools:layout_editor_absoluteX="60dp" />
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/song_recycler" android:id="@+id/song_recycler"
@ -124,7 +121,7 @@
android:overScrollMode="never" android:overScrollMode="never"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@+id/header" app:layout_constraintTop_toBottomOf="@+id/header_title"
tools:itemCount="4" tools:itemCount="4"
tools:listitem="@layout/item_album_song" /> tools:listitem="@layout/item_album_song" />

View file

@ -22,8 +22,7 @@
android:background="?android:attr/windowBackground" android:background="?android:attr/windowBackground"
android:elevation="@dimen/elevation_normal" android:elevation="@dimen/elevation_normal"
app:titleTextAppearance="@style/TextAppearance.Toolbar.Bold" app:titleTextAppearance="@style/TextAppearance.Toolbar.Bold"
app:title="@string/title_library_fragment" app:title="@string/title_library_fragment" />
tools:titleTextColor="@color/blue" />
<androidx.core.widget.NestedScrollView <androidx.core.widget.NestedScrollView
android:layout_width="match_parent" android:layout_width="match_parent"
@ -91,21 +90,39 @@
tools:text="2 Albums, 20 Songs" /> tools:text="2 Albums, 20 Songs" />
<TextView <TextView
android:id="@+id/header" android:id="@+id/header_title"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_marginTop="@dimen/margin_medium"
android:background="@drawable/header_dividers"
android:fontFamily="@font/inter_bold"
android:paddingStart="@dimen/padding_medium"
android:paddingTop="@dimen/padding_small"
android:paddingEnd="@dimen/padding_small"
android:paddingBottom="@dimen/padding_small"
android:text="@string/label_albums" android:text="@string/label_albums"
android:textAppearance="@style/TextAppearance.MaterialComponents.Overline" android:textAppearance="@style/TextAppearance.MaterialComponents.Overline"
android:textSize="16sp" android:textSize="16sp"
android:fontFamily="@font/inter_bold" app:layout_constraintStart_toStartOf="parent"
android:layout_marginTop="@dimen/padding_medium"
android:paddingStart="@dimen/padding_medium"
android:paddingEnd="@dimen/padding_small"
android:paddingTop="@dimen/padding_small"
android:paddingBottom="@dimen/padding_small"
android:background="@drawable/header_dividers"
app:layout_constraintTop_toBottomOf="@+id/artist_counts" /> app:layout_constraintTop_toBottomOf="@+id/artist_counts" />
<!--
<ImageButton
android:id="@+id/sort_button"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_marginTop="@dimen/margin_medium"
android:background="@drawable/header_dividers"
android:contentDescription="@string/description_sort_button"
android:paddingStart="@dimen/padding_medium"
android:paddingTop="@dimen/padding_small"
android:paddingEnd="@dimen/margin_medium"
android:paddingBottom="@dimen/padding_small"
app:layout_constraintBottom_toTopOf="@+id/album_recycler"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/artist_counts" />
-->
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/album_recycler" android:id="@+id/album_recycler"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -115,7 +132,7 @@
android:overScrollMode="never" android:overScrollMode="never"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@+id/header" app:layout_constraintTop_toBottomOf="@+id/header_title"
tools:itemCount="4" tools:itemCount="4"
tools:listitem="@layout/item_album" /> tools:listitem="@layout/item_album" />

View file

@ -58,9 +58,6 @@
<argument <argument
android:name="albumId" android:name="albumId"
app:argType="long" /> app:argType="long" />
<argument
android:name="isFromLibrary"
app:argType="boolean" />
<action <action
android:id="@+id/action_show_parent_artist" android:id="@+id/action_show_parent_artist"
app:enterAnim="@anim/fragment_fade_enter" app:enterAnim="@anim/fragment_fade_enter"

View file

@ -24,5 +24,7 @@
<dimen name="generic_size_min">10sp</dimen>' <dimen name="generic_size_min">10sp</dimen>'
<dimen name="generic_size_increment">2sp</dimen> <dimen name="generic_size_increment">2sp</dimen>
<dimen name="divider_ripple_size">18dp</dimen>
<dimen name="elevation_normal">4dp</dimen> <dimen name="elevation_normal">4dp</dimen>
</resources> </resources>

View file

@ -18,6 +18,7 @@
<string name="description_artist_image">Artist Cover for %s</string> <string name="description_artist_image">Artist Cover for %s</string>
<string name="description_track_number">Track %s</string> <string name="description_track_number">Track %s</string>
<string name="description_error">Error</string> <string name="description_error">Error</string>
<string name="description_sort_button">Change Sorting Mode</string>
<string name="placeholder_genre">Unknown Genre</string> <string name="placeholder_genre">Unknown Genre</string>
<string name="placeholder_artist">Unknown Artist</string> <string name="placeholder_artist">Unknown Artist</string>