0
0
0
share
#Android#RecyclerView#kotlin#loadmore
0 Komentar
Cara Membuat Pagination atau Load More Menggunakan RecyclerView Part 2

Tutorial ini menggunakan bahasa pemrograman Kotlin
Pendahuluan
Pada tutorial sebelumnya Cara Membuat Pagination atau Load More Menggunakan RecyclerView Part 1 sudah saya bahas sedikit tentang pengenalan apa itu pagination dan tujuan dibuatnya fungsi pagination pada aplikasi. Jadi, pada tutorial lanjutan ini saya akan mengajak Anda untuk belajar cara membuat aplikasi di Android yang memanfaatkan fungsi pagination dengan menggunakan data dari TheMovieDb. Dimana, nantinya aplikasi yang akan kita buat akan menampilkan daftar film yang masuk dalam kategori now playing dan kemudian, ketika di-scroll sampai paling bawah maka, aplikasi-nya akan melakukan pengambilan data untuk halaman berikutnya.
Persiapan TheMovieDb
Pembuatan Akun TheMovieDb
Untuk pembuatan akunnya Anda bisa kunjungi situs TheMovieDb. Dan pilih Login jika sudah punya akun dan pilih Sign Up untuk melakukan pendaftaran akun.

Kemudian, lakukan pengisian data akun untuk Anda melakukan pendaftaran akun seperti berikut.

Setelah itu, silakan login dan masuk ke menu settings dan pilih API

Dokumentasi Endpoint TheMovieDb
Untuk melihat dokumentasi endpoint TheMovieDb bisa Anda cari-cari sendiri di halaman website tersebut. Dan endpoint yang akan kita pakai pada tutorial ini adalah https://api.themoviedb.org/3/movie/now_playing?api_key={api_key}&language=en-us&page={page}
Mulai Pembuatan Projek
Buat Projek di Android Studio
Silakan buat projek baru di Android Studio dan isi dengan keterangan berikut. Application name: Codepolitan-Movie Company domain: codepolitan.com

Selanjutnya, pilih Next dan pilih Minimum SDK ke API 15

Setelah itu, di halaman berikutnya pilih Empty Activity

Dan dihalaman berikutnya, biarkan saja dan pilih Finish

Setelah itu, tunggu beberapa saat sampai Android Studio selesai melakukan konfigurasi.

Konfigurasi Dependensi Gradle
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 26
buildToolsVersion "26.0.2"
defaultConfig {
applicationId "com.codepolitan.codepolitan_movie"
minSdkVersion 15
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
flavorDimensions "production", "development"
productFlavors {
production {
applicationIdSuffix ".production"
buildConfigField("String", "API_KEY", "\"Di isi dengan nilai API Key Anda\"") // Ex: \"d6dfdsfdsf\"
buildConfigField("String", "BASE_URL", "\"https://api.themoviedb.org/3/\"")
buildConfigField("String", "LANGUAGE", "\"EN-US\"")
buildConfigField("String", "BASE_URL_IMAGE", "\"https://image.tmdb.org/t/p/w185/\"")
dimension "production"
}
development {
applicationIdSuffix ".development"
buildConfigField("String", "API_KEY", "\"Di isi dengan nilai API Key Anda\"") // Ex: \"d6dfdsfdsf\"
buildConfigField("String", "BASE_URL", "\"https://api.themoviedb.org/3/\"")
buildConfigField("String", "LANGUAGE", "\"EN-US\"")
buildConfigField("String", "BASE_URL_IMAGE", "\"https://image.tmdb.org/t/p/w185/\"")
dimension "development"
}
}
}
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
implementation 'com.android.support:appcompat-v7:26.1.0'
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
testCompile 'junit:junit:4.12'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
implementation 'com.android.support:design:26.1.0'
implementation 'com.squareup.retrofit2:retrofit:2.3.0'
implementation 'com.squareup.retrofit2:retrofit-converters:2.3.0'
implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'
implementation 'io.reactivex.rxjava2:rxjava:2.1.6'
implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'
implementation 'com.google.code.gson:gson:2.8.2'
implementation 'com.github.bumptech.glide:glide:4.3.0'
}
repositories {
mavenCentral()
google()
}
Pada konfigurasi gradle diatas, ada beberapa hal yang konfigurasi yaitu sebagai berikut:
- Konfigurasi
API KEY
TheMovieDb di bagianproductFlavors
dimana, nilai ini harus Anda isi sesuai dengan nilaiAPI Key (v3 auth)
yang ada di menu API pada langkah sebelumnya. - Konfigurasi
BASE URL
dari endpoint yang akan dipakai pada TheMovieDb dimana, nilainya kita sethttps://api.themoviedb.org/3/
- Konfigurasi
LANGUAGE
ini dipakai sebagai parameter ketika memanggil endpoint yang bersangkutan yakni, untuk memanggil endpointhttps://api.themoviedb.org/3/movie/now_playing?api_key={api_key}&language=en-us&page={page}
kita membutuhkan parameterLANGUAGE
- Konfigurasi
BASE_URL_IMAGE
ini dipakai untuk memanggil data poster dari TheMovieDb dimana, nilainya kita sethttps://image.tmdb.org/t/p/w185/
- Setelah, itu ada beberapa dependensi tambahan yang kita pakai pada projek kali ini yaitu,
retrofit
,rxjava
dan beberapa dependensi tambahan lainnya.
Pembuatan layout activity_main.xml
Silakan buka file activity_main.xml dan isi dengan source code berikut.
<?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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.codepolitan.codepolitan_movie.MainActivity">
<ProgressBar
android:id="@+id/progress_bar_horizontal_activity_main"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:indeterminate="true"
android:max="100" />
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view_movie_activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="8dp"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="8dp" />
</RelativeLayout>
Di file layout diatas, kita hanya membuat layout sederhana untuk menampilkan RecyclerView. Kira-kira seperti berikut ini tampilan dari layout diatas.

Pembuatan layout item_movie.xml
Buat file layout baru dengan nama item_movie.xml dan isi dengan source code berikut.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/relative_layout_container_item_movie"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingTop="@dimen/activity_vertical_margin">
<ImageView
android:id="@+id/image_view_poster_item_movie"
android:layout_width="150dp"
android:layout_height="200dp"
android:layout_marginEnd="@dimen/activity_horizontal_margin"
android:layout_marginRight="@dimen/activity_horizontal_margin"
android:contentDescription="@string/image_view_poster_movie"
android:scaleType="fitXY" />
<TextView
android:id="@+id/text_view_title_movie_item_movie"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toEndOf="@+id/image_view_poster_item_movie"
android:layout_toRightOf="@+id/image_view_poster_item_movie"
android:text="@string/title_movie"
android:textSize="20sp"
android:textStyle="bold" />
<ImageView
android:id="@+id/image_view_vote_average_item_movie"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/text_view_title_movie_item_movie"
android:layout_toEndOf="@+id/image_view_poster_item_movie"
android:layout_toRightOf="@+id/image_view_poster_item_movie"
android:contentDescription="@string/image_view_vote_average"
android:src="@drawable/ic_star_pink_24dp" />
<TextView
android:id="@+id/text_view_vote_average_item_movie"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/text_view_title_movie_item_movie"
android:layout_toEndOf="@+id/image_view_vote_average_item_movie"
android:layout_toRightOf="@+id/image_view_vote_average_item_movie"
android:text="@string/_0_0"
android:textSize="15sp"
android:textStyle="bold" />
<TextView
android:id="@+id/text_view_release_date_item_movie"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/text_view_vote_average_item_movie"
android:layout_marginTop="@dimen/activity_vertical_margin"
android:layout_toEndOf="@+id/image_view_poster_item_movie"
android:layout_toRightOf="@+id/image_view_poster_item_movie"
android:text="@string/release_date"
android:textColor="@android:color/darker_gray" />
<TextView
android:id="@+id/text_view_release_date_value_item_movie"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/text_view_release_date_item_movie"
android:layout_toEndOf="@+id/image_view_poster_item_movie"
android:layout_toRightOf="@+id/image_view_poster_item_movie"
android:text="@string/release_date_value" />
<TextView
android:id="@+id/text_view_overview_item_movie"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/text_view_release_date_value_item_movie"
android:layout_marginTop="@dimen/activity_vertical_margin"
android:layout_toEndOf="@+id/image_view_poster_item_movie"
android:layout_toRightOf="@+id/image_view_poster_item_movie"
android:text="@string/overview"
android:textColor="@android:color/darker_gray" />
<TextView
android:id="@+id/text_view_overview_value_item_movie"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/text_view_overview_item_movie"
android:layout_toEndOf="@+id/image_view_poster_item_movie"
android:layout_toRightOf="@+id/image_view_poster_item_movie"
android:ellipsize="end"
android:maxLines="3"
android:text="@string/overview_value" />
</RelativeLayout>
Output dari file layout item_movie.xml ialah seperti berikut.

Pembuatan file model atau data class
Silakan Anda akses endpoint https://api.themoviedb.org/3/movie/now_playing?api_key=API Key Anda&language=EN-US&page=1
melalui aplikasi Postman atau sejenisnya dan kurang lebih seperti inilah data yang mereka berikan.

Kemudian, Anda copy and paste data tersebut ke Android Studio menggunakan plugin JSON To Kotlin Data Class dan jika sudah Anda buat maka, kurang lebih seperti inilah hasil dari file yang dibuatnya.



Pembuatan file ApiTheMovieDb
Selanjutnya, Anda buat file baru bernama ApiTheMovieDb dan isi dengan source code berikut
package com.codepolitan.codepolitan_movie.api
import com.codepolitan.codepolitan_movie.BuildConfig
import com.codepolitan.codepolitan_movie.model.TheMovieDb
import io.reactivex.Observable
import retrofit2.http.GET
import retrofit2.http.Query
/**
* Created by yudisetiawan on 11/4/17.
*/
interface ApiTheMovieDb {
@GET("movie/now_playing")
fun getNowPlaying(
@Query("api_key") apiKey: String = BuildConfig.API_KEY,
@Query("language") language: String = BuildConfig.LANGUAGE,
@Query("page") page: Int
): Observable<TheMovieDb>
}
Dimana, pada file ini Anda akan membuat sebuah interface untuk si Retrofit dan satu endpoint yang nantinya akan Anda panggil untuk mengambil data dari TheMovieDb.
Pembuatan file AdapterTheMovieDb
Buat file baru dengan nama AdapterTheMovieDb dan isi dengan source code berikut
package com.codepolitan.codepolitan_movie.adapter
import android.content.Context
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.bumptech.glide.Glide
import com.codepolitan.codepolitan_movie.BuildConfig
import com.codepolitan.codepolitan_movie.R
import com.codepolitan.codepolitan_movie.model.Result
import kotlinx.android.synthetic.main.item_movie.view.*
/**
* Created by yudisetiawan on 11/4/17.
*/
class AdapterTheMovieDb(private val context: Context, private var resultTheMovieDb: ArrayList<Result>) : RecyclerView.Adapter<AdapterTheMovieDb.ViewHolderTheMovieDb>() {
private val TAG = javaClass.simpleName
override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): ViewHolderTheMovieDb =
ViewHolderTheMovieDb(LayoutInflater
.from(parent?.context)
.inflate(R.layout.item_movie, parent, false)
)
override fun onBindViewHolder(holder: ViewHolderTheMovieDb?, position: Int) {
val resultItem = resultTheMovieDb[position]
Glide
.with(context)
.load(BuildConfig.BASE_URL_IMAGE + resultItem.posterPath)
.into(holder?.itemView?.image_view_poster_item_movie)
holder
?.itemView
?.text_view_title_movie_item_movie
?.text = resultItem.originalTitle
holder
?.itemView
?.text_view_vote_average_item_movie
?.text = resultItem.voteAverage.toString()
holder
?.itemView
?.text_view_release_date_value_item_movie
?.text = resultItem.releaseDate
holder
?.itemView
?.text_view_overview_value_item_movie
?.text = resultItem.overview
}
override fun getItemCount(): Int = resultTheMovieDb.size
fun refreshAdapter(resultTheMovieDb: List<Result>) {
this.resultTheMovieDb.addAll(resultTheMovieDb)
notifyItemRangeChanged(0, this.resultTheMovieDb.size)
}
inner class ViewHolderTheMovieDb(itemView: View?) : RecyclerView.ViewHolder(itemView)
}
Pada file tersebut Anda ada mendeklarasikan inner class ViewHolder
dengan nama class ViewHolderTheMovieDb
. Selanjutnya, pada file AdapterTheMovieDb Anda juga membuat sebuah primary constructor
dimana, ada 2 parameter yang diberikan yaitu, Context
dan ArrayList<Result>
. Selanjutnya, pada fun onBindViewHolder
Anda ada menggunakan Glide
untuk me-load gambar dari TheMovieDb pada syntax berikut.
Glide
.with(context)
.load(BuildConfig.BASE_URL_IMAGE + resultItem.posterPath)
.into(holder?.itemView?.image_view_poster_item_movie)
Setelah, itu Anda juga ada meng-set text pada text_view_title_movie_item_movie
, text_view_vote_average_item_movie
, text_view_release_date_value_item_movie
, text_view_overview_value_item_movie
pada syntax berikut.
holder
?.itemView
?.text_view_title_movie_item_movie
?.text = resultItem.originalTitle
holder
?.itemView
?.text_view_vote_average_item_movie
?.text = resultItem.voteAverage.toString()
holder
?.itemView
?.text_view_release_date_value_item_movie
?.text = resultItem.releaseDate
holder
?.itemView
?.text_view_overview_value_item_movie
?.text = resultItem.overview
Selain itu, Anda juga membuat fun refreshAdapter
dimana, function ini berfungsi untuk me-refresh adapter si RecyclerView
MainActivity
Yang terakhir, silakan buka file MainActivity dan isi dengan source code berikut.
package com.codepolitan.codepolitan_movie
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.util.Log
import android.view.View
import com.codepolitan.codepolitan_movie.adapter.AdapterTheMovieDb
import com.codepolitan.codepolitan_movie.api.ApiTheMovieDb
import com.codepolitan.codepolitan_movie.model.TheMovieDb
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import kotlinx.android.synthetic.main.activity_main.*
import retrofit2.Retrofit
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
import retrofit2.converter.gson.GsonConverterFactory
import kotlin.properties.Delegates
class MainActivity : AppCompatActivity() {
private val TAG = javaClass.simpleName
private var adapterTheMovieDb by Delegates.notNull<AdapterTheMovieDb>()
private var isLoading by Delegates.notNull<Boolean>()
private var page by Delegates.notNull<Int>()
private var totalPage by Delegates.notNull<Int>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
page = 1
totalPage = 0
doLoadData()
initListener()
}
private fun doLoadData() {
Log.d(TAG, "page: $page")
showLoading(true)
val retrofit = Retrofit.Builder()
.baseUrl(BuildConfig.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
val apiTheMovieDb = retrofit.create(ApiTheMovieDb::class.java)
apiTheMovieDb.getNowPlaying(page = page)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ theMovieDb: TheMovieDb ->
val resultTheMovieDb = theMovieDb.results as ArrayList
if (page == 1) {
adapterTheMovieDb = AdapterTheMovieDb(
this@MainActivity,
resultTheMovieDb
)
recycler_view_movie_activity_main.layoutManager = LinearLayoutManager(this@MainActivity)
recycler_view_movie_activity_main.adapter = adapterTheMovieDb
} else {
adapterTheMovieDb.refreshAdapter(resultTheMovieDb)
}
totalPage = theMovieDb.totalPages
},
{ t: Throwable ->
t.printStackTrace()
},
{
hideLoading()
}
)
}
private fun initListener() {
recycler_view_movie_activity_main.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView?, dx: Int, dy: Int) {
val linearLayoutManager = recyclerView?.layoutManager as LinearLayoutManager
val countItem = linearLayoutManager?.itemCount
val lastVisiblePosition = linearLayoutManager?.findLastCompletelyVisibleItemPosition()
val isLastPosition = countItem.minus(1) == lastVisiblePosition
Log.d(TAG, "countItem: $countItem")
Log.d(TAG, "lastVisiblePosition: $lastVisiblePosition")
Log.d(TAG, "isLastPosition: $isLastPosition")
if (!isLoading && isLastPosition && page < totalPage) {
showLoading(true)
page = page.let { it.plus(1) }
doLoadData()
}
}
})
}
private fun showLoading(isRefresh: Boolean) {
isLoading = true
progress_bar_horizontal_activity_main.visibility = View.VISIBLE
recycler_view_movie_activity_main.visibility.let {
if (isRefresh) {
View.VISIBLE
} else {
View.GONE
}
}
}
private fun hideLoading() {
isLoading = false
progress_bar_horizontal_activity_main.visibility = View.GONE
recycler_view_movie_activity_main.visibility = View.VISIBLE
}
}
Pada file ini, Anda ada mendeklarasikan 2 buah variable page
dan totalPage
dimana, kedua variable ini berperan untuk memberikan indikasi pada aplikasi apakah data yang di-load sudah masih memiliki halaman berikutnya atau tidak. Pada fun doLoadData
Anda melakukan pengambilan data dari endpoint yang sudah Anda buat di file interface ApiTheMovieDb
dimana, return value-nya itu Observable
jadi, bisa Anda padukan dengan RxJava
dan kemudian, Anda subscribe
pada callback onNext
untuk set adapter RecyclerView
jika variable page == 1
dan jika tidak maka, refresh adapter RecyclerView
. Selain itu, Anda juga ada mendeklarasikan listener RecyclerView.OnScrollListener
dimana, listener ini berfungsi untuk si RecyclerView melakukan event tertentu ketika si RecyclerView di-scroll. Pada listener ini Anda memberikan kondisi jika data masih di-load dan scroll sudah berada di akhir item dan variable page < totalPage
maka, fun doLoadData
akan dijalankan lagi untuk mengambil data pada halaman berikutnya.
Struktur Project
Berikut adalah struktur project pada tutorial ini.

Output Program
Berikut adalah output dari project yang sudah Anda kerjakan.

Projek pada tutorial ini sudah saya upload ke Github
0
0
0
share