У меня есть проект, в котором я хочу показать пользователю несколько элементов (изображение, текст, видео и т. Д.). Я знаю, как работают несколько типов представлений для RecyclerView. В свой проект я включил большинство рекомендованных Android Architecture Components
, таких как Room
, Paging
, LiveData
и т. Д.
Обобщая дизайн проекта, я следовал этой codelab, которая мне помогла много. Библиотека Room
+ библиотека Paging
с BoundaryCallback
предоставляет хороший способ реализовать автономный кеш. Считать базу данных источником истины и позволять библиотеке Paging
запрашивать дополнительные данные всякий раз, когда Recyclerview
это необходимо через DataSource.Factory
, мне показалось очень хорошим подходом.
Но оборотной стороной этого проекта является то, что они показывают, как весь компоненты архитектуры (Room
и Paging
с BoundaryCallback
) работают только для одного типа элементов. Но у меня несколько типов представлений, и я не мог с этим справиться.
Далее я покажу вам фрагменты кода из своего проекта, чтобы проиллюстрировать, в чем я застрял.
Начнем с моделей. Предположим, у нас есть два типа элементов: изображение и текст.
sealed class Item {
@JsonClass(generateAdapter = true)
@Entity(tableName = "image_table")
data class Image(
@PrimaryKey
@Json(name = "id")
val imageId: Long,
val image: String
): Item()
@JsonClass(generateAdapter = true)
@Entity(tableName = "text_table")
data class Text(
@PrimaryKey
@Json(name = "id")
val textId: Long,
val text:String
):Item()
}
Как видите, мои классы моделей расширяют Item
класс sealed. Итак, я могу рассматривать классы Image
и Text
как Item
s. Тогда класс адаптера выглядит так:
private val ITEM_VIEW_TYPE_IMAGE = 0
private val ITEM_VIEW_TYPE_TEXT = 1
class ItemAdapter():
PagedListAdapter<Item, RecyclerView.ViewHolder>(ItemDiffCallback()) {
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is TextItemViewHolder -> {
val textItem = getItem(position) as Item.Text
holder.bind(textItem)
}
is ImageItemViewHolder -> {
val imageItem = getItem(position) as Item.Image
holder.bind(imageItem)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
ITEM_VIEW_TYPE_IMAGE -> ImageItemViewHolder.from(parent)
ITEM_VIEW_TYPE_TEXT -> TextItemViewHolder.from(parent)
else -> throw ClassCastException("Unknown viewType ${viewType}")
}
}
override fun getItemViewType(position: Int): Int {
return when (getItem(position)) {
is Item.Image -> ITEM_VIEW_TYPE_IMAGE
is Item.Text -> ITEM_VIEW_TYPE_TEXT
}
}
class TextItemViewHolder private constructor(val binding: ListItemTextBinding): RecyclerView.ViewHolder(binding.root) {
fun bind(item: Text) {
binding.text = item
binding.executePendingBindings()
}
companion object {
fun from(parent: ViewGroup): TextItemViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = ListItemTextBinding.inflate(layoutInflater, parent, false)
return TextItemViewHolder(binding)
}
}
}
class ImageItemViewHolder private constructor(val binding: ListItemImageBinding) : RecyclerView.ViewHolder(binding.root){
fun bind(item: Image) {
binding.image = item
binding.executePendingBindings()
}
companion object {
fun from(parent: ViewGroup): ImageItemViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = ListItemImageBinding.inflate(layoutInflater, parent, false)
return ImageItemViewHolder(binding)
}
}
}
}
class ItemDiffCallback : DiffUtil.ItemCallback<Item>() {
// HERE, I have the problem that I can not access the id attribute
override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: Item, newItem: Item): Boolean {
return oldItem == newItem
}
}
}
Здесь я столкнулся с проблемой класса ItemDiffCallback
. В методе areItemsTheSame
я не могу получить доступ к атрибуту id
с помощью суперкласса Item
. Что я могу здесь сделать?
Теперь я перейду к классу репозитория. Как вы, возможно, знаете, класс репозитория отвечает за извлечение данных в первую очередь из базы данных, поскольку в моем проекте база данных является источником истины. Если данных нет или требуется больше данных, библиотека Paging использует класс BoundaryCallback для запроса дополнительных данных из службы и сохранения их в базе данных. Вот мой класс репозитория для Items:
class ItemRepository(private val database: MyLimDatabase, private val service: ApiService) {
fun getItems(): LiveData<PagedList<Item>> {
val dataSourceFactory = ???????????? FROM WHICH TABLE I HAVE TO CHOOSE ???????? database.textDao.getTexts() or database.imageDao.getImages()
val config = PagedList.Config.Builder()
.setPageSize(30) // defines the number of items loaded at once from the DataSource
.setInitialLoadSizeHint(50) // defines how many items to load when first load occurs
.setPrefetchDistance(10) // defines how far from the edge of loaded content an access must be to trigger further loading
.build()
val itemBoundaryCallback = ItemBoundaryCallback(service, database)
return LivePagedListBuilder(dataSourceFactory, config)
.setBoundaryCallback(itemBoundaryCallback)
.build()
}
}
В этом случае у меня проблема в том, что я не знаю, как инициализировать переменную dataSourceFactory
, потому что у меня есть два DAO
класса. Эти:
@Dao
interface ImageDao{
@Query("SELECT * from image_table")
fun getImages(): DataSource.Factory<Int, Image>
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertAll(images: List<Image>)
}
@Dao
interface TextDao{
@Query("SELECT * from text_table")
fun getTexts(): DataSource.Factory<Int, Text>
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertAll(texts: List<Text>)
}
Итак, как с этим справиться?
Может кто поможет?