Fragment

         
 Build.gradle


plugins {
    alias(libs.plugins.androidApplication)
    alias(libs.plugins.jetbrainsKotlinAndroid)
}

android {
    namespace = "com.akashpal.gcrg.mvvm"
    compileSdk = 34

    defaultConfig {
        applicationId = "com.akashpal.gcrg.mvvm"
        minSdk = 24
        targetSdk = 34
        versionCode = 1
        versionName = "1.0"

        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            isMinifyEnabled = false
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
    buildFeatures {
        dataBinding=true
        viewBinding=true
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = "1.8"
    }
}

dependencies {

    implementation(libs.androidx.core.ktx)
    implementation(libs.androidx.appcompat)
    implementation(libs.material)
    implementation(libs.androidx.activity)
    implementation(libs.androidx.constraintlayout)
    testImplementation(libs.junit)
    androidTestImplementation(libs.androidx.junit)
    androidTestImplementation(libs.androidx.espresso.core)

    implementation("com.squareup.retrofit2:retrofit:2.9.0")
    implementation("com.squareup.retrofit2:converter-gson:2.9.0")
    implementation("com.google.code.gson:gson:2.10.1")

    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9")

    implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.4")
    implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.8.4")

    implementation ("androidx.paging:paging-runtime:3.1.1")
    
    
    https://github.com/Akashpal12/AndroidPagging3.git
    https://drive.google.com/file/d/10SQXXa8MhRvGxsIs4-FfHImwhI1D9yjF/view?usp=sharing


}

         
 api/ApiService

package com.akashpal.gcrg.mvvm.api

import com.akashpal.gcrg.mvvm.models.PostModelItem
import retrofit2.Response
import retrofit2.http.GET
import retrofit2.http.Query

interface ApiService {
    @GET("/posts")
    suspend fun getPosts(): Response<List<PostModelItem>>

    @GET("posts")
    suspend fun getPagePosts(
        @Query("_page") page: Int,
        @Query("_limit") limit: Int
    ): List<PostModelItem>
}

    
         
 api/RetrofitClient

package com.akashpal.gcrg.mvvm.api

import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

object RetrofitClient {
    private const val BASE_URL = "https://jsonplaceholder.typicode.com"
    fun getClient(): ApiService {
        val retrofit = Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
        return retrofit.create(ApiService::class.java)
    }
}
    
         
 respository/PostRepository

package com.akashpal.gcrg.mvvm.repository

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.map
import com.akashpal.gcrg.mvvm.api.ApiService
import com.akashpal.gcrg.mvvm.models.PostModelItem

class PostRepository(private val apiService: ApiService) {

    private val postsLiveData = MutableLiveData<List<PostModelItem>>()
    val posts: LiveData<List<PostModelItem>> get() = postsLiveData

    suspend fun getPosts() {
        val result = apiService.getPosts()
        if (result.isSuccessful && result.body() != null) {
            postsLiveData.postValue(result.body())
        }
    }

}
    
         
 respository/PostPagingRepository

package com.akashpal.gcrg.mvvm.repository

import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.liveData
import com.akashpal.gcrg.mvvm.api.ApiService
import com.akashpal.gcrg.mvvm.models.PostModelItem
import com.akashpal.gcrg.mvvm.paging.PostPagingSource

class PostPagingRepository(private val apiService: ApiService) {
    fun getPagePosts() = Pager<Int, PostModelItem>(
        config = PagingConfig(
            pageSize = 10,
        ),
        pagingSourceFactory = { PostPagingSource(apiService) }
    ).liveData
}
    
         
 viewModel/PostViewModel

package com.akashpal.gcrg.mvvm.viewModel

import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.map
import androidx.lifecycle.viewModelScope
import com.akashpal.gcrg.mvvm.models.PostModelItem
import com.akashpal.gcrg.mvvm.repository.PostRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch

class PostViewModel(private val repository: PostRepository) : ViewModel() {
    val posts: LiveData<List<PostModelItem>> get() = repository.posts

    init {
        viewModelScope.launch(Dispatchers.IO) {
            repository.getPosts()
        }
    }

    // Function to get a specific post by ID
    fun getPostById(id: Int): LiveData<PostModelItem?> {
        return posts.map { list ->
            list.find { it.id == id }
        }
    }

    // Function to get a list of post titles for the Spinner
    fun getPostTitles(): LiveData<List<String>> {
        return posts.map { list ->
            list.map { "${it.userId} - ${it.id}" }
        }
    }

}
    
         
 respository/PostViewModelFactory

package com.akashpal.gcrg.mvvm.viewModel

import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.akashpal.gcrg.mvvm.repository.PostRepository

class PostViewModelFactory(private val repository: PostRepository) : ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        return PostViewModel(repository) as T
    }
}
    
         
 respository/PostPagingViewModel

package com.akashpal.gcrg.mvvm.viewModel

import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.PagingData
import androidx.paging.cachedIn
import com.akashpal.gcrg.mvvm.models.PostModelItem
import com.akashpal.gcrg.mvvm.repository.PostPagingRepository

class PostPagingViewModel(private val repository: PostPagingRepository) : ViewModel() {
    val posts: LiveData<PagingData<PostModelItem>> = repository.getPagePosts().cachedIn(viewModelScope)
}
    
         
 respository/PostPagingViewModelFactory

package com.akashpal.gcrg.mvvm.viewModel

import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.akashpal.gcrg.mvvm.repository.PostPagingRepository
import com.akashpal.gcrg.mvvm.repository.PostRepository

class PostPagingViewModelFactory(private val repository: PostPagingRepository) : ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        return PostPagingViewModel(repository) as T
    }
}
    
         
 adpter/PostAdapter(Only showing for recyclerlist)

package com.akashpal.gcrg.mvvm.adapter

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.akashpal.gcrg.mvvm.R
import com.akashpal.gcrg.mvvm.models.PostModelItem

class PostAdapter : ListAdapter<PostModelItem, PostAdapter.PostViewHolder>(DiffCallback()) {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PostViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.item_post, parent, false)
        return PostViewHolder(view)
    }

    override fun onBindViewHolder(holder: PostViewHolder, position: Int) {
        val post = getItem(position)
        if (post != null) {
            holder.titleTextView.text = post.title
            holder.bodyTextView.text = post.body
        }
    }

    class PostViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val titleTextView: TextView = itemView.findViewById(R.id.tvTitle)
        val bodyTextView: TextView = itemView.findViewById(R.id.tvBody)
    }

    class DiffCallback : DiffUtil.ItemCallback<PostModelItem>() {
        override fun areItemsTheSame(oldItem: PostModelItem, newItem: PostModelItem): Boolean =
            oldItem.id == newItem.id

        override fun areContentsTheSame(oldItem: PostModelItem, newItem: PostModelItem): Boolean =
            oldItem == newItem
    }
}
    
         
 paging/PostPagingAdapter(for pagination adapter)

package com.akashpal.gcrg.mvvm.paging

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.paging.PagingDataAdapter
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.akashpal.gcrg.mvvm.R
import com.akashpal.gcrg.mvvm.models.PostModelItem

class PostPagingAdapter : PagingDataAdapter<PostModelItem, PostPagingAdapter.PostViewHolder>(POST_COMPARATOR) {

    class PostViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        private val titleTextView: TextView = view.findViewById(R.id.tvTitle)
        private val bodyTextView: TextView = view.findViewById(R.id.tvBody)

        fun bind(post: PostModelItem) {
            titleTextView.text = post.title
            bodyTextView.text = post.body
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PostViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.item_post, parent, false)
        return PostViewHolder(view)
    }

    override fun onBindViewHolder(holder: PostViewHolder, position: Int) {
        val post = getItem(position)
        if (post != null) {
            holder.bind(post)
        }
    }

    companion object {
        private val POST_COMPARATOR = object : DiffUtil.ItemCallback<PostModelItem>() {
            override fun areItemsTheSame(oldItem: PostModelItem, newItem: PostModelItem): Boolean {
                return oldItem.id == newItem.id
            }

            override fun areContentsTheSame(oldItem: PostModelItem, newItem: PostModelItem): Boolean {
                return oldItem == newItem
            }
        }
    }
}
    
         
 paging/PostPagingSource(for pagination)

package com.akashpal.gcrg.mvvm.paging

import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.akashpal.gcrg.mvvm.api.ApiService
import com.akashpal.gcrg.mvvm.models.PostModelItem

class PostPagingSource(private val apiService: ApiService) : PagingSource<Int, PostModelItem>() {
    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, PostModelItem> {
        return try {
            val currentPage = params.key ?: 1
            val response = apiService.getPagePosts(page = currentPage, limit = params.loadSize)
            LoadResult.Page(
                data = response,
                prevKey = if (currentPage == 1) null else currentPage - 1,
                nextKey = if (response.isEmpty()) null else currentPage + 1
            )
        } catch (e: Exception) {
            LoadResult.Error(e)
        }
    }

    override fun getRefreshKey(state: PagingState<Int, PostModelItem>): Int? {
        return state.anchorPosition?.let { position ->
            state.closestPageToPosition(position)?.prevKey?.plus(1)
                ?: state.closestPageToPosition(position)?.nextKey?.minus(1)
        }
    }
}
    
         
 paging/PostLoadStateAdapter(for loading pages)

package com.akashpal.gcrg.mvvm.adapter

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.ProgressBar
import android.widget.TextView
import androidx.core.view.isVisible
import androidx.paging.LoadState
import androidx.paging.LoadStateAdapter
import androidx.recyclerview.widget.RecyclerView
import com.akashpal.gcrg.mvvm.R

class PostLoadStateAdapter(private val retry: () -> Unit) : LoadStateAdapter<PostLoadStateAdapter.LoadStateViewHolder>() {

    // ViewHolder class to hold and bind the views
    class LoadStateViewHolder(itemView: View, retry: () -> Unit) : RecyclerView.ViewHolder(itemView) {
        private val progressBar: ProgressBar = itemView.findViewById(R.id.progressBar)
        private val btnRetry: Button = itemView.findViewById(R.id.btnRetry)
        private val errorMsg: TextView = itemView.findViewById(R.id.errorMsg)

        init {
            btnRetry.setOnClickListener { retry.invoke() }
        }

        fun bind(loadState: LoadState) {
            progressBar.isVisible = loadState is LoadState.Loading
            btnRetry.isVisible = loadState is LoadState.Error
            errorMsg.isVisible = loadState is LoadState.Error
        }
    }

    // Create ViewHolder from the layout
    override fun onCreateViewHolder(parent: ViewGroup, loadState: LoadState): LoadStateViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.item_load_state, parent, false)
        return LoadStateViewHolder(view, retry)
    }

    // Bind the data to the ViewHolder
    override fun onBindViewHolder(holder: LoadStateViewHolder, loadState: LoadState) {
        holder.bind(loadState)
    }
}
    
         
 res/activity_main.xml(for loading pages)

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:orientation="vertical"
        android:layout_height="match_parent">
        
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="10dp"
            android:background="#FBDFDF">

            <Spinner
                android:id="@+id/spinner_posts"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginVertical="8dp"
                android:padding="6dp" />
                
        </LinearLayout>

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textSize="18dp"
            android:textStyle="bold"
            android:text="Recycler List"
            android:gravity="center"
            android:layout_marginBottom="4dp"
            android:textColor="@color/black"/>

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recyclerView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

    </LinearLayout>

    <Button
        android:id="@+id/btn"
        android:layout_width="match_parent"
        android:layout_alignParentBottom="true"
        android:layout_marginBottom="10dp"
        android:layout_height="wrap_content"
        android:text="Pagination Activity"/>

</RelativeLayout>
    
         
 res/activity_second.xml(for loading pages)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:id="@+id/main"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".SecondActivity">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="18dp"
        android:textStyle="bold"
        android:text="Recycler Pagination"
        android:gravity="center"
        android:layout_marginBottom="4dp"
        android:textColor="@color/black"/>

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</LinearLayout>

    
         
 res/item_load_state.xml(for loading pages)

<!-- res/layout/item_load_state.xml -->
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:padding="16dp"
        android:gravity="center">

        <ProgressBar
            android:id="@+id/progressBar"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

        <TextView
            android:id="@+id/errorMsg"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Error occurred"
            android:visibility="gone"
            android:paddingTop="8dp" />

        <Button
            android:id="@+id/btnRetry"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Retry"
            android:visibility="gone"
            android:paddingTop="8dp" />

    </LinearLayout>
</layout>
    
         
 res/item_post.xml(for loading pages)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:padding="16dp">

    <TextView
        android:id="@+id/tvTitle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textStyle="bold"
        android:textSize="16sp"
        android:text="Post Title" />

    <TextView
        android:id="@+id/tvBody"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="4dp"
        android:textSize="14sp"
        android:text="Post Body" />

</LinearLayout>
    
         
 MainActivity.kt

<package com.akashpal.gcrg.mvvm

import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.Spinner
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.akashpal.gcrg.mvvm.adapter.PostAdapter
import com.akashpal.gcrg.mvvm.api.RetrofitClient
import com.akashpal.gcrg.mvvm.databinding.ActivityMainBinding
import com.akashpal.gcrg.mvvm.repository.PostRepository
import com.akashpal.gcrg.mvvm.viewModel.PostViewModel
import com.akashpal.gcrg.mvvm.viewModel.PostViewModelFactory

class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding
    lateinit var mainViewModel: PostViewModel
    private lateinit var postAdapter: PostAdapter


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
            val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
            insets
        }

        val postService=RetrofitClient.getClient()
        val repository=PostRepository(postService)

        setupRecyclerView()

        mainViewModel= ViewModelProvider(this,PostViewModelFactory(repository)).get(PostViewModel::class.java)
        mainViewModel.posts.observe(this, Observer {
            if (it != null) {
                Log.d("PostData", it.toString())
                postAdapter.submitList(it)
            }
        })
        mainViewModel.getPostById(2).observe(this, Observer {
            if (it != null) {
                Log.d("SpecificPost", "Post with ID 2: ${it.title}")
            } else {
                Log.d("SpecificPost", "No post found with ID $2")
            }
        })

        val spinner: Spinner = findViewById(R.id.spinner_posts)
        mainViewModel.getPostTitles().observe(this) { titles ->
            val adapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, titles)
            adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
            spinner.adapter = adapter
        }


        spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
            override fun onItemSelected(parent: AdapterView<*>, view: View?, position: Int, id: Long) {
                // Get the selected item
                val selectedItem = parent.getItemAtPosition(position) as String

                // Show a Toast with the selected index and value
                Toast.makeText(
                    this@MainActivity, "Selected index: $position, Value: $selectedItem",
                    Toast.LENGTH_SHORT
                ).show()
            }

            override fun onNothingSelected(parent: AdapterView<*>) {
                // Handle the case when nothing is selected if needed
            }
        }

        binding.btn.setOnClickListener {
            val intent = Intent(this, SecondActivity::class.java)
            startActivity(intent)
        }
    }


    private fun setupRecyclerView() {
        val recyclerView = findViewById(R.id.recyclerView)
        recyclerView.layoutManager = LinearLayoutManager(this)
        postAdapter = PostAdapter()
        recyclerView.adapter = postAdapter
    }


}
    
         
 SecondActivity.kt

<package com.akashpal.gcrg.mvvm

import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import com.akashpal.gcrg.mvvm.adapter.PostLoadStateAdapter
import com.akashpal.gcrg.mvvm.api.RetrofitClient
import com.akashpal.gcrg.mvvm.databinding.ActivitySecondBinding
import com.akashpal.gcrg.mvvm.paging.PostPagingAdapter
import com.akashpal.gcrg.mvvm.repository.PostPagingRepository
import com.akashpal.gcrg.mvvm.viewModel.PostPagingViewModel
import com.akashpal.gcrg.mvvm.viewModel.PostPagingViewModelFactory


class SecondActivity : AppCompatActivity() {

    private lateinit var binding: ActivitySecondBinding
    private lateinit var mainViewModel: PostPagingViewModel
    private lateinit var postAdapter: PostPagingAdapter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        binding = ActivitySecondBinding.inflate(layoutInflater)
        setContentView(binding.root)
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
            val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
            insets
        }


        // Initialize Retrofit and Repository
        val postService = RetrofitClient.getClient()
        val repository = PostPagingRepository(postService)

        // Set up RecyclerView with PagingDataAdapter and LoadStateAdapter
        setupRecyclerView()

        // Initialize ViewModel
        mainViewModel = ViewModelProvider(
            this,
            PostPagingViewModelFactory(repository)
        ).get(PostPagingViewModel::class.java)


        mainViewModel.posts.observe(this, Observer { pagingData ->
            postAdapter.submitData(lifecycle, pagingData)
        })
    }
    private fun setupRecyclerView() {
        binding.recyclerView.apply {
            layoutManager = LinearLayoutManager(this@SecondActivity)
            postAdapter = PostPagingAdapter()
            adapter = postAdapter.withLoadStateHeaderAndFooter(
                header = PostLoadStateAdapter { postAdapter.retry() },
                footer = PostLoadStateAdapter { postAdapter.retry() }
            )
        }
    }


}