Pagination , Navigation , Rest api Calling Retrofit Response, Recycler view with DiffUtils, Coroutines and Coil Dependancy in MVVM architecture Kotlin
In Modern Application Development Consider lot of concepts must know. Like managing the applications perfomance and also the minimum usage of source, maximum productive result and better user experience.
Riyas Pullur
In Modern Application Development Consider lot of concepts must know. Like managing the applications perfomance and also the minimum usage of source, maximum productive result and better user experience.
Here Navigation using Fragment navigation instead of actvity in android. That means Activity is huge process class and also heavy weight for execution , take lot of memory and more take more time.
You want more About Fragment official documentation
https://developer.android.com/guide/fragments
You want more About activity official documentation
https://developer.android.com/reference/android/app/Activity
Actually conssider as fragment is a sub class of activity. Navigation is a process in a single activity set a fragment and we want to change that fragment by clicking a button and change that first to second fragment.
You want more About Navigation official documentation
https://developer.android.com/guide/navigation/navigation-getting-started
Retrofit REST Api calling dependancy more about my Previous documentation
Recycler View is the advance Cocept of List View. The whole data showing in a list based on data size. Diffutils used a concepts if that data changes directly show/hide from UI. Recycler before uses to refresh list notifyDataSetChanged. Actually the use of notify data set changed the whole list full refresh that create a big problem like we used api call that time the only one single item modifies the whole list refreshed that make heavy use of internet and bad user experience. So we want to solve that problem used Diffutils.
If You want more about recycler view official Documentation
https://developer.android.com/reference/kotlin/androidx/recyclerview/widget/RecyclerView
If You want more about DiffUtils official Documentation
https://developer.android.com/reference/androidx/recyclerview/widget/DiffUtil
Other We are used here pagination.Pagination is very helps to better user experience. If you have any api that contains millions of data that load in to your android app. This totally loads in your app app will goto failure chances increases and loss big amount of internet data. How solve it? its an idea to developed the millions of data set as a limit number data contains pages and provide each page a number. when we want to load data calling api with page number that helps reduce over flow of internet usage. when we want to call based on page number used an idea its pagination.
You want to know more about pagination look official documentation.
https://developer.android.com/topic/libraries/architecture/paging/v3-overview
Other Here Using Coil Dependancy for loading image from internet in to your image view in the application.
If you want more about coil
https://coil-kt.github.io/coil/
Finally We discuss about MVVM architecture. In android development developers used many type of architecture standards like MVC , MVP and MVVM. The popular architecture is mvvm actually its model view ViewModel. Model represents data related, view represents UI based and view model represents connection between an model and view.
If you want more about MVVM architecture Official Documentation.
https://developer.android.com/topic/architecture
https://developer.android.com/topic/libraries/architecture/viewmodel
Other here used Coroutines. Actually Coroutines are light weight thread That used to mutli tasking proces.
if you want to know more about it
https://developer.android.com/kotlin/coroutines
Ok,
Lets Start with it,
1.Dependancy and in Gradle
plugins
{
id 'com.android.application' id 'org.jetbrains.kotlin.android' id 'kotlin-kapt'
}
android
{
compileSdk 32 defaultConfig
{
applicationId "com.riyas.entriapp" minSdk 21 targetSdk 32 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes
{
release
{
minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
} }
compileOptions
{
sourceCompatibility JavaVersion.
VERSION_1_8
targetCompatibility JavaVersion.
VERSION_1_8
}
kotlinOptions
{
jvmTarget = '1.8'
}
packagingOptions
{
exclude 'META-INF/ASL2.0' exclude 'META-INF/LICENSE' exclude 'META-INF/NOTICE'
}
lintOptions
{
checkOnly("NewApi", "HandlerLeak") baseline(file("lint-baseline.xml")) abortOnError false
}
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach
{
kotlinOptions
{
freeCompilerArgs += [ "-Xjvm-default=all", ]
} }
buildFeatures
{
viewBinding = true
}}
dependencies
{
implementation 'androidx.core:core-ktx:1.8.0' implementation 'androidx.appcompat:appcompat:1.4.2' implementation 'com.google.android.material:material:1.6.1' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' //paging implementation "androidx.paging:paging-runtime-ktx:3.1.1" //retrofit implementation "com.squareup.retrofit2:retrofit:2.9.0" implementation "com.squareup.retrofit2:converter-gson:2.9.0" implementation "com.squareup.okhttp3:okhttp:5.0.0-alpha.8" //Retrofit - interceptor implementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.8' //coroutine implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4" //coroutine lifecycle scope implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.0" implementation "androidx.lifecycle:lifecycle-runtime:2.5.0" implementation "androidx.activity:activity-ktx:1.5.0" // Navigation component : https://developer.android.com/guide/navigation/navigation-getting-started implementation 'androidx.navigation:navigation-fragment-ktx:2.5.0' implementation 'androidx.navigation:navigation-ui-ktx:2.5.0' implementation 'com.github.bumptech.glide:glide:4.13.2' kapt 'com.github.bumptech.glide:compiler:4.13.2' //coil implementation "io.coil-kt:coil:2.1.0" // Lifecycle - ViewModel implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.0" // Lifecycle - LiveData implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.5.0' // Room implementation "androidx.room:room-runtime:2.4.2" annotationProcessor "androidx.room:room-compiler:2.4.2" // optional - Kotlin Extensions and Coroutines support for Room implementation "androidx.room:room-ktx:2.4.2"
}
kapt
{
correctErrorTypes true
}
2. Next, Manifest File of your Project,
<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="com.riyas.entriapp"> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> <application android:allowBackup="true" android:dataExtractionRules="@xml/data_extraction_rules" android:fullBackupContent="@xml/backup_rules" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.EntriApp" tools:targetApi="31"> <activity android:name=".MainActivity" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application></manifest>
3.Layout file activity_main.xml
<?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=".MainActivity"> <fragment android:id="@+id/fragmentMainFLID" android:layout_width="match_parent" android:layout_height="match_parent" app:defaultNavHost="true" android:name="androidx.navigation.fragment.NavHostFragment" app:navGraph="@navigation/nav_graph" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintLeft_toLeftOf="parent" /></androidx.constraintlayout.widget.ConstraintLayout>
4.Layout file fragment_home.xml
<?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=".view.HomeFragment"> <!--
TODO: Update blank fragment layout
--> <androidx.recyclerview.widget.RecyclerView android:id="@+id/recyclerRvId" android:layout_width="match_parent" android:padding="10dp" android:layout_height="match_parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /></androidx.constraintlayout.widget.ConstraintLayout>
5.Layout File movie_card_show.xml (its for showing card in your recycler view attached throu recycler adapter)
<?xml version="1.0" encoding="utf-8"?><androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="180dp" android:layout_height="300dp" xmlns:app="http://schemas.android.com/apk/res-auto" android:elevation="15dp" android:layout_margin="10dp" app:cardCornerRadius="15dp"> <LinearLayout android:background="#E7F6F5" android:padding="5dp" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <ImageView android:layout_margin="10dp" android:layout_width="match_parent" android:layout_height="150dp" android:src="@drawable/ic_launcher_background" android:scaleType="fitXY" android:id="@+id/imageCardID" /> <TextView android:id="@+id/cardTitleID" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Title" android:textColor="@color/black" android:textSize="18dp" android:textStyle="bold" android:layout_marginBottom="5dp" /> <TextView android:id="@+id/releaseDateID" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="release date" android:textColor="@color/black" /> <TextView android:id="@+id/descriptionCardID" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Description" android:textColor="@color/black" /> <TextView android:id="@+id/languageCardID" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Language" android:textColor="@color/black" /> </LinearLayout></androidx.cardview.widget.CardView>
6. Create a navigation folder in res and also provide navigation graph that used not how navigate with fragments so, nav_graph.xml
<?xml version="1.0" encoding="utf-8"?><navigation 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/nav_graph" app:startDestination="@id/homeFragment"> <fragment android:id="@+id/homeFragment" android:name="com.riyas.entriapp.view.HomeFragment" android:label="fragment_home" tools:layout="@layout/fragment_home" > <action android:id="@+id/action_homeFragment_to_detailsFragment" app:destination="@id/detailsFragment" /> </fragment> <fragment android:id="@+id/detailsFragment" android:name="com.riyas.entriapp.view.DetailsFragment" android:label="fragment_details" tools:layout="@layout/fragment_details" /></navigation>
7. MainActivity.kt
import androidx.appcompat.app.AppCompatActivityimport android.os.Bundleclass MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) }}
8. Model Data Classes,
ResultMovie.kt
data class ResultMovie( val adult: Boolean, val backdrop_path: String, val genre_ids: List<Int>, val id: Int, val original_language: String, val original_title: String, val overview: String, val popularity: Double, val poster_path: String, val release_date: String, val title: String, val video: Boolean, val vote_average: Double, val vote_count: Int)
9.Model Data Classes
MovieList.kt
data class MovieList( val page: Int, val results: List<ResultMovie>, val total_pages: Int, val total_results: Int)
10. Used Some Constants in this app like api link, Api key etc…, Constants.kt
object Constants { const val BASE_URL="https://api.themoviedb.org/" const val BASE_URL_IMAGE="https://image.tmdb.org/t/p/w500" const val BASE_URL_VIDEO="" const val API_KEY="Your Api Key Here"}
11. Network Call using interface, ApiService.kt
interface ApiService { @GET("3/discover/movie") suspend fun getAllMovies( @Query("api_key")api_key:String, @Query("page")page:Int ):MovieList}
12. Create instance to call api, MovieInstance.kt
class MovieInstance { companion object{ val interceptor= HttpLoggingInterceptor().
apply
{
this.level = HttpLoggingInterceptor.Level.
BODY
}
val client= OkHttpClient.Builder().
apply
{
this.addInterceptor(interceptor) .connectTimeout(30, TimeUnit.
SECONDS
) .readTimeout(20, TimeUnit.
SECONDS
) .writeTimeout(25, TimeUnit.
SECONDS
)
}
.build() fun getRetrofitInstance(): Retrofit { return Retrofit.Builder() .baseUrl(Constants.BASE_URL) //logging Interceptor .client(client) .addConverterFactory(GsonConverterFactory.create(GsonBuilder().create())) .build() } }}
13.Create a view Model to the fragment, HomeFragmentViewModel.kt
class HomeFragmentViewModel:ViewModel() { val listData= Pager(PagingConfig(pageSize = 1))
{
MoviePagingSource()
}
.flow.
cachedIn
(
viewModelScope
)}
14. Pagianation, MoviePagingSource.kt
class MoviePagingSource : PagingSource<Int, ResultMovie>() { companion object{ lateinit var dataRoom:List<ResultMovie> var pageRoom: Int ?=null } val movieInstance= MovieInstance.getRetrofitInstance().create(ApiService::class.
java
) override fun getRefreshKey(state: PagingState<Int, ResultMovie>): Int? { return null } override suspend fun load(params: LoadParams<Int>): LoadResult<Int, ResultMovie> { return try { val currentPage=params.key ?: 1 pageRoom=currentPage val response=movieInstance.getAllMovies(Constants.API_KEY,currentPage) val data=response.results ?:
emptyList
() dataRoom=data val responseData=
mutableListOf
<ResultMovie>() responseData.addAll(data) LoadResult.Page( data = responseData, prevKey = if (currentPage ==1) null else -1, nextKey = currentPage.plus(1) ) }catch (e:Exception){ LoadResult.Error(e) } }}
15. Create Adapter For Recycler View , MovieRecyclerAdapter.kt
class MovieRecyclerAdapter : PagingDataAdapter<ResultMovie, MovieRecyclerAdapter.MyViewHolder>(diffUtils) { private var pageNo=MoviePagingSource.pageRoom inner class MyViewHolder (val binding:MovieCardShowBinding): RecyclerView.ViewHolder(binding.
root
) companion object{ var itemData:ResultMovie?=null val diffUtils=object :DiffUtil.ItemCallback<ResultMovie>(){ override fun areItemsTheSame(oldItem: ResultMovie, newItem: ResultMovie): Boolean { return oldItem.id == newItem.id } override fun areContentsTheSame(oldItem: ResultMovie, newItem: ResultMovie): Boolean { return oldItem == newItem } } } override fun onBindViewHolder(holder: MyViewHolder, position: Int) { val item =getItem(position) holder.binding.
apply
{
cardTitleID.
text
=item?.title languageCardID.
text
=item?.original_language releaseDateID.
text
=item?.release_date descriptionCardID.
text
=item?.overview val imageLink=Constants.BASE_URL_IMAGE+item?.poster_path imageCardID.
load
(imageLink) if (pageNo==1){ itemData=item }
}
} override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { return MyViewHolder(MovieCardShowBinding.inflate(LayoutInflater.from(parent.
context
),parent,false)) }}
16. To show whole list in your app so , in your home screen, HomeFragment.kt
class HomeFragment: Fragment() { private lateinit var binding: FragmentHomeBinding lateinit var mAdapter:MovieRecyclerAdapter lateinit var mViewModel: HomeFragmentViewModel override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { // Inflate the layout for this fragment binding=FragmentHomeBinding.inflate(
layoutInflater
) return binding.
root
} override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) mViewModel= ViewModelProvider(this).get(HomeFragmentViewModel::class.
java
) try { if (internetConnectionCheck(requireContext())){ initRecycler() loadingData() }else{ Toast.makeText(
context
,"Check Internet",Toast.
LENGTH_SHORT
).show() } }catch (e:Exception){ Log.d("eeee",e.message.
toString
()) } } private fun loadingData() {
lifecycleScope
.
launch
{
mViewModel.listData.collect
{
pagingData
->
mAdapter.submitData(pagingData)
} }
} private fun initRecycler() { mAdapter= MovieRecyclerAdapter() binding.recyclerRvId.
apply
{
layoutManager
=StaggeredGridLayoutManager(2,StaggeredGridLayoutManager.
VERTICAL
)
adapter
= mAdapter setHasFixedSize(true)
}
} /* private fun insertData(){ lifecycleScope.launch { mRoomViewModel.insertData(MovieModelRoom( MovieRecyclerAdapter.itemData!!.id, MovieRecyclerAdapter.itemData!!.original_language, MovieRecyclerAdapter.itemData!!.original_title, MovieRecyclerAdapter.itemData!!.overview, MovieRecyclerAdapter.itemData!!.poster_path, MovieRecyclerAdapter.itemData!!.release_date, MovieRecyclerAdapter.itemData!!.title)) } }*/ private fun internetConnectionCheck(context: Context):Boolean{ val connectivityManager = context.getSystemService(Context.
CONNECTIVITY_SERVICE
) as ConnectivityManager if (Build.VERSION.
SDK_INT
>= Build.VERSION_CODES.
M
) { val network = connectivityManager.
activeNetwork
?: return false val activeNetwork = connectivityManager.getNetworkCapabilities(network) ?: return false return when { activeNetwork.hasTransport(NetworkCapabilities.
TRANSPORT_WIFI
) -> true activeNetwork.hasTransport(NetworkCapabilities.
TRANSPORT_CELLULAR
) -> true else -> false } } else { @Suppress("DEPRECATION") val networkInfo = connectivityManager.
activeNetworkInfo
?: return false @Suppress("DEPRECATION") return networkInfo.
isConnected
} }}
17. If want show details of that used, DetailsFragment.kt
class DetailsFragment : Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { // Inflate the layout for this fragment return inflater.inflate(R.layout.
fragment_details
, container, false) }}
Its an simple app. I thing you simply understand about it If any queries please inform me.
Support me…
Thank You for Reading ….
Upvote
Riyas Pullur
Self taught Software Engineer, Native Android Developer, Flutter Developer, Backend Developer.

Related Articles