{"id":1645,"date":"2022-12-21T09:52:19","date_gmt":"2022-12-21T01:52:19","guid":{"rendered":"https:\/\/badgameshow.com\/fly\/?p=1645"},"modified":"2023-02-08T20:10:57","modified_gmt":"2023-02-08T12:10:57","slug":"%e3%80%90jetpack%e3%80%91android-paging3-%e5%88%86%e9%a0%81%e5%8a%a0%e8%bc%89-%e7%af%84%e4%be%8b","status":"publish","type":"post","link":"https:\/\/badgameshow.com\/fly\/%e3%80%90jetpack%e3%80%91android-paging3-%e5%88%86%e9%a0%81%e5%8a%a0%e8%bc%89-%e7%af%84%e4%be%8b\/","title":{"rendered":"\ud83d\udcc8\u3010Jetpack\u3011Android Paging3 \u5206\u9801\u52a0\u8f09 \u7bc4\u4f8b\ud83d\udd03"},"content":{"rendered":"<h1>\ud83d\udcc8\u3010Jetpack\u3011Android Paging3 \u5206\u9801\u52a0\u8f09 \u7bc4\u4f8b\ud83d\udd03<\/h1>\n<h4>Android Paging3 \u662f\u4e00\u500b\u65b0\u4e00\u4ee3\u7684 Android \u5167\u5b58\u7ba1\u7406\u5eab\uff0c\u5b83\u7684\u76ee\u7684\u662f\u63d0\u4f9b\u4e00\u500b\u5b89\u5168\u53ef\u9760\u7684\u65b9\u6cd5\uff0c\u70ba Android \u6578\u64da\u5eab\u548c\u7db2\u7d61\u6578\u64da\u63d0\u4f9b\u5206\u9801\u6280\u8853\u652f\u6301\u3002<\/h4>\n<h4>\u5b83\u63d0\u4f9b\u4e86\u5f48\u6027\u7684\u65b9\u6cd5\u8b93 UI \u53ef\u4ee5\u52a0\u8f09\u61c9\u7528\u7a0b\u5e8f\u5167\u5bb9\uff0c\u7121\u8ad6\u662f\u5f9e\u6578\u64da\u5eab\u6216\u8005\u7db2\u7d61\u6578\u64da\u6e90\u52a0\u8f09\uff0c\u5167\u5b58\u4f54\u7528\u4e5f\u53ef\u4ee5\u5f97\u5230\u63a7\u5236\uff0c\u4f86\u5e6b\u52a9\u9700\u8981\u8655\u7406\u5927\u91cf\u6578\u64da\u7684\u624b\u6a5f\u61c9\u7528\u958b\u767c\u8005\u63d0\u9ad8\u7522\u54c1\u7684\u6027\u80fd\u8207\u97ff\u61c9\u3002<\/h4>\n<h4>Paging3 \u7684\u6700\u521d\u7684\u76ee\u6a19\u662f\u8655\u7406\u6602\u8cb4\u7684 SQL \u67e5\u8a62\uff0c\u8655\u7406\u6578\u64da\u5eab\u5167\u5bb9\uff0c\u4ee5\u53ca\u52a0\u8f09\u904e\u591a\u6703\u5c0e\u81f4 OutOfMemory \u4fdd\u8b77\u7684\u7db2\u7d61\u6578\u64da\u3002\u5b83\u53ef\u4ee5\u652f\u6301\u540c\u6b65\u548c\u7570\u6b65\u7684\u6578\u64da\u52a0\u8f09\uff0c\u4e26\u4e14\u540c\u6642\u517c\u5bb9\u5927\u90e8\u5206\u7684 Android \u5eab\uff08\u6bd4\u5982 Room \u6578\u64da\u5eab\uff09\u3002<\/h4>\n<p><a class=\"wp-editor-md-post-content-link\" href=\"https:\/\/developer.android.com\/static\/topic\/libraries\/architecture\/images\/paging3-library-architecture.svg\" target=\"_blank\" rel=\"noopener\"><img decoding=\"async\" src=\"https:\/\/developer.android.com\/static\/topic\/libraries\/architecture\/images\/paging3-library-architecture.svg\" alt=\"\" \/><\/a><\/p>\n<hr \/>\n<h4>\u6587\u7ae0\u76ee\u9304<\/h4>\n<ol>\n<li><a href=\"#a\">Paging3 &#038; Retrofit &#038; Fragment KTX \u5c0e\u5165<\/a><\/li>\n<li><a href=\"#b\">Paging3 Response Model<\/a><\/li>\n<li><a href=\"#c\">Paging3 Retrofit &#038; Service<\/a><\/li>\n<li><a href=\"#d\">Paging3 PagingSource<\/a><\/li>\n<li><a href=\"#e\">Paging3 Repository<\/a><\/li>\n<li><a href=\"#f\">Paging3 ViewModel<\/a><\/li>\n<li><a href=\"#g\">Paging3 Adapter<\/a><\/li>\n<li><a href=\"#h\">Paging3 FooterAdapter<\/a><\/li>\n<li><a href=\"#i\">Paging3 <\/a><\/li>\n<li><a href=\"#j\">Developer Documents Paging3<\/a><\/li>\n<\/ol>\n<hr \/>\n<p><a id=\"a\"><\/a><\/p>\n<h4>1.Paging3 &amp; Retrofit &amp; Fragment KTX \u5c0e\u5165<\/h4>\n<h5>build.gradle<\/h5>\n<pre><code class=\"language-groovy line-numbers\">android {\n\n    buildFeatures {\n        dataBinding true\n    }\n}\n\ndependencies {\n    def fragment_version = \"1.5.5\"\n    implementation \"androidx.fragment:fragment-ktx:<span class=\"katex math inline\">fragment_version\"\n\n    implementation 'androidx.paging:paging-runtime:3.2.0-alpha03'\n    implementation 'com.squareup.retrofit2:retrofit:2.9.0'\n    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'\n\n    def paging_version = \"3.1.1\"\n    implementation \"androidx.paging:paging-runtime:<\/span>paging_version\"\n}\n<\/code><\/pre>\n<p><a id=\"b\"><\/a><\/p>\n<h4>2.Paging3 Response Model<\/h4>\n<h5>RepoRec.kt<\/h5>\n<pre><code class=\"language-kotlin line-numbers\">data class RepoRec(\n    @SerializedName(\"items\") val items: List&lt;Repo&gt;\n)\n\ndata class Repo(\n    @SerializedName(\"id\") val id: Int,\n    @SerializedName(\"name\") val name: String,\n    @SerializedName(\"description\") val description: String,\n    @SerializedName(\"stargazers_count\") val starCount: Int\n)\n<\/code><\/pre>\n<p><a class=\"wp-editor-md-post-content-link\" href=\"https:\/\/badgameshow.com\/fly\/wp-content\/uploads\/2022\/12\/1-1.png\"><img decoding=\"async\" src=\"https:\/\/badgameshow.com\/fly\/wp-content\/uploads\/2022\/12\/1-1.png\" alt=\"\" \/><\/a><\/p>\n<p><a id=\"c\"><\/a><\/p>\n<h4>3.Paging3 Retrofit &amp; Service<\/h4>\n<h5>RetrofitUtils.kt<\/h5>\n<pre><code class=\"language-kotlin line-numbers\">class RetrofitUtils {\n\n    companion object {\n        val instance: RetrofitUtils by lazy { RetrofitUtils() }\n    }\n\n    fun &lt;T&gt; getService(clazz: Class&lt;T&gt;): T {\n        return retrofit.create(clazz)\n    }\n\n    private val retrofit = Retrofit.Builder()\n        .addConverterFactory(GsonConverterFactory.create())\n        .baseUrl(\"https:\/\/api.github.com\/\")\n        .build()\n}\n<\/code><\/pre>\n<h5>GitHubService.kt<\/h5>\n<pre><code class=\"language-kotlin line-numbers\">interface GitHubService {\n\n    @GET(\"search\/repositories?sort=stars&amp;q=Android\")\n    suspend fun searchRepositories(@Query(\"page\") page: Int, @Query(\"per_page\") perPage: Int) : RepoRec\n\n}\n<\/code><\/pre>\n<p><a id=\"d\"><\/a><\/p>\n<h4>4.Paging3 PagingSource<\/h4>\n<h5>RepoPagingSource.kt<\/h5>\n<pre><code class=\"language-kotlin line-numbers\">class RepoPagingSource(private val gitHubService: GitHubService): PagingSource&lt;Int, Repo&gt;() {\n\n    override suspend fun load(params: LoadParams&lt;Int&gt;): LoadResult&lt;Int, Repo&gt; {\n        return try {\n            val page = params.key ?: 1\n            val pageSize = params.loadSize\n            val repoRec = gitHubService.searchRepositories(page, pageSize)\n            val repoItems = repoRec.items\n            val prevKey = if (page &gt; 1) page - 1 else null\n            val nextKey = if (repoItems.isNotEmpty()) page + 1 else null\n            LoadResult.Page(repoItems, prevKey, nextKey)\n        } catch (e: Exception) {\n            LoadResult.Error(e)\n        }\n    }\n\n    override fun getRefreshKey(state: PagingState&lt;Int, Repo&gt;): Int? = null\n}\n<\/code><\/pre>\n<p><a id=\"e\"><\/a><\/p>\n<h4>5.Paging3 Repository<\/h4>\n<h5>Repository.kt<\/h5>\n<pre><code class=\"language-kotlin line-numbers\">object Repository {\n\n    private const val PAGE_SIZE = 50\n\n    private val gitHubService = RetrofitUtils.instance.getService(GitHubService::class.java)\n\n    fun getPagingData(): Flow&lt;PagingData&lt;Repo&gt;&gt; = Pager(\n        config = PagingConfig(PAGE_SIZE),\n        pagingSourceFactory = { RepoPagingSource(gitHubService) }\n    ).flow\n\n}\n<\/code><\/pre>\n<p><a id=\"f\"><\/a><\/p>\n<h4>6.Paging3 ViewModel<\/h4>\n<h5>MainViewModel.kt<\/h5>\n<pre><code class=\"language-kotlin line-numbers\">class MainViewModel : ViewModel() {\n\n    fun getPagingData(): Flow&lt;PagingData&lt;Repo&gt;&gt; {\n        return Repository.getPagingData().cachedIn(viewModelScope)\n    }\n\n}\n<\/code><\/pre>\n<p><a id=\"g\"><\/a><\/p>\n<h4>7.Paging3 Adapter<\/h4>\n<h5>repo_item.xml<\/h5>\n<pre data-language=XML><code class=\"language-markup line-numbers\">&lt;?xml version=\"1.0\" encoding=\"utf-8\"?&gt;\n&lt;layout xmlns:android=\"http:\/\/schemas.android.com\/apk\/res\/android\"\n    xmlns:tools=\"http:\/\/schemas.android.com\/tools\"&gt;\n\n    &lt;data&gt;\n        &lt;variable\n            name=\"repo\"\n            type=\"com.example.jetpackdemo.Repo\" \/&gt;\n    &lt;\/data&gt;\n\n    &lt;LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:padding=\"10dp\"\n        android:orientation=\"vertical\"&gt;\n\n        &lt;TextView\n            android:text=\"@{repo.name}\"\n            android:id=\"@+id\/name_text\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:gravity=\"center_vertical\"\n            android:maxLines=\"1\"\n            android:ellipsize=\"end\"\n            android:textColor=\"#5194fd\"\n            android:textSize=\"20sp\"\n            android:textStyle=\"bold\" \/&gt;\n\n        &lt;TextView\n            android:text=\"@{repo.description}\"\n            android:id=\"@+id\/description_text\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"10dp\"\n            android:maxLines=\"10\"\n            android:ellipsize=\"end\" \/&gt;\n\n        &lt;LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"10dp\"\n            android:gravity=\"end\"\n            tools:ignore=\"UseCompoundDrawables\"&gt;\n\n            &lt;ImageView\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center_vertical\"\n                android:layout_marginEnd=\"5dp\"\n                android:src=\"@drawable\/ic_star\"\n                tools:ignore=\"ContentDescription\" \/&gt;\n\n            &lt;TextView\n                android:text=\"@{Integer.toString(repo.starCount)}\"\n                android:id=\"@+id\/star_count_text\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center_vertical\" \/&gt;\n\n        &lt;\/LinearLayout&gt;\n\n    &lt;\/LinearLayout&gt;\n&lt;\/layout&gt;\n<\/code><\/pre>\n<h5>RepoAdapter.kt<\/h5>\n<pre><code class=\"language-kotlin line-numbers\">class RepoAdapter : PagingDataAdapter&lt;Repo, RepoAdapter.RepoViewHolder&gt;(COMPARATOR) {\n\n    companion object {\n        private val COMPARATOR = object : DiffUtil.ItemCallback&lt;Repo&gt;() {\n            override fun areItemsTheSame(oldItem: Repo, newItem: Repo): Boolean {\n                return oldItem.id == newItem.id\n            }\n\n            override fun areContentsTheSame(oldItem: Repo, newItem: Repo): Boolean {\n                return oldItem == newItem\n            }\n        }\n    }\n\n    override fun onBindViewHolder(holder: RepoViewHolder, position: Int) {\n        val repo = getItem(position)\n        holder.bind(repo)\n    }\n\n    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =\n        RepoViewHolder(RepoItemBinding.inflate(LayoutInflater.from(parent.context), parent, false))\n\n    class RepoViewHolder(private val binding: RepoItemBinding) : ViewHolder(binding.root) {\n        fun bind(repo: Repo?) {\n            binding.repo = repo\n            binding.executePendingBindings()\n        }\n    }\n}\n<\/code><\/pre>\n<p><a id=\"h\"><\/a><\/p>\n<h4>8.Paging3 FooterAdapter<\/h4>\n<h5>footer_item.xml<\/h5>\n<pre data-language=XML><code class=\"language-markup line-numbers\">&lt;?xml version=\"1.0\" encoding=\"utf-8\"?&gt;\n&lt;layout xmlns:android=\"http:\/\/schemas.android.com\/apk\/res\/android\"&gt;\n\n    &lt;data&gt;\n\n    &lt;\/data&gt;\n\n    &lt;FrameLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:padding=\"10dp\"&gt;\n\n        &lt;ProgressBar\n            android:id=\"@+id\/progress_bar\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center\" \/&gt;\n\n        &lt;Button\n            android:id=\"@+id\/retry_button\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center\"\n            android:text=\"Retry\" \/&gt;\n\n    &lt;\/FrameLayout&gt;\n&lt;\/layout&gt;\n<\/code><\/pre>\n<h5>FooterAdapter.kt<\/h5>\n<pre><code class=\"language-kotlin line-numbers\">class FooterAdapter(private val retry: () -&gt; Unit) : LoadStateAdapter&lt;FooterAdapter.FooterViewHolder&gt;() {\n\n    override fun onBindViewHolder(holder: FooterViewHolder, loadState: LoadState) {\n        holder.bind(loadState, retry)\n    }\n\n    override fun onCreateViewHolder(parent: ViewGroup, loadState: LoadState): FooterViewHolder {\n        return FooterViewHolder(FooterItemBinding.inflate(\n            LayoutInflater.from(parent.context), parent, false)\n        )\n    }\n\n    class FooterViewHolder(private val binding: FooterItemBinding) : ViewHolder(binding.root) {\n        fun bind(loadState: LoadState, retry: () -&gt; Unit) {\n            binding.retryButton.setOnClickListener {\n                retry()\n            }\n            binding.progressBar.isVisible = loadState is LoadState.Loading\n            binding.retryButton.isVisible = loadState is LoadState.Error\n        }\n    }\n}\n<\/code><\/pre>\n<p><a id=\"i\"><\/a><\/p>\n<h4>9.Paging3<\/h4>\n<h5>activity_main.xml<\/h5>\n<pre data-language=XML><code class=\"language-markup line-numbers\">&lt;?xml version=\"1.0\" encoding=\"utf-8\"?&gt;\n&lt;layout xmlns:android=\"http:\/\/schemas.android.com\/apk\/res\/android\"\n    xmlns:app=\"http:\/\/schemas.android.com\/apk\/res-auto\"\n    xmlns:tools=\"http:\/\/schemas.android.com\/tools\"&gt;\n\n    &lt;data&gt;\n\n    &lt;\/data&gt;\n\n    &lt;FrameLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        tools:context=\".MainActivity\"&gt;\n\n        &lt;androidx.recyclerview.widget.RecyclerView\n            android:id=\"@+id\/recycler_view\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\" \/&gt;\n\n        &lt;ProgressBar\n            android:id=\"@+id\/progress_bar\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center\" \/&gt;\n\n    &lt;\/FrameLayout&gt;\n&lt;\/layout&gt;\n<\/code><\/pre>\n<h5>MainActivity.kt<\/h5>\n<pre><code class=\"language-kotlin line-numbers\">class MainActivity : AppCompatActivity() {\n\n    private lateinit var binding: ActivityMainBinding\n    private val viewModel: MainViewModel by viewModels()\n    private val repoAdapter = RepoAdapter()\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContentView(R.layout.activity_main)\n\n        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)\n\n        binding.recyclerView.apply {\n            layoutManager = LinearLayoutManager(this@MainActivity)\n            adapter = repoAdapter.withLoadStateFooter(FooterAdapter { repoAdapter.retry() })\n        }\n\n        lifecycleScope.launch {\n            viewModel.getPagingData().collect { pagingData -&gt;\n                repoAdapter.submitData(pagingData)\n            }\n        }\n\n        repoAdapter.addLoadStateListener {\n            when (it.refresh) {\n                is LoadState.NotLoading -&gt; {\n                    binding.progressBar.visibility = View.INVISIBLE\n                    binding.recyclerView.visibility = View.VISIBLE\n                }\n                is LoadState.Loading -&gt; {\n                    binding.progressBar.visibility = View.VISIBLE\n                    binding.recyclerView.visibility = View.INVISIBLE\n                }\n                is LoadState.Error -&gt; {\n                    val state = it.refresh as LoadState.Error\n                    binding.progressBar.visibility = View.INVISIBLE\n                    Toast.makeText(this, \"Load Error: ${state.error.message}\", Toast.LENGTH_SHORT)\n                        .show()\n                }\n            }\n        }\n    }\n}\n<\/code><\/pre>\n<p><a id=\"j\"><\/a><\/p>\n<h4>10.Developer Documents Paging3<\/h4>\n<p><a class=\"wp-editor-md-post-content-link\" href=\"https:\/\/developer.android.com\/topic\/libraries\/architecture\/paging\/v3-overview\" title=\"Open in Documents Paging3\" target=\"_blank\" rel=\"noopener\">Open in Documents Paging3<\/a><\/p>\n\n<div style=\"font-size: 0px; height: 0px; line-height: 0px; margin: 0; padding: 0; clear: both;\"><\/div>","protected":false},"excerpt":{"rendered":"<p>\ud83d\udcc8\u3010Jetpack\u3011Android Paging3 \u5206\u9801\u52a0\u8f09 \u7bc4\u4f8b\ud83d\udd03 Android Paging3 \u662f\u4e00\u500b\u65b0 &hellip; <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"pgc_sgb_lightbox_settings":"","footnotes":""},"categories":[106],"tags":[13,107,223],"class_list":["post-1645","post","type-post","status-publish","format-standard","hentry","category-jetpack","tag-android","tag-jetpack","tag-paging3"],"_links":{"self":[{"href":"https:\/\/badgameshow.com\/fly\/wp-json\/wp\/v2\/posts\/1645","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/badgameshow.com\/fly\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/badgameshow.com\/fly\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/badgameshow.com\/fly\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/badgameshow.com\/fly\/wp-json\/wp\/v2\/comments?post=1645"}],"version-history":[{"count":5,"href":"https:\/\/badgameshow.com\/fly\/wp-json\/wp\/v2\/posts\/1645\/revisions"}],"predecessor-version":[{"id":1876,"href":"https:\/\/badgameshow.com\/fly\/wp-json\/wp\/v2\/posts\/1645\/revisions\/1876"}],"wp:attachment":[{"href":"https:\/\/badgameshow.com\/fly\/wp-json\/wp\/v2\/media?parent=1645"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/badgameshow.com\/fly\/wp-json\/wp\/v2\/categories?post=1645"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/badgameshow.com\/fly\/wp-json\/wp\/v2\/tags?post=1645"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}