Belajar Membuat Aplikasi Jadwal Shalat Android dengan Kotlin Coroutines

Yudi Setiawan 27 Desember 2017

Belajar Membuat Aplikasi Jadwal Shalat Android dengan Kotlin Coroutines

Pendahuluan

Di bahasa pemrograman Kotlin, ada salah satu fitur yang menurut penulis cukup bagus yang bernama Kotlin Coroutines. Apa itu Kotlin Coroutines? Berdasarkan situs https://blog.mindorks.com/what-are-coroutines-in-kotlin-bf4fecd476e9 menyebutkan bahwa

Coroutines are a new way of writing asyncrhonous, non-blocking code (and much more)

Jadi, menurut penulis Kotlin Coroutines adalah salah satu metode untuk melakukan operasi asynchronous , namun dengan style atau gaya penulisan kode yang berbentuk synchronous atau dengan kata lain, Kotlin Coroutines lebih mengarah ke multi-thread.

Launch dan Async

Dalam tutorial ini, ada 2 keyword yang akan kita pelajari yaitu, launch dan async. Perbedaannya adalah launch tidak mengembalikan nilai apapun dan async mengembalikan nilai yang bertipe objek Deferred<T>

Mulai Pembuatan Contoh Projek

Jadi, pada tutorial ini saya akan memberikan contoh penggunaan Kotlin Coroutines untuk melakukan pemanggilan request ke API secara asynchronous

Buat Projek Baru

Buka aplikasi Android Studio dan buat projek baru dengan nama projeknya Sholat Yuk dan set min sdk version-nya ke 18. Pada saat pembuatan Activity, ubah nama Activity-nya menjadi SplashScreenActivity dan nama layout-nya menjadi activity_splash_screen

API Key

Pada projek ini, kita akan menggunakan data dari API http://wahidganteng.ga/api/jadwal-sholat. Jadi, silakan lakukan pendaftaran di situs tersebut untuk mendapatkan API Key-nya. API Key

Setup build.gradle

Buka file build.gradle pada level module app dan ubah menjadi seperti berikut.

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

android {
    compileSdkVersion 26
    defaultConfig {
        applicationId "com.codepolitan.sholatyuk"
        minSdkVersion 18
        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 "development"
    productFlavors {
        development {
            applicationIdSuffix ".development"
            buildConfigField("String", "API_KEY", "\"Your API Key\"")
            buildConfigField("String", "BASE_URL", "\"http://wahidganteng.ga/process/api/\"")
            dimension "development"
        }
    }
}

kotlin {
    experimental {
        coroutines "enable"
    }
}

dependencies {
    /** Library from directory libs */
    implementation fileTree(include: ['*.jar'], dir: 'libs')

    /** Library from Standard Android */
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
    implementation 'com.android.support:appcompat-v7:26.1.0'
    implementation 'com.android.support.constraint:constraint-layout:1.0.2'
    implementation 'com.android.support:design:26.1.0'

    /** Library support for Kotlin Coroutines */
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:0.19.3'

    /** An HTTP+HTTP/2 client for Android and Java applications */
    implementation 'com.squareup.okhttp3:okhttp:3.9.0'

    /** A Java serialization and deserialization library to convert Objects into JSON */
    implementation 'com.google.code.gson:gson:2.8.2'

    /** Testing Framework */
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.1'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
}

Pada file tersebut, kita ada menambahkan API Key dan Base Url dari API Server yang akan kita pakai. Sengaja kita buat API Key dan Base Url-nya di file build.gradle agar kita lebih mudah melakukan pemanggilannya. Kemudian, pada file build.gradle kita juga aktifkan fitur Kotlin Coroutines yang ditandai dengan kode berikut

kotlin {
    experimental {
        coroutines "enable"
    }
}

dan tambahkan dependency si Kotlin Coroutines-nya

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:0.19.3'

Selain itu, kita juga ada menggunakan beberapa fitur lainnya seperti Android Kotlin Extensions dan beberapa dependency lainnya yang kita butuhkan pada pembuatan projek kali ini.

Setup colors.xml

Buka file colors.xml dan ubah menjadi seperti berikut

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#512da8</color>
    <color name="colorPrimaryDark">#140078</color>
    <color name="colorPrimaryLight">#8559da</color>
    <color name="colorSecondary">#ea80fc</color>
    <color name="colorSecondaryDark">#b64fc8</color>
    <color name="colorSecondaryLight">#ffb2ff</color>
    <color name="colorAccent">#ea80fc</color>
</resources>

Setup strings.xml

Buka file strings.xml dan ubah menjadi seperti berikut

<resources>
    <string name="app_name">Sholat Yuk</string>
    <string name="image_view_background_splash_screen">image view background splash screen</string>
    <string name="shalat">Shalat</string>
    <string name="yuk">Yuk</string>
    <string name="image_view_location_city">image view location city</string>
    <string name="location_city">Location City</string>
    <string name="medan">Medan</string>
    <string name="submit">Submit</string>
    <string name="choose_location">Choose location</string>
    <string name="please_wait">Please wait</string>
    <string name="indonesia">Indonesia</string>
    <string name="mon_august_29_2015">Mon, August 29, 2015</string>
    <string name="please_choose_location">Please, choose location</string>
    <string name="shubuh">Shubuh</string>
    <string name="esimated_time_5_hours">Esimated time: 5 hours</string>
    <string name="_00">:00</string>
    <string name="_2">2</string>
    <string name="image_view_icon_circle">image view icon circle</string>
    <string name="am">am</string>
    <string name="pm">pm</string>
</resources>

Buat file class Android.kt

Silakan buat package baru dengan nama experimental dan buat file baru dengan nama Android.kt pada package tersebut. Dan isi dengan source code berikut.

package com.codepolitan.sholatyuk.experimental

import android.os.Handler
import android.os.Looper
import kotlin.coroutines.experimental.AbstractCoroutineContextElement
import kotlin.coroutines.experimental.Continuation
import kotlin.coroutines.experimental.ContinuationInterceptor

/**
 * Created by yudisetiawan on 12/23/17.
 */
private class AndroidContinuation<T>(val cont: Continuation<T>) : Continuation<T> by cont {

    override fun resume(value: T) {
        if (Looper.myLooper() == Looper.getMainLooper()) {
            cont.resume(value)
        } else {
            Handler(Looper.getMainLooper()).post {
                cont.resume(value)
            }
        }
    }

    override fun resumeWithException(exception: Throwable) {
        if (Looper.myLooper() == Looper.getMainLooper()) {
            cont.resumeWithException(exception)
        } else {
            Handler(Looper.getMainLooper()).post {
                cont.resumeWithException(exception)
            }
        }
    }

}

object Android: AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {

    override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> =
            AndroidContinuation(continuation)

}

Jadi, kegunaan file ini lebih mengarah ke arah penggunaan Thread. Terlihat dari kode Looper.myLooper() dimana, jika di UI Thread maka, akan diarahkan ke UI Thread dan jika tidak maka akan di arahkan ke Background Thread.

Buat file object ShalatClient.kt

Buat package baru dengan nama network dan didalamnya buat lagi package baru dengan nama api. Dan didalamnya, buat file baru dengan nama ShalatClient. Selanjutnya, isi dengan source code berikut.

package com.codepolitan.sholatyuk.network.api

import com.codepolitan.sholatyuk.BuildConfig
import com.codepolitan.sholatyuk.model.DataJadwalSholat
import com.codepolitan.sholatyuk.model.DataKota
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.Deferred
import kotlinx.coroutines.experimental.async
import okhttp3.OkHttpClient
import okhttp3.Request
import java.text.SimpleDateFormat
import java.util.*

/**
 * Created by yudisetiawan on 12/23/17.
 */
object ShalatClient {

    private val TAG = javaClass.simpleName
    val okhttpClient = OkHttpClient()

    /**
     * @description Get City Data from API
     * @return {Deffered<DataKota>} Return value DataKota from API
     */
    fun getCityData(): Deferred<DataKota> {
        return async(CommonPool) {
            val request = Request.Builder()
                    .url(BuildConfig.BASE_URL + BuildConfig.API_KEY + "/jadwal-sholat/get-kota")
                    .build()
            val response = okhttpClient.newCall(request).execute()
            val dataKota = object : TypeToken<DataKota>() {}.type
            Gson().fromJson<DataKota>(response.body()!!.string(), dataKota)
        }
    }

    /**
     * @description Get Prayer Schedule Data from API
     * @param {Int} id Unique id for each city
     * @return {Deferred<DataJadwalSholat>} Return value DataJadwalSholat from API
     */
    fun getPrayerScheduleData(id: Int): Deferred<DataJadwalSholat> {
        return async(CommonPool) {
            var url = BuildConfig.BASE_URL + BuildConfig.API_KEY + "/jadwal-sholat"
            val strDate = SimpleDateFormat("yyyy-MM-dd", Locale.US).format(Date())
            url += "?idk=$id"
            url += url.let {
                val strMonth = strDate.substring(5, 5 + 2)
                val strYear = strDate.substring(0, 0 + 4)
                "&bln=$strMonth&thn=$strYear"
            }

            val request = Request.Builder()
                    .url(url)
                    .build()
            val response = okhttpClient.newCall(request).execute()
            val dataPrayerSchedule = object : TypeToken<DataJadwalSholat>() {}.type
            Gson().fromJson<DataJadwalSholat>(response.body()!!.string(), dataPrayerSchedule)
        }
    }

}

Pada file tersebut, kita buat 2 buat function yang bernama getCityData untuk mengambil data list kota dari API dan satu lagi function bernama getPrayerScheduleData untuk mengambil data list jadwal sholat dari API. Bisa Anda lihat pada kode diatas, kita menggunakan async karena, kita ingin mengembalikan nilai atau data dari respon si OkHttp di dalam execute block async

Buat file data class DataKota.kt

Buat package baru dengan nama model dan buat file baru dengan nama DataKota.kt dan isi dengan source code berikut.

package com.codepolitan.sholatyuk.model

import android.os.Parcel
import android.os.Parcelable
import com.google.gson.annotations.SerializedName


/**
 * Created by yudisetiawan on 12/23/17.
 */

data class DataKota(
        @SerializedName("status") val status: String,
		@SerializedName("msg") val msg: String,
		@SerializedName("count") val count: Int,
		@SerializedName("data") val data: List<Data>
)

data class Data(
		@SerializedName("id") val id: String,
		@SerializedName("nama_kota") val namaKota: String
) : Parcelable {
    constructor(parcel: Parcel) : this(
            parcel.readString(),
            parcel.readString())

    override fun writeToParcel(parcel: Parcel, flags: Int) {
        parcel.writeString(id)
        parcel.writeString(namaKota)
    }

    override fun describeContents(): Int {
        return 0
    }

    companion object CREATOR : Parcelable.Creator<Data> {
        override fun createFromParcel(parcel: Parcel): Data {
            return Data(parcel)
        }

        override fun newArray(size: Int): Array<Data?> {
            return arrayOfNulls(size)
        }
    }
}

Buat file data class DataJadwalSholat.kt

Buat file baru dengan nama DataJadwalSholat.kt pada package model dan isi dengan source code berikut.

package com.codepolitan.sholatyuk.model

import android.os.Parcel
import android.os.Parcelable
import com.google.gson.annotations.SerializedName


/**
 * Created by yudisetiawan on 12/24/17.
 */

data class DataJadwalSholat(
        @SerializedName("status") val status: String,
        @SerializedName("msg") val msg: String,
        @SerializedName("count") val count: Int,
        @SerializedName("data") val data: List<DataSholat>
) : Parcelable {
    constructor(parcel: Parcel) : this(
            parcel.readString(),
            parcel.readString(),
            parcel.readInt(),
            parcel.createTypedArrayList(DataSholat))

    override fun writeToParcel(parcel: Parcel, flags: Int) {
        parcel.writeString(status)
        parcel.writeString(msg)
        parcel.writeInt(count)
        parcel.writeTypedList(data)
    }

    override fun describeContents(): Int {
        return 0
    }

    companion object CREATOR : Parcelable.Creator<DataJadwalSholat> {
        override fun createFromParcel(parcel: Parcel): DataJadwalSholat {
            return DataJadwalSholat(parcel)
        }

        override fun newArray(size: Int): Array<DataJadwalSholat?> {
            return arrayOfNulls(size)
        }
    }
}

data class DataSholat(
        @SerializedName("tanggal") val tanggal: String,
        @SerializedName("shubuh") val shubuh: String,
        @SerializedName("dzuhur") val dzuhur: String,
        @SerializedName("ashr") val ashr: String,
        @SerializedName("maghrib") val maghrib: String,
        @SerializedName("isya") val isya: String
) : Parcelable {
    constructor(parcel: Parcel) : this(
            parcel.readString(),
            parcel.readString(),
            parcel.readString(),
            parcel.readString(),
            parcel.readString(),
            parcel.readString())

    override fun writeToParcel(parcel: Parcel, flags: Int) {
        parcel.writeString(tanggal)
        parcel.writeString(shubuh)
        parcel.writeString(dzuhur)
        parcel.writeString(ashr)
        parcel.writeString(maghrib)
        parcel.writeString(isya)
    }

    override fun describeContents(): Int {
        return 0
    }

    companion object CREATOR : Parcelable.Creator<DataSholat> {
        override fun createFromParcel(parcel: Parcel): DataSholat {
            return DataSholat(parcel)
        }

        override fun newArray(size: Int): Array<DataSholat?> {
            return arrayOfNulls(size)
        }
    }
}

Catatan: File data class hanya berfungsi sebagai class model atau POJO kalau di Java.

Persiapan file drawable

Untuk semua file drawable pada projek kali ini bisa Anda unduh di sini

Buat file DatabaseHelper

Buat package baru dengan nama db dan buat file class baru didalamnya dengan nama DatabaseHelper.kt dan isi dengan source code berikut.

package com.codepolitan.sholatyuk.db

import android.content.ContentValues
import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import com.codepolitan.sholatyuk.model.Data
import java.sql.SQLException

/**
 * Created by yudisetiawan on 12/23/17.
 */
class DatabaseHelper(context: Context?, name: String?, factory: SQLiteDatabase.CursorFactory?, version: Int) : SQLiteOpenHelper(context, name, factory, version) {

    private val TAG = javaClass.simpleName
    companion object {
        val DATABASE_NAME = "shalatyuk.db"
        val DATABASE_VERSION = 1
    }

    private val CITY_TABLE_NAME = "kota"
    private val CITY_TABLE_NAME_COLUMN_ID = "id"
    private val CITY_TABLE_NAME_COLUMN_CITY_NAME = "name"
    private val CITY_TABLE_SELECT = "select * from $CITY_TABLE_NAME"
    private val CITY_TABLE_CREATE = "create table if not exists $CITY_TABLE_NAME " +
            "($CITY_TABLE_NAME_COLUMN_ID integer primary key," +
            "$CITY_TABLE_NAME_COLUMN_CITY_NAME text " +
            ")"
    private val CITY_TABLE_DROP = "drop table if exists $CITY_TABLE_NAME"

    /**
     * @description For create database
     * @param sqliteDatabase {SQLiteDatabase} object SQLiteDatabase
     */
    override fun onCreate(sqliteDatabase: SQLiteDatabase?) {
        try {
            sqliteDatabase?.execSQL(CITY_TABLE_CREATE)
        } catch (sqle: SQLException) {
            sqle.printStackTrace()
        }
    }

    /**
     * @description For update database
     * @param sqliteDatabase {SQLiteDatabase} object SQLiteDatabase
     * @param oldVersion {Int} old version SQLitedatabase
     * @param newVersion {Int} new version SQLiteDatabase
     */
    override fun onUpgrade(sqliteDatabase: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
        try {
            sqliteDatabase?.execSQL(CITY_TABLE_DROP)
            sqliteDatabase?.execSQL(CITY_TABLE_CREATE)
        } catch (sqle: SQLException) {
            sqle.printStackTrace()
        }
    }

    /**
     * @description Insert data city to city_table
     * @param data {Data} value of Data city
     * @return {Long} return value row ID if new inserted or -1 if an error occurred
     */
    fun insertDataCity(data: Data): Long {
        try {
            val sqliteDatabase = writableDatabase
            val contentValue = ContentValues()
            contentValue.put(CITY_TABLE_NAME_COLUMN_ID, data.id)
            contentValue.put(CITY_TABLE_NAME_COLUMN_CITY_NAME, data.namaKota)
            return sqliteDatabase.insert(
                    CITY_TABLE_NAME,
                    null,
                    contentValue
            )
        } catch (e: Exception) {
            throw e
        }
    }

    /**
     * @description Insert list data city to city_table
     * @param listData {List<Data>} values of Data city
     * @return {Int} return value 1 if success or something else
     */
    fun insertDataCity(listData: List<Data>): Int {
        try {
            val sqliteDatabase = writableDatabase
            val queryInsert = "insert into $CITY_TABLE_NAME " +
                    "($CITY_TABLE_NAME_COLUMN_ID, $CITY_TABLE_NAME_COLUMN_CITY_NAME) " +
                    "values " +
                    "(?, ?)"
            sqliteDatabase.beginTransaction()
            val sqliteStatement = sqliteDatabase.compileStatement(queryInsert)
            for (data in listData) {
                sqliteStatement.bindString(1, data.id)
                sqliteStatement.bindString(2, data.namaKota)
                sqliteStatement.execute()
                sqliteStatement.clearBindings()
            }
            sqliteDatabase.setTransactionSuccessful()
            sqliteDatabase.endTransaction()
            return 1
        } catch (e: Exception) {
            throw e
        }
    }

    /**
     * @description Delete data city in city_table by id
     * @param id {Int} ID of city
     * @return {Int} the number of row
     */
    fun deleteDataCityById(id: Int): Int {
        try {
            val sqliteDatabase = writableDatabase
            return sqliteDatabase.delete(
                    CITY_TABLE_NAME,
                    "$CITY_TABLE_NAME_COLUMN_ID = ?",
                    Array(1, { id.toString() })
            )
        } catch (e: Exception) {
            throw e
        }
    }

    /**
     * @description Delete all data city in city_table
     * @return {Int} return the number of row affected
     */
    fun deleteDataCity(): Int {
        try {
            val sqliteDatabase = writableDatabase
            return sqliteDatabase.delete(
                    CITY_TABLE_NAME,
                    null,
                    null
            )
        } catch (e: Exception) {
            throw e
        }
    }

    /**
     * @description count item data city in city_table
     * @return {Int} return count value item data city in city_table
     */
    fun countDataCity(): Int {
        val itemCount: Int
        try {
            val sqliteDatabase = writableDatabase
            val cursor = sqliteDatabase.rawQuery(
                    CITY_TABLE_SELECT,
                    null,
                    null
            )
            itemCount = cursor.count
            cursor.close()
        } catch (e: Exception) {
            e.printStackTrace()
            throw e
        }
        return itemCount
    }

    /**
     * @description Get data city in city_table
     * @return {List<Data>} return list data city from city_table
     */
    fun getDataCity(): List<Data> {
        val listDataCity = ArrayList<Data>()
        try {
            val sqliteDatabase = writableDatabase
            val cursor = sqliteDatabase.rawQuery(
                    CITY_TABLE_SELECT,
                    null,
                    null
            )
            if (cursor.count > 0) {
                while (cursor.moveToNext()) {
                    val dataKota = Data(
                            id = cursor.getInt(cursor.getColumnIndex(CITY_TABLE_NAME_COLUMN_ID)).toString(),
                            namaKota = cursor.getString(cursor.getColumnIndex(CITY_TABLE_NAME_COLUMN_CITY_NAME))
                    )
                    listDataCity.add(dataKota)
                }
            }
            cursor.close()
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return listDataCity
    }

}

Pada file DatabaseHelper.kt kita ada membuat beberapa function database seperti, memasukkan data ke database, menghitung jumlah data di database, dan menghapus data di database.

Ubah layout activity_splash_screen.xml

Buka file activity_splash_screen.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.sholatyuk.ui.splashscreen.SplashScreenActivity">

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:contentDescription="@string/image_view_background_splash_screen"
        android:scaleType="centerCrop"
        android:src="@drawable/img_backgound_splash_screen" />

    <View
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:alpha="0.7"
        android:background="#000000" />

    <ProgressBar
        android:id="@+id/progress_bar_loading_activity_splash_screen"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_above="@+id/progress_bar_loading_activity_splash_screen"
        android:layout_marginBottom="8dp"
        android:gravity="center_vertical"
        >

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:fontFamily="sans-serif-condensed"
            android:gravity="end"
            android:text="@string/shalat"
            android:textColor="@android:color/white"
            android:textSize="28sp" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:fontFamily="sans-serif-condensed"
            android:gravity="start"
            android:text="@string/yuk"
            android:textColor="@color/colorAccent"
            android:textSize="18sp" />

    </LinearLayout>

</RelativeLayout>

Ubah file SplashScreenActivity

Buka file SplashScreenActivity.kt dan isi dengan source code berikut.

package com.codepolitan.sholatyuk.ui.splashscreen

import android.content.Intent
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import com.codepolitan.sholatyuk.R
import com.codepolitan.sholatyuk.db.DatabaseHelper
import com.codepolitan.sholatyuk.experimental.Android
import com.codepolitan.sholatyuk.network.api.ShalatClient
import com.codepolitan.sholatyuk.ui.home.HomeActivity
import kotlinx.coroutines.experimental.launch

class SplashScreenActivity : AppCompatActivity() {

    private val TAG = javaClass.simpleName
    private val databaseHelper by lazy {
        DatabaseHelper(
                context = this@SplashScreenActivity,
                name = DatabaseHelper.DATABASE_NAME,
                factory = null,
                version = DatabaseHelper.DATABASE_VERSION
        )
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_splash_screen)
        doLoadData()
    }

    /**
     * @description Load data from API and save it to local database
     */
    private fun doLoadData() {
        launch(Android) {
            val itemCountDataCityLocal = databaseHelper.countDataCity()
            val resultDataKota = ShalatClient.getCityData().await()
            val intentHomeActivity = Intent(this@SplashScreenActivity, HomeActivity::class.java)
            intentHomeActivity.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK)
            if (itemCountDataCityLocal == resultDataKota.count) {
                startActivity(intentHomeActivity)
            } else {
                databaseHelper.deleteDataCity()
                databaseHelper.insertDataCity(resultDataKota.data)
                startActivity(intentHomeActivity)
            }
        }
    }
}

Pada file tersebut, kita ada melakukan request data list kota dari API dan kemudian, menyimpannya ke database lokal dimana, fungsi ini bisa Anda lihat di fun doLoadData(). Di dalam function doLoadData ada melakukan pengambilan data dari API Server. ShalatCient.getCityData().await() ini merupakan fungsi Kotlin Coroutines untuk melakukan pengambilan data dari API Server secara asyncrhonous namun, style atau gaya penulisan kode-nya seolah-olah synchronous. Keyword await() berfungsi untuk menahan jalannya alur program sampai proses pemanggilannya selesai dan setelah selesai mengambil data dari API Server, baru dilanjutkan ke proses dibawahnya. Kita menggunakan launch karena, kita tidak ada melakukan pengembalian nilai ketika Kotlin Coroutines dijalankan.

Ubah layout activity_home.xml

Buat activity baru dan beri nama activity-nya HomeActivity.kt dan layout-nya activity_home.xml. Ubah file activity_home.xml menjadi seperti 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"
    android:background="#eeeeee"
    tools:context="com.codepolitan.sholatyuk.ui.home.HomeActivity">

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar_activity_home"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="@color/colorPrimary"
        android:theme="@style/ThemeOverlay.AppCompat.Dark"
        app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />

    <RelativeLayout
        android:id="@+id/relative_layout_container_contet_activity_home"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/toolbar_activity_home"
        android:layout_marginTop="@dimen/activity_vertical_margin"
        android:layout_marginEnd="@dimen/activity_horizontal_margin"
        android:layout_marginBottom="0dp"
        android:layout_marginStart="@dimen/activity_horizontal_margin"
        android:background="@drawable/background_white_rouned"
        android:paddingBottom="@dimen/activity_vertical_margin"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin">

        <ImageView
            android:id="@+id/image_view_location_city_activity_home"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginEnd="@dimen/activity_horizontal_margin"
            android:contentDescription="@string/image_view_location_city"
            android:src="@drawable/ic_location_city_black_24dp" />

        <TextView
            android:id="@+id/text_view_label_location_city_activity_home"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_toEndOf="@+id/image_view_location_city_activity_home"
            android:text="@string/location_city"
            android:textColor="@android:color/darker_gray" />

        <TextView
            android:id="@+id/text_view_value_location_city_activity_home"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@+id/text_view_label_location_city_activity_home"
            android:layout_toEndOf="@+id/image_view_location_city_activity_home"
            android:text="@string/choose_location"
            android:textSize="16sp" />

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/button_submit_activity_home"
            android:text="@string/submit"
            android:textColor="@android:color/white"
            android:textAllCaps="false"
            android:layout_below="@+id/text_view_value_location_city_activity_home"
            android:layout_marginTop="20dp"
            android:background="@drawable/background_button_color_accent_rounded" />

    </RelativeLayout>

</RelativeLayout>

Ubah file HomeActivity

Buka file HomeActivity dan isi dengan source code berikut.

package com.codepolitan.sholatyuk.ui.home

import android.app.Activity
import android.app.ProgressDialog
import android.content.Intent
import android.os.Bundle
import android.support.design.widget.Snackbar
import android.support.v7.app.AppCompatActivity
import android.view.View
import com.codepolitan.sholatyuk.R
import com.codepolitan.sholatyuk.experimental.Android
import com.codepolitan.sholatyuk.model.Data
import com.codepolitan.sholatyuk.network.api.ShalatClient
import com.codepolitan.sholatyuk.ui.city.CityActivity
import com.codepolitan.sholatyuk.ui.schedule.PrayerScheduleActivity
import kotlinx.android.synthetic.main.activity_home.*
import kotlinx.coroutines.experimental.launch

class HomeActivity : AppCompatActivity(), View.OnClickListener {

    private val TAG = javaClass.simpleName
    private val REQUEST_CODE_CITY = 210
    private var dataCity: Data? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_home)
        initListener()
        initToolbar()
    }

    /**
     * @description Initialize toolbar
     */
    private fun initToolbar() {
        setSupportActionBar(toolbar_activity_home)
        supportActionBar?.title = getString(R.string.app_name)
    }

    /**
     * @description Initialize all listener view
     */
    private fun initListener() {
        text_view_value_location_city_activity_home.setOnClickListener(this)
        button_submit_activity_home.setOnClickListener(this)
    }

    /**
     * @description override listener on click
     * @param view {View} view for on click
     */
    override fun onClick(view: View?) {
        view?.id.let {
            when (it) {
                R.id.text_view_value_location_city_activity_home -> {
                    startActivityForResult(Intent(this@HomeActivity, CityActivity::class.java), REQUEST_CODE_CITY)
                }
                R.id.button_submit_activity_home -> {
                    if (text_view_value_location_city_activity_home.text.equals(getString(R.string.choose_location))) {
                        Snackbar.make(
                                findViewById(android.R.id.content),
                                getString(R.string.please_choose_location),
                                Snackbar.LENGTH_SHORT
                        ).show()
                    } else {
                        doLoadDataPrayerSchedule()
                    }
                }
                else -> {
                    /** nothing to do in here */
                }
            }
        }
    }

    /**
     * @description Load data prayer schedule
     */
    private fun doLoadDataPrayerSchedule() {
        val progressDialog = ProgressDialog(this)
        progressDialog.let {
            it.setCancelable(false)
            it.setMessage(getString(R.string.please_wait))
            it.show()
        }
        launch(Android) {
            val resultDataPrayerSchedule = ShalatClient
                    .getPrayerScheduleData(id = dataCity!!.id.toInt())
                    .await()
            progressDialog.let {
                if (it.isShowing) {
                    it.dismiss()
                }
            }
            val intentPrayerScheduleActivity = Intent(this@HomeActivity, PrayerScheduleActivity::class.java)
            intentPrayerScheduleActivity.putExtra("dataPrayer", resultDataPrayerSchedule)
            intentPrayerScheduleActivity.putExtra("locationCity", dataCity!!.namaKota)
            startActivity(intentPrayerScheduleActivity)
        }
    }

    /**
     * @description Activity result from another activity
     * @param requestCode {Int} request code when start other activity
     * @param resultCode {Int} result code when back to this activity
     * @param data {Intent} Intent data from another activity
     */
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        when (resultCode) {
            Activity.RESULT_OK -> {
                when (requestCode) {
                    REQUEST_CODE_CITY -> {
                        val bundle = data?.extras
                        val dataCity = bundle?.get("data") as Data
                        this@HomeActivity.dataCity = dataCity.copy()
                        text_view_value_location_city_activity_home.text = dataCity.namaKota
                    }
                    else -> {
                        /** nothing to do in here */
                    }
                }
            }
            else -> {
                /** nothing to do in here */
            }
        }
        super.onActivityResult(requestCode, resultCode, data)
    }
}

Pada file HomeActivity, kita ada memberikan OnClick Listener pada TextView value choose location dan Button submit. OnClick Listener kita berikan pada TextView value choose location untuk kita arahkan ke CityActivity dan ketika di activity tersebut si user akan memilih lokasi kota-nya. Ketika si user telah memilih lokasi kotanya maka, app secara otomatis akan kembali ke HomeActivity dimana, data yang dipilih si user dari CityActivity akan ditampung di function onActivityResult di HomeActivity. Kemudian, Button submit kita beri OnClick Listener untuk mengirimkan data lokasi kota si user ke PrayerScheduleActivity yang mana, sebelumnya akan divalidasi terlebih dahulu apakah data lokasi kota si user valid ataut tidak. Jika tidak valid maka, app akan memunculkan Snackbar message dan jika valid maka, app akan berpindah ke PrayerScheduleActivity.

Ubah layout activity_city

Buat activity baru dengan nama activity CityActivity dan layout-nya activity_city. Buka file activity_city 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.sholatyuk.ui.city.CityActivity">

    <android.support.v7.widget.Toolbar
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:id="@+id/toolbar_activity_city"
        android:background="@color/colorPrimary"
        android:theme="@style/ThemeOverlay.AppCompat.Dark"
        app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />

    <android.support.v7.widget.RecyclerView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@+id/toolbar_activity_city"
        android:paddingEnd="@dimen/activity_horizontal_margin"
        android:paddingStart="@dimen/activity_horizontal_margin"
        android:id="@+id/recycler_view_data_city_activity_city" />

</RelativeLayout>

Ubah file CityActivity

Buka file CityActivity dan isi dengan source code berikut.

package com.codepolitan.sholatyuk.ui.city

import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.DividerItemDecoration
import android.support.v7.widget.LinearLayoutManager
import android.view.MenuItem
import com.codepolitan.sholatyuk.R
import com.codepolitan.sholatyuk.db.DatabaseHelper
import com.codepolitan.sholatyuk.model.Data
import com.codepolitan.sholatyuk.ui.city.adapter.AdapterDataCity
import kotlinx.android.synthetic.main.activity_city.*

class CityActivity : AppCompatActivity() {

    private val TAG = javaClass.simpleName
    private val databaseHelper by lazy {
        DatabaseHelper(
                context = this@CityActivity,
                name = DatabaseHelper.DATABASE_NAME,
                factory = null,
                version = DatabaseHelper.DATABASE_VERSION
        )
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_city)
        initToolbar()
        doLoadData()
    }

    /**
     * @desription Load data from database local and setup adapter recyclerview
     */
    private fun doLoadData() {
        val listDataCity = databaseHelper.getDataCity()
        recycler_view_data_city_activity_city.let {
            val adapterDataCity = AdapterDataCity(
                    listDataCity = listDataCity,
                    listenerAdapterDataCity = object : AdapterDataCity.ListenerAdapterDataCity {
                        override fun clickItem(data: Data) {
                            val intent = Intent()
                            intent.putExtra("data", data)
                            setResult(Activity.RESULT_OK, intent)
                            finish()
                        }
                    }
            )
            val dividerItemDecoration = DividerItemDecoration(this, DividerItemDecoration.VERTICAL)
            it.addItemDecoration(dividerItemDecoration)
            it.layoutManager = LinearLayoutManager(this)
            it.adapter = adapterDataCity
        }
    }

    /**
     * @description Initialize toolbar
     */
    private fun initToolbar() {
        setSupportActionBar(toolbar_activity_city)
        supportActionBar?.let {
            it.setDisplayHomeAsUpEnabled(true)
            it.title = getString(R.string.choose_location)
        }
    }

    /**
     * @description On options item selected in toolbar
     * @param item {MenuItem} item selected in toolbar
     * @return {Boolean} return true or false when selected item in toolbar
     */
    override fun onOptionsItemSelected(item: MenuItem?): Boolean =
            item?.itemId.let {
                return when (it) {
                    android.R.id.home -> {
                        onBackPressed()
                        true
                    }
                    else -> {
                        super.onOptionsItemSelected(item)
                    }
                }
            }

}

Pada file ini, kita hanya melakukan setup adapter for RecyclerView karena, CityActivity hanya berfungsi untuk si user memilih lokasi kota-nya saja. Jadi, tidak begitu susah logic di file ini.

Buat file layout item_data_city

Buat file item_data_city 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_data_city"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingTop="@dimen/activity_vertical_margin">

    <TextView
        android:id="@+id/text_view_city_name_item_data_city"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="16sp" />

</RelativeLayout>

File ini nanti akan kita gunakan sebagai item layout untuk si RecyclerView di CityActivity

Buat file AdapterDataCity

Buat file AdapterDataCity dan isi dengan source code berikut.

package com.codepolitan.sholatyuk.ui.city.adapter

import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.codepolitan.sholatyuk.R
import com.codepolitan.sholatyuk.model.Data
import kotlinx.android.synthetic.main.item_data_city.view.*

/**
 * Created by yudisetiawan on 12/24/17.
 */
class AdapterDataCity(val listDataCity: List<Data>, val listenerAdapterDataCity: ListenerAdapterDataCity) : RecyclerView.Adapter<AdapterDataCity.ViewHolderItemDataCity>() {

    private val TAG = javaClass.simpleName

    override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): ViewHolderItemDataCity =
            ViewHolderItemDataCity(
                    LayoutInflater.from(parent?.context).inflate(
                            R.layout.item_data_city, null
                    )
            )

    override fun onBindViewHolder(holder: ViewHolderItemDataCity?, position: Int) {
        holder?.itemView?.let {
            it.text_view_city_name_item_data_city.text = listDataCity[position].namaKota
        }
    }

    override fun getItemCount(): Int = listDataCity.size

    inner class ViewHolderItemDataCity(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener {

        init {
            itemView.text_view_city_name_item_data_city.setOnClickListener(this)
            itemView.relative_layout_container_item_data_city.setOnClickListener(this)
        }

        override fun onClick(view: View?) {
            view?.id.let {
                when (it) {
                    R.id.relative_layout_container_item_data_city,
                    R.id.text_view_city_name_item_data_city -> {
                        listenerAdapterDataCity.clickItem(listDataCity[adapterPosition])
                    }
                    else -> {
                        /** nothing to do in here */
                    }
                }
            }
        }

    }

    interface ListenerAdapterDataCity {

        fun clickItem(data: Data)

    }

}

Pada file adapter ini, kita hanya melakukan set text ke TextView city name dan buat interface si adapter agar bisa mengirimkan data kota yang dipilih si user lewat fun clickItem(data: Data)

Buat file activity_prayer_schedule

Buat activity baru dengan nama activity-nya PrayerScheduleActivity dan layout-nya activity_prayer_schedule. Selanjutnya, buka file activity_prayer_schedule 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.sholatyuk.ui.schedule.PrayerScheduleActivity">


    <LinearLayout
        android:id="@+id/linear_layout_container_content_activity_prayer_schedule"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1.5">

            <ImageView
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:scaleType="centerCrop"
                android:src="@drawable/img_mountain" />

            <View
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:alpha="0.5"
                android:background="#000000" />

            <TextView
                android:id="@+id/text_view_location_city_activity_prayer_schedule"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerInParent="true"
                android:layout_marginTop="8dp"
                android:alpha="0.7"
                android:fontFamily="sans-serif-condensed"
                android:text="@string/location_city"
                android:textColor="@android:color/white"
                android:textSize="24sp" />

            <ImageView
                android:layout_width="36dp"
                android:layout_height="36dp"
                android:layout_above="@+id/text_view_location_city_activity_prayer_schedule"
                android:layout_centerHorizontal="true"
                android:alpha="0.8"
                android:src="@drawable/ic_location_on_white_24dp" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_below="@+id/text_view_location_city_activity_prayer_schedule"
                android:layout_centerHorizontal="true"
                android:alpha="0.6"
                android:fontFamily="sans-serif-condensed"
                android:text="@string/indonesia"
                android:textColor="#f5f5f5"
                android:textSize="14sp" />

        </RelativeLayout>

        <TextView
            android:id="@+id/text_view_date_activity_prayer_schedule"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="#e0e0e0"
            android:paddingBottom="@dimen/activity_vertical_margin"
            android:paddingEnd="@dimen/activity_horizontal_margin"
            android:paddingStart="@dimen/activity_horizontal_margin"
            android:paddingTop="@dimen/activity_vertical_margin"
            android:text="@string/mon_august_29_2015"
            android:textAllCaps="true"
            android:textColor="#757575" />

        <android.support.v7.widget.RecyclerView
            android:id="@+id/recycler_view_data_prayer_schedule_activity_prayer_schedule"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1" />

    </LinearLayout>

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar_activity_prayer_schedule"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="@android:color/transparent"
        android:theme="@style/ThemeOverlay.AppCompat.Dark"
        app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />

</RelativeLayout>

Ubah file PrayerScheduleActivity

Buka file PrayerScheduleActivity dan isi dengan source code berikut.

package com.codepolitan.sholatyuk.ui.schedule

import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.LinearLayoutManager
import android.view.MenuItem
import com.codepolitan.sholatyuk.R
import com.codepolitan.sholatyuk.model.DataJadwalSholat
import com.codepolitan.sholatyuk.ui.schedule.adapter.AdapterPrayerSchedule
import kotlinx.android.synthetic.main.activity_prayer_schedule.*
import java.text.SimpleDateFormat
import java.util.*
import kotlin.collections.ArrayList

class PrayerScheduleActivity : AppCompatActivity() {

    private val TAG = javaClass.simpleName

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_prayer_schedule)
        initToolbar()
        doLoadData()
    }

    /**
     * @description Setup data prayer schedule and setup adapter recyclerview
     */
    private fun doLoadData() {
        val bundle = intent.extras
        val dataPrayer = bundle.get("dataPrayer") as DataJadwalSholat
        val locationCity = bundle.getString("locationCity")
        text_view_location_city_activity_prayer_schedule.text = locationCity
        text_view_date_activity_prayer_schedule.text = SimpleDateFormat("HH:mm EEE, MMMM d, yyyy", Locale.US).format(Date())

        val listPrayTime = ArrayList<String>()
        val listPrayName = ArrayList<String>()
        val dateNow = SimpleDateFormat("yyyy-MM-dd", Locale.US).format(Date()).let {
            it.substring(it.length - 2, it.length)
        }
        for (prayerItem in dataPrayer.data) {
            if (prayerItem.tanggal == dateNow) {
                listPrayTime.let {
                    it.add(prayerItem.shubuh)
                    it.add(prayerItem.dzuhur)
                    it.add(prayerItem.ashr)
                    it.add(prayerItem.maghrib)
                    it.add(prayerItem.isya)
                }
                listPrayName.let {
                    it.add("shubuh")
                    it.add("dzuhur")
                    it.add("ashr")
                    it.add("maghrib")
                    it.add("isya")
                }
                break
            }
        }

        recycler_view_data_prayer_schedule_activity_prayer_schedule.let {
            val adapterPrayerSchedule = AdapterPrayerSchedule(
                    this,
                    listPrayTime,
                    listPrayName
            )
            it.layoutManager = LinearLayoutManager(this)
            it.adapter = adapterPrayerSchedule
        }
    }

    /**
     * @description Initialize toolbar
     */
    private fun initToolbar() {
        setSupportActionBar(toolbar_activity_prayer_schedule)
        supportActionBar?.let {
            it.title = ""
            it.setDisplayHomeAsUpEnabled(true)
        }
    }

    /**
     * @description On options item selected
     * @param item {MenuItem} menu item in toolbar
     * @return {Boolean} return true or false when selected menu item
     */
    override fun onOptionsItemSelected(item: MenuItem?): Boolean =
            item?.itemId.let {
                return when (it) {
                    android.R.id.home -> {
                        onBackPressed()
                        true
                    }
                    else -> {
                        super.onOptionsItemSelected(item)
                    }
                }
            }
}

Pada file ini, kita hanya menyajikan data yang dikirim dari activity sebelumnya yaitu, HomeActivity dimana, HomeActivity ada mengirimkan data jadwal sholat ketika, button submit di tap oleh user. Dan di PrayerScheduleActivity menerima data tersebut dan melakukan setup adapter recyclerview dan kemudian, menampilkannya.

Buat file AdapterPrayerSchedule

Buat file class baru dengan nama AdapterPrayerSchedule dan isi dengan source code berikut.

package com.codepolitan.sholatyuk.ui.schedule.adapter

import android.content.Context
import android.support.v4.content.ContextCompat
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.codepolitan.sholatyuk.R
import kotlinx.android.synthetic.main.item_prayer_schedule.view.*
import java.text.SimpleDateFormat
import java.util.*

/**
 * Created by yudisetiawan on 12/24/17.
 */
class AdapterPrayerSchedule(val context: Context, val listPrayTime: List<String>, val listPrayName: List<String>) : RecyclerView.Adapter<AdapterPrayerSchedule.ViewHolderItemPrayerSchedule>() {

    private val TAG = javaClass.simpleName

    override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): AdapterPrayerSchedule.ViewHolderItemPrayerSchedule =
            ViewHolderItemPrayerSchedule(
                    LayoutInflater.from(parent?.context)
                            .inflate(R.layout.item_prayer_schedule, null)
            )

    override fun onBindViewHolder(holder: AdapterPrayerSchedule.ViewHolderItemPrayerSchedule?, position: Int) {
        holder?.itemView?.let {
            val prayerName = listPrayName[position]
            val prayerTime = listPrayTime[position]
            val timeNow = SimpleDateFormat("HH:mm", Locale.US).format(Date())

            val timeHourNow = timeNow.split(":")[0].toInt()
            val timeMinuteNow = timeNow.split(":")[1].toInt()
            val timeHourPrayer = prayerTime.split(":")[0].toInt()
            val timeMinutePrayer = prayerTime.split(":")[1].toInt()
            var estimatedHour: Int
            estimatedHour = if (timeHourNow < timeHourPrayer) {
                timeHourPrayer - timeHourNow
            } else {
                0
            }
            val estimatedMinute: Int
            estimatedMinute = if (timeMinuteNow < timeMinutePrayer && timeHourNow < timeHourPrayer) {
                timeMinutePrayer - timeMinuteNow
            } else {
                if (estimatedHour == 0) {
                    0
                } else {
                    estimatedHour -= 1
                    60 - timeMinuteNow + timeMinutePrayer
                }
            }

            it.text_view_estimated_time_item_prayer_schedule.let {
                if (estimatedHour == 0 && estimatedMinute == 0) {
                    it.text = "Esimated time: skip"
                } else {
                    it.text = "Estimated time: $estimatedHour Hours $estimatedMinute Minutes"
                }
            }

            val hour = prayerTime.split(":")[0].toInt().let {
                if (it > 12) {
                    it - 12
                } else {
                    it
                }
            }
            val minute = prayerTime.split(":")[1].toInt()
            it.text_view_hour_item_prayer_schedule.text = hour.toString()
            it.text_view_minute_item_prayer_schedule.let {
                if (minute < 10) {
                    it.text = ":0$minute"
                } else {
                    it.text = ":$minute"
                }
            }
            it.text_view_prayer_name_item_prayer_schedule.text = prayerName.capitalize()

            when (prayerName) {
                "shubuh" -> {
                    it.image_view_circle_item_prayer_schedule
                            .setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_circle_blue))
                    it.text_view_format_hour_item_prayer_schedule.text = context.getString(R.string.am)
                }
                "dzuhur" -> {
                    it.image_view_circle_item_prayer_schedule
                            .setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_circle_pink))
                    it.text_view_format_hour_item_prayer_schedule.text = context.getString(R.string.pm)
                }
                "ashr" -> {
                    it.image_view_circle_item_prayer_schedule
                            .setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_circle_green))
                    it.text_view_format_hour_item_prayer_schedule.text = context.getString(R.string.pm)
                }
                "maghrib" -> {
                    it.image_view_circle_item_prayer_schedule
                            .setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_circle_lime))
                    it.text_view_format_hour_item_prayer_schedule.text = context.getString(R.string.pm)
                }
                "isya" -> {
                    it.image_view_circle_item_prayer_schedule
                            .setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_circle_brown))
                    it.text_view_format_hour_item_prayer_schedule.text = context.getString(R.string.pm)
                }
                else -> {
                    /** nothing to do in here */
                }
            }
        }
    }

    override fun getItemCount(): Int = listPrayName.size

    inner class ViewHolderItemPrayerSchedule(itemView: View?) : RecyclerView.ViewHolder(itemView)
}

File AdapterPrayerSchedule berfungsi sebagai adapter untuk si RecyclerView di PrayerScheduleActivity dimana, pada file adapter ini di fun onBindViewHolder() ada sedikit logic-nya dimana, app akan menampilkan waktu tersisa atau estimated time dari jadwal shalat berdasarkan waktunya sekarang dan jika sudah lewat maka, app akan menampilkan skip dan jika belum lewat jadwalnya maka, app akan menampilkan sisa waktunya menuju jadwal shalat. Selanjutnya, si app juga menggunakan format am atau pm jadi, logic-nya itu hanya melakukan pengecekan jika value hour lebih besari dari 12 maka, format-nya pm dan jika tidak maka, format-nya am.

Output

Berikut adalah output dari program yang kita buat pada tutorial ini. HomeActivity

CityActivity
PrayerScheduleActivity

Projek lengkapnya bisa Anda lihat di https://github.com/CoderJava/SholatYuk

Kesimpulan

Jadi, Kotlin Coroutines sebenarnya berfungsi untuk melakukan execute sesuatu secara asynchronous dan penggunaan multi-thread.