IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Paging3、Room使用,1、从本地Room数据库加载 2、直接网络获取数据加载 3、网络访问数据到Room数据库再加载 4、封装使用 -> 正文阅读

[移动开发]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) }
        }
    }


}

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-09-13 11:27:25  更:2022-09-13 11:29:54 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 4:39:45-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码