简体   繁体   English

具有多种视图类型的 RecyclerView 和具有边界回调的分页库

[英]RecyclerView with multiple view types and paging library with boundary callback

I have a project in which I want to show multiple Items ( Image, Text, Video etc.) to the user.我有一个项目,我想在其中向用户显示多个项目(图像、文本、视频等)。 I am aware of how multiple view types work for the RecyclerView.我知道多种视图类型如何为 RecyclerView 工作。 In my project, I have included most of the recommended Android Architecture Components like Room , Paging , LiveData , etc.在我的项目中,我包含了大部分推荐的Android Architecture ComponentsRoomPagingLiveData等。

To summarize the project design, I followed this codelab which helped me a lot.为了总结项目设计,我遵循了这个对我帮助很大的代码实验室 The Room library + Paging library with a BoundaryCallback provides a good a way to implement an offline cache.带有BoundaryCallbackRoom库 + Paging库提供了一种实现离线缓存的好方法。 Considering the database as source of truth and letting the Paging library request more data whenever the Recyclerview needs it via a DataSource.Factory , seemed to me a very good approach.将数据库视为事实来源并让Paging库在Recyclerview需要时通过DataSource.Factory请求更多数据,在我看来是一个非常好的方法。

But the downside of that project is that they show how the whole architecture components stuff ( Room and Paging with BoundaryCallback ) works for only one item type.但该项目的缺点是它们展示了整个架构组件(带有BoundaryCallback RoomPaging )如何仅适用于一种项目类型。 But I have multiple view types and I could not handle that.但是我有多种视图类型,我无法处理。

In the following I show you code snippets from my project to illustrate where I was stucked.在下面,我将向您展示我的项目中的代码片段,以说明我遇到的问题。

Let's start with the models.让我们从模型开始。 Suppose, we have two item types: Image and Text.假设,我们有两种项目类型:图像和文本。

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()
}

As you can see, my model classes are extending the Item sealed class.如您所见,我的模型类扩展了Item密封类。 So, I can treat the Image and Text class as Item s.因此,我可以将ImageText类视为Item The adapter class looks then like this:适配器类看起来像这样:

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
    }
}

} }

Here, the problem I am faced with is the ItemDiffCallback class.在这里,我面临的问题是ItemDiffCallback类。 In the areItemsTheSame method I can not access the id attribute using the superclass Item .areItemsTheSame方法中,我无法使用超类Item访问id属性。 What can I do here?我能在这里做什么?

Now, I am going to the repository class.现在,我要去存储库类。 As you might know, the repository class is responsible for retrieving the data first from the database since in my project the database is the source of truth.您可能知道,存储库类负责首先从数据库中检索数据,因为在我的项目中,数据库是事实的来源。 If no data is there or if more data is needed, the Paging library uses a BoundaryCallback class to request more data from the service and store them into the database.如果没有数据或需要更多数据,Paging 库使用 BoundaryCallback 类从服务请求更多数据并将它们存储到数据库中。 Here is my repository class for the 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()
    }
}

In this case, I have the problem that I do not know how to initialize the dataSourceFactory variable because I have two DAO classes.在这种情况下,我遇到的问题是我不知道如何初始化dataSourceFactory变量,因为我有两个DAO类。 These are:这些是:

@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>)
}

So, how to handle this ?那么,如何处理呢?

Can someone help ?有人可以帮忙吗?

Why do you need 2 tables?为什么需要2张桌子? The columns are the same in both.两者中的列相同。 Use a common data class for both types that holds another field to differentiate what that rows text value represents, text, image uri, image path.对包含另一个字段的两种类型使用公共数据类来区分行文本值代表什么,文本,图像 uri,图像路径。 This could easily be done using an IntDef or enum .这可以使用IntDefenum轻松完成。 This then allows you to return one set of data that can be handled accordingly based on that new column.然后,这允许您返回一组可以根据该新列进行相应处理的数据。

For example :例如 :

@IntDef(IMAGE, TEXT)
annotation class ItemType {
    companion object {
        const val IMAGE = 1
        const val TEXT = 2
    }
}

@Entity(tableName = "items_table")
data class Item(
    @PrimaryKey
    val id: Long,
    val itemData: String,
    @ItemType
    val type : Int)

@Dao
interface ItemsDao {
    @Query("SELECT * from items_table")
    fun getItems(): DataSource.Factory<Int, Item>

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insertAll(itemss: List<Item>)
}

class ItemAdapter():
    PagedListAdapter<Item, RecyclerView.ViewHolder>(ItemDiffCallback()) {

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        with(getItem(position)) {
            when (holder) {
                 // this could be simplified if using a common super view holder with a bind method that these subtypes inherit from
                 is TextItemViewHolder -> holder.bind(this)
                 is ImageItemViewHolder -> holder.bind(this)
            }
        }
    }

    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)?.type) {
            IMAGE -> ITEM_VIEW_TYPE_IMAGE
            TEXT -> ITEM_VIEW_TYPE_TEXT
            else -> -1
        }
    }
}

With a single table the problems you are currently facing become non existent.使用单个表,您当前面临的问题变得不存在。 If you are pulling information from a remote data source / api you can easily convert the api data types into the correct data type for your database before inserting, which I'd recommend anyway.如果您从远程数据源/api 中提取信息,您可以在插入之前轻松地将 api 数据类型转换为数据库的正确数据类型,无论如何我都建议这样做。 Without knowing the specifics of the boundary check / service this would seem a better approach based on current code and information provided.在不知道边界检查/服务的细节的情况下,根据当前提供的代码和信息,这似乎是一种更好的方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM