Paging3、Room使用,1、从本地Room数据库加载 2、直接网络获取数据加载 3、网络访问数据到Room数据库再加载 4、封装使用

目录

1、从本地Room数据库加载数据

 viewmodel

fragment中使用

页面

 数据库相关

2、直接网络获取数据加载

3、网络访问数据到Room数据库再加载数据

 自定义RemoteMediator访问网络数据

4、封装使用

BaseViewHolder

列表单个item封装的基类

封装统一的adapter

使用方法

自定义PagingItemView

viewmodel获取数据后,把获取到的pagingData装到自定义的PagingItemView中

fragment中使用


1、从本地Room数据库加载数据

参照官方示例代码,在原代码基础上做了一个小改动增加了一个BaseViewHolder,这样不用每次都要新建一个viewholder,直接在adapter中编写绑定数据的代码

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView

class BaseViewHolder(viewGroup: ViewGroup, layoutRes: Int): RecyclerView.ViewHolder(
  LayoutInflater.from(viewGroup.context).inflate(layoutRes, viewGroup, false)
){
     fun bind( bindView: (View)-> Unit){
        bindView(itemView)
     }
}

 绑定数据的代码移到adapter中,其他代码没有多少改变

class CheeseAdapter : PagingDataAdapter<CheeseListItem, BaseViewHolder>(diffCallback) {

    override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {
        holder.bind {
            val nameView = it.findViewById<TextView>(R.id.name)
            val item = getItem(position)
            if (item is CheeseListItem.Separator) {
                nameView.text = "${item.name} Cheeses"
                nameView.setTypeface(null, Typeface.BOLD)
            } else {
                nameView.text = item?.name
                nameView.setTypeface(null, Typeface.NORMAL)
            }
            nameView.text = item?.name
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {
        return BaseViewHolder(parent,viewType)
    }
    override fun getItemViewType(position: Int): Int {
        return R.layout.home_item
    }


    companion object {
        /**
         * This diff callback informs the PagedListAdapter how to compute list differences when new
         * PagedLists arrive.
         *
         * When you add a Cheese with the 'Add' button, the PagedListAdapter uses diffCallback to
         * detect there's only a single item difference from before, so it only needs to animate and
         * rebind a single view.
         *
         * @see DiffUtil
         */
        val diffCallback = object : DiffUtil.ItemCallback<CheeseListItem>() {
            override fun areItemsTheSame(oldItem: CheeseListItem, newItem: CheeseListItem): Boolean {
                return if (oldItem is CheeseListItem.Item && newItem is CheeseListItem.Item) {
                    oldItem.cheese.id == newItem.cheese.id
                } else if (oldItem is CheeseListItem.Separator && newItem is CheeseListItem.Separator) {
                    oldItem.name == newItem.name
                } else {
                    oldItem == newItem
                }
            }

            /**
             * Note that in kotlin, == checking on data classes compares all contents, but in Java,
             * typically you'll implement Object#equals, and use it to compare object contents.
             */
            override fun areContentsTheSame(oldItem: CheeseListItem, newItem: CheeseListItem): Boolean {
                return oldItem == newItem
            }
        }
    }
}

 

sealed class CheeseListItem(val name: String) {
    data class Item(val cheese: Cheese) : CheeseListItem(cheese.name)
    data class Separator(private val letter: Char) : CheeseListItem(letter.toUpperCase().toString())
}

 viewmodel

class AccountBookViewModel(private val dao: CheeseDao) : BaseViewModel() {


    /**
     * We use the Kotlin [Flow] property available on [Pager]. Java developers should use the
     * RxJava or LiveData extension properties available in `PagingRx` and `PagingLiveData`.
     */
    val allCheeses: Flow<PagingData<CheeseListItem>> = Pager(
        config = PagingConfig(
            pageSize = 10,
            enablePlaceholders = true,
            /**
             * Maximum number of items a PagedList should hold in memory at once.
             *
             * This number triggers the PagedList to start dropping distant pages as more are loaded.
             */
            maxSize = 200
        )
    ) {
        dao.allCheesesByName()
    }.flow
        .map { pagingData ->
            pagingData
                // Map cheeses to common UI model.
                .map { cheese -> CheeseListItem.Item(cheese) }
                .insertSeparators { before: CheeseListItem?, after: CheeseListItem? ->
                    if (before == null && after == null) {
                        // List is empty after fully loaded; return null to skip adding separator.
                        null
                    } else if (after == null) {
                        // Footer; return null here to skip adding a footer.
                        null
                    } else if (before == null) {
                        // Header
                        CheeseListItem.Separator(after.name.first())
                    } else if (!before.name.first().equals(after.name.first(), ignoreCase = true)){
                        // Between two items that start with different letters.
                        CheeseListItem.Separator(after.name.first())
                    } else {
                        // Between two items that start with the same letter.
                        null
                    }
                }
        }
        .cachedIn(viewModelScope)


}

fragment中使用

class AccountBookFragment : BaseViewBindingFragment<FragmentAccountBookBinding>() {

    override fun getViewBinding() = FragmentAccountBookBinding.inflate(layoutInflater)

    private val mViewModel: AccountBookViewModel by viewModels {
        object : ViewModelProvider.Factory{
            override fun <T : ViewModel> create(modelClass: Class<T>): T {
                val cheeseDao = LocalDb.get(BaseApplication.instance).cheeseDao()
                return AccountBookViewModel(cheeseDao)as T
            }
        }
    }

    override fun initView() {
        val adapter = CheeseAdapter()
        mViewBinding?.cheeseList?.adapter = adapter
        lifecycleScope.launch {
            mViewModel.allCheeses.collectLatest { adapter.submitData(it) }
        }
    }

}

页面

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ui.accountbook.AccountBookFragment">

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/cheeseList"
            app:layout_behavior="@string/appbar_scrolling_view_behavior"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scrollbars="vertical"
            app:layoutManager="LinearLayoutManager"/>
</androidx.constraintlayout.widget.ConstraintLayout>

 数据库相关



/**
 * Data class that represents our items.
 */
@Entity
data class Cheese(@PrimaryKey(autoGenerate = true) val id: Int, val name: String)
/**
 * Database Access Object for the Cheese database.
 */
@Dao
interface CheeseDao {
    /**
     * Room knows how to return a LivePagedListProvider, from which we can get a LiveData and serve
     * it back to UI via ViewModel.
     */
    @Query("SELECT * FROM Cheese ORDER BY name COLLATE NOCASE ASC")
    fun allCheesesByName(): PagingSource<Int, Cheese>

    @Insert
    fun insert(cheeses: List<Cheese>)

    @Insert
    fun insert(cheese: Cheese)

    @Delete
    fun delete(cheese: Cheese)
}
/**
 * Singleton database object. Note that for a real app, you should probably use a Dependency
 * Injection framework or Service Locator to create the singleton database.
 */
@Database(entities = [Cheese::class,UserBean::class,WaitDealBean::class], version = 1)
abstract class LocalDb : RoomDatabase() {
    abstract fun cheeseDao(): CheeseDao
    abstract fun userDao(): UserDao
    abstract fun waitdealDao(): WaitDealDao

    companion object {
        private var instance: LocalDb? = null
        @Synchronized
        fun get(context: Context): LocalDb {
            if (instance == null) {
                instance = Room.databaseBuilder(context.applicationContext,
                        LocalDb::class.java, "TestDatabase")
                        .addCallback(object : RoomDatabase.Callback() {
                            override fun onCreate(db: SupportSQLiteDatabase) {
                                fillInDb(context.applicationContext)
                            }
                        }).build()
            }
            return instance!!
        }

        /**
         * fill database with list of cheeses
         */
        private fun fillInDb(context: Context) {
            // inserts in Room are executed on the current thread, so we insert in the background
            ioThread {
                get(context).cheeseDao().insert(CHEESE_DATA.map { Cheese(id = 0, name = it) })
            }
        }
    }
}

private val CHEESE_DATA = arrayListOf(
        "Abbaye de Belloc", "Abbaye du Mont des Cats", "Abertam", "Abondance", "Ackawi",
        "Acorn", "Adelost", "Affidelice au Chablis", "Afuega'l Pitu", "Airag", "Airedale",
        "Aisy Cendre", "Allgauer Emmentaler", "Alverca", "Ambert", "American Cheese",
        "Ami du Chambertin", "Anejo Enchilado", "Anneau du Vic-Bilh", "Anthoriro", "Appenzell",
        "Aragon", "Ardi Gasna", "Ardrahan", "Armenian String", "Aromes au Gene de Marc",
        "Asadero", "Asiago", "Aubisque Pyrenees", "Autun", "Avaxtskyr", "Baby Swiss",
        "Babybel", "Baguette Laonnaise", "Bakers", "Baladi", "Balaton", "Bandal", "Banon",
        "Barry's Bay Cheddar", "Basing", "Basket Cheese", "Bath Cheese", "Bavarian Bergkase",
        "Baylough", "Beaufort", "Beauvoorde", "Beenleigh Blue", "Beer Cheese", "Bel Paese",
        "Bergader", "Bergere Bleue", "Berkswell", "Beyaz Peynir", "Bierkase", "Bishop 
        "Tupi", "Turunmaa", "Tymsboro", "Tyn Grug", "Tyning", "Ubriaco", "Ulloa",
        "Vacherin-Fribourgeois", "Valencay", "Vasterbottenost", "Venaco", "Vendomois",
        "Vieux Corse", "Vignotte", "Vulscombe", "Waimata Farmhouse Blue",
        "Washed Rind Cheese (Australian)", "Waterloo", "Weichkaese", "Wellington",
        "Wensleydale", "White Stilton", "Whitestone Farmhouse", "Wigmore", "Woodside Cabecou",
        "Xanadu", "Xynotyro", "Yarg Cornish", "Yarra Valley Pyramid", "Yorkshire Blue",
        "Zamorano", "Zanetti Grana Padano", "Zanetti Parmigiano Reggiano")

文件名和类名不要太在意,因为参考的官网示例,做具体业务的时候再自己调整

2、直接网络获取数据加载

参考1、从本地Room数据库加载数据

自定义pagingsource访问网络数据

class WaitDealDataSource : PagingSource<Int, WaitDealBean>() {

    override fun getRefreshKey(state: PagingState<Int, WaitDealBean>): Int? = null

    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, WaitDealBean> {
        val nextPageNumber = params.key ?: 1
        val size = params.loadSize
        val result = NetworkService.api.getPlanList(nextPageNumber, size,"planTime")
        return LoadResult.Page(
            data = if(result.success) result.data!! else ArrayList(),
            prevKey = null, // Only paging forward.  只向后加载就给 null
            //nextKey 下一页页码;  尾页给 null;  否则当前页码加1
            nextKey = if(result.currentPage!! >= result.totalPageSize!!) null else (nextPageNumber + 1)
        )
    }
}

ViewModel中使用

 val allWaitDeal: Flow<PagingData<WaitDealItem>> = Pager(
        config = PagingConfig(
            pageSize = 10,
            enablePlaceholders = true
        )
    ) {
        WaitDealDataSource()
    }.flow
        .map { pagingData ->
            pagingData
                // Map cheeses to common UI model.
                .map { cheese -> WaitDealItem.Item(cheese) }
                .insertSeparators { before: WaitDealItem?, after: WaitDealItem? ->
                    if (before == null && after == null) {
                        // List is empty after fully loaded; return null to skip adding separator.
                        null
                    } else if (after == null) {
                        // Footer; return null here to skip adding a footer.
                        null
                    } else if (before == null) {
                        // Header
                        WaitDealItem.Separator(after.sTime.substring(0,10))
                    } else if (!before.sTime.substring(0,10).equals(after.sTime.substring(0,10), ignoreCase = true)){
                        // Between two items that start with different letters.
                        WaitDealItem.Separator(after.sTime.substring(0,10))
                    } else {
                        // Between two items that start with the same letter.
                        null
                    }
                }
        }
        .cachedIn(viewModelScope)

3、网络访问数据到Room数据库再加载数据

参考1、从本地Room数据库加载数据

 自定义RemoteMediator访问网络数据

@OptIn(ExperimentalPagingApi::class)
class WaitDealRemoteMediator (
    private val pageSize: Int,
    private val database: LocalDb
) : RemoteMediator<Int, WaitDealBean>() {
    val waitDealDao = database.waitdealDao()
    override suspend fun initialize(): InitializeAction {
        return InitializeAction.SKIP_INITIAL_REFRESH
    }
    override suspend fun load(
        loadType: LoadType,
        state: PagingState<Int, WaitDealBean>
    ): MediatorResult {

        return try {
            val pageNum = when (loadType) {
                //首次访问 或者调用 PagingDataAdapter.refresh()时
                LoadType.REFRESH -> 1
                //在当前加载的数据集的开头加载数据时
                LoadType.PREPEND ->
                    return MediatorResult.Success(true)
                //下拉加载更多时
                LoadType.APPEND -> {
                    val lastItem = state.lastItemOrNull()
                    if (lastItem == null) {//最后一项为空直接返回
                        return MediatorResult.Success( true)
                    }
                    //获取最后一个page
                    val page = state.pages[state.pages.size - 1]
                    //下一页pageNum = ((列表总数量)/每页数量 )+1
                    (page.itemsBefore+page.data.size)/ pageSize+1
                }
            }
            //无网络则加载本地数据
            if (!NetworkUtils.isConnected()) {
                return MediatorResult.Success(endOfPaginationReached = true)
            }
            //region 更新数据库
            val response = NetworkService.api.getPlanList(pageNum,pageSize)
            database.withTransaction {
                if (loadType == LoadType.REFRESH) {
                    waitDealDao.clearAll()
                }
                waitDealDao.insertAll(response.data)
            }
            //endregion

            //返回true则表示加载完成,返回false会继续加载下一页数据
            val isFinished = response.data == null || response.data!!.size.toLong() >= response.totalSize!!
            MediatorResult.Success(isFinished)
        } catch (e: Exception) {
            ExceptionUtil.catchException(e)
            MediatorResult.Error(e)
        }
    }
}

使用其他地方不变,在pager中添加remoteMediator参数即可

 

4、封装使用

BaseViewHolder

class BaseViewHolder(val viewGroup: ViewGroup, viewType: Int): RecyclerView.ViewHolder(
  LayoutInflater.from(viewGroup.context).inflate(viewType, viewGroup, false)
) 

列表单个item封装的基类

import androidx.annotation.LayoutRes

/**
 * 单个item
 */
abstract class PagingItemView<T : Any>(@LayoutRes val layoutRes: Int,val entity: T) {

    abstract fun onBindView(holder: BaseViewHolder, position: Int)

    abstract fun areItemsTheSame(data: T): Boolean

    abstract fun areContentsTheSame(data: T): Boolean
}

封装统一的adapter


import android.content.Context
import android.view.ViewGroup
import androidx.paging.PagingDataAdapter
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView

/**
 * 统一的adapter
 */
class PagingAdapter<T:Any>(val context: Context) :  PagingDataAdapter<PagingItemView<T>, RecyclerView.ViewHolder>(
    object : DiffUtil.ItemCallback<PagingItemView<T>>() {
        override fun areItemsTheSame(oldItem: PagingItemView<T>, newItem: PagingItemView<T>): Boolean {
            return oldItem.areItemsTheSame(newItem.entity)
        }

        override fun areContentsTheSame(oldItem: PagingItemView<T>, newItem: PagingItemView<T>): Boolean {
            return oldItem.areContentsTheSame(newItem.entity)
        }
    }
){
    // 获取到对应的itemview去调用onBindView方法设置UI
    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        if (position != RecyclerView.NO_POSITION) {
            getItem(position)?.onBindView(holder = holder as BaseViewHolder, position = position)
        }
    }

    // 这里用itemView的layoutRes去作为viewtype,这样不同布局的itemview就可以区分开来
    override fun getItemViewType(position: Int): Int {
        return getItem(position)!!.layoutRes
    }

    // 因为上面是用layoutRes来作为itemType,所以创建viewholder的时候直接用viewType来创建
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return BaseViewHolder(parent,viewType)
    }

}

使用方法

自定义PagingItemView

class ItemWaitDeal(entity: WaitDealBean) : PagingItemView<WaitDealBean>(R.layout.wait_deal_item,entity) {
    override fun areItemsTheSame(data: WaitDealBean): Boolean {
        return entity.id!! == data.id
    }

    override fun areContentsTheSame(data: WaitDealBean): Boolean {
        return entity.id!! == data.id
    }

    override fun onBindView(holder: BaseViewHolder, position: Int) {
        val tvDay  = holder.itemView.findViewById<TextView>(R.id.tv_day)
        val tvMinute  = holder.itemView.findViewById<TextView>(R.id.tv_minute)
        val titleView  = holder.itemView.findViewById<TextView>(R.id.title)
        val contentView  = holder.itemView.findViewById<TextView>(R.id.content)
        titleView.text = entity.title
        contentView.text = entity.content
        contentView.visibility =  if(StringUtils.isTrimEmpty(entity.content)) View.GONE else View.VISIBLE
        val planTime = entity.planTime?.substring(0,10)
        tvDay.text = planTime
    }
}

viewmodel获取数据后,把获取到的pagingData装到自定义的PagingItemView中

class WaitDealViewModel : BaseViewModel() {

    fun <T : Any> queryData(): Flow<PagingData<PagingItemView<T>>> {
        val database = LocalDb.get(BaseApplication.instance)
        val NETWORK_PAGE_SIZE = 10
        @OptIn(ExperimentalPagingApi::class)
        return Pager(
            config = PagingConfig(
                pageSize = NETWORK_PAGE_SIZE,
                enablePlaceholders = false
            ),
            //访问网络数据,检查数据更新
            remoteMediator = WaitDealRemoteMediator(NETWORK_PAGE_SIZE, database)
        ) {
            //查询数据库数据
            database.waitdealDao().queryAll()
        }.flow
            .map { pagingData->
                pagingData.map { item ->
                    //转换数据
                    ItemWaitDeal(item) as (PagingItemView<T>)
                }
            }.cachedIn(viewModelScope)
    }

}

fragment中使用

class WaitDealFragment : BaseViewBindingFragment<FragmentWaitDealBinding>() {

    override fun getViewBinding() = FragmentWaitDealBinding.inflate(layoutInflater)

    private val mViewModel: WaitDealViewModel by viewModels{
        object : ViewModelProvider.Factory{
            override fun <T : ViewModel> create(modelClass: Class<T>): T {
                return WaitDealViewModel()  as T
            }
        }
    }

    override fun initView() {
        val pagingAdapter = PagingAdapter<WaitDealBean>(requireContext())
        mViewBinding?.recyclerView?.layoutManager = LinearLayoutManager(context)
        mViewBinding?.recyclerView?.adapter = pagingAdapter
        mViewBinding?.swipeRefreshLayout?.bindAdapter(pagingAdapter)
        lifecycleScope.launch {
            mViewModel.queryData<WaitDealBean>().collectLatest { pagingAdapter.submitData(it) }
        }
    }


}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值