Merge pull request #530 from tzugen/diff

Use DiffUtil for better performance when refreshing the data..
This commit is contained in:
tzugen 2021-06-21 18:49:17 +02:00 committed by GitHub
commit d6594b8ec4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 51 additions and 10 deletions

View File

@ -1,7 +1,19 @@
package org.moire.ultrasonic.domain package org.moire.ultrasonic.domain
abstract class GenericEntry { abstract class GenericEntry {
// TODO Should be non-null! // TODO: Should be non-null!
abstract val id: String? abstract val id: String?
open val name: String? = null open val name: String? = null
// These are just a formality and will never be called,
// because Kotlin data classes will have autogenerated equals() and hashCode() functions
override operator fun equals(other: Any?): Boolean {
return this === other
}
override fun hashCode(): Int {
var result = id?.hashCode() ?: 0
result = 31 * result + (name?.hashCode() ?: 0)
return result
}
} }

View File

@ -49,8 +49,9 @@ class AlbumListFragment : GenericListFragment<MusicDirectory.Entry, AlbumRowAdap
if (args == null) throw IllegalArgumentException("Required arguments are missing") if (args == null) throw IllegalArgumentException("Required arguments are missing")
val refresh = args.getBoolean(Constants.INTENT_EXTRA_NAME_REFRESH) val refresh = args.getBoolean(Constants.INTENT_EXTRA_NAME_REFRESH)
val append = args.getBoolean(Constants.INTENT_EXTRA_NAME_APPEND)
return listModel.getAlbumList(refresh, refreshListView!!, args) return listModel.getAlbumList(refresh or append, refreshListView!!, args)
} }
/** /**

View File

@ -13,7 +13,8 @@ import org.moire.ultrasonic.util.Util
class AlbumListModel(application: Application) : GenericListModel(application) { class AlbumListModel(application: Application) : GenericListModel(application) {
val albumList: MutableLiveData<List<MusicDirectory.Entry>> = MutableLiveData() val albumList: MutableLiveData<List<MusicDirectory.Entry>> = MutableLiveData(listOf())
var lastType: String? = null
private var loadedUntil: Int = 0 private var loadedUntil: Int = 0
fun getAlbumList( fun getAlbumList(
@ -21,8 +22,14 @@ class AlbumListModel(application: Application) : GenericListModel(application) {
swipe: SwipeRefreshLayout, swipe: SwipeRefreshLayout,
args: Bundle args: Bundle
): LiveData<List<MusicDirectory.Entry>> { ): LiveData<List<MusicDirectory.Entry>> {
// Don't reload the data if navigating back to the view that was active before.
// This way, we keep the scroll position
val albumListType = args.getString(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE)!!
backgroundLoadFromServer(refresh, swipe, args) if (refresh || albumList.value!!.isEmpty() || albumListType != lastType) {
lastType = albumListType
backgroundLoadFromServer(refresh, swipe, args)
}
return albumList return albumList
} }

View File

@ -30,13 +30,17 @@ import org.moire.ultrasonic.service.MusicService
* Provides ViewModel which contains the list of available Artists * Provides ViewModel which contains the list of available Artists
*/ */
class ArtistListModel(application: Application) : GenericListModel(application) { class ArtistListModel(application: Application) : GenericListModel(application) {
private val artists: MutableLiveData<List<Artist>> = MutableLiveData() private val artists: MutableLiveData<List<Artist>> = MutableLiveData(listOf())
/** /**
* Retrieves all available Artists in a LiveData * Retrieves all available Artists in a LiveData
*/ */
fun getItems(refresh: Boolean, swipe: SwipeRefreshLayout): LiveData<List<Artist>> { fun getItems(refresh: Boolean, swipe: SwipeRefreshLayout): LiveData<List<Artist>> {
backgroundLoadFromServer(refresh, swipe) // Don't reload the data if navigating back to the view that was active before.
// This way, we keep the scroll position
if (artists.value!!.isEmpty() || refresh) {
backgroundLoadFromServer(refresh, swipe)
}
return artists return artists
} }

View File

@ -16,20 +16,24 @@ import android.widget.ImageView
import android.widget.PopupMenu import android.widget.PopupMenu
import android.widget.RelativeLayout import android.widget.RelativeLayout
import android.widget.TextView import android.widget.TextView
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import org.moire.ultrasonic.R import org.moire.ultrasonic.R
import org.moire.ultrasonic.data.ActiveServerProvider import org.moire.ultrasonic.data.ActiveServerProvider
import org.moire.ultrasonic.domain.GenericEntry
import org.moire.ultrasonic.domain.MusicFolder import org.moire.ultrasonic.domain.MusicFolder
import org.moire.ultrasonic.view.SelectMusicFolderView import org.moire.ultrasonic.view.SelectMusicFolderView
/* /*
* An abstract Adapter, which can be extended to display a List of <T> in a RecyclerView * An abstract Adapter, which can be extended to display a List of <T> in a RecyclerView
*/ */
abstract class GenericRowAdapter<T>( abstract class GenericRowAdapter<T : GenericEntry>(
val onItemClick: (T) -> Unit, val onItemClick: (T) -> Unit,
val onContextMenuClick: (MenuItem, T) -> Boolean, val onContextMenuClick: (MenuItem, T) -> Boolean,
private val onMusicFolderUpdate: (String?) -> Unit private val onMusicFolderUpdate: (String?) -> Unit
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() { ) : ListAdapter<T, RecyclerView.ViewHolder>(GenericDiffCallback()) {
open var itemList: List<T> = listOf() open var itemList: List<T> = listOf()
protected abstract val layout: Int protected abstract val layout: Int
protected abstract val contextMenuLayout: Int protected abstract val contextMenuLayout: Int
@ -40,11 +44,12 @@ abstract class GenericRowAdapter<T>(
var selectedFolder: String? = null var selectedFolder: String? = null
/** /**
* Sets the data to be displayed in the RecyclerView * Sets the data to be displayed in the RecyclerView,
* using DiffUtil to efficiently calculate the minimum required changes..
*/ */
open fun setData(data: List<T>) { open fun setData(data: List<T>) {
submitList(data)
itemList = data itemList = data
notifyDataSetChanged()
} }
/** /**
@ -136,5 +141,17 @@ abstract class GenericRowAdapter<T>(
companion object { companion object {
internal const val TYPE_HEADER = 0 internal const val TYPE_HEADER = 0
internal const val TYPE_ITEM = 1 internal const val TYPE_ITEM = 1
/**
* Calculates the differences between data sets
*/
class GenericDiffCallback<T : GenericEntry> : DiffUtil.ItemCallback<T>() {
override fun areContentsTheSame(oldItem: T, newItem: T): Boolean {
return oldItem == newItem
}
override fun areItemsTheSame(oldItem: T, newItem: T): Boolean {
return oldItem.id == newItem.id
}
}
} }
} }