Pengenalan Background Process
dan Networking
Selanjutnya kita akan mempelajari background process dan networking.
Berikut ini adalah preview dari kedua modul tersebut:
1. Background Process
Pada modul ini, Anda akan belajar menjalankan proses di
background. Ini merupakan hal yang sangat penting agar aplikasi
yang Anda buat dapat menjalankan proses yang berat dan lama
tanpa memengaruhi UI. Sehingga, pengguna tidak akan merasakan
lagging atau freeze ketika menjalankan aplikasi. Beberapa
komponen yang bisa Anda gunakan untuk menjalankan proses di
background secara asynchronous adalah Handler dan
Executors, sedangkan di dalam Kotlin terdapat Coroutine.
2. Networking
Pada modul ini, Anda akan mempelajari apa itu Web API dan format
JSON. Kemudian, kita belajar cara untuk mengambil data dari Web
API dengan berbagai macam library dan mem-parsing datanya
supaya bisa dibaca oleh aplikasi. Pada latihan pertama, Anda akan
belajar menggunakan library LoopJ untuk mengetahui fundamental
parsing JSON secara manual. Lalu, pada latihan berikutnya Anda
dapat menggunakan library Retrofit untuk mem-parsing JSON
dengan lebih efektif. Materi ini sangat penting karena sebagian
besar tugas dari Android Developer adalah menampilkan dan
mengirimkan data ke server.
Beberapa poin di atas hanyalah pengenalan terhadap komponen-
komponen yang akan kita pelajari di dalam modul Background Process
dan Networking. Penting untuk memahami komponen-komponen dasar
Android ini sebelum melangkah ke materi berikutnya yang lebih dalam.
Selamat belajar!
Teori Background Thread
Aplikasi dijalankan menggunakan proses dalam linux. Ketika sebuah
komponen aplikasi dijalankan, maka sistem android akan menjalankan
sebuah proses linux baru pada aplikasi dan dengan menggunakan suatu
thread tunggal.
Secara umum semua komponen aplikasi di Android berjalan pada proses
dan thread yang sama. Ini disebut main thread atau ui thread. Secara
default ini berarti sistem Android tidak secara otomatis menciptakan
thread lain untuk menjalankan proses secara spesifik.
Ingat! activity, fragment, service, ui toolkit, broadcast receiver dijalankan
di main thread atau ui thread
Dalam pengembangan aplikasi, terkadang kita membutuhkan proses
komputasi yang intensif. Misalnya proses memanipulasi bitmap dan
proses menghubungi server.
Jika proses-proses tersebut dilakukan pada ui thread atau main thread,
maka proses tersebut akan menghambat rendering tampilan aplikasi. Hal
ini ditandai dengan komponen ui widget tidak bisa diklik. Mirip dengan
kondisi hang atau freeze.
Sistem Android akan menghitung selama 5 detik apakah aplikasi yang
sedang berjalan itu responsif atau tidak. Jika tidak, maka sistem Android
akan menampilkan dialog Application Not Responding (ANR). Jelas ini akan
memberi dampak yang negatif ke pengguna. Kesalahan ini dapat
berujung ke pengguna mencopot aplikasi kita (uninstall).
Oleh karena itu kita membutuhkan thread lain. Thread lain ini bernama
background thread atau async task yang bersifat asynchronous. Ia akan
berjalan pada thread yang terpisah dengan main thread. Yang dimaksud
dengan asynchronous di sini yaitu proses tidak berjalan berurutan,
sehingga proses lain bisa masuk dan layar Android masih bisa kita
jalankan. Ini membuat pengalaman pengguna menjadi lebih nyaman
karena aplikasi tetap terasa responsif dan tidak mengalami freeze.
Berbeda dengan kebalikannya yaitu synchronous, proses berjalan secara
berurutan, sehingga kita harus menunggu proses tersebut selesai terlebih
dahulu sebelum melakukan proses lainnya. Hal ini membuat layar Android
tidak bisa kita jalankan (freeze).
Ada dua aturan yang harus kita perhatikan agar tercipta pengalaman
pengguna yang baik ketika menerapkan proses komputasi intensif yang
memakan waktu.
Jangan memblok ui thread atau main thread. Ini berarti kita harus
menciptakan background thread atau async task yang bersifat
asynchronous. Ini akan menjaga aplikasi tetap responsif.
Jangan melakukan pemanggilan komponen ui widget (seperti TextView,
Button, ImageView dsb.) di dalam background thread atau thread yang
sedang berjalan secara asynchronous. Ini karena Android UI Toolkit
merupakan komponen yang hanya berjalan pada ui thread atau
main thread.
Oiya, bagi yang belum tahu apa itu thread berikut penjelasan singkatnya.
Thread adalah sekumpulan perintah (instruksi) yang dapat dilaksanakan
(dieksekusi) secara beriringan dengan thread lainnya. Hal ini dicapai
dengan menggunakan mekanisme time slice (ketika satu CPU melakukan
perpindahan antara satu thread ke thread lainnya) atau multiprocess
(ketika thread tersebut dijalankan oleh CPU yang berbeda dalam satu
sistem).
Gambarannya sebagai berikut:
Thread A dan Thread B masih tetep berjalan beriringan dengan
main thread.
Thread
Dengan menggunakan background thread, kita dapat menciptakan
thread baru dan menjalankan proses di thread tersebut. Terciptanya
thread baru dapat memenuhi aturan no 1 tentang pengalaman pengguna
yang baik, yaitu dengan tidak memblokir ui thread.
Contoh bagaimana menggunakan threads yaitu:
Kotlin
Java
1. fun onClick(v: View) {
2. Thread(Runnable {
3. val txt = loadStringFromNetwork("[Link]
4. [Link](txt)
5. }).start()
6. }
Sayangnya, contoh background thread di atas tidak memenuhi aturan no
2, yaitu jangan melakukan pemanggilan ui di luar ui thread atau main
thread. Perhatikan metode setText. Metode ini adalah bagian dari
textView. Sementara itu textView adalah salah satu komponen ui.
Sehingga, ia tidak diperbolehkan untuk dijalankan di background thread.
Ada beberapa fungsi yang dapat dimanfaatkan untuk memenuhi aturan 2
yaitu:
1. [Link](Runnable)
2. [Link](Runnable)
3. [Link](Runnable , long)
Agar kode di atas memenuhi aturan no 2, kita dapat menggunakan
metode post milik ui atau view. Contohnya:
Kotlin
Java
1. fun onClick(v: View) {
2. Thread(Runnable {
3. val txt = loadStringFromNetwork("[Link]
4. [Link](Runnable() {
5. [Link](txt)
6. }
7. }).start()
8. }
Kode Thread dan Runnable di awal digunakan untuk membuat thread
baru, sehingga proses dapat berjalan secara asynchronous di background
thread. Kemudian untuk berpindah ke main thread lagi, dibutuhkan fungsi
post supaya tidak terjadi eror. Namun perlu diperhatikan bahwa fungsi ini
hanya bisa digunakan di masing-masing komponen saja.
Handler
Beberapa fungsi seperti [Link] dapat menyelesaikan masalah tentang
background thread. Akan tetapi menggunakan fungsi [Link] akan
menjadikan kode lebih kompleks dan sulit untuk dikelola.
Solusinya adalah dengan menggunakan handler yang dapat mengirim dan
memproses message dan objek runnable lainnya yang berhubungan
dengan thread. Ketika handler diciptakan, maka dia terkait dengan thread
di mana diciptakan.
Berikut adalah contoh perubahan yang dapat kita lakukan:
Kotlin
Java
1. private val handler = Handler([Link]()) {
2. fun handleMessage(msg:Message) {
3. val message = [Link] as String
4. [Link] = message
5. }
6. }
7.
8. fun onClick(v:View) {
9. Thread(Runnable {
10. public override fun run() {
11. val txt = loadStringFromNetwork("[Link]
12. val msg = [Link]()
13. [Link] = txt
14. [Link](handler)
15. [Link]()
16. }
17. }).start()
18. }
Dengan mendeklarasikan obyek handler pada ui thread maka obyek
handlers diikat ke ui thread. Ketika suatu message dikirim ke handler,
maka handler akan memprosesnya. Proses dari message tersebut terjadi
pada ui thread sehingga aturan no 2 dapat terpenuhi.
Executor
Executor adalah komponen pada Android yang memiliki kemampuan
untuk memanajemen banyak thread sekaligus, baik dari segi urutan
proses, penjadwalan, maupun menjalankan proses secara paralel. Alih-alih
menjalankan Thread satu per satu, Anda sebaiknya menggunakan
Executor seperti berikut:
Kotlin
Java
1. val executor:Executor = anExecutor()
2. [Link](RunnableTask1())
3. [Link](RunnableTask2())
4. ...
Kemudian untuk mengatur jumlah thread yang dibuat, Anda bisa
memanfaatkan beberapa jenis Executor berikut:
1. newSingleThreadExecutor, digunakan jika Anda hanya ingin membuat
satu thread saja. Karena hanya ada satu thread saja, apabila ada
beberapa request yang berjalan maka request selanjutnya akan dijalankan
setelah thread pertama selesai.
2. newFixedThreadPool(nThreads), digunakan jika Anda ingin membuat
banyak thread dengan memasukkan nThread dengan jumlah thread yang
ingin Anda buat. Misal newFixedThreadPool(4), maka akan membuat 4
thread yang tetap. Sehingga apabila ada beberapa request yang berjalan,
maka akan dibagi ke 4 thread tersebut secara paralel.
3. newCachedThreadPool, thread akan dibuat sesuai dengan kebutuhan,
ia juga akan menggunakan thread sebelumnya jika bisa dipakai. Lalu
thread yang sudah tidak dipakai selama 1 menit akan otomatis dihapus
dari cache.
Selain Executor, juga ada yang namanya ExecutorService yang
merupakan turunan dari Executor. Bedanya yaitu pada ExecutorService
kita bisa memantau proses yang berjalan dengan menggunakan fungsi
submit yang menghasilkan Future. Selain itu ada fungsi shutdown yang
berfungsi untuk menolak task baru dan cancel untuk membatalkan task.
Berikut ini adalah contoh penggunaan ExecutorService dengan Handler
untuk meng-update View di UI Thread
Kotlin
Java
1. val executor: ExecutorService = [Link]()
2. val handler = Handler([Link]())
3. [Link] {
4. //do background process
5. val txt: String = loadStringFromNetwork("[Link]
6. [Link] {
7. //do ui update
8. [Link](txt)
9. }
10. }
Coroutine (Kotlin)
Coroutine adalah salah satu fitur unggulan dari Kotlin yang diperkenalkan
sejak versi 1.3. Walaupun coroutines dan threads bekerja dengan cara
sama, coroutines jauh lebih ringan dan efisien. Jutaan coroutines dapat
berjalan pada beberapa threads. Jika dibandingkan dengan threads,
coroutines tidak hanya mudah diterapkan, melainkan juga jauh lebih
powerful. Kelebihan tersebut terutama berlaku pada lingkungan mobile, di
mana setiap milliseconds kenaikan kinerja sangat diperhitungkan. Selain
itu, perbedaan lainnya adalah coroutines dikelola oleh pengguna,
sedangkan threads dikelola oleh sistem operasi. Dengan coroutine kita
tidak memerlukan lagi callback sehingga kode menjadi lebih simpel dan
ringkas.
Untuk mulai menggunakan Coroutine kita perlu menambahkan
dependency berikut terlebih dahulu:
1. implementation "[Link]:kotlinx-coroutines-core:1.1.1"
2. implementation "[Link]:kotlinx-coroutines-android:1.1.1"
Ada dua jenis cara untuk memanggil coroutine yaitu:
1. Launch, yaitu kita memanggil proses tanpa ada nilai kembalian alias fire
and forget.
Contoh dari penerapan fungsi ini yaitu seperti ini:
1. fun main(args: Array<String>) = runBlocking {
2. launch {
3. delay(1000)
4. println("Coroutines!")
5. }
6. println("Hello")
7. }
8.
9. //print
10. Hello
11. Coroutines!
Kata Hello akan ditampilkan lebih awal, dan kata Coroutines! akan ditampilkan
1 detik setelahnya. Mengapa demikian? Padahal jika diperhatikan, kode untuk
menampilkan kata Coroutines! dituliskan lebih dulu. Fungsi delay(1000) di
dalam launch digunakan untuk menunda kode berikutnya selama 1 detik. Delay
adalah fungsi yang spesial pada coroutines. Ia merupakan sebuah suspending
function yang tidak akan memblokir sebuah thread. Selama proses penundaan
tersebut, main thread akan terus berjalan sehingga fungsi println("Hello,") akan
langsung dijalankan. Setelah 1 detik, baru fungsi println("Coroutines!") akan
dijalankan.
Async, yaitu kita memanggil proses dengan menunggu nilai kembalian dalam
bentuk Deferred. Untuk mendapatkan hasil dari proses, kita bisa menggunakan
fungsi await.
Contoh dari penerapan fungsi ini yaitu seperti ini:
1. fun main(args: Array<String>) = runBlocking {
2. val first = async { getNumber() }
3. val result = [Link]()
4. println(result)
5. }
6. suspend fun getNumber(): Int {
7. delay(1000)
8. return 3*2
9. }
10.
11. //print
12. 6
Di sini kita menggunakan async dan kemudian membuat suspending
function yang memilik nilai kembalian berupa integer, kemudian untuk
mendapatkan datanya kita menggunakan await. Cukup mudah bukan?
Selain itu, kita juga perlu mengetahui Dispatcher untuk mengetahui di
mana proses coroutine kita berjalan. Ada beberapa macam pilihan
Dispatcher yang bisa kita pilih:
1. [Link], adalah default dispatcher jika tidak ada
Dispatcher yang didefinisikan, cocok untuk fungsi yang membutuhkan
proses CPU yang tinggi, seperti parsing 100 data.
2. [Link], untuk menjalankan fungsi yang berisi read-write data ke
Network/Disk, seperti menulis atau mengambil data ke database dan ke
server.
3. [Link], untuk menjalankan fungsi di Main Thread, biasanya
digunakan untuk mengupdate View UI.
Selain itu, dengan menggunakan Coroutines, kita dapat mengurangi
potensi memory leaks. Hal ini karena setiap Coroutine harus dijalankan di
dalam Scope yang membentuk structured concurrency. Scope ini
berfungsi untuk mengontrol seberapa lama coroutine menjalankan
tugasnya. Berikut ini adalah beberapa scope yang bisa Anda pakai di
Android.
CoroutineScope: Scope umum yang digunakan untuk untuk
melacak coroutine yang sedang berjalan di dalamnya. Coroutine
dapat dibatalkan dengan memanggil fungsi cancel() kapan saja.
LifecycleScope: Scope khusus yang digunakan di dalam Activity
atau Fragment. Dengan menggunakan scope ini, sistem akan
membatalkan coroutine secara otomatis ketika lifecycle masuk ke
keadaan mati (onDestroy).
ViewModelScope: Scope yang khusus digunakan di ViewModel.
Dengan menggunakan scope ini, sistem akan membatalkan
coroutine secara otomatis ketika ViewModel dibersihkan
(onCleared).
Untuk lebih mendalami topik ini, Anda dapat membaca tautan berikut:
Process and Threads
Threading
Coroutine
Understanding Kotlin Coroutines with this mental model
Latihan Simulasi Proses Asynchronous dengan Background Thread
Tujuan
Codelab ini bertujuan untuk mengimplementasikan Background Process
pada aplikasi yang Anda bangun. Harapannya aktivitas ini dapat memberi
gambaran yang jelas tentang cara kerja proses yang di dalam
background.
Codelab kali ini akan menghasilkan aplikasi berikut ini:
Alur Latihan
Berikut alur yang akan kita lakukan dalam latihan kali ini:
1. Membuat project baru dengan nama MyBackgroundThread.
2. Mengatur tampilan pada berkas layout XML.
3. Memembuat countdown.
4. Menjalankan aplikasi dan mengalami freeze.
5. Menambahkan Executor Service.
6. Menjalankan aplikasi kembali dan tidak terjadi freeze.
7. Menambahkan Coroutine sebagai alternative Executor Service di Kotlin.
8. Menjalankan aplikasi kembali.
Codelab Background Process Sederhana
1. Buat Project baru di Android Studio dengan kriteria sebagai berikut:
Nama Project MyBackgroundThread
Target & Minimum Target Phone and Tablet, Api
SDK level 21
Tipe Activity Empty Activity
Activity Name MainActivity
Use AndroidX Artifacts True
Language Kotlin/Java
2. Jika sudah, kondisikan agar activity_main.xml menjadi seperti ini:
1. <?xml version="1.0" encoding="utf-8"?>
2. <ScrollView xmlns:android="[Link]
3. xmlns:tools="[Link]
4. android:layout_width="match_parent"
5. android:layout_height="wrap_content"
6. tools:context=".MainActivity">
7.
8. <LinearLayout
9. android:layout_width="match_parent"
10. android:layout_height="wrap_content"
11. android:gravity="center"
12. android:orientation="vertical"
13. android:padding="16dp">
14.
15. <Button
16. android:id="@+id/btn_start"
17. android:layout_width="wrap_content"
18. android:layout_height="wrap_content"
19. android:layout_marginTop="300dp"
20. android:text="@string/start_task" />
21.
22. <TextView
23. android:id="@+id/tv_status"
24. android:layout_width="wrap_content"
25. android:layout_height="wrap_content"
26. tools:text="@string/status" />
27.
28. <TextView
29. android:layout_width="wrap_content"
30. android:layout_height="wrap_content"
31. android:layout_marginTop="400dp"
32. android:text="@string/hidden_text" />
33. </LinearLayout>
34.
35. </ScrollView>
Jangan lupa masukkan teks yang ada ke dalam [Link] beserta teks lain
yang akan kita gunakan pada langkah selanjutnya.
1. <resources>
2. <string name="app_name">MyBackgroundThread</string>
3. <string name="start_task">Start Task</string>
4. <string name="status">Status</string>
5. <string name="hidden_text">Now you see me!</string>
6. <string name="task_completed">Task Completed</string>
7. <string name="compressing">Compresssing %d %%</string>
8. </resources>
Pada awal latihan ini kita akan mencoba untuk menjalankan suatu proses yang
lama di UI thread dan melihat bagaimana efeknya. Sebagai contoh di sini kita
mensimulasikan proses compress yang memakan waktu 5 detik dengan
menambahkan kode berikut di dalam MainActivity:
Kotlin
Java
1. class MainActivity : AppCompatActivity() {
2.
3. override fun onCreate(savedInstanceState: Bundle?) {
4. [Link](savedInstanceState)
5. setContentView([Link].activity_main)
6.
7. val btnStart = findViewById<Button>([Link].btn_start)
8. val tvStatus = findViewById<TextView>([Link].tv_status)
9.
10. [Link] {
11. try {
12. //simulate process compressing
13. for (i in 0..10) {
14. [Link](500)
15. val percentage = i * 10
16. if (percentage == 100) {
17. [Link]([Link].task_completed)
18. } else {
19. [Link] =
[Link](getString([Link]), percentage)
20. }
21. }
22. } catch (e: InterruptedException) {
23. [Link]()
24. }
25. }
26. }
27. }
Selanjutnya jalankan project Anda dan klik tombol START TASK. Selama proses
berlangsung (5 detik) cobalah untuk scroll layar dan lihat apa yang terjadi.
Dapat dilihat saat simulasi proses berlangsung, aplikasi terlihat tidak bisa discroll
alias freeze. Baru setelah 5 detik proses scrolling 5 detik. Hal ini karena Anda
menjalankan suatu proses yang lama di ui thread, sehingga Anda tidak dapat
melakukan interkasi UI lainnya ketika proses tersebut berlangsung. Hal ini
tentunya akan menyebabkan pengalaman user menjadi tidak nyaman karena
aplikasi terasa tertinggal (lagging).
Belum lagi jika proses tersebut berjalan sangat lama (misal 500 detik dengan
mengganti Thread Sleep menjadi 50000) maka aplikasi dipastikan akan
mengalami force close dikarenakan eror Application Not Responding (ANR).
Untuk mengatasinya, pada langkah selanjutnya Anda akan menggunakan
Executor untuk membuat thread baru dan Handler untuk update hasil dari
proses ke komponen UI berupa TextView.
Kita masuk ke kelas MainActivity dan ubah kode pada kelas tersebut menjadi
seperti berikut:
Kotlin
Java
1. class MainActivity : AppCompatActivity() {
2.
3. override fun onCreate(savedInstanceState: Bundle?) {
4. [Link](savedInstanceState)
5. setContentView([Link].activity_main)
6.
7. val btnStart = findViewById<Button>([Link].btn_start)
8. val tvStatus = findViewById<TextView>([Link].tv_status)
9.
10. val executor = [Link]()
11. val handler = Handler([Link]())
12.
13. [Link] {
14. [Link] {
15. try {
16. //simulate process in background thread
17. for (i in 0..10) {
18. [Link](500)
19. val percentage = i * 10
20. [Link] {
21. //update ui in main thread
22. if (percentage == 100) {
23. [Link]([Link].task_completed)
24. } else {
25. [Link] = [Link](getString([Link]),
percentage)
26. }
27. }
28. }
29. } catch (e: InterruptedException) {
30. [Link]()
31. }
32. }
33. }
34. }
35. }
Selanjutnya jalankan kembali aplikasi Anda dan lihat perbedaannya.
Dapat dilihat sekarang latihan aplikasi dapat berjalan mulus saat menjalankan
suatu proses. Tidak ada lagi freeze dan lagging pada aplikasi Anda. User pun
mendapatkan pengalaman berinteraksi yang nyaman. Mantap!
Selamat! Anda sudah mencoba menjalankan background process dengan
menggunakan Executor. Kali ini khusus untuk Kotlin ada fitur spesial untuk
mengatur background process, yaitu dengan menggunakan Coroutine. Kita akan
mencoba me-refactor kode yang sudah dibuat dengan Executor + Handler
menjadi menggunakan Coroutine. Salah satu kelebihan menggunakan Coroutine
yaitu kita tidak memerlukan Callback lagi.
Untuk menggunakan Coroutine kita perlu menambahkan
dependencies terlebih dahulu di [Link](Module: app).
1. implementation "[Link]:kotlinx-coroutines-core:1.3.7"
2. implementation "[Link]:kotlinx-coroutines-android:1.3.9"
3. implementation "[Link]:lifecycle-runtime-ktx:2.2.0"
Kemudian ubah kode di bagian MainActivity menjadi seperti ini:
1. class MainActivity : AppCompatActivity() {
2.
3. override fun onCreate(savedInstanceState: Bundle?) {
4. [Link](savedInstanceState)
5. setContentView([Link].activity_main)
6.
7. val btnStart = findViewById<Button>([Link].btn_start)
8. val tvStatus = findViewById<TextView>([Link].tv_status)
9.
10. [Link] {
11. [Link]([Link]) {
12. //simulate process in background thread
13. for (i in 0..10) {
14. delay(500)
15. val percentage = i * 10
16. withContext([Link]) {
17. //update ui in main thread
18. if (percentage == 100) {
19. [Link]([Link].task_completed)
20. } else {
21. [Link] =
[Link](getString([Link]), percentage)
22. }
23. }
24. }
25. }
26. }
27. }
28. }
8. Kemudian jalankan kembali maka kita akan mendapatkan hasil yang
sama namun dengan kode yang lebih simpel dan ringkas.
Bedah Kode
Executor+Handler
Proyek yang kita kerjakan adalah contoh sederhana dari penggunaan
Executor untuk membuat proses berjalan secara asynchronous dan
Handler untuk tetap bisa berkomunikasi (synchronize) dengan ui thread.
Karena itulah di awal kita memerlukan kode untuk inisialisasi kedua
Object tersebut:
Kotlin
Java
1. val executor = [Link]()
2. val handler = Handler([Link]())
Di sini Anda menggunakan newSingleThreadExecutor yang artinya hanya
satu thread yang Anda buat. Sehingga ketika Anda klik tombol berkali-kali
hanya satu proses yang dijalankan dan proses lainnya akan dieksekusi
setelahnya proses sebelumnya selesai. Jika Anda ingin lebih dari satu
thread dan melihat perbedaannya, Anda bisa menggunakan
newFixedThreadPool atau newCachedThreadPool.
Kemudian pada Handler di sini Anda menggunakan getMainLooper karena
Anda ingin proses yang di dalam Handler dijalankan di main/ui thread.
Pada kasus lain, jika Anda ingin Handler berjalan dengan thread yang
sama dengan sebelumnya Anda bisa menggunakan myLooper.
Setelah kedua Object siap, berikut adalah kode untuk eksekusinya.
Kotlin
Java
1. [Link] {
2. try {
3. //simulate process in background thread
4. for (i in 0..10) {
5. [Link](500)
6. val percentage = i * 10
7. [Link] {
8. //update ui in main thread
9. if (percentage == 100) {
10. [Link]([Link].task_completed)
11. } else {
12. [Link] = [Link](getString([Link]),
percentage)
13. }
14. }
15. }
16. } catch (e: InterruptedException) {
17. [Link]()
18. }
19. }
Pada latihan ini Anda mensimulasikan sebuah proses compressing yang
panjang dengan menggunakan [Link]() dengan parameter dalam
satuan millisecond. Sehingga total waktu yang dibutuhkan yaitu 500 ms x
10 = 5000 ms / 5 detik.
Kemudian ketika akan menampilkan status pada sebuah TextView, Anda
tidak dapat melakukannya langsung dari background thread, melainkan
Anda harus melakukannya di main thread, karena itulah dibutuhkan
[Link] untuk berpindah antar thread tersebut.
Sederhana bukan? Dengan menggunakan asynchronous task untuk
menyelesaikan proses yang memakan waktu dan membutuhkan
komputasi intensif, aplikasi pun akan tetap responsif.
Coroutine
1. [Link]([Link]) { ... }
lifecycleScope merupakan scope yang sudah disediakan library lifecycle-
runtime-ktx untuk menjalankan coroutine pada Activity yang sudah
aware dengan lifecycle. Dengan begitu instance coroutine akan otomatis
dihapus ketika aplikasi dalam keadaan onDestroy sehingga aplikasi tidak
mengalami memory leak (kebocoran memori).
Selanjutnya di sini kita menggunakan method launch karena kita akan
memulai background process tanpa nilai kembalian alias fire and
forget. Sedangkan scope [Link] dipilih karena kita akan
melakukan proses biasa di Background Thread yang tidak memerlukan
proses read-write.
1. delay(500)
delay adalah method khusus yang berasal dari library coroutine. Secara
sekilas memiliki fungsi yang hampir mirip dengan [Link], namun
secara fundamental mereka sangatlah berbeda. Jika kita menggunakan
[Link] yaitu dia akan mem-block proses secara keseluruhan.
Sedangkan jika kita menggunakan delay, yang berhenti hanya yang di
dalam coroutine itu saja, sedangkann coroutine lain masih bisa dijalankan.
1. withContext([Link]) { ... }
Salah satu kelebihan coroutine yaitu kita bisa berpindah antar
thread lebih mudah. Di sini kita menggunakan
withContext([Link]) karena kita perlu pindah ke Main Thread
untuk update UI berupa TextView, jika tidak menggunakan ini, maka
UI/TextView tidak akan pernah ter-update.
Selamat! Kamu sudah belajar cukup jauh tentang fundamental aplikasi
Android.
Anda bisa mengunduh source code di atas di :
Source Code Latihan Background Thread
Teori Service
Kita telah belajar mengenai activity dan implementasinya. Activity dan
fragment merupakan dua komponen yang memberikan pengalaman
langsung kepada pengguna. Pengguna dapat melihat dan berinteraksi di
atasnya.
Service berada pada sisi yang lain. Komponen ini tidak memiliki
antarmuka dan bahkan pengguna tidak akan tahu bagaimana ia bekerja.
Pengalaman yang diberikan oleh service hanya berupa proses yang tak
terlihat. Ia digunakan untuk menjalankan beragam macam proses yang
memakan waktu lama.
Walaupun berjalan secara background, pada dasarnya service dan
komponen Android lainnya running pada satu proses dan thread yang
sama yaitu main thread atau ui thread. Bekerja di background bukan
berarti ia bekerja secara asynchronous. Service tetap membutuhkan
thread terpisah jika kita ingin melakukan proses yang membutuhkan
komputasi intensif atau yang memakan waktu.
Contoh pemanfaatan service sudah banyak sekali, antara lain:
Aplikasi sosial media atau aplikasi yang memiliki kemampuan untuk
menerima push notification. Aplikasi semacam ini pasti memiliki sebuah
service yang berjalan dalam posisi stand by untuk selalu menerima pesan
yang masuk.
Aplikasi chat juga membutuhkan service untuk melakukan pengiriman dan
menerima pesan yang dikirimkan oleh pengguna.
Aplikasi pemutar musik juga melakukan hal yang sama. Untuk
memberikan pengalaman yang lebih baik kepada pengguna, aplikasi
pemutar musik biasanya meletakkan proses streaming atau memainkan
musik di komponen service dengan tetap mempertahankan integrasi
dengan komponen lain, misalnya notifikasi. Seperti gambar berikut:
Secara umum, terdapat dua cara untuk memulai service:
1. Started Service
Service berjenis ini adalah tipe yang dijalankan oleh komponen lain, misal
activity. Sekali dijalankan, service ini akan berjalan selama belum
dimatikan atau proses yang dijalankan selesai. Service akan tetap berjalan
walaupun komponen yang lain dimatikan oleh sistem Android. Umumnya
penggunaan service ini adalah untuk melakukan proses yang tidak
memberikan nilai balik ke komponen yang memanggilnya. Contohnya
adalah, mengunduh atau mengunggah berkas.
2. Bound Service
Service jenis ini merupakan tipe service yang dijalankan oleh komponen
lain, namun saling mengikat. Hubungan yang terjadi antar kedua
komponen tersebut seperti client-server. Bisa saling menerima hasil dan
menerima request yang ada. Pada service ini dimungkinkan terjadinya
proses IPC (Interprocess Communication).
Service ini akan tetap berjalan di background selama masih ada
komponen lain yang mengikatnya. Jika tidak, maka service akan dimatikan
oleh sistem. Aplikasi pemutar musik merupakan salah satu jenis aplikasi
yang mengimplementasikan service jenis ini.
Pada bagian ini kita akan sepenuhnya membahas service berjenis started.
Tipe service tersebut akan dibagi menjadi dua bagian dalam
implementasinya:
Kelas Service yang inherit langsung kepada kelas Service
Ketika sebuah kelas java inherit ke service ingin menjalankan proses
yang memakan waktu lama, maka kelas tersebut diharuskan
membuat thread terpisah agar tidak memblokade ui thread yang
ada. Service ini akan selalu hidup di background selama tidak ada
komponen yang memanggil stopService() atau dimatikan oleh
sistem.
Kelas Service yang inherit ke JobIntentService
Ini adalah kelas yang sangat memudahkan hidup kita. Apa pasal? Ia
bersifat fire and forget. Setelah menunaikan tugasnya, ia akan
mematikan dirinya.
Poin-poin penting lain tentang service di antaranya:
Setiap kelas Java dinyatakan sebuah service bila kelas tersebut
inherit/extends ke kelas Service atau IntentService.
Service memiliki life cycle-nya sendiri dan bergantung pada tipe service
apa yang digunakan, started atau bound service.
Untuk menjalankan service dari komponen lain seperti activity, cukup
gunakan startService(Intent).
Sebaliknya untuk mematikan/stop service terdapat dua cara. Pertama
stopService(Intent) dijalankan dari komponen yang memanggil dan
stopSelf() dari kelas Service itu sendiri.
Untuk mendalami topik Service lebih lanjut, Anda dapat membaca tautan
berikut:
Services
Component Services
Service Element
Latihan Service
Tujuan
Codelab ini menitikberatkan pada implementasi kelas service untuk
melakukan aksi di latar belakang. Bagian dari codelab ini akan menjawab
beberapa pertanyaan umum dalam pengembangan aplikasi Android
sebagai berikut:
Bagaimana penggunaan kelas service dalam android studio?
Bagaimana cara memodifikasi kelas service dengan Intent Service?
Bagaimana cara service berjalan dan berhenti?
Codelab selanjutnya adalah membuat aplikasi yang dapat menerapkan
kegunaan service. Seperti ini tampilannya.
Alur Latihan
Berikut alur yang akan kita lakukan dalam latihan kali ini:
1. Membuat project baru.
2. Mengatur tampilan pada berkas layout XML.
3. Membuat kelas MyService untuk mengatur konfigurasi service.
4. Mengimplementasikan MyService ke dalam Activity untuk menjalankan
dan mengentikan service.
5. Menggunakan Executor Service (Java) atau Corotine (Kotlin) sebagai
pengelola background process untuk mematikan service yang berjalan.
6. Menjalankan aplikasi.
Codelab Service Sederhana
Ikuti langkah-langkah berikut ini.
1. Buat proyek dengan kriteria sebagai berikut:
Nama Project MyService
Target & Minimum Target Phone and Tablet, Api
SDK level 21
Tipe Activity Empty Activity
Activity Name MainActivity
Use AndroidX Android True
Language Kotlin/Java
2. Setelah proyek tercipta, lengkapi activity_main.xmldengan contoh
seperti ini:
1. <?xml version="1.0" encoding="utf-8"?>
2. <LinearLayout xmlns:android="[Link]
3. xmlns:tools="[Link]
4. android:layout_width="match_parent"
5. android:layout_height="match_parent"
6. android:padding="16dp"
7. android:orientation="vertical"
8. tools:context=".MainActivity">
9.
10. <Button
11. android:id="@+id/btn_start_service"
12. android:layout_width="match_parent"
13. android:layout_height="wrap_content"
14. android:text="@string/start_service"
15. android:layout_marginBottom="16dp"/>
16.
17. <Button
18. android:id="@+id/btn_start_job_intent_service"
19. android:layout_width="match_parent"
20. android:layout_height="wrap_content"
21. android:text="@string/start_job_intent_service"
22. android:layout_marginBottom="16dp"/>
23.
24. <LinearLayout
25. android:layout_width="match_parent"
26. android:layout_height="wrap_content"
27. android:orientation="horizontal"
28. android:weightSum="2"
29. android:gravity="center">
30. <Button
31. android:id="@+id/btn_start_bound_service"
32. android:layout_width="0dp"
33. android:layout_height="wrap_content"
34. android:layout_marginEnd="16dp"
35. android:layout_weight="1"
36. android:text="@string/start_bound_service" />
37. <Button
38. android:id="@+id/btn_stop_bound_service"
39. android:layout_width="0dp"
40. android:layout_height="wrap_content"
41. android:layout_weight="1"
42. android:text="@string/stop_bound_service" />
43. </LinearLayout>
44. </LinearLayout>
Akan ada bagian eror pada bagian android:text. Nah, seperti pada modul
sebelumnya kita perlu tambahkan resource string-nya.
Masukkan beberapa
baris kode di bawah ini di dalam res → values → [Link].
1. <resources>
2. <string name="app_name">MyServiceApp</string>
3. <string name="start_service">Start Service</string>
4. <string name="start_job_intent_service">Start Job Intent
Service</string>
5. <string name="start_bound_service">Start Bound Service</string>
6. <string name="stop_bound_service">Stop bound Service</string>
7. </resources>
Resource string ini akan kita gunakan selama latihan service.
Pada MainActivitysilakan lengkapi kodenya untuk mengenalkan obyek yang
sudah kita masukkan. Simak berikut ini:
Kotlin
Java
1. class MainActivity : AppCompatActivity() {
2.
3. override fun onCreate(savedInstanceState: Bundle?) {
4. [Link](savedInstanceState)
5. setContentView([Link].activity_main)
6.
7. val btnStartService = findViewById<Button>([Link].btn_start_service)
8. [Link] {
9.
10. }
11.
12. val btnStartJobIntentService =
findViewById<Button>([Link].btn_start_job_intent_service)
13. [Link] {
14.
15. }
16.
17. val btnStartBoundService =
findViewById<Button>([Link].btn_start_bound_service)
18. [Link] {
19.
20. }
21.
22. val btnStopBoundService =
findViewById<Button>([Link].btn_stop_bound_service)
23. [Link] {
24.
25. }
26. }
27. }
Selanjutnya buat kelas service bernama MyService dengan cara klik
kanan pada package project → New → Service → Service. MyService akan
inherit (extends) kepada kelas Service.
Selanjutnya pada dialog yang tampil, isikan nama kelas service yang diinginkan.
Di sini kita menamainya sebagai MyService dan pastikan exported dan enabled
tercentang. Klik Finish untuk menyelesaikan proses.
Selanjutnya buka berkas AndroidManifest pada package manifest
dan perhatikan isi berkas tersebut. Service yang baru saja kita buat, sudah ada
di dalam tag <application> :
1. <?xml version="1.0" encoding="utf-8"?>
2. <manifest xmlns:android="[Link]
3. package="[Link]">
4.
5. <application
6. android:allowBackup="true"
7. android:icon="@mipmap/ic_launcher"
8. android:label="@string/app_name"
9. android:roundIcon="@mipmap/ic_launcher_round"
10. android:supportsRtl="true"
11. android:theme="@style/AppTheme">
12. <activity android:name=".MainActivity">
13. <intent-filter>
14. <action android:name="[Link]" />
15.
16. <category android:name="[Link]" />
17. </intent-filter>
18. </activity>
19.
20. <service
21. android:name=".MyService"
22. android:enabled="true"
23. android:exported="true"/>
24. </application>
25.
26. </manifest>
Kode pada AndroidManifest sudah dibuat secara otomatis. Dengan demikian
kita sudah bisa menjalankan kelas service tersebut. Namun, sebelum
menjalankan aplikasi, lengkapi kode pada MyServicemenjadi seperti berikut:
Kotlin
Java
1. class MyService : Service() {
2.
3. companion object {
4. internal val TAG = MyService::[Link]
5. }
6.
7. override fun onBind(intent: Intent): IBinder? {
8. throw UnsupportedOperationException("Not yet implemented")
9. }
10.
11. override fun onStartCommand(intent: Intent, flags: Int, startId: Int):
Int {
12. Log.d(TAG, "Service dijalankan...");
13. return START_STICKY
14. }
15. }
Selanjutnya pada MainActivity tambahkan baris berikut:
Kotlin
Java
1. [Link] {
2. val mStartServiceIntent = Intent(this, MyService::[Link])
3. startService(mStartServiceIntent)
4. }
Sekarang jalankan aplikasi. Klik tombol start service dan perhatikan pada log-
nya.
MyService telah dijalankan dan tak akan pernah mati sampai dimatikan oleh
sistem atau metode stopSelf() atau stopService() dijalankan.
Baik. Sekarang kita akan membuat sebuah background process seperti pada
modul sebelumnya untuk mematikan/menghentikan service yang telah
dijalankan dengan memanggil metode stopSelf()setelah 3 detik.
Kotlin
Java
Tambahkan terlebih dahulu library Coroutine di [Link] (Module: app):
1. dependencies {
2. ...
3. implementation "[Link]:kotlinx-coroutines-core:1.3.7"
4. implementation "[Link]:kotlinx-coroutines-
android:1.3.9"
5. }
Kemudian klik Sync Now di pojok kanan atas untuk men-download library.
Setelah selesai, di kelas MyService tambahkan kode berikut:
1. class MyService : Service() {
2.
3. companion object {
4. internal val TAG = MyService::[Link]
5. }
6.
7. private val serviceJob = Job()
8. private val serviceScope = CoroutineScope([Link] +
serviceJob)
9.
10. override fun onBind(intent: Intent): IBinder? {
11. throw UnsupportedOperationException("Not yet implemented")
12. }
13.
14. override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
15. Log.d(TAG, "Service dijalankan...")
16. [Link] {
17. delay(3000)
18. stopSelf()
19. Log.d(TAG, "Service dihentikan")
20. }
21. return START_STICKY
22. }
23.
24. override fun onDestroy() {
25. [Link]()
26. [Link]()
27. Log.d(TAG, "onDestroy: ")
28. }
29.
30. }
10. Jalankan aplikasinya. Klik tombol start service dan perhatikan log-
nya dengan kata kunci MyService. Service dijalankan secara
asynchronous dan mematikan dirinya sendiri setelah proses selesai.
[Link] berhasil dijalankan, log android monitor akan seperti ini:
Bedah Kode
Kita baru saja berkenalan dengan service melalui kelas MyService. Kita
inherit kelas dasar dari service seperti yang dijelaskan sebelumnya.
Service jenis ini berada pada thread yang sama yaitu ui thread.
StartServices
Service di atas dijalankan dengan cara berikut ini:
Kotlin
Java
1. val mStartServiceIntent = Intent(this@MainActivity, MyService::[Link])
2. startService(mStartServiceIntent)
Ingat! Bukan startActivity() seperti pada contoh sebelumnya. Namun
startService() karena kita menginginkan sebuah service yang berjalan.
Setelah dijalankan, metode onStartCommand() pada MyService akan
dijalankan.
Kotlin
Java
1. override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
2. Log.d(TAG, "Service dijalankan...")
3. [Link] {
4. delay(3000)
5. stopSelf()
6. Log.d(TAG, "Service dihentikan")
7. }
8. return START_STICKY
9. }
Pada metode tersebut kita menjalankan sebuah background process
untuk melakukan simulasi proses yang sulit. Dan ia berjalan secara
asynchronous.
Kekurangan dari service tipe ini adalah ia tak menyediakan
background thread diluar ui thread secara default. Jadi tiada cara lainnya
selain membuat thread secara sendiri.
START_STICKY menandakan bahwa bila service tersebut dimatikan oleh
sistem Android karena kekurangan memori, ia akan diciptakan kembali
jika sudah ada memori yang bisa digunakan. Metode onStartCommand()
juga akan kembali dijalankan.
stopSelf() berfungsi untuk memberhentikan atau mematikan MyService
dari sistem Android.
Latihan JobIntentService
Pada titik ini Anda sudah bisa membuat sebuah service yang berjalan di
background. Nah, selenjutnya Anda akan belajar menggunakan
JobIntentService untuk melakukan suatu aksi yang dapat berhenti sendiri
di background thread.
1. Sekarang buat kembali sebuah service dengan nama
MyJobIntentService dengan cara klik kanan pada package utama
project → New → Service → Service (Intent Service).
2. Pada dialog yang tampil masukkan nama MyJobIntentService seperti ini:
Klik Finish untuk menyelesaikan proses. Pastikan untuk
tidak mencentang include helper. Include helper akan menambahkan
metode yang dapat membantu di dalam pembuatan services. Akan tetapi
pada modul ini kita tidak menggunakan metode tersebut.
3. Perhatikan pada [Link] dan tambahkan permission berikut
pada komponen service yang baru saja dibuat.
1. <service
2. android:name=".MyJobIntentService"
3. android:permission="[Link].BIND_JOB_SERVICE"
4. android:exported="false" />
Setelah kelas MyJobIntentService diciptakan, terdapat banyak kode auto-
generatedyang telah disertakan. Kita tidak membutuhkan semua metode
tersebut. Sehingga ubah kelas tersebut menjadi seperti contoh di bawah ini:
Kotlin
Java
1. class MyJobIntentService : JobIntentService() {
2.
3. override fun onHandleWork(intent: Intent) {
4.
5. }
6.
7. }
Skenarionya adalah sebagai berikut:
Kita menjalankan IntentService tersebut dengan sebuah obyek Intent dari
MainActivity dengan membawa data, dalam konteks ini adalah nilai
integer yang menentukan berapa lama background process akan
dijalankan.
MyIntentService dijalankan dan kemudian memproses obyek Intent yang
dikirimkan untuk menjalankan background.
Seperti sifatnya, IntentService tidak perlu mematikan dirinya sendiri.
Secara otomatis ketika proses yang dilakukan selesai, maka
JobIntentService berhenti dengan sendirinya.
Pada MyJobIntentService lengkapilah kodenya, fungsi onHandleWork dan
onDestroy menjadi seperti berikut:
Kotlin
Java
1. class MyJobIntentService : JobIntentService() {
2.
3. companion object {
4. private const val JOB_ID = 1000
5. internal const val EXTRA_DURATION = "extra_duration"
6. private val TAG = MyJobIntentService::[Link]
7.
8. fun enqueueWork(context: Context, intent: Intent){
9. enqueueWork(context, MyJobIntentService::[Link], JOB_ID,
intent)
10. }
11. }
12.
13. override fun onHandleWork(intent: Intent) {
14. Log.d(TAG, "onHandleWork: Mulai.....")
15. val duration = [Link](EXTRA_DURATION, 0)
16. try {
17. [Link](duration)
18. Log.d(TAG, "onHandleWork: Selesai.....")
19. } catch (e: InterruptedException) {
20. [Link]()
21. [Link]().interrupt()
22. }
23. }
24.
25. override fun onDestroy() {
26. [Link]()
27. Log.d(TAG, "onDestroy()")
28. }
29.
30. }
Selanjutnya pada MainActivity di metode onClick()
bagian [Link].btn_start_intent_service, kita tambahkan tiga baris berikut untuk
menjalankan JobIntentService yang baru saja dibuat.
Kotlin
Java
1. [Link] {
2. val mStartIntentService = Intent(this,
MyJobIntentService::[Link])
3.
[Link](MyJobIntentService.EXTRA_DURATIO
N, 5000L)
4. [Link](this, mStartIntentService)
5. }
7. Setelah selesai semua, silakan jalankan kembali aplikasinya dan
perhatikan proses yang terekam pada log aplikasi di Android Studio.
Bedah Kode
Kita tinjau kembali skenarionya. Kita menjalankan JobIntentService
tersebut dengan sebuah obyek intent yang membawa data dari
MainActivity. Kita dapat menentukan berapa lama service ini akan
berjalan.
JobIntentService
Kotlin
Java
1. val mStartIntentService = Intent(this, MyJobIntentService::[Link])
2. [Link](MyJobIntentService.EXTRA_DURATION, 5000L)
3. [Link](this, mStartIntentService)
MyJobIntentService dijalankan. Service tersebut akan melakukan
pemrosesan obyek Intent yang dikirimkan dan menjalankan suatu proses
yang berjalan di background.
Kotlin
Java
1. override fun onHandleWork(intent: Intent) {
2. Log.d(TAG, "onHandleWork: Mulai.....")
3. val duration = [Link](EXTRA_DURATION, 0)
4. try {
5. [Link](duration)
6. Log.d(TAG, "onHandleWork: Selesai.....")
7. } catch (e: InterruptedException) {
8. [Link]()
9. [Link]().interrupt()
10. }
11. }
Kode di atas akan dijalankan pada thread terpisah secara asynchronous.
Jadi kita tak lagi perlu membuat background thread seperti pada service
sebelumnya.
Terakhir, IntentService tak perlu mematikan dirinya sendiri. Service ini
akan berhenti dengan sendirinya ketika sudah selesai menyelesaikan
tugasnya.
Latihan Bound Service
Pada titik ini Anda sudah bisa membuat sebuah service dan
Intent service yang berjalan di background.
1. Sekarang buat kembali sebuah service dengan nama MyBoundService.
Carannya klik kanan pada package utama project → New → Service →
Service (Service).
2. Selanjutnya pada dialog yang tampil isikan nama kelas service yang
diinginkan. Di sini kita menamainya sebagai MyBoundService dan
pastikan exported dan enabled tercentang. Klik Finish untuk
menyelesaikan proses.
3. Setelah terbentuk, kode dari kelas MyBoundService adalah seperti ini.
o Kotlin
o Java
1. class MyBoundService : Service() {
2.
3. override fun onBind(intent: Intent): IBinder {
4. TODO("Return the communication channel to the service.")
5. }
6. }
Selanjutnya kita buat kelas MyBinder yang kita warisi dari kelas Binder di dalam
kelas MyBoundService.
Kotlin
Java
1. class MyBoundService : Service() {
2.
3. companion object {
4. private val TAG = MyBoundService::[Link]
5. }
6. private var mBinder = MyBinder()
7. private val startTime = [Link]()
8.
9. internal inner class MyBinder : Binder() {
10. val getService: MyBoundService = this@MyBoundService
11. }
12.
13. ...
14. }
Setelah membuat kelas MyBinder, kita buat sebuah variabel untuk menghitung
mundur. Kita gunakan bantuan CountTimerDown seperti berikut ini.
Kotlin
Java
1. class MyBoundService : Service() {
2.
3. ...
4.
5. private var mTimer: CountDownTimer = object :
CountDownTimer(100000, 1000) {
6. override fun onTick(l: Long) {
7. val elapsedTime = [Link]() - startTime
8. Log.d(TAG, "onTick: $elapsedTime")
9. }
10.
11. override fun onFinish() {
12.
13. }
14. }
15. }
Selanjutnya kita bisa menerapkan CountTimerDown ke dalam kelas
MyBoundService. Gunanya untuk memulai service.
Kotlin
Java
1. class MyBoundService : Service() {
2.
3. ...
4.
5. override fun onCreate() {
6. [Link]()
7. Log.d(TAG, "onCreate: ")
8. }
9.
10. override fun onBind(intent: Intent): IBinder? {
11. Log.d(TAG, "onBind: ")
12. [Link]()
13. return mBinder
14. }
15.
16. override fun onDestroy() {
17. [Link]()
18. Log.d(TAG, "onDestroy: ")
19. }
20.
21. override fun onUnbind(intent: Intent): Boolean {
22. Log.d(TAG, "onUnbind: ")
23. [Link]()
24. return [Link](intent)
25. }
26.
27. override fun onRebind(intent: Intent) {
28. [Link](intent)
29. Log.d(TAG, "onRebind: ")
30. }
31. }
Maka kelas MyBoundService menjadi seperti ini:
Kotlin
Java
1. class MyBoundService : Service() {
2.
3. companion object {
4. private val TAG = MyBoundService::[Link]
5. }
6. private var mBinder = MyBinder()
7. private val startTime = [Link]()
8.
9. private var mTimer: CountDownTimer = object : CountDownTimer(100000,
1000) {
10. override fun onTick(l: Long) {
11. val elapsedTime = [Link]() - startTime
12. Log.d(TAG, "onTick: $elapsedTime")
13. }
14.
15. override fun onFinish() {
16.
17. }
18. }
19.
20. override fun onCreate() {
21. [Link]()
22. Log.d(TAG, "onCreate: ")
23. }
24.
25. override fun onBind(intent: Intent): IBinder? {
26. Log.d(TAG, "onBind: ")
27. [Link]()
28. return mBinder
29. }
30.
31. override fun onDestroy() {
32. [Link]()
33. Log.d(TAG, "onDestroy: ")
34. }
35.
36. override fun onUnbind(intent: Intent): Boolean {
37. Log.d(TAG, "onUnbind: ")
38. [Link]()
39. return [Link](intent)
40. }
41.
42. override fun onRebind(intent: Intent) {
43. [Link](intent)
44. Log.d(TAG, "onRebind: ")
45. }
46.
47. internal inner class MyBinder : Binder() {
48. val getService: MyBoundService = this@MyBoundService
49. }
50. }
Selanjutnya pada MainActivity kita akan membuat ServiceConnection untuk
menghubungkan MainActivity dengan MyBoundService seperti berikut ini:
Kotlin
Java
1. class MainActivity : AppCompatActivity() {
2.
3. private var mServiceBound = false
4. private lateinit var mBoundService: MyBoundService
5.
6. private val mServiceConnection = object : ServiceConnection {
7.
8. override fun onServiceDisconnected(name: ComponentName) {
9. mServiceBound = false
10. }
11.
12. override fun onServiceConnected(name: ComponentName, service:
IBinder) {
13. val myBinder = service as MyBinder
14. mBoundService = [Link]
15. mServiceBound = true
16. }
17. }
18. ...
19. }
Setelah membuat penghubung kelas MyBoundService dengan MainActivity, kita
akan memberi aksi untuk button memulai dan mengakhiri MyBoundService
seperti berikut ini:
Kotlin
Java
1. val btnStartBoundService = findViewById<Button>([Link].btn_start_bound_service)
2. [Link] {
3. val mBoundServiceIntent = Intent(this, MyBoundService::[Link])
4. bindService(mBoundServiceIntent, mServiceConnection,
BIND_AUTO_CREATE)
5. }
6.
7. val btnStopBoundService = findViewById<Button>([Link].btn_stop_bound_service)
8. [Link] {
9. unbindService(mServiceConnection)
10. }
Tambahkan beberapa kode di metode onDestroy(). Tujuannya agar ketika
aplikasi sudah keluar, service akan berhenti secara otomatis.
Kotlin
Java
1. class MainActivity : AppCompatActivity() {
2.
3. ...
4.
5. override fun onDestroy() {
6. [Link]()
7. if (mServiceBound) {
8. unbindService(mServiceConnection)
9. }
10. }
11. }
Maka kelas MainActivity menjadi seperti ini:
Kotlin
Java
1. class MainActivity : AppCompatActivity() {
2.
3. private var mServiceBound = false
4. private lateinit var mBoundService: MyBoundService
5.
6. private val mServiceConnection = object : ServiceConnection {
7.
8. override fun onServiceDisconnected(name: ComponentName) {
9. mServiceBound = false
10. }
11.
12. override fun onServiceConnected(name: ComponentName, service:
IBinder) {
13. val myBinder = service as MyBinder
14. mBoundService = [Link]
15. mServiceBound = true
16. }
17.
18. }
19.
20. override fun onCreate(savedInstanceState: Bundle?) {
21. [Link](savedInstanceState)
22. setContentView([Link].activity_main)
23.
24. val btnStartService = findViewById<Button>([Link].btn_start_service)
25. [Link] {
26. val mStartServiceIntent = Intent(this, MyService::[Link])
27. startService(mStartServiceIntent)
28. }
29.
30. val btnStartJobIntentService =
findViewById<Button>([Link].btn_start_job_intent_service)
31. [Link] {
32. val mStartIntentService = Intent(this,
MyJobIntentService::[Link])
33.
[Link](MyJobIntentService.EXTRA_DURATION,
5000L)
34. [Link](this, mStartIntentService)
35. }
36.
37. val btnStartBoundService =
findViewById<Button>([Link].btn_start_bound_service)
38. [Link] {
39. val mBoundServiceIntent = Intent(this,
MyBoundService::[Link])
40. bindService(mBoundServiceIntent, mServiceConnection,
BIND_AUTO_CREATE)
41. }
42.
43. val btnStopBoundService =
findViewById<Button>([Link].btn_stop_bound_service)
44. [Link] {
45. unbindService(mServiceConnection)
46. }
47. }
48.
49. override fun onDestroy() {
50. [Link]()
51. if (mServiceBound) {
52. unbindService(mServiceConnection)
53. }
54. }
55. }
11. Sekarang jalankan aplikasi. Klik tombol Start Bound Service
kemudian klik tombol Stop Bound Service dan perhatikan pada log-nya.
Bedah Kode
MyBoundService
Kita baru saja berkenalan dengan service melalui kelas
MyBoundService. Kita inherit kelas dasar dari service seperti yang
dijelaskan sebelumnya. Service jenis ini berada pada thread yang sama
yaitu ui thread.
Service di atas dijalankan dengan cara berikut ini:
Kotlin
Java
1. val mBoundServiceIntent = Intent(this@MainActivity, MyBoundService::[Link])
2. bindService(mBoundServiceIntent, mServiceConnection,
Context.BIND_AUTO_CREATE)
Pada kode di atas kita menggunakan bindService yang digunakan untuk
memulai mengikat kelas MyBoundService ke kelas MainActivity.
Sedangkan mBoundServiceIntent adalah sebuah intent eksplisit yang
digunakan untuk menjalankan komponen dari dalam sebuah aplikasi.
Sedangkan mServiceConnection adalah sebuah ServiceConnection
berfungsi sebagai callback dari kelas MyBoundService. Kemudian ada
juga BIND_AUTO_CREATE yang membuat sebuah service jika service tersebut
belum aktif. Selain BIND_AUTO_CREATE, fungsi-fungsi lain yang
memungkinkan adalah:
BIND_ABOVE_CLIENT : yang digunakan ketika sebuah service lebih penting
daripada aplikasi itu sendiri.
BIND_ADJUST_WITH_ACTIVITY : saat mengikat sebuah service dari activity,
maka ia akan mengizinkan untuk menargetkan service mana yang lebih
penting berdasarkan activity yang terlihat oleh pengguna.
BIND_ALLOW_OOM_MANAGEMENT : memungkinkan untuk mengikat
service hosting untuk mengatur memori secara normal.
BIND_AUTO_CREATE : secara otomatis membuat service selama binding-
nya aktif.
BIND_DEBUG_UNBIND : berfungsi sebagai bantuan ketika debug
mengalami masalah pada pemanggilan unBind.
BIND_EXTERNAL_SERVICE : merupakan service yang terikat dengan
service eksternal yang terisolasi.
BIND_IMPORTANT : service ini sangat penting bagi klien, jadi harus dibawa
ke tingkat proses foreground.
BIND_NOT_FOREGROUND : pada service ini tak disarankan untuk
mengubah ke tingkat proses foreground.
BIND_WAIVE_PRIORITY : service ini tidak akan mempengaruhi penjadwalan
atau prioritas manajemen memori dari target proses layanan hosting.
Lihat kode pada mServiceConnection berikut ini:
Kotlin
Java
1. private val mServiceConnection = object : ServiceConnection {
2.
3. override fun onServiceDisconnected(name: ComponentName) {
4. mServiceBound = false
5. }
6.
7. override fun onServiceConnected(name: ComponentName, service: IBinder) {
8. val myBinder = service as MyBinder
9. mBoundService = [Link]
10. mServiceBound = true
11. }
12.
13. }
Kode di atas merupakan sebuah listener untuk menerima callback dari
ServiceConnetion. Kalau dilihat ada dua callback, yakni ketika mulai
terhubung dengan kelas service dan juga ketika kelas service sudah
terputus.
Binder
Kotlin
Java
1. internal inner class MyBinder : Binder() {
2. val getService: MyBoundService = this@MyBoundService
3. }
Kode di atas adalah kelas yang dipanggil di metode onServiceConnected
untuk memanggil kelas service. Fungsinya untuk mengikat kelas service.
Kelas MyBinder yang diberi turunan kelas Binder, mempunyai fungsi untuk
melakukan mekanisme pemanggilan prosedur jarak jauh.
Perhatikan kode onDestroy() yang ada di MainActivity.
Kotlin
Java
1. override fun onDestroy() {
2. [Link]()
3. if (mServiceBound) {
4. unbindService(mServiceConnection)
5. }
6. }
Kode onDestroy() seperti yang dijelaskan di metode sebelumnya, akan
memanggil unBindService atau melakukan pelepasan service dari Activity.
Pemanggilan unbindService di dalam onDestroy ditujukan untuk
mencegah memory leaks dari bound services.
Selanjutnya di dalam MyBoundService terdapat mTimer.
Kotlin
Java
1. private var mTimer: CountDownTimer = object : CountDownTimer(100000, 1000)
{
2. override fun onTick(l: Long) {
3. val elapsedTime = [Link]() - startTime
4. Log.d(TAG, "onTick: $elapsedTime")
5. }
6.
7. override fun onFinish() {
8.
9. }
10. }
Ketika CountDownTimer dijalankan, countdown timer akan berjalan
sampai 100.000 milisecond atau 100 detik. Intervalnya setiap 1.000
milisecond atau 1 detik akan menampilkan log. Hitungan mundur tersebut
berfungsi untuk melihat proses terikatnya kelas MyBoundService ke
MainActivity.
Kemudian perhatikan metode metode yang ada di kelas
MyBoundService.
Kotlin
Java
1. override fun onCreate() {
2. [Link]()
3. Log.d(TAG, "onCreate: ")
4. }
Metode onCreate() dipanggil ketika memulai pembentukan kelas
MyBoundService.
Setelah onCreate() terjadi maka akan lanjut ke onBind().
Kotlin
Java
1. override fun onBind(intent: Intent): IBinder? {
2. Log.d(TAG, "onBind: ")
3. [Link]()
4. return mBinder
5. }
Pada metode onBind(), service akan berjalan dan diikatkan atau
ditempelkan dengan activity pemanggil. Pada metode ini juga, mTimer
akan mulai berjalan.
Setelah service mulai terikat maka mTimer akan berjalan sesuai dengan
yang ditentukan. Selanjutnya untuk mengakhiri MyBoundService yang
masih terikat, bisa gunakan kode berikut:
Kotlin
Java
1. unbindService(mServiceConnection)
Kode di atas berfungsi untuk melepaskan service dari activity pemanggil.
Secara tidak langsung maka ia akan memanggil metode onUnbind yang
ada di kelas MyBoundService.
Kotlin
Java
1. override fun onUnbind(intent: Intent): Boolean {
2. Log.d(TAG, "onUnbind: ")
3. [Link]()
4. return [Link](intent)
5. }
Kode di atas berfungsi untuk melepaskan service dari activity pemanggil.
Kemudian setelah metode onUnbind dipanggil, maka ia akan memanggil
metode onDestroy() di kelas MyBoundService.
Kotlin
Java
1. override fun onDestroy() {
2. [Link]()
3. Log.d(TAG, "onDestroy: ")
4. }
Metode onDestroy() yang ada di MyBoundService ini berfungsi untuk
melakukan penghapusan kelas MyBoundService dari memori. Jadi
setelah service sudah terlepas dari kelas MainActivity, kelas
MyBoundService juga terlepas dari memori android.
Sampai titik ini Anda sudah mempelajari teori dasar dari service. Pada
modul berikutnya Anda akan menerapkan service pada kasus yang lebih
nyata.
Silakan unduh source code dari contoh di atas di tautan berikut ini.
Source Code Latihan Service
Teori BroadcastReceiver
Ketika mengembangkan sebuah aplikasi di Android, adakalanya kita butuh
menjalankan suatu proses tertentu ketika sebuah proses lain telah terjadi.
Misal kita ingin menjalankan sesuatu ketika device Android sudah selesai
booting dengan sempurna. Kemampuan ini dapat kita manfaatkan untuk
memberi pengalaman yang lebih baik.
Di sinilah komponen bernama Broadcast Receiver berperan penting.
Menurut definisinya, broadcast receiver adalah cara sederhana untuk
menindaklanjuti sebuah broadcast message yang dipancarkan oleh
dirinya sendiri, aplikasi lain atau sistem Android. Sementara
itu, broadcast message adalah pesan yang akan dipancarkan melalui
obyek Intent.
Saat ini banyak aplikasi yang menggunakan validasi pengguna dengan
mengirimkan kode verifikasi melalui SMS. Tanpa disadari, ketika SMS
tersebut masuk dan ia berasal dari provider aplikasi, maka akan ada
komponen dari aplikasi lain yang akan menindaklanjuti dan memproses
pesan tadi. Misalnya dengan menkonfirmasi proses validasi tersebut
dengan cara menghubungi sebuah API di server.
Interaksi antara BroadcastReceiver dengan sitem Android dapat
digambarkan dengan ilustrasi berikut ini:
Untuk mendalami lebih lanjut tentang broadcast receiver, Anda dapat
membuka tautan berikut ini:
BroadcastReceiver
BroadcastReceiver Article
BroadcastReceiver Article 2
Latihan BroadcastReceiver dari Event SMS
Tujuan
Pada materi kali ini Anda akan belajar menerapkan dua mekanisme umum
dalam memanfaatkan komponen broadcast receiver yaitu :
Implementasi broadcast receiver untuk menerima event yang di-
broadcast oleh sistem Android. Kali ini kita akan menerima broadcast
ketika ada SMS yang masuk.
Implementasi broadcast receiver untuk komunikasi antar aplikasi.
Alur Latihan
Berikut alur yang akan kita lakukan dalam latihan kali ini:
1. Membuat project baru dengan nama MyBroadcastReceiver .
2. Mengatur tampilan pada berkas layout XML.
3. Membuat kelas FileModel untuk menampung sementara data pengguna.
4. Membuat kelas SmsReceiver untuk mengatur konfigurasi broadcast
receiver.
5. Mengimplementasikan SmsReceiver ke dalam Activity.
6. Menambahkan permission untuk mengakses SMS.
7. Menjalankan aplikasi dan memicu munculnya SMS.
Latihan BroadcastReceiver dari Event SMS
1. Buat Project baru di Android Studio dengan kriteria sebagai berikut:
Nama Project MyBroadcastReceiver
Target & Minimum Target Phone and Tablet, Api
SDK level 21
Tipe Activity Empty Activity
Activity Name MainActivity
Use AndroidX Android True
Language Kotlin/Java
2. Setelah terbentuk proyek, kita lihat activity_main.xml dan ubah
komponen TextViewmenjadi:
1. <?xml version="1.0" encoding="utf-8"?>
2. <LinearLayout xmlns:android="[Link]
3. xmlns:tools="[Link]
4. android:id="@+id/activity_main"
5. android:layout_width="match_parent"
6. android:layout_height="match_parent"
7. android:padding="16dp"
8. android:orientation="vertical"
9. tools:context="[Link]">
10.
11. <TextView
12. android:layout_width="match_parent"
13. android:layout_height="wrap_content"
14. android:text="@string/this_sms_receiver_app"
15. android:layout_marginBottom="16dp" />
16.
17. <Button
18. android:id="@+id/btn_permission"
19. android:layout_width="match_parent"
20. android:layout_height="wrap_content"
21. android:text="@string/check_sms_permission" />
22. </LinearLayout>
Akan ada yang eror pada bagian android:text. Seperti pada modul sebelumnya
kita perlu menambahkan resource string-nya.
Tambahkan beberapa baris kode di bawah ini di dalam res → values →
[Link].
1. <resources>
2. <string name="app_name">MyBroadcastReceiver</string>
3. <string name="this_sms_receiver_app">Ini Aplikasi Sms
Receiver</string>
4. <string name="download_file">Download a File</string>
5. <string name="coming_from">Coming From</string>
6. <string name="this_play">Oii, main kesini jadi ngga? sekarang
yak.</string>
7. <string name="close">Tutup</string>
8. <string name="check_sms_permission">Check SMS
Permission</string>
9. <string name="incoming_message">Incoming Message</string>
10. <string name="from">from : %s</string>
11. </resources>
Resource string ini akan kita gunakan selama latihan broadcast receiver.
Selanjutnya kita tambahkan listener pada button yang sudah kita buat di
activity_main.xml ke dalam MainActivity.
Kotlin
Java
1. class MainActivity : AppCompatActivity(), [Link] {
2.
3. private var binding: ActivityMainBinding? = null
4.
5. override fun onCreate(savedInstanceState: Bundle?) {
6. [Link](savedInstanceState)
7.
8. binding = [Link](layoutInflater)
9. setContentView(binding?.root)
10.
11. binding?.btnPermission?.setOnClickListener(this)
12.
13. }
14.
15. override fun onClick(v: View) {
16. when ([Link]) {
17.
18. }
19. }
20.
21. override fun onDestroy() {
22. [Link]()
23.
24. binding = null
25. }
26. }
Jangan lupa untuk menambahkan konfigurasi untuk mengaktifkan ViewBinding
pada [Link] (module: app) seperti berikut:
1. android {
2.
3. ...
4.
5. buildFeatures {
6. viewBinding = true
7. }
8. }
Kemudian buatlah sebuah activity baru bernama SmsReceiverActivity.
Pada activity_sms_receiver.xml sempurnakan kode yang ada menjadi seperti
berikut:
1. <?xml version="1.0" encoding="utf-8"?>
2. <LinearLayout xmlns:android="[Link]
3. xmlns:tools="[Link]
4. android:id="@+id/activity_sms_receiver"
5. android:layout_width="match_parent"
6. android:layout_height="wrap_content"
7. android:orientation="vertical"
8. android:padding="16dp"
9.
tools:context="[Link]
ty">
10.
11. <TextView
12. android:id="@+id/tv_from"
13. android:layout_width="match_parent"
14. android:layout_height="wrap_content"
15. android:layout_marginBottom="16dp"
16. android:text="@string/coming_from" />
17.
18. <TextView
19. android:id="@+id/tv_message"
20. android:layout_width="match_parent"
21. android:layout_height="wrap_content"
22. android:layout_marginBottom="16dp"
23. android:text="@string/this_play"
24. android:textSize="18sp" />
25.
26. <Button
27. android:id="@+id/btn_close"
28. android:layout_width="wrap_content"
29. android:layout_height="?actionBarSize"
30. android:padding="8dp"
31. android:layout_marginBottom="24dp"
32. android:layout_gravity="end"
33. android:text="@string/close" />
34. </LinearLayout>
Lalu buka berkas [Link] pada direktori resource res → values dan
tambahkan style baru seperti ini:
1. <style name="[Link]"
parent="[Link]" />
Sehingga kode dari [Link] secara keseluruhan akan menjadi seperti ini:
1. <resources xmlns:tools="[Link]
2. <!-- Base application theme. -->
3. <style name="[Link]"
parent="[Link]">
4. <!-- Primary brand color. -->
5. <item name="colorPrimary">@color/purple_500</item>
6. <item name="colorPrimaryVariant">@color/purple_700</item>
7. <item name="colorOnPrimary">@color/white</item>
8. <!-- Secondary brand color. -->
9. <item name="colorSecondary">@color/teal_200</item>
10. <item name="colorSecondaryVariant">@color/teal_700</item>
11. <item name="colorOnSecondary">@color/black</item>
12. <!-- Status bar color. -->
13. <item name="android:statusBarColor"
tools:targetApi="l">?attr/colorPrimaryVariant</item>
14. <!-- Customize your theme here. -->
15. </style>
16.
17. <style name="[Link]"
parent="[Link]" />
18. </resources>
Idenya adalah menampilkan sebuah Activity seolah-olah menjadi sebuah dialog.
Kemudian pasang style yang sudah kita buat ke SmsReceiverActivity pada
AndroidManifest.
1. <activity
2. android:name=".SmsReceiverActivity"
3. android:theme="@style/[Link]" />
Langkah selanjutnya adalah dengan menambahkan kode di
SmsReceiverActivity untuk memberi aksi pada Textview dan Button yang
sudah kita masukkan di activity_sms_receiver.xml. Sehingga kodenya menjadi
seperti berikut ini:
Kotlin
Java
1. class SmsReceiverActivity : AppCompatActivity() {
2.
3. companion object {
4. const val EXTRA_SMS_NO = "extra_sms_no"
5. const val EXTRA_SMS_MESSAGE = "extra_sms_message"
6. }
7.
8. private var binding: ActivitySmsReceiverBinding? = null
9.
10. override fun onCreate(savedInstanceState: Bundle?) {
11. [Link](savedInstanceState)
12.
13. binding = [Link](layoutInflater)
14. setContentView(binding?.root)
15.
16. title = getString([Link].incoming_message)
17.
18. binding?.btnClose?.setOnClickListener {
19. finish()
20. }
21.
22. val senderNo = [Link](EXTRA_SMS_NO)
23. val senderMessage = [Link](EXTRA_SMS_MESSAGE)
24.
25. binding?.tvFrom?.text = getString([Link], senderNo)
26. binding?.tvMessage?.text = senderMessage
27. }
28.
29. override fun onDestroy() {
30. [Link]()
31. binding = null
32. }
33. }
Selanjutnya kita akan membuat sebuah kelas Receiver dengan cara klik kanan
pada package project utama → New → Other → BroadcastReceiver.
Pada dialog yang tampil isikan SmsReceiver dan klik Finish untuk
menyelesaikan.
Setelah tercipta secara otomatis kelas SmsReceiver yang kita buat sudah
teregistrasi di berkas AndroidManifest.
1. <receiver
2. android:name=".SmsReceiver"
3. android:enabled="true"
4. android:exported="true"/>
Namun jika Anda membuat Kelas SmsReceiver secara manual, Anda harus
memasukkan kode di atas secara manual juga. Perikasa kembali
AndroidManifest Anda.
Kemudian kita akan menambahkan kode untuk menerima data dari pesan yang
masuk.
Kotlin
Java
1. class SmsReceiver : BroadcastReceiver() {
2.
3. companion object {
4. private val TAG = SmsReceiver::[Link]
5. }
6.
7. override fun onReceive(context: Context, intent: Intent) {
8.
9. }
10.
11. private fun getIncomingMessage(aObject: Any, bundle: Bundle):
SmsMessage {
12. val currentSMS: SmsMessage
13. val format = [Link]("format")
14. currentSMS = if ([Link].SDK_INT >= 23) {
15. [Link](aObject as ByteArray, format)
16. } else [Link](aObject as ByteArray)
17. return currentSMS
18. }
19. }
Setelah metode getIncomingMessage dibuat, ia akan diterapkan ke metode
onReceive di kelas SmsReceiver.
Kotlin
Java
1. class SmsReceiver : BroadcastReceiver() {
2.
3. companion object {
4. private val TAG = SmsReceiver::[Link]
5. }
6.
7. override fun onReceive(context: Context, intent: Intent) {
8. val bundle = [Link]
9. try {
10. if (bundle != null) {
11. /*
12. Bundle dengan key "pdus" sudah merupakan standar yang
digunakan oleh system
13. */
14. val pdusObj = [Link]("pdus") as Array<*>
15. for (aPdusObj in pdusObj) {
16. val currentMessage = getIncomingMessage(aPdusObj as
Any, bundle)
17. val senderNum = [Link]
18. val message = [Link]
19. Log.d(TAG, "senderNum: $senderNum; message:
$message")
20. val showSmsIntent = Intent(context,
SmsReceiverActivity::[Link])
21. [Link] = Intent.FLAG_ACTIVITY_NEW_TASK
22.
[Link](SmsReceiverActivity.EXTRA_SMS_NO,
senderNum)
23.
[Link](SmsReceiverActivity.EXTRA_SMS_MESSAGE,
message)
24. [Link](showSmsIntent)
25. }
26. }
27. } catch (e: Exception) {
28. Log.d(TAG, "Exception smsReceiver $e")
29. }
30. }
31.
32. ...
33. }
Maka kelas SmsReceiver menjadi seperti berikut:
Kotlin
Java
1. class SmsReceiver : BroadcastReceiver() {
2.
3. companion object {
4. private val TAG = SmsReceiver::[Link]
5. }
6.
7. override fun onReceive(context: Context, intent: Intent) {
8. val bundle = [Link]
9. try {
10. if (bundle != null) {
11. /*
12. Bundle dengan key "pdus" sudah merupakan standar yang digunakan
oleh system
13. */
14. val pdusObj = [Link]("pdus") as Array<*>
15. for (aPdusObj in pdusObj) {
16. val currentMessage = getIncomingMessage(aPdusObj as Any, bundle)
17. val senderNum = [Link]
18. val message = [Link]
19. Log.d(TAG, "senderNum: $senderNum; message: $message")
20. val showSmsIntent = Intent(context, SmsReceiverActivity::[Link])
21. [Link] = Intent.FLAG_ACTIVITY_NEW_TASK
22. [Link](SmsReceiverActivity.EXTRA_SMS_NO,
senderNum)
23. [Link](SmsReceiverActivity.EXTRA_SMS_MESSAGE,
message)
24. [Link](showSmsIntent)
25. }
26. }
27. } catch (e: Exception) {
28. Log.d(TAG, "Exception smsReceiver $e")
29. }
30. }
31.
32. private fun getIncomingMessage(aObject: Any, bundle: Bundle): SmsMessage {
33. val currentSMS: SmsMessage
34. val format = [Link]("format")
35. currentSMS = if ([Link].SDK_INT >= 23) {
36. [Link](aObject as ByteArray, format)
37. } else [Link](aObject as ByteArray)
38. return currentSMS
39. }
40. }
Selanjutnya pada berkas AndroidManifest tambahkan permission di atas tag
seperti ini:
1. <uses-permission android:name="[Link].RECEIVE_SMS"/>
2. <uses-permission android:name="[Link].READ_SMS" />
Dan tambahkan baris intent-filterdi dalam tag yang kita punya seperti di bawah
ini:
1. <intent-filter>
2. <action android:name="[Link].SMS_RECEIVED"
/>
3. </intent-filter>
Sehingga berkas [Link] akan menjadi seperti ini:
1. <?xml version="1.0" encoding="utf-8"?>
2. <manifest xmlns:android="[Link]
3. package="[Link]">
4. <uses-permission android:name="[Link].RECEIVE_SMS"
/>
5. <uses-permission android:name="[Link].READ_SMS" />
6. <application
7. android:allowBackup="true"
8. android:icon="@mipmap/ic_launcher"
9. android:label="@string/app_name"
10. android:supportsRtl="true"
11. android:theme="@style/[Link]">
12. <activity android:name=".MainActivity">
13. <intent-filter>
14. <action android:name="[Link]" />
15. <category android:name="[Link]" />
16. </intent-filter>
17. </activity>
18. <activity android:name=".SmsReceiverActivity"
19. android:theme="@style/[Link]"/>
20. <receiver
21. android:name=".SmsReceiver"
22. android:enabled="true"
23. android:exported="true">
24. <intent-filter>
25. <action
android:name="[Link].SMS_RECEIVED" />
26. </intent-filter>
27. </receiver>
28. </application>
29. </manifest>
Selesai sudah bagian pertama dalam memahami broadcast receiver. Silakan
jalankan aplikasi Anda. Cobalah mengirim SMS ke peranti Android Anda dan
perhatikan hasilnya.
Jika dilihat dari hasil pesan yang masuk, aplikasi masih belum bisa membaca dan
menampilkan pesan tersebut ke layar Android. Mengapa demikian? Peranti
tersebut menggunakan OS Android 9.0. Bila Anda memiliki peranti Android
dengan versi Marshmallow ke atas, Anda perlu memberikan permission pada
aplikasi untuk mengirimkan SMS. Jadi kita perlu menambahkan kelas untuk
mengatur permission.
Buat object(Kotlin)/classs(Java) baru dan beri nama PermissionManager.
Selanjutnya kita tambahkan metode untuk pengecekan permission ke dalam
PermissionManager.
Kotlin
Java
1. object PermissionManager {
2. fun check(activity: Activity, permission: String, requestCode: Int) {
3. if ([Link](activity, permission) !=
PackageManager.PERMISSION_GRANTED) {
4. [Link](activity,
arrayOf(permission), requestCode)
5. }
6. }
7. }
Setelah kita buat metode untuk checkpermission, panggil metode check ke
dalam MainActivity seperti berikut:
Kotlin
Java
1. class MainActivity : AppCompatActivity(), [Link] {
2.
3. companion object {
4. private const val SMS_REQUEST_CODE = 101
5. }
6.
7. ...
8.
9. override fun onClick(v: View) {
10. when {
11. [Link] == [Link].btn_permission -> [Link](this,
[Link].RECEIVE_SMS, SMS_REQUEST_CODE)
12. }
13. }
14.
15. override fun onRequestPermissionsResult(requestCode: Int,
permissions: Array<String>, grantResults: IntArray) {
16. [Link](requestCode,
permissions, grantResults)
17. if (requestCode == SMS_REQUEST_CODE) {
18. when {
19. grantResults[0] ==
PackageManager.PERMISSION_GRANTED -> [Link](this,
"Sms receiver permission diterima", Toast.LENGTH_SHORT).show()
20. else -> [Link](this, "Sms receiver permission
ditolak", Toast.LENGTH_SHORT).show()
21. }
22. }
23. }
24. }
[Link] menambahkan permission, jalankan aplikasi Anda. Cobalah kirim
SMS ke peranti Android Anda dan perhatikan hasilnya. Setiap SMS yang
masuk akan ditampilkan langsung dilayar. Keren kan!
[Link] memberikan permission dari SMS, Anda bisa menonaktifkan
dengan caranya, buka settings → App & notifications →
MyBroadcastReceiver → Permissions → SMS.
Bedah Kode
Bedah kode kita mulai dari awal, yaitu bagian ide. Kali ini kami mengajak
Anda untuk membuat sebuah aplikasi sederhana yang memiliki fungsi
untuk menampilkan setiap SMS yang masuk ke layar pengguna. Intinya
agar pengguna dapat melihat SMS yang masuk pada panel notifikasi
perangkat Android-nya.
SmsReceiver
Logika sederhananya adalah sebagai berikut:
Komponen broadcast receiver yaitu SmsReceiver akan bereaksi ketika
ada SMS yang masuk. Hal ini tertuang di dalam [Link]
seperti berikut:
1. <receiver
2. android:name=".SmsReceiver"
3. android:enabled="true"
4. android:exported="true">
5. <intent-filter>
6. <action android:name="[Link].SMS_RECEIVED" />
7. </intent-filter>
8. </receiver>
Pada bagian action
android:name="[Link].SMS_RECEIVED" berfungsi
sebagai flag intent-filter di mana SmsReceiver kita bisa merespon jika
ada SMS masuk dengan permission yang diberikan.
1. <uses-permission android:name="[Link].RECEIVE_SMS" />
2. <uses-permission android:name="[Link].READ_SMS" />
Di metode onReceive()receiver akan memproses metadata dari SMS yang
masuk.
Selanjutnya ReceiverActivity akan dijalankan dengan membawa data
melalui sebuah intent showSmsIntent.
Kotlin
Java
1. override fun onReceive(context: Context, intent: Intent) {
2. val bundle = [Link]
3. try {
4. if (bundle != null) {
5. val pdusObj = [Link]("pdus") as Array<Any>
6. for (aPdusObj in pdusObj) {
7. val currentMessage = getIncomingMessage(aPdusObj, bundle)
8. val senderNum = [Link]
9. val message = [Link]
10. Log.d(TAG, "senderNum: $senderNum; message: $message")
11. val showSmsIntent = Intent(context, SmsReceiverActivity::[Link])
12. [Link] = Intent.FLAG_ACTIVITY_NEW_TASK
13. [Link](SmsReceiverActivity.EXTRA_SMS_NO,
senderNum)
14. [Link](SmsReceiverActivity.EXTRA_SMS_MESSAGE,
message)
15. [Link](showSmsIntent)
16. }
17. }
18. } catch (e: Exception) {
19. Log.d(TAG, "Exception smsReceiver$e")
20. }
21. }
Kita memanfaatkan fasilitas yang terdapat pada kelas SmsManager dan
SmsMesssage untuk melakukan pemrosesan SMS. Untuk memperoleh
obyek dari kelas SmsMessage, yaitu obyek currentMessage, kita
menggunakan metode getIncomingMessage(). Metode ini akan
mengembalikan currentMessage berdasarkan OS yang dijalankan oleh
perangkat Android. Hal ini perlu dilakukan karena metode
[Link]((object); sudah deprecated di peranti
dengan OS Marshmallow atau versi setelahnya.
Kotlin
Java
1. private fun getIncomingMessage(aObject: Any, bundle: Bundle): SmsMessage {
2. val currentSMS: SmsMessage
3. if ([Link].SDK_INT >= 23) {
4. val format = [Link]("format")
5. currentSMS = [Link](aObject as ByteArray, format)
6. } else currentSMS = [Link](aObject as ByteArray)
7. return currentSMS
8. }
Flag pada [Link](Intent.FLAG_ACTIVITY_NEW_TASK);
akan menjalankan Activity pada task yang berbeda. Bila Activity
tersebut sudah ada di dalam stack, maka ia akan ditampilkan ke layar.
Anda dapat membaca penjelasan lebih lanjut mengenai konsep ini pada
tautan berikut:
Task and Back Stack
Setelah obyek intent diterima, ReceiverActivity akan ditampilkan
dengan background transparan. Ini akan membuatnya seolah-olah
menjadi sebuah dialog. Dengan cara seperti ini, pesan dapat ditampilkan.
Kotlin
Java
1. val senderNo = [Link](EXTRA_SMS_NO)
2. val senderMessage = [Link](EXTRA_SMS_MESSAGE)
3.
4. binding?.tvFrom?.text = getString([Link], senderNo)
5. binding?.tvMessage?.text = senderMessage
Wow! Sekarang Anda sudah bisa membuat aplikasi yang bisa
menindaklanjuti sebuah event penting pada perangkat Android. Anda juga
dapat mengembangkan aplikasi tersebut lebih lanjut. Misalnya membuat
fitur untuk mendeteksi SMS spam semacam "Papa, Mama minta saham…"
Latihan BroadcastReceiver dari Custom Event
Selanjutnya, mari beranjak ke kasus kedua di mana aplikasi kita bisa
mem-broadcast sebuah event. Diumpamakan:
Aplikasi melakukan pengunduhan berkas menggunakan background
service dan asynchronous.
Ketika proses mengunduh selesai, service akan melakukan broadcast
sebuah event dan akan ada Activity yang merespon.
Jadi, ayo kita ngoding lagi.
1. Buka activity_main.xml dan silakan kondisikan kode di dalamnya
menjadi seperti berikut.
1. <?xml version="1.0" encoding="utf-8"?>
2. <LinearLayout xmlns:android="[Link]
3. xmlns:tools="[Link]
4. android:id="@+id/activity_main"
5. android:layout_width="match_parent"
6. android:layout_height="match_parent"
7. android:padding="16dp"
8. android:orientation="vertical"
9. tools:context="[Link]">
10.
11. <TextView
12. android:layout_width="match_parent"
13. android:layout_height="wrap_content"
14. android:text="@string/this_sms_receiver_app"
15. android:layout_marginBottom="16dp" />
16.
17. <Button
18. android:id="@+id/btn_permission"
19. android:layout_width="match_parent"
20. android:layout_height="wrap_content"
21. android:text="@string/check_sms_permission" />
22.
23. <Button
24. android:id="@+id/btn_download"
25. android:layout_width="match_parent"
26. android:layout_height="wrap_content"
27. android:text="@string/download_file" />
28. </LinearLayout>
Setelah selesai, lengkapi kode pada MainActivity untuk mengenalkan obyek
yang udah dibuat di activity_main.xml menjadi:
Kotlin
Java
1. class MainActivity : AppCompatActivity(), [Link] {
2.
3. companion object {
4. const val ACTION_DOWNLOAD_STATUS = "download_status"
5. private const val SMS_REQUEST_CODE = 101
6. }
7.
8. private lateinit var downloadReceiver: BroadcastReceiver
9. private var binding: ActivityMainBinding? = null
10.
11. override fun onCreate(savedInstanceState: Bundle?) {
12. [Link](savedInstanceState)
13.
14. binding = [Link](layoutInflater)
15. setContentView(binding?.root)
16.
17. binding?.btnPermission?.setOnClickListener(this)
18. binding?.btnDownload?.setOnClickListener(this)
19. }
20.
21. override fun onClick(v: View) {
22. when ([Link]) {
23. [Link].btn_permission -> [Link](this,
[Link].RECEIVE_SMS, SMS_REQUEST_CODE)
24. [Link].btn_download -> {
25.
26. }
27. }
28. }
29.
30. ...
31. }
Buat sebuah class baru dengan nama DownloadService dengan cara klik kanan
pada package utama project → new → Java/Kotlin Class. Setelah tercipta
sesuaikan kode di dalamnya menjadi seperti berikut:
Kotlin
Java
1. class DownloadService : JobIntentService() {
2.
3. companion object {
4. val TAG: String = DownloadService::[Link]
5. }
6.
7. override fun onStartCommand(intent: Intent?, flags: Int, startId: Int):
Int {
8. if (intent != null) {
9. enqueueWork(this, this::[Link], 101, intent)
10. }
11. return [Link](intent, flags, startId)
12. }
13.
14. override fun onHandleWork(intent: Intent) {
15. Log.d(TAG, "Download Service dijalankan")
16. try {
17. [Link](5000)
18. } catch (e: InterruptedException) {
19. [Link]()
20. }
21.
22. val notifyFinishIntent =
Intent(MainActivity.ACTION_DOWNLOAD_STATUS)
23. sendBroadcast(notifyFinishIntent)
24. }
25. }
Kemudian tambahkan kode berikut pada AndroidManifest untuk mendaftarkan
service yang telah kita buat:
1. <?xml version="1.0" encoding="utf-8"?>
2. <manifest xmlns:android="[Link]
3. package="[Link]">
4. ...
5. <application
6. ...
7. <receiver
8. android:name=".SmsReceiver"
9. android:enabled="true"
10. android:exported="true">
11. <intent-filter>
12. <action android:name="[Link].SMS_RECEIVED" />
13. </intent-filter>
14. </receiver>
15.
16. <service
17. android:name=".DownloadService"
18. android:permission="[Link].BIND_JOB_SERVICE" />
19.
20. </application>
21.
22. </manifest>
Terakhir, Pada MainActivity lengkapi kode yang ada dengan menambahkan
sebuah Intent untuk menjalankan DownloadService di metode onClick()sehingga
kode di dalamnya menjadi seperti ini:
Kotlin
Java
1. class MainActivity : AppCompatActivity(), [Link] {
2.
3. companion object {
4. const val ACTION_DOWNLOAD_STATUS = "download_status"
5. private const val SMS_REQUEST_CODE = 101
6. }
7.
8. private lateinit var downloadReceiver: BroadcastReceiver
9. private var binding: ActivityMainBinding? = null
10.
11. override fun onCreate(savedInstanceState: Bundle?) {
12. [Link](savedInstanceState)
13.
14. ...
15.
16. downloadReceiver = object : BroadcastReceiver() {
17. override fun onReceive(context: Context, intent: Intent) {
18. Log.d([Link], "Download Selesai")
19. [Link](context, "Download Selesai",
Toast.LENGTH_SHORT).show()
20. }
21. }
22. val downloadIntentFilter =
IntentFilter(ACTION_DOWNLOAD_STATUS)
23. registerReceiver(downloadReceiver, downloadIntentFilter)
24. }
25.
26. override fun onClick(v: View) {
27. when ([Link]) {
28. [Link].btn_permission -> [Link](this,
[Link].RECEIVE_SMS, SMS_REQUEST_CODE)
29. [Link].btn_download -> {
30. val downloadServiceIntent = Intent(this,
DownloadService::[Link])
31. startService(downloadServiceIntent)
32. }
33. }
34. }
35.
36. override fun onDestroy() {
37. [Link]()
38. unregisterReceiver(downloadReceiver)
39. binding = null
40. }
41.
42. ...
43. }
Dan kode MainActivity menjadi seperti ini:
Kotlin
Java
1. class MainActivity : AppCompatActivity(), [Link] {
2.
3. companion object {
4. const val ACTION_DOWNLOAD_STATUS = "download_status"
5. private const val SMS_REQUEST_CODE = 101
6. }
7.
8. private lateinit var downloadReceiver: BroadcastReceiver
9. private var binding: ActivityMainBinding? = null
10.
11. override fun onCreate(savedInstanceState: Bundle?) {
12. [Link](savedInstanceState)
13.
14. binding = [Link](layoutInflater)
15. setContentView(binding?.root)
16.
17. binding?.btnPermission?.setOnClickListener(this)
18. binding?.btnDownload?.setOnClickListener(this)
19.
20. downloadReceiver = object : BroadcastReceiver() {
21. override fun onReceive(context: Context, intent: Intent) {
22. Log.d([Link], "Download Selesai")
23. [Link](context, "Download Selesai",
Toast.LENGTH_SHORT).show()
24. }
25. }
26. val downloadIntentFilter = IntentFilter(ACTION_DOWNLOAD_STATUS)
27. registerReceiver(downloadReceiver, downloadIntentFilter)
28. }
29.
30. override fun onClick(v: View) {
31. when ([Link]) {
32. [Link].btn_permission -> [Link](this,
[Link].RECEIVE_SMS, SMS_REQUEST_CODE)
33. [Link].btn_download -> {
34. val downloadServiceIntent = Intent(this,
DownloadService::[Link])
35. startService(downloadServiceIntent)
36. }
37. }
38. }
39.
40. override fun onDestroy() {
41. [Link]()
42. unregisterReceiver(downloadReceiver)
43. binding = null
44. }
45.
46. override fun onRequestPermissionsResult(requestCode: Int, permissions:
Array<String>, grantResults: IntArray) {
47. [Link](requestCode,
permissions, grantResults)
48. if (requestCode == SMS_REQUEST_CODE) {
49. when (PackageManager.PERMISSION_GRANTED) {
50. grantResults[0] -> [Link](this, "Sms receiver
permission diterima", Toast.LENGTH_SHORT).show()
51. else -> [Link](this, "Sms receiver permission
ditolak", Toast.LENGTH_SHORT).show()
52. }
53. }
54. }
55. }
6. Selesai! Sekarang jalankan aplikasi Anda dan klik pada tombol Download
File dan lihat pada log Android Monitor di Android Studio Anda.
7. Skenario di atas sudah terimplementasi dengan baik. Sekarang ayo kita
bahas kodenya.
Bedah Kode
Mari kita bedah skenario yang telah dijabarkan di awal modul ini.
Aplikasi melakukan pengunduhan berkas menggunakan background
service secara asynchronous.
onHandleWork
Di sini kita akan menjalankan Intent Service yang akan melakukan proses
mengunduh file secara Asynchronous di background. Kelas
DownloadService mengambil peran di sini. Pada kenyataanya,
DownloadService ini hanya melakukan proses sleep() selama 5 detik
dan kemudian mem-broadcast sebuah IntentFilter dengan Action yang
telah ditentukan, ACTION_DOWNLOAD_STATUS. Kodenya sebagai berikut:
Kotlin
Java
1. override fun onHandleWork(intent: Intent) {
2. Log.d(TAG, "Download Service dijalankan")
3. try {
4. [Link](5000)
5. } catch (e: InterruptedException) {
6. [Link]()
7. }
8.
9. val notifyFinishIntent = Intent(MainActivity.ACTION_DOWNLOAD_STATUS)
10. sendBroadcast(notifyFinishIntent)
11. }
Ketika proses pengunduhan berkas tersebut selesai, service akan mem-
broadcast sebuah event dan akan ada Activity yang merespon.
BroadcastReceiver
Kembali ke MainActivity, perhatikan ada dua variable tambahan di atas
metode onCreate(), yaitu:
Kotlin
Java
1. companion object {
2. const val ACTION_DOWNLOAD_STATUS = "download_status"
3. ...
4. }
5.
6. private lateinit var downloadReceiver: BroadcastReceiver
Pada metode onCreate() kita membuat sebuah obyek dari
DownloadReceiver. Kemudian MainActivity diregistrasikan untuk
mendengar event/action dengan tag:
ACTION_DOWNLOAD_STATUS. Ketika event/action tersebut ditangkap oleh
MainActivity, maka obyek downloadReceiver akan dijalankan.
Kotlin
Java
1. downloadReceiver = object : BroadcastReceiver() {
2. override fun onReceive(context: Context, intent: Intent) {
3. Log.d([Link], "Download Selesai")
4. [Link](context, "Download Selesai",
Toast.LENGTH_SHORT).show()
5. }
6. }
7. val downloadIntentFilter = IntentFilter(ACTION_DOWNLOAD_STATUS)
8. registerReceiver(downloadReceiver, downloadIntentFilter)
Ketika baris ini dijalankan pada DownloadService.
Kotlin
Java
1. val notifyFinishIntent = Intent(MainActivity.ACTION_DOWNLOAD_STATUS)
2. sendBroadcast(notifyFinishIntent)
Maka seketika itu pula metode onReceive() di bawah ini akan merespon
untuk melakukan proses di dalamnya:
Kotlin
Java
1. override fun onReceive(context: Context, intent: Intent) {
2. Log.d([Link], "Download Selesai")
3. [Link](context, "Download Selesai",
Toast.LENGTH_SHORT).show()
4. }
Pada konteks di atas, kita hanya menampilkan sebuah Toast dan Log.
Terakhir, ketika Activity dimatikan atau dihancurkan oleh sistem Android
maka kita tidak boleh lupa untuk melakukan proses pencopotan
(unregister) obyek receiver yang telah diregistrasikan sebelumnya,
seperti yang dituangkan oleh baris berikut:
Kotlin
Java
1. override fun onDestroy() {
2. [Link]()
3. unregisterReceiver(downloadReceiver)
4.
5. ...
6. }
Jika ada yang menyadari bahwa ini adalah proses yang sama dengan cara
sebelumnya, namun lebih manual, maka itu benar sekali! Kami
menunjukan bahwa kita bisa menggunakan metode manual dengan
pemrograman Java pada proses manipulasi di Activity/Fragment.
Berikut adalah 3 poin penting yang menjadi kesimpulan dari proses
manual di atas:
Registrasikan sebuah obyek BroadcastReceiver pada komponen aplikasi
seperti Activity dan Fragment dan tentukan action/event apa yang ingin
didengar/direspon.
Lakukan proses terkait pada metode onReceive() ketika event atau
action yang dipantau di-broadcast oleh komponen lain.
Jangan lupa untuk mencopot pemasangan obyek receiver sebelum
komponen tersebut dihancurkan atau dimatikan.
Selamat, satu lagi komponen penting dari pengembangan aplikasi Android
telah Anda pelajari. Untuk lebih mendalami pemahaman tentang
komponen ini kami menekankan Anda untuk membaca lebih lanjut
dokumentasi teknis dari link yang kami berikan.
Untuk source code project ini bisa Anda unduh di:
Source Code Latihan Broadcast Receiver
Teori Notification
Seperti yang telah kita ketahui bersama, banyak aplikasi yang dapat
mengabari kita ketika ada informasi baru yang diperolehnya. Informasi
tersebut bernama notification. Misalnya saat ada pesan masuk di
WhatsApp, pemberitahuan di Facebook, dan lain-lain.
Ada beragam jenis notifikasi. Ada yang berupa pop-up, dan ada juga yang
muncul di status bar perangkat kita.
Pada modul ini, Anda akan mempelajari bagaimana menampilkan
notifikasi sederhana. Anda juga akan mempelajari bagaimana mengirim
notifikasi tersebut menggunakan PendingIntent.
Design Considerations
Notifikasi sebagai bagian yang penting dari antarmuka Android, memiliki
design guidelines sendiri. Sejak material design diperkenalkan, Anda perlu
mempelajari bagaimana material design mempengaruhi rancangan
sebuah notifikasi.
Selengkapnya, temukan di tautan berikut:
[Link]
Creating Notifications
Anda bisa menentukan informasi user interface dan action dari notifikasi
dengan obyek [Link]. Untuk membuat notifikasi itu
sendiri, Anda bisa menggunakan [Link]() yang
akan mengembalikan sebuah obyek notifikasi. Untuk memberikan
notifikasi, Anda bisa menggunakan [Link]().
Required Notifications Contents
Berikut adalah parameter yang harus ada dalam membuat notifikasi:
1. Small Icon, dapat diatur dengan setSmallIcon().
2. Title, dapat diatur dengan setContentTitle().
3. Detail Text, dapat diatur dengan setContentText().
Notification Actions
Meskipun ini tidak wajib, Anda sebaiknya menambahkan sebuah action
untuk notifikasi yang Anda buat. Action ini digunakan untuk melihat
informasi yang lebih detail dari notifikasi tersebut.
Pada praktiknya, sebuah notifikasi bisa memiliki lebih dari satu action.
Misalnya dalam notifikasi terdapat sebuah tombol, seperti pada notifikasi
SMS, yang dengannya Anda dapat langsung membalas SMS tersebut.
Dalam notifikasi tersebut, sebuah action didefinisikan dengan
PendingIntent dan dimasukkan ke dalam [Link].
Sebagai contoh jika Anda ingin membuka sebuah activity ketika notifikasi
tersebut disentuh, Anda bisa menggunakan PendingIntent dengan
memanggil setContentIntent().
Contoh penggunaanya yaitu:
Kotlin
Java
1. var notificationIntent = Intent(this, MainActivity::[Link])
2. var notificationPendingIntent = [Link](this,
NOTIFICATION_ID, notificationIntent,
PendingIntent.FLAG_UPDATE_CURRENT)
3. var mBuilder = [Link](this)
4. .setSmallIcon([Link].notification_icon)
5. .setContentTitle("Hey, check this app now!")
6. .setContentText("Something important you need to know.")
7. .setContentIntent(notificationPendingIntent)
Notification Priority
Anda juga bisa mengatur prioritas notifikasi yang Anda buat. Anda bisa
mengatur bagaimana notifikasi akan ditampilkan.
Untuk mengatur prioritas dari notifikasi, Anda bisa menggunakan
[Link]() kemudian di masukkan ke dalam
NotificationCompat. Ada 5 (lima) tingkatan prioritas, mulai dari
PRIORITY_MIN (-2) sampai PRIORITY_MAX (2). Secara default prioritas
diatur dengan PRIORITY_DEFAULT (0).
Contoh penggunaanya yaitu:
Kotlin
Java
1. [Link](Notification.PRIORITY_HIGH)
Peeking
Notification dengan priority MAX atau HIGH bisa menggunakan fitur
peeking. Di mana notification akan muncul sebentar di layar kita seperti
mengintip (peeking), tidak terpengaruh dengan aplikasi apapun yang
sedang kita buka. Sebagai catatan, pada Android versi 6.0 ke atas fitur ini
bisa dinon-aktifkan oleh user melalui menu Setting.
Contoh penggunannya yaitu:
Kotlin
Java
1. val mBuilder =
2. [Link](this)
3. .setSmallIcon([Link].notification_icon)
4. .setContentTitle("My notification")
5. .setContentText("Hello World!")
6. .setPriority(NotificationCompat.PRIORITY_HIGH)
7. .setDefaults(NotificationCompat.DEFAULT_ALL)
Expanded view layouts
Saat memunculkan notifikasi, selain menggunakan normal view, kita juga
bisa menggunakan expanded view. Expanded View ini mulai dikenalkan
pada Android 4.1. Ada beberapa macam expanded view yang bisa kita
gunakan, yaitu:
1. BigTextStyle, untuk notifikasi dengan isian text yang banyak.
2. InboxStyle, untuk notifikasi yang berisi list berupa text, biasanya
diguanakan oleh aplikasi chatting.
3. MediaStyle, untuk notifikasi yang khusus menangani Media Player, di
mana sudah tersedia tombol play, pause, maupun stop.
4. BigPictureStyle, untuk notifikasi yang menampilkan gambar di dalamnya,
kita sering melihatnya dipakai oleh aplikasi seperti Instagram.
Berikut contoh penggunaan dari BigPictureStyle:
Kotlin
Java
1. val androidImage = [Link](resources,
[Link].mascot_1)
2.
3. val notif = [Link](this)
4. .setContentTitle("New photo from " + [Link]())
5. .setContentText(subject)
6. .setSmallIcon([Link].new_post)
7. .setLargeIcon(androidImage)
8. .setStyle([Link]()
9. .bigPicture(androidImage)
10. .setBigContentTitle("Large Notification Title"))
11. .build()
Notification pada Android O ke Atas
Mulai Android Oreo, Anda harus membuat channel agar notifikasi bisa
berjalan. Channel di sini berfungsi untuk mengelompokkan notifikasi
dalam beberapa grup seperti kanal (channel). Kelebihannya yaitu kita
mengatur notifikasi yang dalam satu channel tersebut dalam sekali
waktu, seperti mengatur prioritas, suara, maupun getaran. Hal ini tentu
akan memudahkan pengguna untuk mem-filter notifikasi yang masuk,
sehingga meningkatkan kenyamanan user dalam menggunakan aplikasi.
Langkah yang harus dilakukan yaitu dengan menambahkan kode berikut:
Kotlin
Java
1. if ([Link].SDK_INT >= Build.VERSION_CODES.O) {
2. val channel = NotificationChannel(CHANNEL_ID, CHANNEL_NAME,
NotificationManager.IMPORTANCE_DEFAULT)
3. [Link](CHANNEL_ID)
4. [Link](channel)
5. }
Pembahasan lebih lanjut mengenai notification dapat Anda baca pada
tautan berikut:
Notifications : Documentation
Latihan Notification dengan NotificationBuilder & NotificatonManager
Tujuan
Codelab kali ini akan membahas secara lengkap tentang notifications
beserta cara mengatur notifikasi. Beberapa poin yang bisa didapatkan
pada codelab kali ini adalah:
1. Menggunakan notifikasi secara sederhana.
2. Memanfaatkan pendingintent ke dalam notifikasi.
Codelab ini akan menjadi sebuah aplikasi seperti ini:
Alur Latihan
Berikut alur yang akan kita lakukan dalam latihan kali ini:
1. Membuat project baru dengan nama SimpleNotif.
2. Menambahkan kode untuk memunculkan notifikasi.
3. Mengatur tampilan pada berkas layout XML.
4. Menjalankan aplikasi.
Codelab Menampilkan Notifikasi
1. Buat Project baru di Android Studio dengan kriteria sebagai berikut:
Nama Project SimpleNotif
Target & Minimum Target Phone and Tablet, Api
SDK level 21
Tipe Activity Empty Activity
Activity Name MainActivity
Use AndroidX Artifacts True
Language Kotlin/Java
2. Setelah membuat sebuah project, tambahkan beberapa string pada
[Link], sehingga menjadi seperti berikut:
1. <resources>
2. <string name="app_name">SimpleNotif</string>
3. <string name="content_title">Content title</string>
4. <string name="content_text">Content text</string>
5. <string name="subtext">Sub text</string>
6. <string name="send_notif">Send Notif</string>
7. </resources>
Selanjutnya kita butuh mengatur tampilan dari notifikasi. Caranya,
gunakan [Link]. OK, silakan tambahkan
NotificationBuilder pada MainActivity:
Kotlin
Java
1. class MainActivity : AppCompatActivity() {
2. companion object {
3. private const val NOTIFICATION_ID = 1
4. private const val CHANNEL_ID = "channel_01"
5. private const val CHANNEL_NAME = "dicoding channel"
6. }
7.
8. override fun onCreate(savedInstanceState: Bundle?) {
9. [Link](savedInstanceState)
10. setContentView([Link].activity_main)
11. }
12.
13. //aksi untuk onClick pada button
14. fun sendNotification(view: View) {
15.
16. val mNotificationManager =
getSystemService(Context.NOTIFICATION_SERVICE) as
NotificationManager
17. val mBuilder = [Link](this, CHANNEL_ID)
18. .setSmallIcon([Link].ic_notifications_white_48px)
19. .setLargeIcon([Link](resources,
[Link].ic_notifications_white_48px))
20. .setContentTitle([Link]([Link].content_title))
21. .setContentText([Link]([Link].content_text))
22. .setSubText([Link]([Link]))
23. .setAutoCancel(true)
24.
25. val notification = [Link]()
26.
27. [Link](NOTIFICATION_ID, notification)
28. }
29. }
Anda dapat mengunduh ikon pada halaman ini:
[Link]
Namun jika anda jalankan di OS Oreo ke atas, notifkasi tidak akan muncul.
Hal ini terjadi karena kita perlu menambahkan kode tentang Notification
Channel, sehingga kelas MainActivity menjadi seperti berikut:
Kotlin
Java
1. class MainActivity : AppCompatActivity() {
2. companion object {
3. private const val NOTIFICATION_ID = 1
4. private const val CHANNEL_ID = "channel_01"
5. private const val CHANNEL_NAME = "dicoding channel"
6. }
7.
8. override fun onCreate(savedInstanceState: Bundle?) {
9. [Link](savedInstanceState)
10. setContentView([Link].activity_main)
11. }
12.
13. //aksi untuk onClick pada button
14. fun sendNotification(view: View) {
15.
16. val mNotificationManager =
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
17. val mBuilder = [Link](this, CHANNEL_ID)
18. .setSmallIcon([Link].ic_notifications_white_48px)
19. .setLargeIcon([Link](resources,
[Link].ic_notifications_white_48px))
20. .setContentTitle([Link]([Link].content_title))
21. .setContentText([Link]([Link].content_text))
22. .setSubText([Link]([Link]))
23. .setAutoCancel(true)
24.
25. /*
26. Untuk android Oreo ke atas perlu menambahkan notification
channel
27. */
28.
29. if ([Link].SDK_INT >= Build.VERSION_CODES.O) {
30.
31. /* Create or update. */
32. val channel = NotificationChannel(CHANNEL_ID, CHANNEL_NAME,
NotificationManager.IMPORTANCE_DEFAULT)
33. [Link] = CHANNEL_NAME
34.
35. [Link](CHANNEL_ID)
36. [Link](channel)
37. }
38.
39. val notification = [Link]()
40.
41. [Link](NOTIFICATION_ID, notification)
42. }
43. }
Tahap selanjutnya adalah membuat layout yang berisi button dan
memanggil fungsi sendNotification() langsung dari layout dengan
menggunakan onClick. Silakan tambahkan kode berikut pada
activity_main.xml.
1. <RelativeLayout
2. xmlns:android="[Link]
3. xmlns:tools="[Link]
4. android:id="@+id/activity_main"
5. android:layout_width="match_parent"
6. android:layout_height="match_parent"
7. tools:context=".MainActivity">
8.
9. <Button
10. android:id="@+id/button"
11. android:layout_width="wrap_content"
12. android:layout_height="wrap_content"
13. android:layout_centerInParent="true"
14. android:layout_gravity="center"
15. android:onClick="sendNotification"
16. android:text="@string/send_notif"
17. android:textColor="@android:color/white" />
18.
19. </RelativeLayout>
5. Sampai di sini Anda sudah bisa melihat hasilnya. Silakan jalankan
aplikasinya.
Bedah Kode
1. <Button
2. android:id="@+id/button"
3. android:layout_width="wrap_content"
4. android:layout_height="wrap_content"
5. android:layout_centerInParent="true"
6. android:layout_gravity="center"
7. android:onClick="sendNotification"
8. android:text="@string/send_notif"
9. android:textColor="@android:color/white" />
Digunakan untuk memanggil fungsi langsung dari layout, syaratnya yaitu
fungsi tersebut memiliki parameter View.
Kotlin
Java
1. fun sendNotification(view: View) {
Kelebihan dari cara ini yakni kita tidak perlu menginisialisasi Button
tersebut di onCreate, sehingga pemanggilan kode bisa lebih cepat.
Notification Builder
Kotlin
Java
1. val mBuilder = [Link](this, CHANNEL_ID)
2. .setSmallIcon([Link].ic_notifications_white_48px)
3. .setLargeIcon([Link](resources,
[Link].ic_notifications_white_48px))
4. .setContentTitle([Link]([Link].content_title))
5. .setContentText([Link]([Link].content_text))
6. .setSubText([Link]([Link]))
7. .setAutoCancel(true)
Kode di atas digunakan untuk menambahkan elemen apa saja yang akan
ditampikan pada notifikasi seperti icon, action, title, content, dan lain-
lain.
Dalam notification builder terdapat beberapa komponen sebagai berikut:
Small Icon : Ikon ini yang akan muncul pada status bar (wajib ada).
Large Icon : Ikon ini yang akan muncul di sebelah kiri dari text
notifikasi.
Content Intent : Intent ini sebagai action jika notifikasi ditekan.
Content Title : Judul dari notifikasi (wajib ada).
Content Text : Text yang akan muncul di bawah judul notifikasi
(wajib ada).
Subtext : Text ini yang akan muncul di bawah content text.
Auto Cancel : Digunakan untuk menghapus notifikasi setelah
ditekan.
NotificationManager
Kotlin
Java
1. val mNotificationManager = getSystemService(Context.NOTIFICATION_SERVICE)
as NotificationManager
2. [Link](NOTIFICATION_ID, notification)
Kode di atas digunakan untuk mengirim notifikasi sesuai dengan id yang
kita berikan. Fungsi id di sini nanti juga bisa untuk membatalkan notifikasi
yang sudah muncul.
Notif Untuk OS Oreo ke Atas
Kotlin
Java
1. if ([Link].SDK_INT >= Build.VERSION_CODES.O) {
2. val channel = NotificationChannel(CHANNEL_ID, CHANNEL_NAME,
NotificationManager.IMPORTANCE_DEFAULT)
3. [Link] = CHANNEL_NAME
4. [Link](CHANNEL_ID)
5. [Link](channel)
6. }
Untuk memunculkan notifkasi pada OS Oreo ke atas, Anda harus
membuat channel agar notifikasi bisa berjalan.
Pada modul berikutnya Anda akan coba menambahkan aksi ketika sebuah
notifikasi diklik. Tetap semangat ya!
Latihan Notification dengan PendingIntent
Sekarang kita akan mengatur aplikasi tersebut dengan menggunakan
pending intent.
Pending intent adalah sebuah token yang diberikan untuk aplikasi lainnya
(termasuk juga aplikasi itu sendiri). Contohnya seperti
NotificationManager, AlarmManager, HomeScreenAppWidgetManager, dan
lain-lain.
Di sini kita kan menggunakan pending intent untuk mengirimkan
notifikasi. Kita juga akan menggunakan HandlerpostDelayed agar kita bisa
mengatur delay untuk mengirimkan notifikasi tersebut. Di sini kita atur
waktu jedanya (delay) 5000 ms atau 5 detik.
Selain itu, kita juga bisa mengatur action dari intent tersebut. Pada contoh
ini kita mengatur action untuk membuka sebuah web url dengan
menggunakan Intent.ACTION_VIEW dan menambahkan url pada [Link].
1. Kemudian tambahkan PendingIntent untuk diterapkan di mBuilder
pada MainActivity seperti di bawah ini:
o Kotlin
o Java
1. fun sendNotification(view: View) {
2. val intent = Intent(Intent.ACTION_VIEW,
[Link]("[Link]
3. val pendingIntent = [Link](this, 0, intent, 0)
4.
5. val mNotificationManager =
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
6. val mBuilder = [Link](this, CHANNEL_ID)
7. .setContentIntent(pendingIntent)
8. .setSmallIcon([Link].ic_notifications_white_48px)
9. .setLargeIcon([Link](resources,
[Link].ic_notifications_white_48px))
10. .setContentTitle([Link]([Link].content_title))
11. .setContentText([Link]([Link].content_text))
12. .setSubText([Link]([Link]))
13. .setAutoCancel(true)
14.
15. ...
16. }
Maka kelas MainActivity menjadi seperti ini :
Kotlin
Java
1.
1. class MainActivity : AppCompatActivity() {
2. companion object {
3. private const val NOTIFICATION_ID = 1
4. private const val CHANNEL_ID = "channel_01"
5. private const val CHANNEL_NAME = "dicoding channel"
6. }
7.
8. override fun onCreate(savedInstanceState: Bundle?) {
9. [Link](savedInstanceState)
10. setContentView([Link].activity_main)
11. }
12.
13. //aksi untuk onClick pada button
14. fun sendNotification(view: View) {
15. val intent = Intent(Intent.ACTION_VIEW,
[Link]("[Link]
16. val pendingIntent = [Link](this, 0, intent, 0)
17.
18. val mNotificationManager =
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
19. val mBuilder = [Link](this, CHANNEL_ID)
20. .setContentIntent(pendingIntent)
21. .setSmallIcon([Link].ic_notifications_white_48px)
22. .setLargeIcon([Link](resources,
[Link].ic_notifications_white_48px))
23. .setContentTitle([Link]([Link].content_title))
24. .setContentText([Link]([Link].content_text))
25. .setSubText([Link]([Link]))
26. .setAutoCancel(true)
27.
28. /*
29. Untuk android Oreo ke atas perlu menambahkan notification channel
30. */
31. if ([Link].SDK_INT >= Build.VERSION_CODES.O) {
32.
33. /* Create or update. */
34. val channel = NotificationChannel(CHANNEL_ID, CHANNEL_NAME,
NotificationManager.IMPORTANCE_DEFAULT)
35.
36. [Link](CHANNEL_ID)
37.
38. [Link](channel)
39. }
40.
41. val notification = [Link]()
42.
43. [Link](NOTIFICATION_ID, notification)
44. }
45. }
2. Lalu jalankan aplikasinya. Jika Anda tekan notifikasi tersebut, Anda
akan masuk ke website Dicoding.
Bedah Kode
PendingIntent
Kotlin
Java
1. val intent = Intent(Intent.ACTION_VIEW, [Link]("[Link]
2. val pendingIntent = [Link](this, 0, intent, 0)
Digunakan untuk memberikan action jika notifikasi disentuh. Selain ke
halaman Web, anda juga bisa mengganti intent tersebut ke Activity
dengan menggunakan Intent untuk memanggil activity seperti biasanya.
Selamat Anda sudah belajar Notification dengan Pending Intent!
Anda dapat mengunduh source code pada tautan ini:
Source Code Latihan Notification
Teori Task dan Back Stack
Sebelumnya kita telah belajar bagaimana activity dan intent bekerja
dalam membentuk sebuah hierarki dalam sebuah aplikasi. Pengguna
dapat berpindah dari satu activity ke activity lain melalui fasilitas intent.
Tidak hanya itu, activity dapat berinteraksi dengan activity lain pada
aplikasi yang berbeda.
Kita juga belajar bagaimana sebuah activity memiliki sebuah daur hidup
dan terkumpul dalam sebuah stack dengan konsep last in, first out
(LIFO). Activity yang terakhir masuk akan berada pada posisi paling atas
dalam tumpukan stack. Ketika pengguna menekan tombol back pada
peranti, maka activity tersebut yang akan pertama kali dikeluarkan dari
stack. Activity yang dikeluarkan tadi akan kembali muncul di layar
pengguna. Ketika pengguna menekan tombol back, semua task terkait
aplikasi tersebut akan dihapus dari memori.
Pola umum dalam sebuah aplikasi Android bersifat list to detail seperti ini:
Di mana interaksi pertama pengguna dengan aplikasi adalah dengan
menekan ikon aplikasi di layar home screen atau application gallery.
Kemudian sistem Android akan memeriksa apakah Aplikasi tersebut
memiliki task atau tidak.
Bila aplikasi tidak memiliki task, maka aplikasi akan membuat sebuah
task baru. Aplikasi tersebut akan mencari Activity yang memiliki
IntentFilter dengan action sebagai launcher yang memiliki kategori main
di [Link].
Contohnya seperti baris berikut:
1. <activity android:name=".MainActivity">
2. <intent-filter>
3. <action android:name="[Link]" />
4. <category android:name="[Link]" />
5. </intent-filter>
6. </activity>
MainActivity ditampilkan ke layar pengguna. Activity ini masuk ke dalam
sebuah stack yang dinamakan back stack. Activity ini akan berada pada
posisi foreground/running. Ia menjadi root activity. MainActivity tersebut
berbentuk list email seperti pada gambar di atas.
Ketika pengguna menekan salah satu item pada list, MainActivity akan
diberhentikan dan semua state-nya akan disimpan di obyek bundle secara
otomatis. Kemudian metode callback onStop() pada activity tersebut akan
dijalankan.
Pada tahap berikutnya, DetailEmailActivity akan muncul ke layar. Ia
berada pada posisi foreground dan berada pada posisi teratas di dalam
back stack.
Ketika pengguna menekan physical back button pada perangkat Android
atau menekan tombol back pada toolbar DetailEmailActivity, maka
DetailEmailActivity akan dikeluarkan dari back stack. Metode onDestroy()
akan dijalankan dan DetailEmailActivity akan dihancurkan dari memori.
Berikutnya, MainActivity akan muncul kembali dilayar.
Penjelasan di atas dapat digambarkan pada ilustrasi di bawah ini.
Proses di atas dinamakan back stack. Secara arsitektur, setiap aplikasi di
Android memiliki back stack dan task-nya masing-masing. Back stack dan
task tadi terbungkus dalam sebuah virtual sandbox sehingga integritas
masing-masing aplikasi lebih terjaga.
Task yang dimaksud di sini adalah kumpulan dari beberapa activity yang
melakukan sebuah pekerjaan. Jika digambarkan seperti ini:
Task A di atas akan melakukan sebuah pekerjaan yaitu mengirim email
dari salah satu opsi yang diklik pada activity detail.
Sebuah aplikasi Android bisa memiliki beberapa task. Namun hanya ada
satu task yang dapat berjalan di foreground atau tampil di layar. Sisanya
akan berada di background. Model ini yang memungkinkan Android
menerapkan mutlitasking.
Anda dapat memahami lebih dalam topik task dan back stack pada tautan
berikut ini:
Task and Back Stack
Launch Mode dan Flag
Secara umum, fungsi default dari back stack ini sudah bagus dan
konsisten. Namun di beberapa kasus Anda perlu mengubahnya. Sebagai
contoh Anda memiliki SearchView untuk melakukan pencarian. Ketika
Anda menekan Enter, ia akan memanggil Activity baru untuk
menampilkan hasil pencarian. Bayangkan jika Anda melakukan pencarian
berkali-kali, maka akan terdapat banyak Activity yang sama terbuat. Dan
untuk kembali ke halaman awal, Anda harus menekan tombol back
berulang-ulang kali. Untuk mengatasinya, Anda bisa memanfaatkan
launchMode dan flag. Dalam kasus ini yaitu dengan dengan
menambahkan launchMode=”singleTop” di Android Manifest atau
dengan menambahkan flag Intent.FLAG_ACTIVITY_SINGLE_TOP pada
Intent.
Berikut adalah macam-macam launch mode dan flag lainnya:
Misal
sudah
Misal sudah
ada A dan
ada A dan B
B apa
apa yang
Mode Proses yang terjadi yang
terjadi jika
terjadi
memanggil
jika
B?
memangg
il A?
Setiap kali Intent,
A B
Instance baru terbuat.
Standard (default) B B
Bisa lebih dari satu di
A A
dalam sebuah task.
Gunakan lagi instance
jika sudah ada di
paling atas task dan
singleTop | A
memanggil B
FLAG_ACTIVITY_SINGLE_ B
onNewIntent(). Jika A
TOP A
tidak di paling atas,
instance akan dibuat
lagi.
Gunakan lagi instance
jika sudah ada di
singleTask |
dalam task yang sama B
FLAG_ACTIVITY_NEW_TA A
di mana pun posisinya, A
SK
kemudian memanggil
onNewIntent().
Hanya ada satu
singleInstance instance di dalam satu A B
task.
Catatan:
Susunan di atas menunjukkan susunan back stack dari atas ke bawah, di mana
yang paling atas adalah yang terlihat di layar.
Untuk memahami lebih lanjut contohnya di dalam aplikasi, Anda dapat
melihat tautan berikut:
Android Tasks and Back Stack
TaskStackBuilder
Jika Anda membuat notifikasi yang ketika diklik menuju suatu activity
yang cukup dalam, DetailActivity misalnya. Ada satu yang harus Anda
perhatikan di sini. Yaitu ketika Anda menekan tombol back, secara
default aplikasi akan langsung keluar. Padahal lumrahnya ia menuju ke
halaman MainActivity (awal) terlebih dahulu.
Mari kita lihat contoh notifikasi chat dari aplikasi WhatsApp. Ketika
notifikasi tersebut diklik, Anda akan menuju halaman detail chat. Ketika
anda tekan tombol back, apa yang terjadi? Aplikasi menuju ke halaman
list chat. Sebagaimana ketika Anda membuka aplikasi dari awal menuju
ke detail chat. Seperti itulah seharusnya navigasi dari suatu aplikasi
mengalir.
Lalu bagaimana cara untuk membuat alur navigasi seperti itu? Di sinilah
TaskStackBuilder datang sebagai solusi. Dengan menggunakan
TaskStackBuilder Anda dapat membangun back stack sendiri dan
dikirimkan melalui Intent.
Namun syarat yang perlu dilakukan yaitu Anda perlu menambahkan
hierarki dari Activity Anda pada [Link]. Caranya yaitu
dengan menambahkan kode android:parentActivityName pada setiap
Activity yang dituju dari notifikasi. Sebagai contohnya seperti berikut:
1. <activity
2. android:name=".MainActivity"
3. android:label="@string/app_name" >
4. <intent-filter>
5. <action android:name="[Link]" />
6. <category android:name="[Link]" />
7. </intent-filter>
8. </activity>
9. <activity
10. android:name=".DetailActivity"
11. android:parentActivityName=".MainActivity" />
12. ...
13. </activity>
Selanjutnya ini adalah cara default untuk membuat PendingIntent di
dalam Notification:
Kotlin
Java
1. val intent = Intent(this, DetailActivity::[Link]).apply {
2. flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
3. }
4. val pendingIntent: PendingIntent = [Link](this, 0, intent, 0)
Alih alih menggunakan cara di atas, Anda akan menggunakan
TaskStackBuilder untuk membuat PendingIntent yang memiliki back stack
seperti ini:
Kotlin
Java
1. val pendingIntent = [Link](this)
2. .addParentStack(DetailActivity::[Link])
3. .addNextIntent(notifDetailIntent)
4. .getPendingIntent(110, PendingIntent.FLAG_UPDATE_CURRENT)
Dengan menggunakan PendingIntent ini, maka ketika Anda menuju
DetailActivity dari notifikasi dan tekan tombol back, maka aplikasi akan
kembali ke halaman MainActivity.
Untuk memahami lebih lanjut Anda dapat melihat tautan berikut:
Start an Activity from a Notification
Up and Back Button
Selanjutnya, Anda pasti sudah mengenali dua tombol back yang ada di
Android. Pertama tombol yang berada di bagian bawah dan menempel ke
peranti, yang bernama system back button. Tombol back kedua berada
pada toolbar yang disebut up button.
Penggunaan kedua tombol di atas hampir sama. Yaitu memungkinkan
pengguna pindah ke activity sebelumnya. Bedanya, up button terbatas
hanya untuk hierarki antar layar dalam sebuah Aplikasi. Sementara itu,
system back button akan mengembalikan pengguna ke halaman home
screen bila pada saat itu pengguna berada pada root activity.
Kita telah mempelajari alur activity pada penjelasan di atas. Kita juga
telah belajar bagaimana menerapkan fitur notifikasi. Pada modul
berikutnya, kita akan mempelajari bagaimana menangani kejadian ketika
notifikasi di atas ditekan oleh pengguna dan mengarahkan pengguna ke
activity yang berkaitan. Kita juga akan mempelajari bagaimana
menambahkan up button pada toolbar di tiap activity.
Latihan Task dan Back Stack dengan TaskStackBuilder
Tujuan
Pada latihan kali ini kita akan mencoba menerapkan skenario yang mirip
dengan kejadian pada gambar berikut:
Pada skenario di atas ketika pengguna menakan tombol membuat email
baru, maka pengguna akan diarahkan ke activity yang bernama
ComposeActivity. Di Activity ini, ketika pengguna menekan tombol up
button, maka pada halaman utama aplikasi akan tampil InboxActivity.
Hal yang sama akan terjadi bila pada ComposeActivity, pengguna
menekan system back button. InboxActivity akan ditampilkan pula.
Perbedaan baru muncul ketika pengguna berada di halaman utama,
InboxActivity. Hanya system back button saja yang dapat digunakan
untuk menutup aplikasi. Ketika tombol ini ditekan, maka activity utama
akan dihancurkan. Semua task yang dimiliki aplikasi akan dihapus dari
memori. Di tahap akhir, pengguna akan dikembalikan ke halaman home
screen dari perangkat Androidnya.
Alur di atas tidak hanya berlaku pada widget saja. Ia juga dapat terjadi
pada notifikasi. Ketika terjadi pada notifikasi, mekanisme tersebut
bernama deeplink.
Pada codelab kali ini, Anda akan membuat aplikasi seperti berikut:
Alur Latihan
Berikut alur yang akan kita lakukan dalam latihan kali ini:
1. Membuat project baru dengan nama MyDeepNavigation.
2. Mengatur tampilan pada berkas layout XML.
3. Menambahkan TaskStackBuilder (PendingIntent) untuk melakukan
perpindahan ke Activity tertentu.
4. Menjalankan aplikasi.
Codelab TaskStackBuilder Sederhana
1. Buat Project baru di Android Studio dengan kriteria sebagai berikut:
Nama Project MyDeepNavigation
Target & Minimum Target Phone and Tablet, Api
SDK level 21
Tipe Activity Empty Activity
Activity Name MainActivity
Use AndroidX Artifacts True
Language Kotlin/Java
2. Yang pertama khusus bahasa Kotlin tambahkan view binding pada
[Link](Module) seperti ini:
1. android {
2. ...
3. buildFeatures {
4. viewBinding = true
5. }
6. }
Kemudian pada activity_main.xml sesuaikan kodenya menjadi seperti ini:
1. <?xml version="1.0" encoding="utf-8"?>
2. <LinearLayout xmlns:android="[Link]
3. xmlns:tools="[Link]
4. android:id="@+id/activity_main"
5. android:layout_width="match_parent"
6. android:layout_height="match_parent"
7. android:padding="16dp"
8. android:orientation="horizontal"
9. tools:context="[Link]">
10. <Button
11. android:id="@+id/btn_open_detail"
12. android:layout_width="match_parent"
13. android:layout_height="wrap_content"
14. android:text="@string/open_detail"/>
15. </LinearLayout>
Selanjutnya, buat activity baru dengan nama DetailActivity dan sesuaikan kode
di dalam activity_detail.xml menjadi seperti berikut:
1. <?xml version="1.0" encoding="utf-8"?>
2. <LinearLayout xmlns:android="[Link]
3. xmlns:tools="[Link]
4. android:id="@+id/activity_detail"
5. android:layout_width="match_parent"
6. android:layout_height="match_parent"
7. android:padding="16dp"
8. android:orientation="vertical"
9. tools:context="[Link]">
10. <TextView
11. android:id="@+id/tv_title"
12. android:layout_width="match_parent"
13. android:layout_height="wrap_content"
14. android:text="@string/title"
15. android:textStyle="bold"
16. android:layout_marginBottom="16dp"/>
17. <TextView
18. android:id="@+id/tv_message"
19. android:layout_width="match_parent"
20. android:layout_height="wrap_content"
21. android:text="@string/message"
22. android:layout_marginBottom="16dp"/>
23. </LinearLayout>
Jangan lupa untuk menambahkan teks yang ada di layout ke dalam [Link].
1. <resources>
2. <string name="app_name">MyDeepNavigation</string>
3. <string name="activity_home">Home</string>
4. <string name="activity_detail">Detail</string>
5.
6. <string name="hello_blank_fragment">Hello blank fragment</string>
7. <string name="title">Title</string>
8. <string name="message">Message</string>
9. <string name="open_detail">Open Detail</string>
10. <string name="notification_title">Hi, how are you?</string>
11. <string name="notification_message">Do you have any plan this
weekend? Let\'s hangout</string>
12. <string name="detail_title">Hola, Good News</string>
13. <string name="detail_message">Now you can learn android in
Dicoding</string>
14. </resources>
Pada DetailActivity lengkapi kodenya menjadi seperti berikut:
Kotlin
Java
1. class DetailActivity : AppCompatActivity() {
2.
3. private lateinit var binding: ActivityDetailBinding
4.
5. companion object {
6. const val EXTRA_TITLE = "extra_title"
7. const val EXTRA_MESSAGE = "extra_message"
8. }
9.
10. override fun onCreate(savedInstanceState: Bundle?) {
11. [Link](savedInstanceState)
12. binding = [Link](layoutInflater)
13. setContentView([Link])
14.
15. val title = [Link](EXTRA_TITLE)
16. val message = [Link](EXTRA_MESSAGE)
17.
18. [Link] = title
19. [Link] = message
20. }
21. }
Buka berkas [Link]. Tambahkan atribut
android:launchMode="singleTop" pada MainActivity dan
android:parentActivityName=".MainActivity" pada DetailActivity. Kemudian
tambahkan beberapa baris berikut ke dalam DetailActivity.
1. <meta-data
2. android:name="[Link].PARENT_ACTIVITY"
3. android:value=".MainActivity" />
Sehingga secara keseluruhan [Link] kita miliki menjadi
seperti berikut.
1. <?xml version="1.0" encoding="utf-8"?>
2. <manifest xmlns:android="[Link]
3. package="[Link]">
4.
5. <application
6. android:allowBackup="true"
7. android:icon="@mipmap/ic_launcher"
8. android:label="@string/app_name"
9. android:roundIcon="@mipmap/ic_launcher_round"
10. android:supportsRtl="true"
11. android:theme="@style/AppTheme">
12. <activity
13. android:name=".MainActivity"
14. android:launchMode="singleTop">
15. <intent-filter>
16. <action android:name="[Link]" />
17. <category android:name="[Link]" />
18. </intent-filter>
19. </activity>
20. <activity
21. android:name=".DetailActivity"
22. android:label="@string/activity_detail"
23. android:parentActivityName=".MainActivity">
24. <meta-data
25. android:name="[Link].PARENT_ACTIVITY"
26. android:value=".MainActivity" />
27. </activity>
28. </application>
29.
30. </manifest>
Dan terakhir pada MainActivity lengkapi kodenya menjadi seperti berikut:
Kotlin
Java
1. class MainActivity : AppCompatActivity(), [Link] {
2.
3. private lateinit var binding: ActivityMainBinding
4.
5. override fun onCreate(savedInstanceState: Bundle?) {
6. [Link](savedInstanceState)
7. binding = [Link](layoutInflater)
8. setContentView([Link])
9.
10. [Link](this)
11. }
12.
13.
14. override fun onClick(v: View) {
15. if ([Link] == [Link].btn_open_detail) {
16. val detailIntent = Intent(this@MainActivity,
DetailActivity::[Link])
17. [Link](DetailActivity.EXTRA_TITLE,
getString([Link].detail_title))
18. [Link](DetailActivity.EXTRA_MESSAGE,
getString([Link].detail_message))
19. startActivity(detailIntent)
20. }
21. }
22. }
Setelah itu buat fungsi untuk menampilkan notification sehingga kode pada
MainActivity menjadi seperti ini:
Kotlin
Java
1. class MainActivity : AppCompatActivity(), [Link] {
2.
3. private lateinit var binding: ActivityMainBinding
4.
5. override fun onCreate(savedInstanceState: Bundle?) {
6. [Link](savedInstanceState)
7. binding = [Link](layoutInflater)
8. setContentView([Link])
9.
10. [Link](this)
11.
12.
13. showNotification(this@MainActivity,
getString([Link].notification_title),
getString([Link].notification_message), 110)
14.
15. }
16.
17.
18. override fun onClick(v: View) {
19. if ([Link] == [Link].btn_open_detail) {
20. val detailIntent = Intent(this@MainActivity,
DetailActivity::[Link])
21. [Link](DetailActivity.EXTRA_TITLE,
getString([Link].detail_title))
22. [Link](DetailActivity.EXTRA_MESSAGE,
getString([Link].detail_message))
23. startActivity(detailIntent)
24. }
25. }
26.
27. private fun showNotification(context: Context, title: String,
message: String, notifId: Int) {
28. val CHANNEL_ID = "Channel_1"
29. val CHANNEL_NAME = "Navigation channel"
30.
31. val notifDetailIntent = Intent(this, DetailActivity::[Link])
32. [Link](DetailActivity.EXTRA_TITLE, title)
33. [Link](DetailActivity.EXTRA_MESSAGE,
message)
34.
35. val pendingIntent = [Link](this)
36. .addParentStack(DetailActivity::[Link])
37. .addNextIntent(notifDetailIntent)
38. .getPendingIntent(110,
PendingIntent.FLAG_UPDATE_CURRENT)
39.
40. val notificationManagerCompat =
[Link](Context.NOTIFICATION_SERVICE) as
NotificationManager
41. val alarmSound =
[Link](RingtoneManager.TYPE_NOTIFICA
TION)
42. val builder = [Link](context,
CHANNEL_ID)
43. .setContentTitle(title)
44. .setSmallIcon([Link].ic_email_black_24dp)
45. .setContentText(message)
46. .setColor([Link](context,
[Link]))
47. .setVibrate(longArrayOf(1000, 1000, 1000, 1000, 1000))
48. .setSound(alarmSound)
49. .setContentIntent(pendingIntent)
50. .setAutoCancel(true)
51.
52. if ([Link].SDK_INT >= Build.VERSION_CODES.O) {
53. val channel = NotificationChannel(CHANNEL_ID,
54. CHANNEL_NAME,
55. NotificationManager.IMPORTANCE_DEFAULT)
56.
57. [Link](true)
58. [Link] = longArrayOf(1000, 1000, 1000,
1000, 1000)
59.
60. [Link](CHANNEL_ID)
61.
62.
[Link](channel)
63. }
64.
65. val notification = [Link]()
66.
67. [Link](notifId, notification)
68. }
69. }
Untuk ikon yang digunakan oleh notifikasi, Anda bisa mengunduhnya di
sini. Pilih ikon email pada kategori communication. Gunakan yang
berwarna hitam.
Setelah diunduh, buka direktorinya dan gunakan ikon yang berada di
direktori hdpi.
7. Selesai. Sekarang jalankan aplikasinya. Perhatikan notifikasi yang muncul
3 detik setelah aplikasi dijalankan, dan lihat apa yang terjadi di Activity
Detail.
Ketika notifikasi muncul dan Anda menekannya, Anda akan diarahkan ke
DetailActivity. Di DetailActivity, bila Anda menekan up button, Anda
akan diarahkan ke MainActivity.
Implementasi seperti ini lumrah terjadi ketika Anda sudah membuat
sebuah aplikasi dengan alur yang kompleks, apalagi ketika sudah
terintegrasi dengan layanan push notification. Esensi dari latihan di atas
adalah Anda harus bisa menerapkan mekanisme task dan stack yang tidak
standard namun sesuai dengan pola navigasi yang dianjurkan. Anda bisa
menemukan implementasi ini di banyak aplikasi hebat seperti Twitter,
Gmail, Instagram dan juga Tokopedia.
Bedah Kode
TaskStackBuilder
Coba perhatikan baris berikut ini:
Kotlin
Java
1. val notifDetailIntent = Intent(this, DetailActivity::[Link])
2. [Link](DetailActivity.EXTRA_TITLE, title)
3. [Link](DetailActivity.EXTRA_MESSAGE, message)
4.
5. val pendingIntent = [Link](this)
6. .addParentStack(DetailActivity::[Link])
7. .addNextIntent(notifDetailIntent)
8. .getPendingIntent(110, PendingIntent.FLAG_UPDATE_CURRENT)
Di sini kita memanfaatkan TaskStackBuilder API untuk membuat sebuah
back stack baru yang akan dimasukkan ke dalam task yang sudah ada.
Ketika kelas DetailActivity dijalankan, kemudian pengguna menekan
tombol back baik itu system back button maupun up button, maka
pengguna akan diarahkan ke ParentActivity dari DetailActivity.
ParentActivity tersebut didefinisikan di berkas [Link]
sebagai berikut:
1. <activity
2. android:name=".DetailActivity"
3. android:label="@string/activity_detail"
4. android:parentActivityName=".MainActivity">
5. <meta-data
6. android:name="[Link].PARENT_ACTIVITY"
7. android:value=".MainActivity" />
8. </activity>
Sedangkan, penambahan atribut launchMode pada MainActivity di
[Link] dimaksudkan agar MainActivity tidak selalu
menciptakan dirinya kembali (recreate) dan akan diarahkan ke instance
MainActivity yang telah tercipta di memori.
1. <activity android:name=".MainActivity"
2. android:launchMode="singleTop">
3. <intent-filter>
4. <action android:name="[Link]" />
5. <category android:name="[Link]" />
6. </intent-filter>
7. </activity>
Pada materi ini, Anda telah mempelajari proses yang tidak baku dalam
menjalankan sebuah Activity. Gunakan tautan di bawah untuk memahami
topik bahasan modul ini lebih dalam.
Task and Back Stack
Navigation
Proper Back Navigation
Up Navigation
Back and Up : Navigations
Anda bisa mengakses source code di:
Latihan Task dan Back Stack dengan TaskStackBuilder
Teori AlarmManager
Adakalanya kita memerlukan suatu mekanisme untuk mengulang sebuah
proses di waktu tertentu. Misalnya, kita ingin memiliki fungsi pengingat
(reminder) pada aplikasi event, jadwal tayang film di bioskop, atau
aplikasi alarm untuk mengingatkan kita bangun di pagi hari. Di sinilah
alarm manager berperan penting.
Alarm manager akan memberikan kemudahan bagi aplikasi untuk
melakukan operasi berbasis waktu di luar daur hidup aplikasi itu sendiri.
Contohnya seperti yang baru saja disebutkan di atas. Umumnya alarm
memiliki beberapa karakteristik, antara lain:
1. Dapat menjalankan obyek intent berdasarkan waktu dan interval yang
ditentukan.
2. Bisa bekerja dengan baik bersama broadcast receiver untuk menjalankan
komponen lain seperti service untuk melakukan operasi tertentu.
3. Alarm berjalan di luar daur hidup aplikasi induknya sehingga kita bisa
merancang fungsi alarm untuk melakukan sebuah aksi ketika aplikasi
sedang tidak dijalankan, bahkan ketika peranti dalam keadaan idle atau
sleep.
4. Esensi utama dari alarm manager adalah untuk meminimalkan
penggunaan resource dan menghindari penggunaan timer dan
background service yang berkepanjangan untuk melakukan operasi atau
aksi yang dibutuhkan.
Penggunaan dan perancangan alarm manager yang tepat akan membuat
aplikasi kita menjadi lebih baik, serta tentunya memberikan pengalaman
yang juga berkesan. Namun sebaliknya desain yang buruk akan menjadi
bumerang bagi aplikasi kita. Akibat dari rancangan yang tidak sesuai akan
menguras baterai di peranti kita.
Penggunaan alarm manager yang baik adalah dengan menerapkan cara
berikut ini:
1. Lakukan hanya untuk proses di lokal.
2. Jaga frekuensi alarm yang dijalankan dengan interval yang tidak pendek.
3. Jika tidak terlalu penting, hindari penggunaan tipe alarm yang dapat
membangunkan peranti seperti RTC_WAKEUP.
4. Jika alarm manager digunakan untuk menjadwalkan scheduling task yang
terhubung ke jaringan dan melakukan request ke server, misalnya unduh
data terbaru atau sinkronisasi data, maka hindari penggunaan
setRepeating() dan gunakan setInExactRepeating() dengan penggunaan
tipe alarm berbasis ELAPSED_REALTIME. Pendekatan tersebut merupakan
strategi untuk mengelompokkan proses alarm dari aplikasi yang berbeda
dengan dijalankan secara bersamaan agar lebih menghemat daya baterai.
Catatan : Di atas API 19 KitKat semua pemanggilan repeating
alarm adalah menggunakan in exact repeating. Baca tautan berikut ini.
Untuk lebih mendalami topik alarm manager, Anda dapat membaca
tautan berikut ini:
Alarm Manager
Scheduling Alarm
Latihan One Time AlarmManager
Tujuan
Kali ini kita akan membuat sebuah proyek sederhana dengan skenario
seperti berikut:
1. Aplikasi akan memiliki kemampuan untuk melakukan one-time-execution
alarm dan alarm yang berulang berdasarkan waktu yang ditentukan.
2. Aplikasi akan menyimpan beberapa nilai untuk kedua tipe alarm tersebut.
3. Aplikasi akan menampilkan notifikasi dengan suara dan getar ketika alarm
dijalankan.
Codelab pada kali ini akan menghasilkan aplikasi seperti ini:
Alur Latihan
Berikut alur yang akan kita lakukan dalam latihan kali ini:
1. Membuat project baru dengan nama MyAlarmManager.
2. Mengatur tampilan pada berkas layout XML.
3. Membuat kelas AlarmReceiver untuk mengatur konfigurasi penyimpanan
file.
4. Mengimplementasikan AlarmReceiver ke dalam Activity.
5. Menjalankan aplikasi.
Codelab Alarm Manager Sederhana
1. Buat Project baru di Android Studio dengan kriteria sebagai berikut:
Nama Project MyAlarmManager
Target & Minimum Target Phone and Tablet, Api
SDK level 21
Tipe Activity Empty Activity
Activity Name MainActivity
Use AndroidX Artifacts True
Language Kotlin/Java
2. Sebelum kita melangkah lebih jauh, yuk kita siapkan dulu beberapa aset
yang akan digunakan pada aplikasi ini. Caranya cukup klik kanan pada
folder drawable → New → Vector Asset. Kemudian cara icon date pada
Clip Art seperti berikut:
3. Ganti nama seperti di atas, kemudian klik Next dan Finish. Lakukan hal
yang sama untuk menambahkan icon baru untuk date seperti berikut:
4. Selanjutnya pada activity_main.xmlsesuaikan kodenya menjadi seperti
berikut.
1. <?xml version="1.0" encoding="utf-8"?>
2. <LinearLayout xmlns:android="[Link]
3. xmlns:tools="[Link]
4. android:id="@+id/activity_main"
5. android:layout_width="match_parent"
6. android:layout_height="match_parent"
7. android:orientation="vertical"
8. android:padding="16dp"
9. tools:context="[Link]">
10.
11. <TextView
12. android:layout_width="match_parent"
13. android:layout_height="wrap_content"
14. android:layout_marginBottom="16dp"
15. android:text="@string/one_time_alarm"
16. android:textSize="16sp"
17. android:textStyle="bold" />
18.
19. <LinearLayout
20. android:layout_width="match_parent"
21. android:layout_height="wrap_content"
22. android:gravity="center_vertical"
23. android:orientation="horizontal">
24.
25. <ImageButton
26. android:id="@+id/btn_once_date"
27. style="@style/[Link]"
28. android:layout_width="50dp"
29. android:layout_height="wrap_content"
30. android:src="@drawable/ic_date_range_black" />
31.
32. <TextView
33. android:id="@+id/tv_once_date"
34. android:layout_width="wrap_content"
35. android:layout_height="wrap_content"
36. android:text="@string/alarm_date" />
37. </LinearLayout>
38.
39. <LinearLayout
40. android:layout_width="match_parent"
41. android:layout_height="wrap_content"
42. android:gravity="center_vertical"
43. android:orientation="horizontal">
44.
45. <ImageButton
46. android:id="@+id/btn_once_time"
47. android:layout_width="50dp"
48. android:layout_height="wrap_content"
49. android:src="@drawable/ic_access_time_black" />
50.
51. <TextView
52. android:id="@+id/tv_once_time"
53. android:layout_width="wrap_content"
54. android:layout_height="wrap_content"
55. android:text="@string/alarm_time" />
56. </LinearLayout>
57.
58. <EditText
59. android:id="@+id/edt_once_message"
60. android:layout_width="match_parent"
61. android:layout_height="wrap_content"
62. android:layout_marginBottom="16dp"
63. android:hint="@string/alarm_message"
64. android:inputType="text" />
65.
66. <Button
67. android:id="@+id/btn_set_once_alarm"
68. android:layout_width="match_parent"
69. android:layout_height="wrap_content"
70. android:layout_marginBottom="16dp"
71. android:text="@string/set_one_time_alarm" />
72. </LinearLayout>
Jangan lupa untuk menambahkan beberapa resource string di bawah ini di dalam
res → values → [Link].
1. <resources>
2. <string name="app_name">MyAlarmManager</string>
3. <string name="one_time_alarm">One Time Alarm</string>
4. <string name="alarm_date">Tanggal Alarm</string>
5. <string name="set_one_time_alarm">Set One Time Alarm</string>
6. <string name="alarm_message">Pesan Alarm</string>
7. <string name="repeating_alarm">Repeating Alarm</string>
8. <string name="set_repeating_alarm">Set Repeating Alarm</string>
9. <string name="cancel_alarm">Batalkan Alarm</string>
10. <string name="alarm_time">Waktu Alarm</string>
11. </resources>
Resource string ini akan kita gunakan selama latihan .
Sehingga ketika dijalankan, aplikasi dari activity_main.xml seperti berikut:
Setelah itu kita akan membuat sebuah kelas baru dengan nama
AlarmReceiver.
Caranya, klik kanan pada package project yang ada saat ini → New →
Other → Broadcast Receiver.
Setelah itu kita akan membuat metode-metode tambahan untuk kelas
AlarmReceiver.
Kotlin
Java
1. class AlarmReceiver : BroadcastReceiver() {
2.
3. companion object {
4. const val TYPE_ONE_TIME = "OneTimeAlarm"
5. const val TYPE_REPEATING = "RepeatingAlarm"
6. const val EXTRA_MESSAGE = "message"
7. const val EXTRA_TYPE = "type"
8.
9. // Siapkan 2 id untuk 2 macam alarm, onetime dan repeating
10. private const val ID_ONETIME = 100
11. private const val ID_REPEATING = 101
12. }
13.
14. override fun onReceive(context: Context, intent: Intent) {
15.
16. }
17. }
Kemudian kita tambahkan kode di dalam metode onReceive() dan kita
tambahkan metode showToast di dalam kelas AlarmReceiver.
Kotlin
Java
1. class AlarmReceiver : BroadcastReceiver() {
2. ...
3.
4. override fun onReceive(context: Context, intent: Intent) {
5. val type = [Link](EXTRA_TYPE)
6. val message = [Link](EXTRA_MESSAGE)
7.
8. val title = if ([Link](TYPE_ONE_TIME, ignoreCase = true))
TYPE_ONE_TIME else TYPE_REPEATING
9. val notifId = if ([Link](TYPE_ONE_TIME, ignoreCase = true))
ID_ONETIME else ID_REPEATING
10.
11. showToast(context, title, message)
12. }
13.
14. private fun showToast(context: Context, title: String, message:
String?) {
15. [Link](context, "$title : $message",
Toast.LENGTH_LONG).show()
16. }
17. }
Berikutnya kita tambahkan metode untuk mengatur alarm manager.
Kotlin
Java
1. ...
2. import [Link].*
3. import [Link]
4. import [Link]
5.
6. class AlarmReceiver : BroadcastReceiver() {
7.
8. companion object {
9. ...
10. private const val DATE_FORMAT = "yyyy-MM-dd"
11. private const val TIME_FORMAT = "HH:mm"
12. }
13. ...
14. fun setOneTimeAlarm(context: Context, type: String, date: String,
time: String, message: String) {
15.
16. if (isDateInvalid(date, DATE_FORMAT) || isDateInvalid(time,
TIME_FORMAT)) return
17.
18. val alarmManager =
[Link](Context.ALARM_SERVICE) as AlarmManager
19. val intent = Intent(context, AlarmReceiver::[Link])
20. [Link](EXTRA_MESSAGE, message)
21. [Link](EXTRA_TYPE, type)
22.
23. Log.e("ONE TIME", "$date $time")
24. val dateArray = [Link]("-").toTypedArray()
25. val timeArray = [Link](":").toTypedArray()
26.
27. val calendar = [Link]()
28. [Link]([Link], [Link](dateArray[0]))
29. [Link]([Link], [Link](dateArray[1]) - 1)
30. [Link](Calendar.DAY_OF_MONTH,
[Link](dateArray[2]))
31. [Link](Calendar.HOUR_OF_DAY,
[Link](timeArray[0]))
32. [Link]([Link], [Link](timeArray[1]))
33. [Link]([Link], 0)
34.
35. val pendingIntent = [Link](context,
ID_ONETIME, intent, 0)
36. [Link](AlarmManager.RTC_WAKEUP,
[Link], pendingIntent)
37.
38. [Link](context, "One time alarm set up",
Toast.LENGTH_SHORT).show()
39. }
40.
41. private fun isDateInvalid(date: String, format: String): Boolean {
42. return try {
43. val df = SimpleDateFormat(format, [Link]())
44. [Link] = false
45. [Link](date)
46. false
47. } catch (e: ParseException) {
48. true
49. }
50. }
51. }
Lalu kita tambahkan notifikasi untuk melihat hasil dari alarm manager yang
sudah dibuat sebelumnya.
Kotlin
Java
1. class AlarmReceiver : BroadcastReceiver() {
2. ...
3.
4. private fun showAlarmNotification(context: Context, title: String,
message: String, notifId: Int) {
5.
6. val channelId = "Channel_1"
7. val channelName = "AlarmManager channel"
8.
9. val notificationManagerCompat =
[Link](Context.NOTIFICATION_SERVICE) as
NotificationManager
10. val alarmSound =
[Link](RingtoneManager.TYPE_NOTIFICATION)
11. val builder = [Link](context, channelId)
12. .setSmallIcon([Link].ic_access_time_black)
13. .setContentTitle(title)
14. .setContentText(message)
15. .setColor([Link](context,
[Link]))
16. .setVibrate(longArrayOf(1000, 1000, 1000, 1000, 1000))
17. .setSound(alarmSound)
18.
19. if ([Link].SDK_INT >= Build.VERSION_CODES.O) {
20.
21. val channel = NotificationChannel(channelId,
22. channelName,
23. NotificationManager.IMPORTANCE_DEFAULT)
24.
25. [Link](true)
26. [Link] = longArrayOf(1000, 1000, 1000, 1000,
1000)
27.
28. [Link](channelId)
29.
30. [Link](channel)
31. }
32.
33. val notification = [Link]()
34.
35. [Link](notifId, notification)
36.
37. }
38. }
Setelah membuat sebuah AlarmReceiver, kita tambahkan kelas untuk
membantu kita mengambil waktu. Pertama, kita buat fragment untuk setup
tanggal. Buatlah kelas dengan nama DatePickerFragment.
Selanjutnya ubah kelas DatePickerFragmentmenjadi sebagai berikut:
Kotlin
Java
1. ...
2. import [Link]
3. import [Link].*
4.
5. class DatePickerFragment : DialogFragment(),
[Link] {
6.
7. private var mListener: DialogDateListener? = null
8.
9. override fun onAttach(context: Context) {
10. [Link](context)
11. mListener = context as DialogDateListener?
12. }
13.
14. override fun onDetach() {
15. [Link]()
16. if (mListener != null) {
17. mListener = null
18. }
19. }
20.
21. override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
22. val calendar = [Link]()
23. val year = [Link]([Link])
24. val month = [Link]([Link])
25. val date = [Link]([Link])
26.
27. return DatePickerDialog(activity as Context, this, year, month,
date)
28. }
29.
30. override fun onDateSet(view: DatePicker, year: Int, month: Int,
dayOfMonth: Int) {
31. mListener?.onDialogDateSet(tag, year, month, dayOfMonth)
32. }
33.
34. interface DialogDateListener {
35. fun onDialogDateSet(tag: String?, year: Int, month: Int,
dayOfMonth: Int)
36. }
37.
38. }
Ketika mengimport komponen Calendar, pastikan Anda menggunakan
[Link]. Jangan sampai keliru dengan komponen calendar lainnya yang
baru ditambahkan pada API 24.
Selanjutnya kita buat kelas baru lagi dengan nama TimePickerFragment. Kali
ini kelas fragment ini akan membantu untuk setup jam.
Ubah kelas TimePickerFragmentmenjadi sebagai berikut:
Kotlin
Java
1. ...
2. import [Link]
3. import [Link].*
4.
5. class TimePickerFragment : DialogFragment(),
[Link] {
6.
7. private var mListener: DialogTimeListener? = null
8.
9. override fun onAttach(context: Context) {
10. [Link](context)
11. mListener = context as DialogTimeListener?
12. }
13.
14. override fun onDetach() {
15. [Link]()
16. if (mListener != null) {
17. mListener = null
18. }
19. }
20.
21. override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
22. val calendar = [Link]()
23. val hour = [Link](Calendar.HOUR_OF_DAY)
24. val minute = [Link]([Link])
25. val formatHour24 = true
26. return TimePickerDialog(activity, this, hour, minute, formatHour24)
27. }
28.
29. override fun onTimeSet(view: TimePicker, hourOfDay: Int, minute: Int)
{
30. mListener?.onDialogTimeSet(tag, hourOfDay, minute)
31. }
32.
33. interface DialogTimeListener {
34. fun onDialogTimeSet(tag: String?, hourOfDay: Int, minute: Int)
35. }
36. }
Sekarang saatnya kita memberikan aksi pada View di dalam kelas MainActivity.
Kotlin
Java
1. class MainActivity : AppCompatActivity(), [Link] {
2.
3. private var binding: ActivityMainBinding? = null
4. private lateinit var alarmReceiver: AlarmReceiver
5.
6. override fun onCreate(savedInstanceState: Bundle?) {
7. [Link](savedInstanceState)
8.
9. binding = [Link](layoutInflater)
10. setContentView(binding?.root)
11.
12. // Listener one time alarm
13. binding?.btnOnceDate?.setOnClickListener(this)
14. binding?.btnOnceTime?.setOnClickListener(this)
15. binding?.btnSetOnceAlarm?.setOnClickListener(this)
16.
17. alarmReceiver = AlarmReceiver()
18. }
19.
20. override fun onClick(v: View) {
21. when ([Link]) {
22. [Link].btn_once_date -> {
23.
24. }
25. [Link].btn_once_time -> {
26.
27. }
28. [Link].btn_set_once_alarm -> {
29.
30. }
31. }
32. }
33.
34. override fun onDestroy() {
35. [Link]()
36.
37. binding = null
38. }
39. }
Jangan lupa untuk menambahkan konfigurasi untuk mengaktifkan ViewBinding
pada [Link] (module: app) seperti berikut dan sync project:
1. android {
2.
3. ...
4.
5. buildFeatures {
6. viewBinding = true
7. }
8. }
Kita telah mengimplementasikan onClick(). Selanjutnya kita implementasikan
kelas untuk pengambil waktu yang sudah kita buat sebelumnya dan memberikan
logika untuk onClick()tersebut.
Kotlin
Java
1. class MainActivity : AppCompatActivity(),
[Link], [Link],
[Link] {
2.
3. ...
4.
5. companion object {
6. private const val DATE_PICKER_TAG = "DatePicker"
7. private const val TIME_PICKER_ONCE_TAG = "TimePickerOnce"
8. private const val TIME_PICKER_REPEAT_TAG = "TimePickerRepeat"
9. }
10.
11. ...
12.
13. override fun onClick(v: View) {
14. when ([Link]) {
15. [Link].btn_once_date -> {
16. val datePickerFragment = DatePickerFragment()
17. [Link](supportFragmentManager,
DATE_PICKER_TAG)
18. }
19. [Link].btn_once_time -> {
20. val timePickerFragmentOne = TimePickerFragment()
21. [Link](supportFragmentManager,
TIME_PICKER_ONCE_TAG)
22. }
23. [Link].btn_set_once_alarm -> {
24. val onceDate = binding?.tvOnceDate?.[Link]()
25. val onceTime = binding?.tvOnceTime?.[Link]()
26. val onceMessage = binding?.edtOnceMessage?.[Link]()
27.
28. [Link](this,
AlarmReceiver.TYPE_ONE_TIME,
29. onceDate,
30. onceTime,
31. onceMessage)
32. }
33. }
34. }
35.
36. override fun onDialogDateSet(tag: String?, year: Int, month: Int,
dayOfMonth: Int) {
37.
38. // Siapkan date formatter-nya terlebih dahulu
39. val calendar = [Link]()
40. [Link](year, month, dayOfMonth)
41. val dateFormat = SimpleDateFormat("yyyy-MM-dd",
[Link]())
42.
43. // Set text dari textview once
44. binding?.tvOnceDate?.text = [Link]([Link])
45. }
46.
47. override fun onDialogTimeSet(tag: String?, hourOfDay: Int, minute:
Int) {
48.
49. // Siapkan time formatter-nya terlebih dahulu
50. val calendar = [Link]()
51. [Link](Calendar.HOUR_OF_DAY, hourOfDay)
52. [Link]([Link], minute)
53.
54. val dateFormat = SimpleDateFormat("HH:mm", [Link]())
55.
56. // Set text dari textview berdasarkan tag
57. when (tag) {
58. TIME_PICKER_ONCE_TAG -> binding?.tvOnceTime?.text =
[Link]([Link])
59. TIME_PICKER_REPEAT_TAG -> {}
60. else -> {
61. }
62. }
63. }
64.
65. ...
66. }
Maka kelas MainActivitymenjadi seperti berikut:
Kotlin
Java
1. class MainActivity : AppCompatActivity(), [Link],
[Link], [Link]
{
2.
3. private var binding: ActivityMainBinding? = null
4. private lateinit var alarmReceiver: AlarmReceiver
5.
6. companion object {
7. private const val DATE_PICKER_TAG = "DatePicker"
8. private const val TIME_PICKER_ONCE_TAG = "TimePickerOnce"
9. private const val TIME_PICKER_REPEAT_TAG = "TimePickerRepeat"
10. }
11.
12. override fun onCreate(savedInstanceState: Bundle?) {
13. [Link](savedInstanceState)
14.
15. binding = [Link](layoutInflater)
16. setContentView(binding?.root)
17.
18. // Listener one time alarm
19. binding?.btnOnceDate?.setOnClickListener(this)
20. binding?.btnOnceTime?.setOnClickListener(this)
21. binding?.btnSetOnceAlarm?.setOnClickListener(this)
22.
23. alarmReceiver = AlarmReceiver()
24. }
25.
26. override fun onClick(v: View) {
27. when ([Link]) {
28. [Link].btn_once_date -> {
29. val datePickerFragment = DatePickerFragment()
30. [Link](supportFragmentManager,
DATE_PICKER_TAG)
31. }
32. [Link].btn_once_time -> {
33. val timePickerFragmentOne = TimePickerFragment()
34. [Link](supportFragmentManager,
TIME_PICKER_ONCE_TAG)
35. }
36. [Link].btn_set_once_alarm -> {
37. val onceDate = binding?.tvOnceDate?.[Link]()
38. val onceTime = binding?.tvOnceTime?.[Link]()
39. val onceMessage = binding?.edtOnceMessage?.[Link]()
40.
41. [Link](this, AlarmReceiver.TYPE_ONE_TIME,
42. onceDate,
43. onceTime,
44. onceMessage)
45. }
46. }
47. }
48.
49. override fun onDialogDateSet(tag: String?, year: Int, month: Int, dayOfMonth:
Int) {
50.
51. // Siapkan date formatter-nya terlebih dahulu
52. val calendar = [Link]()
53. [Link](year, month, dayOfMonth)
54. val dateFormat = SimpleDateFormat("yyyy-MM-dd", [Link]())
55.
56. // Set text dari textview once
57. binding?.tvOnceDate?.text = [Link]([Link])
58. }
59.
60. override fun onDialogTimeSet(tag: String?, hourOfDay: Int, minute: Int) {
61.
62. // Siapkan time formatter-nya terlebih dahulu
63. val calendar = [Link]()
64. [Link](Calendar.HOUR_OF_DAY, hourOfDay)
65. [Link]([Link], minute)
66.
67. val dateFormat = SimpleDateFormat("HH:mm", [Link]())
68.
69. // Set text dari textview berdasarkan tag
70. when (tag) {
71. TIME_PICKER_ONCE_TAG -> binding?.tvOnceTime?.text =
[Link]([Link])
72. TIME_PICKER_REPEAT_TAG -> {}
73. else -> {
74. }
75. }
76. }
77.
78. override fun onDestroy() {
79. [Link]()
80.
81. binding = null
82. }
83. }
Jangan lupa untuk menambahkan permission pada [Link]
baris berikut:
1. <?xml version="1.0" encoding="utf-8"?>
2. <manifest xmlns:android="[Link]
3. package="[Link]">
4.
5. <uses-permission android:name="[Link].WAKE_LOCK" />
6. <uses-permission android:name="[Link]" />
7.
8. <application
9. ... >
10. <activity android:name=".MainActivity">
11. ...
12. </activity>
13.
14. <receiver
15. android:name=".AlarmReceiver"
16. android:enabled="true"
17. android:exported="true" />
18.
19. </application>
20. </manifest>
Jika sudah selesai, silakan jalankan aplikasi Anda. Dengan menekan tombol yang
ada pada sebelah TextView tanggal dan waktu, Anda dapat mengganti tanggal
dan waktu sesuai keinginan. Isikan juga pesan alarmmya.
Setelah tanggal, waktu dan pesan telah Anda isi, lanjutkan dengan klik tombol
SET ONE TIME ALARM dan silakan tunggu sampai waktu yang ditentukan
sehingga alarm bekerja. Jika sukses, maka waktu yang kita set di awal akan
tampil pada obyek TextView yang tersedia di sana.
Ketika waktunya sudah sesuai, maka alarm bekerja dan menampilkan notifikasi
getaran dan suara. Anda bisa mengaktifkan notifikasi dengan menambahkan
kode berikut di kelas AlarmReceiver pada baris berikut:
Kotlin
Java
1. override fun onReceive(context: Context, intent: Intent) {
2. ...
3. if (message != null) {
4. showAlarmNotification(context, title, message, notifId)
5. }
6. }
Maka akan menjadi seperti berikut ini:
Mudah kan? Sekarang Anda sudah bisa mengimplementasikan one-time-
alarm untuk tanggal dan jam yang telah ditentukan. Fungsi ini bisa
berguna untuk beragam fitur lain. Misalnya jika Anda membuat aplikasi
tentang event, pasti fungsi ini akan sangat berguna sebagai mekanisme
pengingat.
Bedah Kode
Kita mulai dengan skenario nya terlebih dahulu.
Kode pada aplikasi di atas berfungsi untuk menjalankan sebuah alarm
yang bersifat one-time-execution. Logika dasarnya adalah, kita
memanfaatkan fasilitas alarm manager yang terdapat dalam framework
Android untuk menjalankan tugas atau proses tertentu berdasarkan waktu
yang ditentukan.
Di sini kita akan melakukan proses penampilan notifikasi berdasarkan nilai
yang dimasukkan oleh pengguna. Baik itu tanggal, waktu dan pesannya.
Ketika waktu yang ditentukan cocok dengan waktu yang berjalan pada
sistem, maka metode onReceive pada BroadcastReceiver akan
dijalankan.
Logika yang sederhana bukan?
DatePicker dan TimePicker
Ada beberapa istilah baru yang bisa jadi teman-teman pun baru
menemukannya di modul ini. Untuk memahaminya, mari kita lanjut
membedah kode.
Kita awali dengan onClick yang ada di kelas MainActivity. Terdapat
beberapa kondisi when/switch-case yang berguna untuk membedakan
button mana yang disentuh. Button dengan id btn_once_date dan
btn_once_time memiliki fungsi memanggil kelas util untuk menampilkan
date picker dan time picker.
Kotlin
Java
1. when ([Link]) {
2. [Link].btn_once_date -> {
3. val datePickerFragment = DatePickerFragment()
4. [Link](supportFragmentManager,
DATE_PICKER_TAG)
5. }
6. [Link].btn_once_time -> {
7. val timePickerFragmentOne = TimePickerFragment()
8. [Link](supportFragmentManager,
TIME_PICKER_ONCE_TAG)
9. }
10. ...
11. }
Selanjutnya, perhatikan pada kelas DatePickerFragment.
Kotlin
Java
1. class DatePickerFragment : DialogFragment(),
[Link] {
2. ...
3.
4. override fun onDateSet(view: DatePicker, year: Int, month: Int,
dayOfMonth: Int) {
5. mListener?.onDialogDateSet(tag, year, month, dayOfMonth)
6. }
7.
8. ...
9. }
Fungsi onDateSet akan dipanggil ketika kita memilih tanggal yang kita
inginkan. Kemudian setelah tanggal dipilih maka variable tanggal, bulan
dan tahun akan dikirim ke MainActivity menggunakan bantuan interface
DialogDateListener.
Kotlin
Java
1. interface DialogDateListener {
2. fun onDialogDateSet(tag: String?, year: Int, month: Int, dayOfMonth: Int)
3. }
Masih di kelas DatePickerFragment, perhatikan baris di bawah ini:
Kotlin
Java
1. override fun onAttach(context: Context?) {
2. [Link](context)
3. mListener = context as DialogDateListener?
4. }
5.
6. override fun onDetach() {
7. [Link]()
8. if (mListener != null) {
9. mListener = null
10. }
11. }
Fungsi onAttach() hanya sekali dipanggil dalam fragment dan berfungsi
untuk mengkaitkan dengan activity pemanggil, sedangkan onDetach()
hanya dipanggil sebelum fragmen tidak lagi dikaitkan dengan activity
pemanggil.
Selanjutnya adalah kelas TimePickerFragment.
Kotlin
Java
1. class TimePickerFragment : DialogFragment(),
[Link] {
2. ...
3.
4. override fun onTimeSet(view: TimePicker, hourOfDay: Int, minute: Int)
{
5. mListener?.onDialogTimeSet(tag, hourOfDay, minute)
6. }
7.
8. ...
9. }
Berbeda dengan kelas DatePickerFragment,
TimePickerFragment diimplementasikan dengan
metode [Link]. Variabel yang berbeda
adalah jam dan menit.
Selanjutnya kita coba kembali ke kelas MainActivity. Kode di bawah ini
adalah hasil dari implementasi dari
[Link],
[Link].
Kotlin
Java
1. class MainActivity : AppCompatActivity(), [Link],
[Link], [Link]
{
2.
3. ...
4.
5. override fun onDialogDateSet(tag: String?, year: Int, month: Int, dayOfMonth:
Int) {
6.
7. // Siapkan date formatter-nya terlebih dahulu
8. val calendar = [Link]()
9. [Link](year, month, dayOfMonth)
10. val dateFormat = SimpleDateFormat("yyyy-MM-dd", [Link]())
11.
12. // Set text dari textview once
13. binding?.tvOnceDate?.text = [Link]([Link])
14. }
15.
16. override fun onDialogTimeSet(tag: String?, hourOfDay: Int, minute: Int) {
17.
18. // Siapkan time formatter-nya terlebih dahulu
19. val calendar = [Link]()
20. [Link](Calendar.HOUR_OF_DAY, hourOfDay)
21. [Link]([Link], minute)
22.
23. val dateFormat = SimpleDateFormat("HH:mm", [Link]())
24.
25. // Set text dari textview berdasarkan tag
26. when (tag) {
27. TIME_PICKER_ONCE_TAG -> binding?.tvOnceTime?.text =
[Link]([Link])
28. TIME_PICKER_REPEAT_TAG -> {}
29. else -> {
30. }
31. }
32. }
33.
34. ...
35. }
Kode di atas adalah hasil dari callback dari
[Link] dan
[Link] yang kemudian akan ditampilkan
hasilnya.
Kotlin
Java
1. val dateFormat = SimpleDateFormat("yyyy-MM-dd", [Link]())
Perhatikan kode di atas. Kita menggunakan komponen SimpleDateFormat.
Perhatikan kode “yyyy-MM-dd”, inilah format untuk menampilkan
tanggal dengan format seperti 2016-09-29. Begitu juga untuk waktu
seperti berikut:
Kotlin
Java
1. val dateFormat = SimpleDateFormat("HH:mm", [Link]())
Terdapat beberapa format yang dapat kita gunakan dengan komponen
SimpleDateFormat. Untuk mengetahui lebih dalam mengenai
SimpleDateFormat, cek tautan berikut:
SimpleDateFormat
AlarmReceiver
Lihat pada onClick() bagian dari MainActivity berikut ini:
Kotlin
Java
1. [Link].btn_set_once_alarm -> {
2. val onceDate = binding?.tvOnceDate?.[Link]()
3. val onceTime = binding?.tvOnceTime?.[Link]()
4. val onceMessage = binding?.edtOnceMessage?.[Link]()
5.
6. [Link](this, AlarmReceiver.TYPE_ONE_TIME,
7. onceDate,
8. onceTime,
9. onceMessage)
10. }
Kode di atas berfungsi untuk memanggil metode setOneTimeAlarm yang
berada di dalam AlarmReceiver.
Selanjutnya pada AlarmReceiver kita membuat beberapa variabel
konstanta penanda (flag) yang akan digunakan di keseluruhan bagian
kode, di antaranya:
Kotlin
Java
1. const val TYPE_ONE_TIME = "OneTimeAlarm"
2. const val TYPE_REPEATING = "RepeatingAlarm"
Dua baris di atas adalah konstanta untuk menentukan tipe alarm. Dan
selanjutnya, dua baris di bawah ini adalah konstanta untuk intent key.
Kotlin
Java
1. const val EXTRA_MESSAGE = "message"
2. const val EXTRA_TYPE = "type"
Di sini kita menggunakan dua konstanta bertipe data integer untuk
menentukan notif ID sebagai ID untuk menampilkan notifikasi kepada
pengguna.
Kotlin
Java
1. private const val ID_ONETIME = 100
2. private const val ID_REPEATING = 101
Selanjutnya, bagian penting pada metode setOneTimeAlarm() adalah:
Kotlin
Java
1. fun setOneTimeAlarm(context: Context, type: String, date: String, time: String,
message: String) {
2.
3. // Validasi inputan date dan time terlebih dahulu
4. if (isDateInvalid(date, DATE_FORMAT) || isDateInvalid(time, TIME_FORMAT))
return
5.
6. val alarmManager = [Link](Context.ALARM_SERVICE) as
AlarmManager
7. val intent = Intent(context, AlarmReceiver::[Link])
8. [Link](EXTRA_MESSAGE, message)
9. [Link](EXTRA_TYPE, type)
10.
11. Log.e("ONE TIME", "$date $time")
12. val dateArray = [Link]("-").toTypedArray()
13. val timeArray = [Link](":").toTypedArray()
14.
15. val calendar = [Link]()
16. [Link]([Link], [Link](dateArray[0]))
17. [Link]([Link], [Link](dateArray[1]) - 1)
18. [Link](Calendar.DAY_OF_MONTH, [Link](dateArray[2]))
19. [Link](Calendar.HOUR_OF_DAY, [Link](timeArray[0]))
20. [Link]([Link], [Link](timeArray[1]))
21. [Link]([Link], 0)
22.
23. val pendingIntent = [Link](context, ID_ONETIME, intent,
0)
24. [Link](AlarmManager.RTC_WAKEUP, [Link],
pendingIntent)
25.
26. [Link](context, "One time alarm set up",
Toast.LENGTH_SHORT).show()
27. }
Pada kode di atas kita membuat sebuah obyek untuk AlarmManager.
Kemudian kita menyiapkan sebuah Intent yang akan menjalankan
AlarmReceiver dan membawa data berupa alarm dan pesan.
Kotlin
Java
1. val dateArray = [Link]("-").toTypedArray()
2. val timeArray = [Link](":").toTypedArray()
3.
4. val calendar = [Link]()
5. [Link]([Link], [Link](dateArray[0]))
6. [Link]([Link], [Link](dateArray[1]) - 1)
7. [Link](Calendar.DAY_OF_MONTH, [Link](dateArray[2]))
8. [Link](Calendar.HOUR_OF_DAY, [Link](timeArray[0]))
9. [Link]([Link], [Link](timeArray[1]))
10. [Link]([Link], 0)
Pada kode di atas kita memecah data date dan time untuk mengambil
nilai tahun, bulan, hari, jam dan menit.
Kotlin
Java
1. [Link]([Link], [Link](dateArray[1]) - 1);
Mengapa kode di atas, data kita kurangi 1? Misal tanggal yang kita
masukkan adalah 2016-09-27. Jika kita pecah, maka kita akan
memperoleh nilai 2016 (tahun), 9 (bulan), dan 27 (hari).
Masalahnya adalah, nilai bulan ke 9 pada kelas Calendar bukanlah bulan
September. Ini karena indeksnya dimulai dari 0. Jadi, untuk memperoleh
bulan September, maka nilai 9 tadi harus kita kurangi 1.
Intent yang dibuat akan dieksekusi ketika waktu alarm sama dengan
waktu pada sistem Android. Di sini komponen PendingIntent akan
diberikan kepada BroadcastReceiver.
Kotlin
Java
1. val pendingIntent = [Link](context, ID_ONETIME, intent, 0)
2. [Link](AlarmManager.RTC_WAKEUP, [Link],
pendingIntent)
Yang membedakan satu alarm dengan alarm lain adalah pada ID.
Kotlin
Java
1. val pendingIntent = [Link](context, ID_ONETIME, intent, 0)
Jika kita merubah nilai waktu dan menjalankan ulang alarm dengan ID
yang sama, maka akan merubah yang sudah diset sebelumnya.
Maksud dari baris ini [Link](AlarmManager.RTC_WAKEUP,
[Link](), pendingIntent) adalah kita memasang alarm yang
dibuat dengan tipe RTC_WAKEUP. Tipe alarm ini dapat membangunkan
peranti (jika dalam posisi sleep) untuk menjalankan obyek PendingIntent.
Ketika kondisi sesuai, maka akan BroadcastReceiver akan
running dengan semua proses yang terdapat di dalam metode
onReceive().
Kotlin
Java
1. val type = [Link](EXTRA_TYPE)
2. val message = [Link](EXTRA_MESSAGE)
3. val title = if ([Link](TYPE_ONE_TIME, ignoreCase = true)) TYPE_ONE_TIME
else TYPE_REPEATING
4. val notifId = if ([Link](TYPE_ONE_TIME, ignoreCase = true)) ID_ONETIME else
ID_REPEATING
5.
6. showToast(context, title, message)
7.
8. if (message != null) {
9. showAlarmNotification(context, title, message, notifId)
10. }
Maka akan ada Toast yang akan tampil. Anda pun juga bisa memanggil
sebuah notifikasi sederhana agar tampil di panel notifikasi pengguna
dengan bunyi notifikasi umum dan getaran. Di sini metode
showAlarmNotification bekerja.
Kotlin
Java
1. private fun showAlarmNotification(context: Context, title: String, message: String,
notifId: Int) {
2.
3. val channelId = "Channel_1"
4. val channelName = "AlarmManager channel"
5.
6. val notificationManagerCompat =
[Link](Context.NOTIFICATION_SERVICE) as
NotificationManager
7. val alarmSound =
[Link](RingtoneManager.TYPE_NOTIFICATION)
8. val builder = [Link](context, channelId)
9. .setSmallIcon([Link].ic_access_time_black)
10. .setContentTitle(title)
11. .setContentText(message)
12. .setColor([Link](context, [Link]))
13. .setVibrate(longArrayOf(1000, 1000, 1000, 1000, 1000))
14. .setSound(alarmSound)
15.
16. if ([Link].SDK_INT >= Build.VERSION_CODES.O) {
17. val channel = NotificationChannel(channelId,
18. channelName,
19. NotificationManager.IMPORTANCE_DEFAULT)
20.
21. [Link](true)
22. [Link] = longArrayOf(1000, 1000, 1000, 1000, 1000)
23.
24. [Link](channelId)
25.
26. [Link](channel)
27. }
28.
29. val notification = [Link]()
30.
31. [Link](notifId, notification)
32.
33. }
Metode di atas merupakan sebuah metode untuk membuat dan
menampilkan notifikasi yang kompatibel dengan beragam API dari
Android. Metode ini memanfaatkan fasilitas NotificationCompat. Anda
akan lebih jauh memahami tentang Notifikasi ini pada modul berikutnya.
Untuk bagian OS Oreo ke atas, harus menggunakan NotificationChannel
untuk menampilkan Notifikasi.
Di titik ini kami harap Anda mendapatkan gambaran menyeluruh tentang
ide, konsep dan skenario yang diimplementasikan pada bagian
"menjalankan one-time-alarm manager."
Selanjutnya, kita akan membuat Repeating Alarm untuk melakukan tugas
yang berulang. Jadi ayo kita ngoding lagi.
Latihan Repeating AlarmManager
Kita lanjutkan ngoding untuk latihan alarm manager. Kali ini kita akan
membuat latihan sederhana tentang repeating-alarm.
1. Pada AlarmReceiver silakan buat metode setRepeatingAlarm() pada
baris berikut:
o Kotlin
o Java
1. fun setRepeatingAlarm(context: Context, type: String, time: String,
message: String) {
2.
3. if (isDateInvalid(time, TIME_FORMAT)) return
4.
5. val alarmManager =
[Link](Context.ALARM_SERVICE) as AlarmManager
6. val intent = Intent(context, AlarmReceiver::[Link])
7. [Link](EXTRA_MESSAGE, message)
8. val putExtra = [Link](EXTRA_TYPE, type)
9.
10. val timeArray = [Link](":".toRegex()).dropLastWhile { [Link]()
}.toTypedArray()
11.
12. val calendar = [Link]()
13. [Link](Calendar.HOUR_OF_DAY, [Link](timeArray[0]))
14. [Link]([Link], [Link](timeArray[1]))
15. [Link]([Link], 0)
16.
17. val pendingIntent = [Link](context,
ID_REPEATING, intent, 0)
18. [Link](AlarmManager.RTC_WAKEUP,
[Link], AlarmManager.INTERVAL_DAY, pendingIntent)
19.
20. [Link](context, "Repeating alarm set up",
Toast.LENGTH_SHORT).show()
21. }
Sehingga kode AlarmReceiver kita saat ini menjadi seperti ini:
Kotlin
Java
1. class AlarmReceiver : BroadcastReceiver() {
2.
3. companion object {
4. const val TYPE_ONE_TIME = "OneTimeAlarm"
5. const val TYPE_REPEATING = "RepeatingAlarm"
6. const val EXTRA_MESSAGE = "message"
7. const val EXTRA_TYPE = "type"
8.
9. // Siapkan 2 id untuk 2 macam alarm, onetime dan repeating
10. private const val ID_ONETIME = 100
11. private const val ID_REPEATING = 101
12.
13. private const val DATE_FORMAT = "yyyy-MM-dd"
14. private const val TIME_FORMAT = "HH:mm"
15.
16. }
17.
18. override fun onReceive(context: Context, intent: Intent) {
19. val type = [Link](EXTRA_TYPE)
20. val message = [Link](EXTRA_MESSAGE)
21.
22. val title = if ([Link](TYPE_ONE_TIME, ignoreCase = true))
TYPE_ONE_TIME else TYPE_REPEATING
23. val notifId = if ([Link](TYPE_ONE_TIME, ignoreCase = true))
ID_ONETIME else ID_REPEATING
24.
25. //Jika Anda ingin menampilkan dengan toast anda bisa menghilangkan
komentar pada baris dibawah ini.
26. // showToast(context, title, message)
27.
28. if (message != null) {
29. showAlarmNotification(context, title, message, notifId)
30. }
31. }
32.
33. // Gunakan metode ini untuk menampilkan toast
34.
35. private fun showToast(context: Context, title: String, message: String?) {
36. [Link](context, "$title : $message", Toast.LENGTH_LONG).show()
37. }
38.
39. // Gunakan metode ini untuk menampilkan notifikasi
40. private fun showAlarmNotification(context: Context, title: String, message:
String, notifId: Int) {
41.
42. val CHANNEL_ID = "Channel_1"
43. val CHANNEL_NAME = "AlarmManager channel"
44.
45. val notificationManagerCompat =
[Link](Context.NOTIFICATION_SERVICE) as
NotificationManager
46. val alarmSound =
[Link](RingtoneManager.TYPE_NOTIFICATION)
47. val builder = [Link](context, CHANNEL_ID)
48. .setSmallIcon([Link].ic_access_time_black)
49. .setContentTitle(title)
50. .setContentText(message)
51. .setColor([Link](context,
[Link]))
52. .setVibrate(longArrayOf(1000, 1000, 1000, 1000, 1000))
53. .setSound(alarmSound)
54.
55. /*
56. Untuk android Oreo ke atas perlu menambahkan notification channel
57. Materi ini akan dibahas lebih lanjut di modul extended
58. */
59. if ([Link].SDK_INT >= Build.VERSION_CODES.O) {
60.
61. /* Create or update. */
62. val channel = NotificationChannel(CHANNEL_ID,
63. CHANNEL_NAME,
64. NotificationManager.IMPORTANCE_DEFAULT)
65.
66. [Link](true)
67. [Link] = longArrayOf(1000, 1000, 1000, 1000, 1000)
68.
69. [Link](CHANNEL_ID)
70.
71. [Link](channel)
72. }
73.
74. val notification = [Link]()
75.
76. [Link](notifId, notification)
77.
78. }
79.
80. // Metode ini digunakan untuk menjalankan alarm one time
81.
82. fun setOneTimeAlarm(context: Context, type: String, date: String, time: String,
message: String) {
83.
84. // Validasi inputan date dan time terlebih dahulu
85. if (isDateInvalid(date, DATE_FORMAT) || isDateInvalid(time, TIME_FORMAT))
return
86.
87. val alarmManager = [Link](Context.ALARM_SERVICE) as
AlarmManager
88. val intent = Intent(context, AlarmReceiver::[Link])
89. [Link](EXTRA_MESSAGE, message)
90. [Link](EXTRA_TYPE, type)
91.
92. Log.e("ONE TIME", "$date $time")
93. val dateArray = [Link]("-").toTypedArray()
94. val timeArray = [Link](":").toTypedArray()
95.
96. val calendar = [Link]()
97. [Link]([Link], [Link](dateArray[0]))
98. [Link]([Link], [Link](dateArray[1]) - 1)
99. [Link](Calendar.DAY_OF_MONTH, [Link](dateArray[2]))
100. [Link](Calendar.HOUR_OF_DAY, [Link](timeArray[0]))
101. [Link]([Link], [Link](timeArray[1]))
102. [Link]([Link], 0)
103.
104. val pendingIntent = [Link](context, ID_ONETIME,
intent, 0)
105. [Link](AlarmManager.RTC_WAKEUP, [Link],
pendingIntent)
106.
107. [Link](context, "One time alarm set up",
Toast.LENGTH_SHORT).show()
108. }
109.
110. // Metode ini digunakan untuk menjalankan alarm repeating
111. fun setRepeatingAlarm(context: Context, type: String, time: String, message:
String) {
112.
113. // Validasi inputan waktu terlebih dahulu
114. if (isDateInvalid(time, TIME_FORMAT)) return
115.
116. val alarmManager = [Link](Context.ALARM_SERVICE)
as AlarmManager
117. val intent = Intent(context, AlarmReceiver::[Link])
118. [Link](EXTRA_MESSAGE, message)
119. val putExtra = [Link](EXTRA_TYPE, type)
120.
121. val timeArray = [Link](":".toRegex()).dropLastWhile { [Link]()
}.toTypedArray()
122.
123. val calendar = [Link]()
124. [Link](Calendar.HOUR_OF_DAY, [Link](timeArray[0]))
125. [Link]([Link], [Link](timeArray[1]))
126. [Link]([Link], 0)
127.
128. val pendingIntent = [Link](context, ID_REPEATING,
intent, 0)
129. [Link](AlarmManager.RTC_WAKEUP,
[Link], AlarmManager.INTERVAL_DAY, pendingIntent)
130.
131. [Link](context, "Repeating alarm set up",
Toast.LENGTH_SHORT).show()
132. }
133.
134. // Gunakan metode ini untuk mengecek apakah alarm tersebut sudah
terdaftar di alarm manager
135. fun isAlarmSet(context: Context, type: String): Boolean {
136. val intent = Intent(context, AlarmReceiver::[Link])
137. val requestCode = if ([Link](TYPE_ONE_TIME, ignoreCase = true))
ID_ONETIME else ID_REPEATING
138.
139. return [Link](context, requestCode, intent,
PendingIntent.FLAG_NO_CREATE) != null
140. }
141.
142. // Metode ini digunakan untuk validasi date dan time
143. private fun isDateInvalid(date: String, format: String): Boolean {
144. return try {
145. val df = SimpleDateFormat(format, [Link]())
146. [Link] = false
147. [Link](date)
148. false
149. } catch (e: ParseException) {
150. true
151. }
152. }
153. }
Selanjutnya di activity_main.xml kita tambahkan kode berikut:
1. <?xml version="1.0" encoding="utf-8"?>
2. <LinearLayout xmlns:android="[Link]
3. xmlns:tools="[Link]
4. android:id="@+id/activity_main"
5. android:layout_width="match_parent"
6. android:layout_height="match_parent"
7. android:orientation="vertical"
8. android:padding="16dp"
9. tools:context=".MainActivity">
10.
11. ...
12.
13. <TextView
14. android:layout_width="match_parent"
15. android:layout_height="wrap_content"
16. android:layout_marginBottom="16dp"
17. android:text="@string/repeating_alarm"
18. android:textSize="16sp"
19. android:textStyle="bold" />
20.
21. <LinearLayout
22. android:layout_width="match_parent"
23. android:layout_height="wrap_content"
24. android:gravity="center_vertical"
25. android:orientation="horizontal">
26.
27. <ImageButton
28. android:id="@+id/btn_repeating_time"
29. android:layout_width="50dp"
30. android:layout_height="wrap_content"
31. android:src="@drawable/ic_access_time_black" />
32.
33. <TextView
34. android:id="@+id/tv_repeating_time"
35. android:layout_width="wrap_content"
36. android:layout_height="wrap_content"
37. android:text="@string/alarm_time" />
38. </LinearLayout>
39.
40. <EditText
41. android:id="@+id/edt_repeating_message"
42. android:layout_width="match_parent"
43. android:layout_height="wrap_content"
44. android:layout_marginBottom="16dp"
45. android:hint="@string/alarm_message"
46. android:inputType="text" />
47.
48. <LinearLayout
49. android:layout_width="match_parent"
50. android:layout_height="wrap_content"
51. android:gravity="center"
52. android:orientation="horizontal">
53.
54. <Button
55. android:id="@+id/btn_set_repeating_alarm"
56. android:layout_width="wrap_content"
57. android:layout_height="wrap_content"
58. android:layout_weight="0.5"
59. android:text="@string/set_repeating_alarm" />
60. </LinearLayout>
61. </LinearLayout>
Maka activity_main.xml menjadi seperti ini:
1. <?xml version="1.0" encoding="utf-8"?>
2. <LinearLayout xmlns:android="[Link]
3. xmlns:tools="[Link]
4. android:id="@+id/activity_main"
5. android:layout_width="match_parent"
6. android:layout_height="match_parent"
7. android:orientation="vertical"
8. android:padding="16dp"
9. tools:context=".MainActivity">
10.
11. <TextView
12. android:layout_width="match_parent"
13. android:layout_height="wrap_content"
14. android:layout_marginBottom="16dp"
15. android:text="@string/one_time_alarm"
16. android:textSize="16sp"
17. android:textStyle="bold" />
18.
19. <LinearLayout
20. android:layout_width="match_parent"
21. android:layout_height="wrap_content"
22. android:gravity="center_vertical"
23. android:orientation="horizontal">
24.
25. <ImageButton
26. android:id="@+id/btn_once_date"
27. style="@style/[Link]"
28. android:layout_width="50dp"
29. android:layout_height="wrap_content"
30. android:src="@drawable/ic_date_range_black" />
31.
32. <TextView
33. android:id="@+id/tv_once_date"
34. android:layout_width="wrap_content"
35. android:layout_height="wrap_content"
36. android:text="@string/alarm_date" />
37. </LinearLayout>
38.
39. <LinearLayout
40. android:layout_width="match_parent"
41. android:layout_height="wrap_content"
42. android:gravity="center_vertical"
43. android:orientation="horizontal">
44.
45. <ImageButton
46. android:id="@+id/btn_once_time"
47. android:layout_width="50dp"
48. android:layout_height="wrap_content"
49. android:src="@drawable/ic_access_time_black" />
50.
51. <TextView
52. android:id="@+id/tv_once_time"
53. android:layout_width="wrap_content"
54. android:layout_height="wrap_content"
55. android:text="@string/alarm_time" />
56. </LinearLayout>
57.
58. <EditText
59. android:id="@+id/edt_once_message"
60. android:layout_width="match_parent"
61. android:layout_height="wrap_content"
62. android:layout_marginBottom="16dp"
63. android:hint="@string/alarm_message"
64. android:inputType="text" />
65.
66. <Button
67. android:id="@+id/btn_set_once_alarm"
68. android:layout_width="match_parent"
69. android:layout_height="wrap_content"
70. android:layout_marginBottom="16dp"
71. android:text="@string/set_one_time_alarm" />
72.
73. <TextView
74. android:layout_width="match_parent"
75. android:layout_height="wrap_content"
76. android:layout_marginBottom="16dp"
77. android:text="@string/repeating_alarm"
78. android:textSize="16sp"
79. android:textStyle="bold" />
80.
81. <LinearLayout
82. android:layout_width="match_parent"
83. android:layout_height="wrap_content"
84. android:gravity="center_vertical"
85. android:orientation="horizontal">
86.
87. <ImageButton
88. android:id="@+id/btn_repeating_time"
89. android:layout_width="50dp"
90. android:layout_height="wrap_content"
91. android:src="@drawable/ic_access_time_black" />
92.
93. <TextView
94. android:id="@+id/tv_repeating_time"
95. android:layout_width="wrap_content"
96. android:layout_height="wrap_content"
97. android:text="@string/alarm_time" />
98. </LinearLayout>
99.
100. <EditText
101. android:id="@+id/edt_repeating_message"
102. android:layout_width="match_parent"
103. android:layout_height="wrap_content"
104. android:layout_marginBottom="16dp"
105. android:hint="@string/alarm_message"
106. android:inputType="text" />
107.
108. <LinearLayout
109. android:layout_width="match_parent"
110. android:layout_height="wrap_content"
111. android:gravity="center"
112. android:orientation="horizontal">
113.
114. <Button
115. android:id="@+id/btn_set_repeating_alarm"
116. android:layout_width="wrap_content"
117. android:layout_height="wrap_content"
118. android:layout_weight="0.5"
119. android:text="@string/set_repeating_alarm" />
120. </LinearLayout>
121. </LinearLayout>
Selanjutnya, setelah kita menambahkan beberapa View ke dalam
activity_main.xml, kita masukkan juga kode untuk memberikan aksi pada
komponen tersebut di dalam MainActivity.
Kotlin
Java
1. class MainActivity : AppCompatActivity(), [Link],
[Link], [Link]
{
2.
3. ...
4.
5. override fun onCreate(savedInstanceState: Bundle?) {
6. [Link](savedInstanceState)
7.
8. ...
9.
10. // Listener repeating alarm
11. binding?.btnRepeatingTime?.setOnClickListener(this)
12. binding?.btnSetRepeatingAlarm?.setOnClickListener(this)
13.
14. alarmReceiver = AlarmReceiver()
15. }
16.
17. override fun onClick(v: View) {
18. when ([Link]) {
19.
20. ...
21.
22. [Link].btn_repeating_time -> {
23.
24. }
25. [Link].btn_set_repeating_alarm -> {
26.
27. }
28. }
29. }
30.
31. ...
32. }
Selanjutnya adalah kita tambahkan kode metode onClick(), menjadi seperti ini:
Kotlin
Java
1. override fun onClick(v: View) {
2. when ([Link]) {
3.
4. ...
5.
6. [Link].btn_repeating_time -> {
7. val timePickerFragmentRepeat = TimePickerFragment()
8. [Link](supportFragmentManager,
TIME_PICKER_REPEAT_TAG)
9. }
10. [Link].btn_set_repeating_alarm -> {
11. val repeatTime = binding?.tvRepeatingTime?.[Link]()
12. val repeatMessage =
binding?.edtRepeatingMessage?.[Link]()
13. [Link](this,
AlarmReceiver.TYPE_REPEATING,
14. repeatTime, repeatMessage)
15. }
16. }
17. }
Tambahkan juga kode repeating pada onDialogTimeSet:
Kotlin
Java
1. override fun onDialogTimeSet(tag: String?, hourOfDay: Int, minute: Int) {
2. ...
3.
4. when (tag) {
5. TIME_PICKER_ONCE_TAG -> binding?.tvOnceTime?.text =
[Link]([Link])
6. TIME_PICKER_REPEAT_TAG -> binding?.tvRepeatingTime?.text =
[Link]([Link])
7. else -> {
8. }
9. }
10. }
Sehingga kode MainActivity menjadi seperti berikut:
Kotlin
Java
1. class MainActivity : AppCompatActivity(), [Link],
[Link],
[Link] {
2.
3. private var binding: ActivityMainBinding? = null
4. private lateinit var alarmReceiver: AlarmReceiver
5.
6. companion object {
7. private const val DATE_PICKER_TAG = "DatePicker"
8. private const val TIME_PICKER_ONCE_TAG = "TimePickerOnce"
9. private const val TIME_PICKER_REPEAT_TAG = "TimePickerRepeat"
10. }
11.
12. override fun onCreate(savedInstanceState: Bundle?) {
13. [Link](savedInstanceState)
14.
15. binding = [Link](layoutInflater)
16. setContentView(binding?.root)
17.
18. // Listener one time alarm
19. binding?.btnOnceDate?.setOnClickListener(this)
20. binding?.btnOnceTime?.setOnClickListener(this)
21. binding?.btnSetOnceAlarm?.setOnClickListener(this)
22.
23. // Listener repeating alarm
24. binding?.btnRepeatingTime?.setOnClickListener(this)
25. binding?.btnSetRepeatingAlarm?.setOnClickListener(this)
26.
27. alarmReceiver = AlarmReceiver()
28. }
29.
30. override fun onClick(v: View) {
31. when ([Link]) {
32. [Link].btn_once_date -> {
33. val datePickerFragment = DatePickerFragment()
34. [Link](supportFragmentManager,
DATE_PICKER_TAG)
35. }
36. [Link].btn_once_time -> {
37. val timePickerFragmentOne = TimePickerFragment()
38. [Link](supportFragmentManager,
TIME_PICKER_ONCE_TAG)
39. }
40. [Link].btn_set_once_alarm -> {
41. val onceDate = binding?.tvOnceDate?.[Link]()
42. val onceTime = binding?.tvOnceTime?.[Link]()
43. val onceMessage = binding?.edtOnceMessage?.[Link]()
44.
45. [Link](this,
AlarmReceiver.TYPE_ONE_TIME,
46. onceDate,
47. onceTime,
48. onceMessage)
49. }
50. [Link].btn_repeating_time -> {
51. val timePickerFragmentRepeat = TimePickerFragment()
52. [Link](supportFragmentManager,
TIME_PICKER_REPEAT_TAG)
53. }
54. [Link].btn_set_repeating_alarm -> {
55. val repeatTime = binding?.tvRepeatingTime?.[Link]()
56. val repeatMessage =
binding?.edtRepeatingMessage?.[Link]()
57. [Link](this,
AlarmReceiver.TYPE_REPEATING,
58. repeatTime, repeatMessage)
59. }
60. }
61. }
62.
63. override fun onDialogDateSet(tag: String?, year: Int, month: Int,
dayOfMonth: Int) {
64.
65. // Siapkan date formatter-nya terlebih dahulu
66. val calendar = [Link]()
67. [Link](year, month, dayOfMonth)
68. val dateFormat = SimpleDateFormat("yyyy-MM-dd",
[Link]())
69.
70. // Set text dari textview once
71. binding?.tvOnceDate?.text = [Link]([Link])
72. }
73.
74. override fun onDialogTimeSet(tag: String?, hourOfDay: Int, minute: Int)
{
75.
76. // Siapkan time formatter-nya terlebih dahulu
77. val calendar = [Link]()
78. [Link](Calendar.HOUR_OF_DAY, hourOfDay)
79. [Link]([Link], minute)
80.
81. val dateFormat = SimpleDateFormat("HH:mm", [Link]())
82.
83. // Set text dari textview berdasarkan tag
84. when (tag) {
85. TIME_PICKER_ONCE_TAG -> binding?.tvOnceTime?.text =
[Link]([Link])
86. TIME_PICKER_REPEAT_TAG -> binding?.tvRepeatingTime?.text =
[Link]([Link])
87. else -> {
88. }
89. }
90. }
91.
92. override fun onDestroy() {
93. [Link]()
94.
95. binding = null
96. }
97. }
6. Setelah selesai, silakan jalankan aplikasi dan pilih waktu yang diinginkan
untuk menampilkan notifikasi harian seperti berikut:
7. Tidak sulit, kan? Saat ini anda sudah bisa membuat sebuah alarm
berulang yang berikutnya bisa Anda gunakan untuk scheduling
task, seperti melakukan sinkronisasi ke server. Thumbs Up!
Bedah Kode
Tidak ada perubahan yang signifikan ketika kita membuat metode
setRepeatingAlarm() dengan method setOneTimeAlarm(). Yang
membedakan adalah pada pemanggilan metode terkait. Pada
setOneTimeAlarm() kita menggunakan metode set() sedangkan pada
setRepeatingAlarm() kita menggunakan setInexactRepeating() .
setInexactRepeating
Kotlin
Java
1. fun setRepeatingAlarm(context: Context, type: String, time: String, message:
String) {
2. if (isDateInvalid(time, TIME_FORMAT)) return
3.
4. val alarmManager = [Link](Context.ALARM_SERVICE) as
AlarmManager
5. val intent = Intent(context, AlarmReceiver::[Link])
6. [Link](EXTRA_MESSAGE, message)
7. val putExtra = [Link](EXTRA_TYPE, type)
8.
9. val timeArray = [Link](":".toRegex()).dropLastWhile { [Link]()
}.toTypedArray()
10.
11. val calendar = [Link]()
12. [Link](Calendar.HOUR_OF_DAY, [Link](timeArray[0]))
13. [Link]([Link], [Link](timeArray[1]))
14. [Link]([Link], 0)
15.
16. val pendingIntent = [Link](context, ID_REPEATING,
intent, 0)
17. [Link](AlarmManager.RTC_WAKEUP,
[Link], AlarmManager.INTERVAL_DAY, pendingIntent)
18.
19. [Link](context, "Repeating alarm set up",
Toast.LENGTH_SHORT).show()
20. }
Secara umum proses yang disusun pada metode di atas adalah sama.
Yang membedakan hanya ide dan skenarionya. Di sini kita akan
mendemonstrasikan sebuah proses yang berulang dengan satuan waktu
yang ditentukan dengan memanfaatkan fitur AlarmManager.
Kotlin
Java
1. [Link](AlarmManager.RTC_WAKEUP,
[Link], AlarmManager.INTERVAL_DAY, pendingIntent)
Baris di atas akan menjalankan obyek PendingIntent pada setiap waktu
yang ditentukan dalam millissecond dengan interval per hari. Penggunaan
metode setInexactRepeating() adalah pilihan lebih tepat karena Android
akan menjalankan alarm ini secara bersamaan dengan alarm lain.
Meskipun waktu antara tiap alarm tersebut tidak sama persis. Ini penting
agar baterai peranti user jadi lebih hemat dan tidak cepat habis. Ingat!
Penggunaan alarm manager yang tidak tepat akan memakan daya baterai
yang cukup besar.
Latihan Membatalkan AlarmManager
Pada modul sebelumnya, kami belum menjelaskan bagaimana
membatalkan sebuah alarm manager yang sudah dipasang. Modul ini
akan mencoba membedah mekanisme tersebut.
1. Tambahkan kode berikut ke dalam AlarmReceiver:
o Kotlin
o Java
1. fun cancelAlarm(context: Context, type: String) {
2. val alarmManager =
[Link](Context.ALARM_SERVICE) as
AlarmManager
3. val intent = Intent(context, AlarmReceiver::[Link])
4. val requestCode = if ([Link](TYPE_ONE_TIME,
ignoreCase = true)) ID_ONETIME else ID_REPEATING
5. val pendingIntent = [Link](context,
requestCode, intent, 0)
6. [Link]()
7.
8. [Link](pendingIntent)
9.
10. [Link](context, "Repeating alarm dibatalkan",
Toast.LENGTH_SHORT).show()
11. }
Maka AlarmReceiver menjadi seperti berikut:
Kotlin
Java
1. class AlarmReceiver : BroadcastReceiver() {
2.
3. companion object {
4. const val TYPE_ONE_TIME = "OneTimeAlarm"
5. const val TYPE_REPEATING = "RepeatingAlarm"
6. const val EXTRA_MESSAGE = "message"
7. const val EXTRA_TYPE = "type"
8.
9. // Siapkan 2 id untuk 2 macam alarm, onetime dan repeating
10. private const val ID_ONETIME = 100
11. private const val ID_REPEATING = 101
12.
13. private const val DATE_FORMAT = "yyyy-MM-dd"
14. private const val TIME_FORMAT = "HH:mm"
15.
16. }
17.
18. override fun onReceive(context: Context, intent: Intent) {
19. val type = [Link](EXTRA_TYPE)
20. val message = [Link](EXTRA_MESSAGE)
21.
22. val title = if ([Link](TYPE_ONE_TIME, ignoreCase = true))
TYPE_ONE_TIME else TYPE_REPEATING
23. val notifId = if ([Link](TYPE_ONE_TIME, ignoreCase = true))
ID_ONETIME else ID_REPEATING
24.
25. showToast(context, title, message)
26.
27. if (message != null) {
28. showAlarmNotification(context, title, message, notifId)
29. }
30. }
31.
32. private fun showToast(context: Context, title: String, message: String?)
{
33. [Link](context, "$title : $message",
Toast.LENGTH_LONG).show()
34. }
35.
36. fun showAlarmNotification(context: Context, title: String,
private
message: String, notifId: Int) {
37.
38. val CHANNEL_ID = "Channel_1"
39. val CHANNEL_NAME = "AlarmManager channel"
40.
41. val notificationManagerCompat =
[Link](Context.NOTIFICATION_SERVICE) as
NotificationManager
42. val alarmSound =
[Link](RingtoneManager.TYPE_NOTIFICATION)
43. val builder = [Link](context, CHANNEL_ID)
44. .setSmallIcon([Link].ic_access_time_black)
45. .setContentTitle(title)
46. .setContentText(message)
47. .setColor([Link](context,
[Link]))
48. .setVibrate(longArrayOf(1000, 1000, 1000, 1000, 1000))
49. .setSound(alarmSound)
50.
51. if ([Link].SDK_INT >= Build.VERSION_CODES.O) {
52.
53. val channel = NotificationChannel(CHANNEL_ID,
54. CHANNEL_NAME,
55. NotificationManager.IMPORTANCE_DEFAULT)
56.
57. [Link](true)
58. [Link] = longArrayOf(1000, 1000, 1000, 1000,
1000)
59.
60. [Link](CHANNEL_ID)
61.
62.
[Link](channel)
63. }
64.
65. val notification = [Link]()
66.
67. [Link](notifId, notification)
68.
69. }
70.
71. fun setOneTimeAlarm(context: Context, type: String, date: String,
time: String, message: String) {
72.
73. if (isDateInvalid(date, DATE_FORMAT) || isDateInvalid(time,
TIME_FORMAT)) return
74.
75. val alarmManager =
[Link](Context.ALARM_SERVICE) as AlarmManager
76. val intent = Intent(context, AlarmReceiver::[Link])
77. [Link](EXTRA_MESSAGE, message)
78. [Link](EXTRA_TYPE, type)
79.
80. Log.e("ONE TIME", "$date $time")
81. val dateArray = [Link]("-").toTypedArray()
82. val timeArray = [Link](":").toTypedArray()
83.
84. val calendar = [Link]()
85. [Link]([Link], [Link](dateArray[0]))
86. [Link]([Link], [Link](dateArray[1]) - 1)
87. [Link](Calendar.DAY_OF_MONTH,
[Link](dateArray[2]))
88. [Link](Calendar.HOUR_OF_DAY,
[Link](timeArray[0]))
89. [Link]([Link], [Link](timeArray[1]))
90. [Link]([Link], 0)
91.
92. val pendingIntent = [Link](context,
ID_ONETIME, intent, 0)
93. [Link](AlarmManager.RTC_WAKEUP,
[Link], pendingIntent)
94.
95. [Link](context, "One time alarm set up",
Toast.LENGTH_SHORT).show()
96. }
97.
98. fun setRepeatingAlarm(context: Context, type: String, time: String,
message: String) {
99.
100. if (isDateInvalid(time, TIME_FORMAT)) return
101.
102. val alarmManager =
[Link](Context.ALARM_SERVICE) as AlarmManager
103. val intent = Intent(context, AlarmReceiver::[Link])
104. [Link](EXTRA_MESSAGE, message)
105. val putExtra = [Link](EXTRA_TYPE, type)
106.
107. val timeArray = [Link](":".toRegex()).dropLastWhile
{ [Link]() }.toTypedArray()
108.
109. val calendar = [Link]()
110. [Link](Calendar.HOUR_OF_DAY,
[Link](timeArray[0]))
111. [Link]([Link], [Link](timeArray[1]))
112. [Link]([Link], 0)
113.
114. val pendingIntent = [Link](context,
ID_REPEATING, intent, 0)
115.
[Link](AlarmManager.RTC_WAKEUP,
[Link], AlarmManager.INTERVAL_DAY, pendingIntent)
116.
117. [Link](context, "Repeating alarm set up",
Toast.LENGTH_SHORT).show()
118. }
119.
120.
121. fun cancelAlarm(context: Context, type: String) {
122. val alarmManager =
[Link](Context.ALARM_SERVICE) as AlarmManager
123. val intent = Intent(context, AlarmReceiver::[Link])
124. val requestCode = if ([Link](TYPE_ONE_TIME, ignoreCase
= true)) ID_ONETIME else ID_REPEATING
125. val pendingIntent = [Link](context,
requestCode, intent, 0)
126. [Link]()
127.
128. [Link](pendingIntent)
129.
130. [Link](context, "Repeating alarm dibatalkan",
Toast.LENGTH_SHORT).show()
131. }
132.
133. private fun isDateInvalid(date: String, format: String): Boolean {
134. return try {
135. val df = SimpleDateFormat(format, [Link]())
136. [Link] = false
137. [Link](date)
138. false
139. } catch (e: ParseException) {
140. true
141. }
142. }
143. }
Berkas activity_main.xml sekarang seharusnya berisi kode berikut yang sudah
ditambahkan dari modul sebelumnya:
1. <Button
2. android:id="@+id/btn_cancel_repeating_alarm"
3. android:layout_width="wrap_content"
4. android:layout_height="wrap_content"
5. android:layout_weight="0.5"
6. android:text="@string/cancel_alarm" />
Isi activity_main.xml secara keseluruhan menjadi seperti berikut ini:
1. <?xml version="1.0" encoding="utf-8"?>
2. <LinearLayout xmlns:android="[Link]
3. xmlns:tools="[Link]
4. android:id="@+id/activity_main"
5. android:layout_width="match_parent"
6. android:layout_height="match_parent"
7. android:orientation="vertical"
8. android:padding="16dp"
9. tools:context="[Link]">
10.
11. <TextView
12. android:layout_width="match_parent"
13. android:layout_height="wrap_content"
14. android:layout_marginBottom="16dp"
15. android:text="@string/one_time_alarm"
16. android:textSize="16sp"
17. android:textStyle="bold" />
18.
19. <LinearLayout
20. android:layout_width="match_parent"
21. android:layout_height="wrap_content"
22. android:gravity="center_vertical"
23. android:orientation="horizontal">
24.
25. <ImageButton
26. android:id="@+id/btn_once_date"
27. style="@style/[Link]"
28. android:layout_width="50dp"
29. android:layout_height="wrap_content"
30. android:src="@drawable/ic_date_range_black" />
31.
32. <TextView
33. android:id="@+id/tv_once_date"
34. android:layout_width="wrap_content"
35. android:layout_height="wrap_content"
36. android:text="@string/alarm_date" />
37. </LinearLayout>
38.
39. <LinearLayout
40. android:layout_width="match_parent"
41. android:layout_height="wrap_content"
42. android:gravity="center_vertical"
43. android:orientation="horizontal">
44.
45. <ImageButton
46. android:id="@+id/btn_once_time"
47. android:layout_width="50dp"
48. android:layout_height="wrap_content"
49. android:src="@drawable/ic_access_time_black" />
50.
51. <TextView
52. android:id="@+id/tv_once_time"
53. android:layout_width="wrap_content"
54. android:layout_height="wrap_content"
55. android:text="@string/alarm_time" />
56. </LinearLayout>
57.
58. <EditText
59. android:id="@+id/edt_once_message"
60. android:layout_width="match_parent"
61. android:layout_height="wrap_content"
62. android:layout_marginBottom="16dp"
63. android:hint="@string/alarm_message"
64. android:inputType="text" />
65.
66. <Button
67. android:id="@+id/btn_set_once_alarm"
68. android:layout_width="match_parent"
69. android:layout_height="wrap_content"
70. android:layout_marginBottom="16dp"
71. android:text="@string/set_one_time_alarm" />
72.
73. <TextView
74. android:layout_width="match_parent"
75. android:layout_height="wrap_content"
76. android:layout_marginBottom="16dp"
77. android:text="@string/repeating_alarm"
78. android:textSize="16sp"
79. android:textStyle="bold" />
80.
81. <LinearLayout
82. android:layout_width="match_parent"
83. android:layout_height="wrap_content"
84. android:gravity="center_vertical"
85. android:orientation="horizontal">
86.
87. <ImageButton
88. android:id="@+id/btn_repeating_time"
89. android:layout_width="50dp"
90. android:layout_height="wrap_content"
91. android:src="@drawable/ic_access_time_black" />
92.
93. <TextView
94. android:id="@+id/tv_repeating_time"
95. android:layout_width="wrap_content"
96. android:layout_height="wrap_content"
97. android:text="@string/alarm_time" />
98. </LinearLayout>
99.
100. <EditText
101. android:id="@+id/edt_repeating_message"
102. android:layout_width="match_parent"
103. android:layout_height="wrap_content"
104. android:layout_marginBottom="16dp"
105. android:hint="@string/alarm_message"
106. android:inputType="text" />
107.
108. <LinearLayout
109. android:layout_width="match_parent"
110. android:layout_height="wrap_content"
111. android:gravity="center"
112. android:orientation="horizontal">
113.
114. <Button
115. android:id="@+id/btn_cancel_repeating_alarm"
116. android:layout_width="wrap_content"
117. android:layout_height="wrap_content"
118. android:layout_weight="0.5"
119. android:text="@string/cancel_alarm" />
120.
121. <Button
122. android:id="@+id/btn_set_repeating_alarm"
123. android:layout_width="wrap_content"
124. android:layout_height="wrap_content"
125. android:layout_weight="0.5"
126. android:text="@string/set_repeating_alarm" />
127. </LinearLayout>
128. </LinearLayout>
Selanjutnya pada MainActivity tambahkan aksi pada komponen button:
Kotlin
Java
1. class MainActivity : AppCompatActivity(), [Link],
[Link], [Link]
{
2.
3. ...
4.
5. override fun onCreate(savedInstanceState: Bundle?) {
6. [Link](savedInstanceState)
7.
8. ...
9.
10.
binding?.btnCancelRepeatingAlarm?.setOnClickListener(this)
11.
12. alarmReceiver = AlarmReceiver()
13. }
14.
15. override fun onClick(v: View) {
16. when ([Link]) {
17.
18. ...
19.
20. [Link].btn_cancel_repeating_alarm ->
21. }
22. }
23.
24. ...
25. }
Selanjutnya kita tambahkan kode seleksi baru pada metode onClick() yang akan
menjalankan proses pembatalan repeating alarm ketika tombol Batalkan
Alarm diklik.
Kotlin
Java
1. override fun onClick(v: View) {
2. when ([Link]) {
3.
4. ...
5.
6. [Link].btn_cancel_repeating_alarm ->
[Link](this,
AlarmReceiver.TYPE_REPEATING)
7. }
8. }
Sehingga kode MainActivity kita secara keseluruhan menjadi seperti berikut:
Kotlin
Java
1. class MainActivity : AppCompatActivity(), [Link],
[Link],
[Link] {
2.
3. private var binding: ActivityMainBinding? = null
4. private lateinit var alarmReceiver: AlarmReceiver
5.
6. companion object {
7. private const val DATE_PICKER_TAG = "DatePicker"
8. private const val TIME_PICKER_ONCE_TAG = "TimePickerOnce"
9. private const val TIME_PICKER_REPEAT_TAG =
"TimePickerRepeat"
10. }
11.
12. override fun onCreate(savedInstanceState: Bundle?) {
13. [Link](savedInstanceState)
14.
15. binding = [Link](layoutInflater)
16. setContentView(binding?.root)
17.
18. // Listener one time alarm
19. binding?.btnOnceDate?.setOnClickListener(this)
20. binding?.btnOnceTime?.setOnClickListener(this)
21. binding?.btnSetOnceAlarm?.setOnClickListener(this)
22.
23. // Listener repeating alarm
24. binding?.btnRepeatingTime?.setOnClickListener(this)
25. binding?.btnSetRepeatingAlarm?.setOnClickListener(this)
26.
binding?.btnCancelRepeatingAlarm?.setOnClickListener(this)
27.
28. alarmReceiver = AlarmReceiver()
29. }
30.
31. override fun onClick(v: View) {
32. when ([Link]) {
33. [Link].btn_once_date -> {
34. val datePickerFragment = DatePickerFragment()
35.
[Link](supportFragmentManager,
DATE_PICKER_TAG)
36. }
37. [Link].btn_once_time -> {
38. val timePickerFragmentOne = TimePickerFragment()
39.
[Link](supportFragmentManager,
TIME_PICKER_ONCE_TAG)
40. }
41. [Link].btn_set_once_alarm -> {
42. val onceDate = binding?.tvOnceDate?.[Link]()
43. val onceTime = binding?.tvOnceTime?.[Link]()
44. val onceMessage =
binding?.edtOnceMessage?.[Link]()
45.
46. [Link](this,
AlarmReceiver.TYPE_ONE_TIME,
47. onceDate,
48. onceTime ,
49. onceMessage )
50. }
51. [Link].btn_repeating_time -> {
52. val timePickerFragmentRepeat = TimePickerFragment()
53.
[Link](supportFragmentManager,
TIME_PICKER_REPEAT_TAG)
54. }
55. [Link].btn_set_repeating_alarm -> {
56. val repeatTime =
binding?.tvRepeatingTime?.[Link]()
57. val repeatMessage =
binding?.edtRepeatingMessage?.[Link]()
58. [Link](this,
AlarmReceiver.TYPE_REPEATING,
59. repeatTime, repeatMessage)
60. }
61. [Link].btn_cancel_repeating_alarm ->
[Link](this, AlarmReceiver.TYPE_REPEATING)
62. }
63. }
64.
65. override fun onDialogDateSet(tag: String?, year: Int, month: Int,
dayOfMonth: Int) {
66.
67. // Siapkan date formatter-nya terlebih dahulu
68. val calendar = [Link]()
69. [Link](year, month, dayOfMonth)
70. val dateFormat = SimpleDateFormat("yyyy-MM-dd",
[Link]())
71.
72. // Set text dari textview once
73. binding?.tvOnceDate?.text =
[Link]([Link])
74. }
75.
76. override fun onDialogTimeSet(tag: String?, hourOfDay: Int,
minute: Int) {
77.
78. // Siapkan time formatter-nya terlebih dahulu
79. val calendar = [Link]()
80. [Link](Calendar.HOUR_OF_DAY, hourOfDay)
81. [Link]([Link], minute)
82.
83. val dateFormat = SimpleDateFormat("HH:mm",
[Link]())
84.
85. // Set text dari textview berdasarkan tag
86. when (tag) {
87. TIME_PICKER_ONCE_TAG -> binding?.tvOnceTime?.text
= [Link]([Link])
88. TIME_PICKER_REPEAT_TAG ->
binding?.tvRepeatingTime?.text =
[Link]([Link])
89. else -> {
90. }
91. }
92. }
93.
94. override fun onDestroy() {
95. [Link]()
96.
97. binding = null
98. }
99. }
4. Ketika aplikasi dijalankan, maka tampilannya kurang lebih menjadi seperti
ini:
Selesai! Sekarang mari kita uji kodenya. Skenarionya seperti berikut:
1. Ubah nilai pada variabel repeatingTime di MainActivity sesuai dengan
waktu yang mendekati saat ini (bedakan sekitar 5 menit dari sekarang).
Sebagai contoh, saat ini waktu dipasang pada pukul 01.45
2. Klik tombol SET REPEATING ALARM untuk menjalankan alarm untuk
waktu yang ditentukan.
3. Klik tombol BATALKAN ALARM untuk membatalkan alarm yang baru saja
dijalankan.
4. Jika sukses seharusnya pada waktu yang diberikan tidak tampil notifikasi
alarm seperti sebelumnya.
Bedah Kode
CancelAlarm
Kotlin
Java
1. fun cancelAlarm(context: Context, type: String) {
2. val alarmManager =
[Link](Context.ALARM_SERVICE) as AlarmManager
3. val intent = Intent(context, AlarmReceiver::[Link])
4. val requestCode = if ([Link](TYPE_ONE_TIME, ignoreCase =
true)) ID_ONETIME else ID_REPEATING
5. val pendingIntent = [Link](context,
requestCode, intent, 0)
6. [Link]()
7.
8. [Link](pendingIntent)
9.
10. [Link](context, "Repeating alarm dibatalkan",
Toast.LENGTH_SHORT).show()
11. }
Karena tipe alarm yang dikirimkan adalah TYPE_REPEATING, maka id yang
digunakan adalah NOTIF_ID_REPEATING yang bernilai 101. Setelah
memperoleh id tersebut, maka kita dapat mengetahui intent-nya. Dan
kemudian kita dapat membatalkan alarmnya.
Dan itulah esensi membatalkan alarm yang telah dipasang.
Di materi selanjutnya kita akan berkenalan dengan yang lebih seru lagi,
jobschedule untuk melakukan proses scheduling task/proses berulang
dengan cara yang lebih efisien dari sisi pemanfaatan sumber daya.
Untuk keseluruhan kode proyek ini bisa kamu unduh di:
Source Code Latihan Alarm Manager
Teori Koneksi ke Web API & Parsing JSON
Tahukah Anda bahwa salah satu skill yang dibutuhkan seorang Android
Developer adalah memahami JSON API dan Networking. Mengapa
demikian? Tentunya karena kebanyakan aplikasi membutuhkan data yang
berasal dari server. Misalnya Anda akan membuat aplikasi cuaca, maka
tentu Anda perlu terhubung ke server untuk mendapatkan data cuaca
yang terbaru.
Nah, hal-hal yang perlu Anda perhatikan saat ingin mengambil data
secara online yaitu:
Apa itu Web API?
Apa itu JSON?
Bagaimana cara menampilkannya ke dalam aplikasi?
Tak perlu bingung. Kita akan membahas ini satu per satu secara
mendetail. Tetap semangat!
Web API
Saat kita membutuhkan data cuaca terbaru, ada dua cara yang bisa Anda
lakukan. Pertama, membuat sensor cuaca dan menyimpannya ke
server sendiri. Kedua, Anda bisa memanfaatkan pihak ketiga yang sudah
menyediakan data cuaca. Nah, layanan data ini biasanya disebut dengan
Web API atau Web Service.
Web API (Application Programming Interface) adalah suatu layanan yang
mengizinkan dua aplikasi saling terhubung melalui jaringan, sehingga kita
dapat mengambil dan mengirim data ke layanan tersebut dengan mudah.
Kelebihan dari Web API adalah membuat proses development menjadi
lebih efisien. Mengapa? Karena cukup dengan sekali membuat API, Anda
dapat mengkonsumsinya di berbagai macam platform, seperti mobile,
desktop maupun web. Sebagai catatan, konsumsi API adalah istilah yang
sering dipakai untuk ketika client mengambil data dari Web API.
Yang menarik yaitu ada Web API yang bersifat public atau sering
disebut Public API. Karena sifatnya yang public, siapa pun pun bisa
memakainya. Tentu hal ini akan memudahkan Anda dalam
mengembangkan aplikasi. Beberapa kumpulan public API dapat Anda lihat
di sini:
Any API
Public APIs by ToddMotto
Daftar API Lokal Indonesia by Farizdotid
Jika dilihat satu per satu Web API pada tautan di atas, semuanya hampir
memiliki pola yang sama. Hal ini karena terdapat gaya atau aturan
standar dalam perancangan Web API, salah satunya yaitu REST
(REpresentation State Transfer). Arsitektur ini terkenal karena lebih simpel
dan mudah dipelajari daripada arsitektur yang lainnya. Data yang
diberikan pun bisa berupa format text, JSON atau XML.
REST ini hampir selalu menggunakan HTTP (Hypertext Transfer Protocol)
untuk berkomunikasi data. HTTP adalah protokol yang memungkinkan
Anda saling bertukar data antar server dan client. Pada protokol ini,
terdapat beberapa request method yang biasa digunakan untuk proses
CRUD (Create Read Update Delete), di antaranya adalah:
POST : Untuk membuat data (Create)
GET : Untuk membaca data (Read)
PUT : Untuk mengubah data (Update)
DELETE : Untuk menghapus data (Delete)
Berikut ini adalah contoh URL yang merupakan API dan parameter-
parameternya:
[Link]
Path : “users”
Query 1 : “page” bernilai “1”
Query 2 : “per_page” bernilai “10”
Gunakan “?” sebagai separator sebelum parameter pertama
Gunakan “&” sebagai separator untuk parameter selanjutnya
Gunakan “=” untuk mengisi query dengan suatu nilai/value
Untuk memahami lebih lanjut tentang API, Anda dapat melihat video pada
tautan berikut:
What is an API?
Akses Web API melalui Postman
Nah, sekarang kita akan mengakses salah satu contoh dummy API yang
ada di [Link]. Kemudian untuk uji coba proses CRUD, Anda dapat
memanfaatkan Postman yang dapat diunduh di [Link].
Postman adalah GUI API Caller yang dapat memasukkan parameter atau
body dengan mudah pada API melalui form tanpa perlu kode. Mungkin
Anda bisa mencoba API yang menggunakan method GET langsung melalui
browser, namun untuk method lainnya seperti POST, Anda akan
kesusahan. Tak mau kan? Di sinilah Postman hadir sebagai solusi.
Setelah menginstal Postman, buat request baru dengan method GET dan
masukkan URL berikut: [Link] Supaya lebih
mudah dibaca, ubah pula pengaturan pada response menjadi Pretty dan
formatnya menjadi JSON. Selanjutnya klik tombol Send.
Jika koneksi berhasil, maka Anda akan mendapatkan respon seperti ini:
1. {
2. "page": 1,
3. "per_page": 6,
4. "total": 12,
5. "total_pages": 2,
6. "data": [
7. {
8. "id": 1,
9. "email": "[Link]@[Link]",
10. "first_name": "George",
11. "last_name": "Bluth",
12. "avatar":
"[Link]
13. },
14. {
15. "id": 2,
16. "email": "[Link]@[Link]",
17. "first_name": "Janet",
18. "last_name": "Weaver",
19. "avatar":
"[Link]
20. },
21. {
22. "id": 3,
23. "email": "[Link]@[Link]",
24. "first_name": "Emma",
25. "last_name": "Wong",
26. "avatar":
"[Link]
27. },
28. {
29. "id": 4,
30. "email": "[Link]@[Link]",
31. "first_name": "Eve",
32. "last_name": "Holt",
33. "avatar":
"[Link]
34. },
35. {
36. "id": 5,
37. "email": "[Link]@[Link]",
38. "first_name": "Charles",
39. "last_name": "Morris",
40. "avatar":
"[Link]
41. },
42. {
43. "id": 6,
44. "email": "[Link]@[Link]",
45. "first_name": "Tracey",
46. "last_name": "Ramos",
47. "avatar":
"[Link]
48. }
49. ]
50. }
Dengan mengakses URL tersebut, Anda akan mendapatkan daftar biodata dalam
format JSON. Selanjutnya coba masukkan URL
berikut: [Link] Anda akan mendapatkan response biodata
dengan id 2.
1. {
2. "data": {
3. "id": 2,
4. "email": "[Link]@[Link]",
5. "first_name": "Janet",
6. "last_name": "Weaver",
7. "avatar":
"[Link]
8. }
9. }
Lalu bagaimana jika Anda ingin menambahkan data? Gunakan
method POST dan masukkan parameter di bagian Body → x-www-form-
urlencoded. Isikan key sesuai dengan parameter yang ada di
documentation. Jika berhasil Anda akan mendapatkan response seperti
ini:
JSON
Sebelumnya Anda sudah mencoba mengakses API dan mendapatkan
response JSON. Lalu sebenarnya apa JSON itu? JSON (Javascript Object
Notation) merupakan format pertukaran & penyimpanan data yang ringan
dan mudah dibaca (dibanding XML). Format ini dapat digunakan di hampir
semua bahasa pemrograman. Karena itulah kami sangat menyarankan
para developer memahami cara berinteraksi dengan format ini.
Sebagai contoh Anda memiliki database dengan tabel seperti berikut:
Maka format JSON dari tabel tersebut adalah seperti ini:
Dalam JSON, ada dua hal penting yang perlu diperhatikan, yaitu
JSONArray dan JSONObject. JSONArray direpresentasikan dengan tanda [
] (kurung siku). Jenis ini berisi kumpulan JSONObject dengan tanda
koma sebagai pemisahnya. Sedangkan JSONObject direpresentasikan
dengan tanda { } (kurung kurawal).
Pada contoh di atas, “data” merupakan JSONArray yang berisi dua
JSONObject. Object ke-0 berisi kumpulan data dengan id 1 dan Object ke-1
berisi kumpulan data dengan id 2.
Struktur dari JSON menggunakan format key-value untuk menampilkan
field data. Seperti contoh di atas, "page" adalah key dan "1" adalah
value. Berikut adalah cara mengetahui tipe data yang terdapat pada
value:
String : teks (dibungkus dengan “ “)
Integer : angka bulat (misal: 0, 3, 40)
Double : angka desimal (misal 0.2, 3.14, 40.0)
Boolean : true/false
Array : [“value1”, “value2”]
Object : { “key” : “value” }
Null : null
Parsing JSON
Nah, sekarang Anda akan mempelajari cara mem-parsing JSON. Yang
dimaksud dengan parsing yaitu membaca dan mengubahnya sesuai
dengan bahasa pemrograman yang kita pakai. Contohnya yaitu seperti
berikut ini:
JSON Field
Untuk method yang digunakan menyesuaikan dengan tipe data
yang diambil. Misalnya Anda ingin mengambil data “page” yang
bernilai 1, maka untuk mengambilnya Anda dapat menggunakan
kode:
o Kotlin
o Java
1. val jsonObject = JSONObject(response)
2. val page = [Link]("page")
Catatan :
Pastikan parameternya sama persis dengan key yang ada di Web API-nya.
JSON Array
Untuk mengambil data yang bertanda [ ] (kurung siku), Anda dapat
menggunakan getJSONArray.
Kotlin
Java
1. val jsonObject = JSONObject(response)
2. val dataArray = [Link]("data")
JSON Object
Untuk mengambil data yang bertanda { } (kurung kurawal), Anda dapat
menggunakan getJSONObject(index).
Berikut adalah contoh mengambil data email pada Object pertama (ke-0):
Kotlin
Java
1. val jsonObject = JSONObject(response);
2. val dataArray = [Link]("data")
3. val dataObject = [Link](0)
4. val email = [Link]("email")
Jadi, untuk ntuk mengambil data first_name, Anda perlu mengambil JSON
Array "data" dan JSONObject-nya terlebih dulu.
Langkah di atas juga dapat disingkat menjadi seperti ini:
Kotlin
Java
1. val jsonObject = JSONObject(response);
2. val dataArray = [Link]("data")
3. val email = [Link](0).getString("email")
Catatan :
Jika diperhatikan JSON di atas diawali dan diakhiri dengan { }, sehingga
ada JSONObject di tiap awal kode.
Untuk lebih membuat kamu paham tentang JSON Anda bisa mempelajari
lebih lanjut pada URL ini:
JSON
JSON Parsing
Dicoding Live : Journey to Parsing JSON in Android
Library untuk Networking
Untuk memudahkan proses transaksi data dengan web service, kita dapat
menggunakan beragam HTTP library yang tersedia seperti LoopJ, Retrofit,
dan Volley. Pada kesempatan ini kita akan menggunakan
library AsyncHttpClient (LoopJ).
Kita menggunakan gradle untuk mengintegrasikan AsyncHttpClient ke
aplikasi. Caranya dengan menambahkan satu baris gradle script pada
bagian dependency yang ada di [Link](Module: app):
1. dependencies {
2. ...
3. implementation '[Link]:android-async-http:1.4.9'
4. }
Kemudian kita cukup memilih sync now untuk mengunduh
library tersebut.
Selanjutnya bagaimana cara memakainya? Misalnya Anda mau
mengambil data dari [Link] maka Anda perlu
memakai method GET seperti berikut:
Kotlin
Java
1. val client = AsyncHttpClient()
2. val url = "[Link]
3. [Link](url, object : AsyncHttpResponseHandler() {
4. override fun onSuccess(statusCode: Int, headers: Array<Header>,
responseBody: ByteArray) {
5. // called when response HTTP status is "200 OK"
6. }
7.
8. override fun onFailure(statusCode: Int, headers: Array<Header>,
responseBody: ByteArray, error: Throwable) {
9. // called when response HTTP status is "4XX" (eg. 401, 403, 404)
10. }
11. })
Di atas kita menggunakan AsyncHttpClient, berarti kita akan
menggunakan client yang bertanggung jawab untuk koneksi data dan
sifatnya adalah asynchronous. Jika tidak dilakukan secara asynchronous,
Anda akan mengalami eror networkOnMainThreadException.
Callback onSuccess() dipanggil ketika response memberikan kode status
200, yang artinya koneksi berhasil. Di dalamnya Anda dapat melakukan
proses parsing JSON dan menampilkan data ke aplikasi. Sementara itu,
callback onFailure() dipanggil ketika response memberikan kode status
4xx (seperti 401, 403, 404, dll), yang artinya koneksi gagal.
Berikut adalah penjelasan arti dari status kode yang sering ditemui:
Status
Arti Penjelasan
Kode
200 OK Request berhasil dipenuhi oleh server.
Bad Server tidak mengerti request yang dikirimkan
400
Request client, misal karena kesalahan syntax.
Unauthoriz
401 Request membutuhkan authorization.
ed
Server mengerti request dari client tapi menolak
memprosesnya. Hal ini terjadi karena
403 Forbidden
resource tersebut memang tidak boleh diakses
secara permanen atau dibatasi oleh waktu.
404 Not Found Resource yang diminta tidak ditemukan.
500 Server Server mengalami kesalahan yang tidak diketahui
Error saat melakukan pemrosesan terhadap request yang
Status
Arti Penjelasan
Kode
dikirimkan oleh client.
Lalu bagaimana jika Anda ingin mengirim data dengan parameter ke
server? Anda bisa menggunakan method post() dan menambahkan
RequestParams untuk memasukkan parameter seperti berikut:
Kotlin
Java
1. val client = AsyncHttpClient()
2. val params = RequestParams()
3. [Link]("name", "arif faizin")
4. [Link]("job", "academy content writer")
5. val url = "[Link]
6. [Link](url, params, object : AsyncHttpResponseHandler() {
7. override fun onSuccess(statusCode: Int, headers: Array<Header>,
responseBody: ByteArray) {
8. // called when response HTTP status is "200 OK"
9. }
10.
11. override fun onFailure(statusCode: Int, headers: Array<Header>,
responseBody: ByteArray, error: Throwable) {
12. // called when response HTTP status is "4XX" (eg. 401, 403, 404)
13. }
14. })
Selanjutnya ini adalah contoh penggunaan method put() untuk update:
Kotlin
Java
1. val client = AsyncHttpClient()
2. val params = RequestParams()
3. [Link]("name", "arif faizin")
4. [Link]("job", "academy content writer")
5. val url = "[Link]
6. [Link](url, object : AsyncHttpResponseHandler() {
7. override fun onSuccess(statusCode: Int, headers: Array<Header>,
responseBody: ByteArray) {
8. // called when response HTTP status is "200 OK"
9. }
10.
11. override fun onFailure(statusCode: Int, headers: Array<Header>,
responseBody: ByteArray, error: Throwable) {
12. // called when response HTTP status is "4XX" (eg. 401, 403, 404)
13. }
14. })
Dan ini adalah contoh penggunaan method delete() untuk menghapus
data:
Kotlin
Java
1. val client = AsyncHttpClient()
2. val params = RequestParams()
3. [Link]("id", "21");
4. val url = "[Link]
5. [Link](url, object : AsyncHttpResponseHandler() {
6. override fun onSuccess(statusCode: Int, headers: Array<Header>,
responseBody: ByteArray) {
7. // called when response HTTP status is "200 OK"
8. }
9.
10. override fun onFailure(statusCode: Int, headers: Array<Header>,
responseBody: ByteArray, error: Throwable) {
11. // called when response HTTP status is "4XX" (eg. 401, 403, 404)
12. }
13. })
Kalau dilihat dari semua kode di atas, maka pembedanya hanyalah di
bagian method-nya.
Selamat! Anda telah mempelajari dasar-dasar untuk mengambil dan
mengirim data ke server. Mulai dari memahami Web API, JSON dan cara
parsing-nya, serta library yang digunakan untuk networking di Android.
Pada modul selanjutnya kita akan latihan bersama membuat aplikasi yang
dapat menampilkan data dari API. Tetap semangat ya!
Latihan Networking dengan LoopJ & Parsing JSON
Tujuan
Pada codelab kali ini Anda akan belajar menerapkan library LoopJ untuk
mengambil data dari Web API. Target Anda adalah menghasilkan
output seperti di bawah ini:
Alur Latihan
Berikut alur yang akan kita lakukan dalam latihan kali ini:
1. Membuat project baru dengan nama MyQuote.
2. Menambahkan library ke dalam project.
3. Mengatur tampilan pada berkas layout XML.
4. Membuat fungsi getRandomQuote untuk mengambil data dari API.
5. Mengimplementasikan fungsi getRandomQuote ke dalam Activity.
6. Menjalankan aplikasi.
Codelab Web API di Android
1. Buat project baru di Android Studio dengan kriteria sebagai berikut:
Nama Project MyQuote
Target & Minimum Target SDK Phone and Tablet, Api level 21
Tipe Activity Empty Activity
Activity Name MainActivity
Use AndroidX Artifacts True
Language Kotlin/Java
2. Di awal telah dijelaskan bahwa Anda akan membuat sebuah aplikasi
yang akan menampilkan random quote saat ini dengan library
LoopJ. Di sini Anda akan menggunakan data API dari [Link]
[Link] (Alternatif). API yang akan kita gunakan yaitu:
o Random Quote : [Link]
o List Quotes : [Link]
3. Cobalah buka URL tersebut di browser. Maka hasilnya seperti ini:
1. {"_id":"5a9b22c72bad9600044b700b","en":"Reliable computations are obtainable
from buggy programs, which after all, are the only kind of programs there
are.","author":"Daniel T. Barry","id":"5a9b22c72bad9600044b700b"}
Supaya terlihat rapi, Anda bisa menambahkan extension JSON Formatter
pada Chrome sehingga tampilannya akan mudah dibaca seperti ini:
1. {
2. "_id": "5a9ab0372bad9600044b6fbf",
3. "en": "When I invented the web, I didn't have to ask anyone's
permission.",
4. "author": "Tim Berners-Lee",
5. "id": "5a9ab0372bad9600044b6fbf"
6. }
3. Selanjutnya Anda akan menggunakan library AsyncHttpClient
(LoopJ) yang terdapat pada website [Link]. Untuk
menambahkannya, buka berkas [Link](Module: app).
Kemudian tambahkan baris berikut:
1. dependencies {
2. ...
3. implementation '[Link]:android-async-http:1.4.9'
4. }
Lalu klik pilihan Sync Now di pojok kanan atas Android Studio
seperti ini:
Ketika proses synchronize selesai, maka library telah berhasil
dipasang.
4. Okay, pertama kita mulai dengan membuat desain aplikasinya
terlebih dahulu. Buka activity_main.xml dan buat layout
menggunakan Constraint Layout seperti ini:
1. <?xml version="1.0" encoding="utf-8"?>
2. <[Link] xmlns:android="http://
[Link]/apk/res/android"
3. xmlns:app="[Link]
4. xmlns:tools="[Link]
5. android:id="@+id/activity_main"
6. android:layout_width="match_parent"
7. android:layout_height="match_parent"
8. android:padding="16dp"
9. android:orientation="vertical"
10. tools:context="[Link]">
11.
12. <TextView
13. android:id="@+id/tvQuote"
14. android:layout_width="match_parent"
15. android:layout_height="wrap_content"
16. android:layout_margin="16dp"
17. android:textAlignment="center"
18. android:textSize="18sp"
19. android:textStyle="bold"
20. app:layout_constraintBottom_toBottomOf="parent"
21. app:layout_constraintEnd_toEndOf="parent"
22. app:layout_constraintStart_toStartOf="parent"
23. app:layout_constraintTop_toTopOf="parent"
24. tools:text="@string/quote" />
25.
26. <TextView
27. android:id="@+id/tvAuthor"
28. android:layout_width="match_parent"
29. android:layout_height="wrap_content"
30. android:layout_margin="16dp"
31. android:textAlignment="center"
32. android:textSize="14sp"
33. android:textStyle="italic"
34. app:layout_constraintTop_toBottomOf="@+id/tvQuote"
35. tools:layout_editor_absoluteX="16dp"
36. tools:text="@string/author" />
37.
38. <ProgressBar
39. android:id="@+id/progressBar"
40. style="?android:attr/progressBarStyle"
41. android:layout_width="wrap_content"
42. android:layout_height="wrap_content"
43. app:layout_constraintBottom_toBottomOf="parent"
44. app:layout_constraintEnd_toEndOf="parent"
45. app:layout_constraintStart_toStartOf="parent"
46. app:layout_constraintTop_toTopOf="parent" />
47.
48. <Button
49. android:id="@+id/btnAllQuotes"
50. android:layout_width="wrap_content"
51. android:layout_height="wrap_content"
52. android:layout_margin="16dp"
53. android:text="@string/show_list_quotes"
54. app:layout_constraintBottom_toBottomOf="parent"
55. app:layout_constraintEnd_toEndOf="parent"
56. app:layout_constraintStart_toStartOf="parent" />
57.
58. </[Link]>
Tambahkan juga resource di [Link] seperti berikut:
1. <resources>
2. <string name="app_name">My Quote</string>
3. <string name="quote">When do you think people die? When
they are shot through the heart by the bullet of a pistol? No.
When they are ravaged by an incurable disease? No. When they
drink a soup made from a poisonous mushroom!? No! It’s when…
they are FORGOTTEN.</string>
4. <string name="author">- Dr. Hiluluk</string>
5. <string name="show_list_quotes">Show me list of
quotes</string>
6. </resources>
Sehingga desain dari aplikasi utamanya akan menjadi seperti ini:
5. Selanjutnya buka MainActivity dan buat method dengan nama
getRandomQuote().
o Kotlin
o Java
1. class MainActivity : AppCompatActivity() {
2.
3. companion object {
4. private val TAG = MainActivity::[Link]
5. }
6.
7. private lateinit var binding: ActivityMainBinding
8.
9. override fun onCreate(savedInstanceState: Bundle?) {
10. [Link](savedInstanceState)
11. binding = [Link](layoutInflater)
12. setContentView([Link])
13.
14. getRandomQuote()
15. }
16.
17. private fun getRandomQuote() {
18.
19. }
20. }
Jangan lupa untuk mengaktifkan ViewBinding terlebih dahulu pada
[Link](module: app) dengan menambahkan kode berikut:
1. android {
2. ...
3. buildFeatures {
4. viewBinding = true
5. }
6. }
6. Selanjutnya kita mulai untuk mengimplementasikan LoopJ untuk
mengambil data dari Web API seperti berikut:
o Kotlin
o Java
1. private fun getRandomQuote() {
2. [Link] = [Link]
3. val client = AsyncHttpClient()
4. val url = "[Link]
5. [Link](url, object : AsyncHttpResponseHandler() {
6. override fun onSuccess(statusCode: Int, headers:
Array<Header>, responseBody: ByteArray) {
7. // Jika koneksi berhasil
8. }
9.
10. override fun onFailure(statusCode: Int, headers:
Array<Header>, responseBody: ByteArray, error: Throwable) {
11. // Jika koneksi gagal
12. }
13. })
14. }
Di sini terdapat ProgressBar yang ditampilkan untuk
mengindikasikan proses loading data. Loading ini akan hilang ketika
data sudah ditampilkan.
7. Kemudian lihat kembali API untuk mengambil random quote.
Di sini terdapat 1 JSONObject yang berisi 4 data. Namun hanya 2
data yang diperlukan, yaitu data dengan key “en” dan “author”.
Berikut ini adalah cara untuk mem-parsing data tersebut di dalam
method onSuccess:
o Kotlin
o Java
1. private fun getRandomQuote() {
2. [Link] = [Link]
3. val client = AsyncHttpClient()
4. val url = "[Link]
5. [Link](url, object : AsyncHttpResponseHandler() {
6. override fun onSuccess(statusCode: Int, headers: Array<Header>,
responseBody: ByteArray) {
7. // Jika koneksi berhasil
8. [Link] = [Link]
9.
10. val result = String(responseBody)
11. Log.d(TAG, result)
12. try {
13. val responseObject = JSONObject(result)
14.
15. val quote = [Link]("en")
16. val author = [Link]("author")
17.
18. [Link] = quote
19. [Link] = author
20.
21. } catch (e: Exception) {
22. [Link](this@MainActivity, [Link],
Toast.LENGTH_SHORT).show()
23. [Link]()
24. }
25.
26. }
27.
28. override fun onFailure(statusCode: Int, headers: Array<Header>,
responseBody: ByteArray, error: Throwable) {
29. // Jika koneksi gagal
30. [Link] = [Link]
31.
32. val errorMessage = when (statusCode) {
33. 401 -> "$statusCode : Bad Request"
34. 403 -> "$statusCode : Forbidden"
35. 404 -> "$statusCode : Not Found"
36. else -> "$statusCode : ${[Link]}"
37. }
38. [Link](this@MainActivity, errorMessage,
Toast.LENGTH_SHORT).show()
39. }
40. })
41. }
Data kemudian ditampilkan di TextView jika berhasil. Jika tidak, akan
muncul Toast dan memberitahukan eror apa yang terjadi.
8. Terakhir, karena Anda menggunakan koneksi internet, Anda perlu
menambahkan permission di AndroidManifest seperti berikut:
1. <?xml version="1.0" encoding="utf-8"?>
2. <manifest xmlns:android="[Link]
3. xmlns:tools="[Link]
4. package="[Link]">
5.
6. <uses-permission
android:name="[Link]" />
7.
8. <application
9. ...
10. </application>
11. </manifest>
9. Setelah semuanya selesai, jalankan aplikasi yang Anda buat. Maka
hasilnya akan menjadi seperti ini:
10. Selamat! Anda sudah menerapkan LoopJ untuk mengakses
Web API yang berbentuk satu JSONObject. Lalu, bagaimana jika
datanya berbentuk JSONArray yang berisi banyak data? Kita
lanjutkan di langkah berikutnya. Semangat!
11. Buatlah Activity baru dengan cara klik kanan pada nama
package → new → activity → empty activity. Kemudian beri
nama ListQuotesActivity.
12. Buka activit_list_quotes.xml, kemudian tambahkan
RecyclerView dan ProgressBar di dalamnya seperti ini:
1. <?xml version="1.0" encoding="utf-8"?>
2. <[Link]
xmlns:android="[Link]
3. xmlns:app="[Link]
4. xmlns:tools="[Link]
5. android:layout_width="match_parent"
6. android:layout_height="match_parent"
7. tools:context=".ListQuotesActivity">
8.
9. <[Link]
10. android:id="@+id/listQuotes"
11. android:layout_width="match_parent"
12. android:layout_height="match_parent"
13. app:layout_constraintBottom_toTopOf="parent"
14. app:layout_constraintEnd_toEndOf="parent"
15. app:layout_constraintStart_toStartOf="parent"
16. app:layout_constraintTop_toTopOf="parent" />
17.
18. <ProgressBar
19. android:id="@+id/progressBar"
20. style="?android:attr/progressBarStyle"
21. android:layout_width="wrap_content"
22. android:layout_height="wrap_content"
23. app:layout_constraintBottom_toBottomOf="parent"
24. app:layout_constraintEnd_toEndOf="parent"
25. app:layout_constraintStart_toStartOf="parent"
26. app:layout_constraintTop_toTopOf="parent" />
27. </[Link]>
12. Sebelum melangkah lebih jauh, buatlah terlebih dahulu layout
untuk item RecyclerView dengan cara klik kanan pada folder
layout → New → Layout resource file dan beri nama
item_quote. Lalu buat desain seperti berikut:
1. <?xml version="1.0" encoding="utf-8"?>
2. <TextView xmlns:android="[Link]
3. xmlns:tools="[Link]
4. android:id="@+id/tvItem"
5. android:layout_width="match_parent"
6. android:layout_height="wrap_content"
7. android:gravity="center_vertical"
8. android:minHeight="?android:attr/listPreferredItemHeightSmall"
9. android:paddingStart="?android:attr/listPreferredItemPaddingStart"
10. android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
11. android:textAppearance="?android:attr/textAppearanceListItemSmall"
12. tools:text="Quote" />
Kemudian buat class baru untuk adapter RecyclerView dengan nama
QuoteAdapter dan tuliskan kode berikut:
Kotlin
Java
1. class QuoteAdapter(private val listReview: ArrayList<String>) :
[Link]<[Link]>() {
2. override fun onCreateViewHolder(viewGroup: ViewGroup, viewType:
Int): ViewHolder {
3. val view =
[Link]([Link]).inflate([Link].item_quote,
viewGroup, false)
4. return ViewHolder(view)
5. }
6.
7. override fun onBindViewHolder(viewHolder: ViewHolder, position: Int)
{
8. [Link] = listReview[position]
9. }
10.
11. override fun getItemCount(): Int {
12. return [Link]
13. }
14.
15. class ViewHolder(view: View) : [Link](view) {
16. val tvItem: TextView = [Link]([Link])
17. }
18. }
Kemudian buka ListQuotesActivity dan buat method getListQuotes
untuk mengambil data list quotes.
Kotlin
Java
1. class ListQuotesActivity : AppCompatActivity() {
2.
3. companion object {
4. private val TAG = ListQuotesActivity::[Link]
5. }
6.
7. private lateinit var binding: ActivityListQuotesBinding
8.
9. override fun onCreate(savedInstanceState: Bundle?) {
10. [Link](savedInstanceState)
11. binding = [Link](layoutInflater)
12. setContentView([Link])
13.
14. supportActionBar?.title = "List of Quotes"
15.
16. val layoutManager = LinearLayoutManager(this)
17. [Link](layoutManager)
18. val itemDecoration = DividerItemDecoration(this,
[Link])
19. [Link](itemDecoration)
20.
21. getListQuotes()
22. }
23.
24. private fun getListQuotes() {
25. [Link] = [Link]
26. val client = AsyncHttpClient()
27. val url = "[Link]
28. [Link](url, object : AsyncHttpResponseHandler() {
29. override fun onSuccess(statusCode: Int, headers:
Array<Header>, responseBody: ByteArray) {
30. // Jika koneksi berhasil
31. [Link] = [Link]
32. }
33.
34. override fun onFailure(statusCode: Int, headers:
Array<Header>, responseBody: ByteArray, error: Throwable) {
35. // Jika koneksi gagal
36. [Link] = [Link]
37. val errorMessage = when (statusCode) {
38. 401 -> "$statusCode : Bad Request"
39. 403 -> "$statusCode : Forbidden"
40. 404 -> "$statusCode : Not Found"
41. else -> "$statusCode : ${[Link]}"
42. }
43. [Link](this@ListQuotesActivity, errorMessage,
Toast.LENGTH_SHORT).show()
44. }
45. })
46. }
47. }
13. Sebelum menulis kode yang ada di onSuccess, lihat kembali
API untuk mengambil list quotes.
Di sini kita lihat terdapat JSONArray yang berisi banyak JSON Object.
Maka Anda perlu melakukan perulangan untuk mendapatkan data
tersebut dengan cara seperti ini:
o Kotlin
o Java
1. override fun onSuccess(statusCode: Int, headers: Array<Header>,
responseBody: ByteArray) {
2. // Jika koneksi berhasil
3. [Link] = [Link]
4.
5. val listQuote = ArrayList<String>()
6.
7. val result = String(responseBody)
8. Log.d(TAG, result)
9. try {
10. val jsonArray = JSONArray(result)
11.
12. for (i in 0 until [Link]()) {
13. val jsonObject = [Link](i)
14. val quote = [Link]("en")
15. val author = [Link]("author")
16. [Link]("\n$quote\n — $author\n")
17. }
18.
19. val adapter = QuoteAdapter(listQuote)
20. [Link] = adapter
21. } catch (e: Exception) {
22. [Link](this@ListQuotesActivity, [Link],
Toast.LENGTH_SHORT).show()
23. [Link]()
24. }
25.
26. }
14. Sekarang ListQuoteActivity sudah siap diakses. Tambahkan
Button di activity_main.xml untuk navigasi ke halaman list quotes.
1. <?xml version="1.0" encoding="utf-8"?>
2. <[Link]
xmlns:android="[Link]
3. xmlns:app="[Link]
4. xmlns:tools="[Link]
5. android:id="@+id/activity_main"
6. android:layout_width="match_parent"
7. android:layout_height="match_parent"
8. android:padding="16dp"
9. android:orientation="vertical"
10. tools:context="[Link]">
11.
12. <TextView
13. android:id="@+id/tvQuote"
14. android:layout_width="match_parent"
15. android:layout_height="wrap_content"
16. android:layout_margin="16dp"
17. android:textAlignment="center"
18. android:textSize="18sp"
19. android:textStyle="bold"
20. app:layout_constraintBottom_toBottomOf="parent"
21. app:layout_constraintEnd_toEndOf="parent"
22. app:layout_constraintStart_toStartOf="parent"
23. app:layout_constraintTop_toTopOf="parent"
24. tools:text="@string/quote" />
25.
26. <TextView
27. android:id="@+id/tvAuthor"
28. android:layout_width="match_parent"
29. android:layout_height="wrap_content"
30. android:layout_margin="16dp"
31. android:textAlignment="center"
32. android:textSize="14sp"
33. android:textStyle="italic"
34. app:layout_constraintTop_toBottomOf="@+id/tvQuote"
35. tools:layout_editor_absoluteX="16dp"
36. tools:text="@string/author" />
37.
38. <ProgressBar
39. android:id="@+id/progressBar"
40. style="?android:attr/progressBarStyle"
41. android:layout_width="wrap_content"
42. android:layout_height="wrap_content"
43. app:layout_constraintBottom_toBottomOf="parent"
44. app:layout_constraintEnd_toEndOf="parent"
45. app:layout_constraintStart_toStartOf="parent"
46. app:layout_constraintTop_toTopOf="parent" />
47.
48. <Button
49. android:id="@+id/btnAllQuotes"
50. android:layout_width="wrap_content"
51. android:layout_height="wrap_content"
52. android:layout_margin="16dp"
53. android:text="@string/show_list_quotes"
54. app:layout_constraintBottom_toBottomOf="parent"
55. app:layout_constraintEnd_toEndOf="parent"
56. app:layout_constraintStart_toStartOf="parent" />
57. </[Link]>
15. Kemudian pada MainActivity tambahkan aksi pada Button
tersebut seperti ini:
o Kotlin
o Java
1. class MainActivity : AppCompatActivity() {
2.
3. ...
4.
5. override fun onCreate(savedInstanceState: Bundle?) {
6. [Link](savedInstanceState)
7. setContentView([Link].activity_main)
8.
9. getRandomQuote()
10.
11. [Link] {
12. startActivity(Intent(this@MainActivity,
ListQuotesActivity::[Link]))
13. }
14. }
15. ...
16. }
16. Setelah semuanya selesai, jalankan kembali aplikasi yang
Anda buat. Maka hasilnya akan menjadi seperti ini:
Bedah Kode
LoopJ
Perhatikan kode berikut pada MainActivity:
Kotlin
Java
1. private fun getRandomQuote() {
2. [Link] = [Link]
3. val client = AsyncHttpClient()
4. val url = "[Link]
5. [Link](url, object : AsyncHttpResponseHandler() {
6. override fun onSuccess(statusCode: Int, headers: Array<Header>,
responseBody: ByteArray) {
7. // Jika koneksi berhasil
8. [Link] = [Link]
9.
10. ...
11. }
12.
13. override fun onFailure(statusCode: Int, headers: Array<Header>,
responseBody: ByteArray, error: Throwable) {
14. // Jika koneksi gagal
15. [Link] = [Link]
16.
17. ...
18. }
19. })
20. }
Untuk menggunakan library LoopJ, Anda cukup menggunakan
AsyncHttpClient untuk membuat koneksi ke server secara asynchronous.
Kemudian karena kita hanya mengambil data (READ), maka Anda
menggunakan kode [Link](). Ada 2 object yang dihasilkan oleh
AsyncHttpResponseHandler yaitu onSuccess dan onFailure. onSuccess
digunakan jika koneksi berhasil dan onFailure digunakan jika koneksi
gagal. ProgressBar digunakan untuk memberitahu proses yang sedang
berjalan, ketika data sudah tampil ProgressBar akan hilang.
Status Code
Lihatlah kode di dalam method onFailure pada LoopJ.
Kotlin
Java
1. val errorMessage = when (statusCode) {
2. 401 -> "$statusCode : Bad Request"
3. 403 -> "$statusCode : Forbidden"
4. 404 -> "$statusCode : Not Found"
5. else -> "$statusCode : ${[Link]}"
6. }
7. [Link](this@MainActivity, errorMessage,
Toast.LENGTH_SHORT).show()
Di sini Anda menggunakan percabangan untuk mengganti pesan yang
ditampilkan jika terjadi eror saat koneksi ke server. Jika status code
memberikan hasil 401, maka pesan yang tampil yaitu “Bad Request”.
Jika 403 tampil pesan “Forbidden”. Dan jika 404 tampil pesan “Not
Found”. Lalu jika selain kode di atas Anda dapat
menggunakan [Link]() untuk mendapatkan pesan eror.
Parsing JSON Quote Random
Selanjutnya perhatikan cara mem-parsing JSON dari [Link]
[Link]/random pada method onSuccess di MainActivity:
Kotlin
Java
1. val result = String(responseBody)
2. Log.d(TAG, result)
3. try {
4. val responseObject = JSONObject(result)
5.
6. val quote = [Link]("en")
7. val author = [Link]("author")
8.
9. [Link] = quote
10. [Link] = author
11.
12. } catch (e: Exception) {
13. [Link](this@MainActivity, [Link], Toast.LENGTH_SHORT).show()
14. [Link]()
15. }
Di sini Anda membuat Log terlebih dahulu untuk menampilkan
response di Logcat. Hal ini penting untuk mengecek masalah apabila data
tidak tampil. Jika data yang di Logcat ada tapi list tidak tampil, maka
kemungkinan kesalahan terjadi pada saat parsing JSON atau pada saat
menampilkan RecyclerView.
Selanjutnya lihat format JSON pada API berikut:
Karena dimulai dengan { }, artinya ia bertipe JSONObject. maka untuk
mengambil datanya Anda bisa menggunakan kode di bawah ini.
Kotlin
Java
1. val responseObject = JSONObject(result)
Selanjutnya untuk mengambil data quote dan author, sesuai dengan tipe
data dari value tersebut.
Kotlin
Java
1. val quote = [Link]("en")
2. val author = [Link]("author")
Pastikan juga parameter yang digunakan sama persis dengan key yang
ada di web API.
Parsing JSON List Quote
Selanjutnya perhatikan cara mem-parsing JSON dari [Link]
[Link]/list pada method onSuccess di ListQuotesActivity:
Kotlin
Java
1. override fun onSuccess(statusCode: Int, headers: Array<Header>, responseBody:
ByteArray) {
2. // Jika koneksi berhasil
3. [Link] = [Link]
4.
5. val listQuote = ArrayList<String>()
6.
7. val result = String(responseBody)
8. Log.d(TAG, result)
9. try {
10. val jsonArray = JSONArray(result)
11.
12. for (i in 0 until [Link]()) {
13. val jsonObject = [Link](i)
14. val quote = [Link]("en")
15. val author = [Link]("author")
16. [Link]("\n$quote\n — $author\n")
17. }
18.
19. val adapter = QuoteAdapter(listQuote)
20. [Link] = adapter
21. } catch (e: Exception) {
22. [Link](this@ListQuotesActivity, [Link],
Toast.LENGTH_SHORT).show()
23. [Link]()
24. }
25.
26. }
Sama seperti sebelumnya, lihat terlebih dahulu hasil JSON dari API-nya.
Karena dimulai dengan [ ], artinya ia bertipe JSONArray. Karena itu, untuk
mengambil datanya Anda menggunakan:
Kotlin
Java
1. val jsonArray = JSONArray(result)
Selanjutnya kita menggunakan perulangan untuk mendapatkan
JSONObject yang ada di dalamnya seperti ini:
Kotlin
Java
1. val listQuote = ArrayList<String>()
2.
3. for (i in 0 until [Link]()) {
4. val jsonObject = [Link](i)
5. val quote = [Link]("en")
6. val author = [Link]("author")
7. [Link]("\n$quote\n — $author\n")
8. }
Setelah mendapatkan data dalam bentuk ArrayList, maka Anda dapat
memasukkannya ke adapter RecyclerView seperti biasa seperti ini:
Kotlin
Java
1. val adapter = QuoteAdapter(listQuote)
2. [Link] = adapter
Selamat Anda sudah belajar tentang menggunakan LoopJ dan Parsing
JSON yang merupakan skill dasar seorang Android Developer. Pahami
baik-baik dan baca ulang jika kurang mengerti. Semangat!
Source code dapat Anda unduh di:
Source Code Latihan LoopJ
Teori WorkManager
Sebelumnya Anda sudah mempelajari JobScheduler untuk melakukan
proses di background dengan serangkaian kondisi yang ditentukan.
Sayangnya, JobScheduler hanya dapat dijalankan di Android dengan
minimum API versi 21. Sedangkan untuk di bawahnya Anda harus
menggunakan FirebaseJobDispatcher, namun permasalahannya yaitu
komponen tersebut sudah deprecated. Tapi tunggu dulu, ada kabar
gembiranya! Terdapat komponen baru yang mengatasi permasalahan ini,
yaitu WorkManager.
Sekarang kita kenalan dulu yuk dengan komponen baru yang super keren
ini. WorkManager adalah sebuah komponen baru yang termasuk di dalam
paket Jetpack. Ia menjanjikan proses background yang pasti dieksekusi
(deferrable) walaupun aplikasi ditutup atau bahkan device di-restart. Dan
yang lebih penting lagi yaitu komponen ini sudah ramah baterai, sehingga
tidak menghabiskan banyak baterai.
Kelebihan WorkManager
Berikut adalah beberapa kelebihan dari WorkManager:
Mendukung One-Time Task maupun Periodic Task.
Bisa membuat chain (proses berkelanjutan) antar task.
Mendukung sampai API Level 14.
Tetap dapat berjalan walaupun tanpa Google Play Service.
Berjalan dengan mulus dengan doze mode.
Mendukung LiveData untuk melihat status Work.
Kapan menggunakan WorkManager
WorkManager cocok digunakan untuk aksi-aksi yang
bersifat deferrable (tetap berguna walaupun tidak langsung dilakukan),
seperti berikut ini:
Mengirim log ke server.
Mengirim media ke server.
Sync database secara periodic dari server.
Menerapkan filter ke image.
Berikut adalah flow untuk memilih komponen mana yang cocok
digunakan berdasarkan guide dari dokumentasi Android:
Jadi, jika aplikasi yang ingin Anda buat bersifat deferrable dan ada fitur
Sync, maka WorkManager merupakan pilihan yang paling tepat. Namun
jika proses tersebut terpantik(triggered) secara online, Anda bisa
menggunakan Firebase Cloud Messaging dan WorkManager. Namun jika
task tersebut harus segera dilakukan, maka Foreground Service cocok
untuk Anda gunakan. Dan jika task tersebut perlu dieksekusi pada waktu
yang tepat, maka AlarmManager adalah pilihan yang paling sesuai.
Komponen di WorkManager
Berikut ini adalah beberapa komponen yang Anda perlu ketahui sebelum
menggunakan WorkManager.
Worker, adalah sebuah kelas dengan method doWork(). Di sinilah aksi
yang akan dieksekusi di background thread diletakkan.
WorkRequest, ada dua jenis, yaitu OneTimeWorkRequest untuk sekali
eksekusi dan PeriodicWorkRequest untuk eksekusi yang dilakukan secara
periodik.
Constraint, digunakan untuk mengatur kapan proses tersebut dieksekusi,
misal: ketika mendapatkan koneksi internet, ketika peranti sedang diisi
dayanya, atau ketika dalam kondisi idle.
Data, digunakan untuk mengirim data ke Worker dengan format Key-
Value.
WorkManager, adalah kelas yang menjalankan WorkRequest dan juga
bisa digunakan untuk mengatur task-chaining.
Result, digunakan untuk melihat status worker, apakah masih menunggu,
gagal, atau berhasil.
Berikut adalah contoh kelas yang mengimplementasi Worker
Kotlin
Java
1. class MyWorker(ctx: Context, params: WorkerParameters) : Worker(ctx, params) {
2. override fun doWork(): Result {
3. try{
4. //proses di background
5. return [Link]()
6. }catch(e: Exception){
7. return [Link]()
8. }
9. }
10. }
Kemudian untuk memanggil kelas Worker bisa dengan cara berikut:
Kotlin
Java
1. val myData= [Link]()
2. [Link](LETTER_ID, letterId)
3. [Link]()
4. val myConstraints = [Link]()
5. .setRequiredNetworkType([Link])
6. .build()
7. val myRequest = OneTimeWorkRequestBuilder<NotificationWorker>()
8. .setInputData(myData)
9. .setConstraints(myConstraints)
10. .addTag(NOTIFICATION_CHANNEL_ID)
11. .build()
12. [Link]().enqueue(myRequest)
Anda juga bisa menerapkan task-chaining dengan cara berikut:
Kotlin
Java
1. [Link]().beginWith(myRequest).then(uploadRequest).enqueue
()
Berikut adalah alur WorkManager bekerja:.
Seperti biasa kami menyarankan Anda untuk menyimak lebih jauh
tentang WorkManager melalui tautan di bawah ini:
Introducing WorkManager
Codelabs WorkManager
WorkManager
Guide to Background Processing
Mari kita mulai mengimplementasikan WorkManager dengan mengikuti
latihan pada modul berikutnya.
Latihan Scheduler dengan WorkManager
Tujuan
Pada materi kali ini Anda akan belajar memanfaatkan komponen
WorkManager. Aplikasi buatan Anda akan menjadi seperti ini:
Alur Latihan
Berikut alur yang akan kita lakukan dalam latihan kali ini:
1. Membuat project baru dengan nama MyWorkManager.
2. Menambahkan library ke dalam project.
3. Membuat kelas MyWorker untuk mengatur konfigurasi WorkManager
pada Aplikasi.
4. Mengatur tampilan pada berkas layout XML.
5. Mengimplementasikan MyWorker ke dalam Activity.
6. Menjalankan aplikasi.
Codelab WorkManager
Pada codelab latihan kali ini Anda akan membuat proses untuk
mengunduh data cuaca baik secara langsung maupun secara periodik
(per 15 menit sekali). Aplikasi akan menampilkan notifikasi cuaca saat ini.
1. Buat Project baru di Android Studio dengan kriteria sebagai berikut:
Nama Project MyWorkManager
Target & Minimum Target Phone and Tablet, API
SDK level 21
Tipe Activity Empty Activity
Activity Name MainActivity
Use AndroidX Artifacts True
Language Kotlin/Java
2. Di awal telah dijelaskan bahwa Anda akan membuat sebuah aplikasi yang
akan menampilkan cuaca saat ini dengan library WorkManager. Di sini
Anda akan menggunakan data API dari
[Link] Ini berarti aplikasi Anda butuh
mengambil data dari Internet. Untuk mempermudah urusan tersebut,
Anda akan menggunakan library AsyncHttpClient (LoopJ) yang terdapat
pada website [Link]
Untuk menambahkannya, buka berkas [Link](Module: app).
Kemudian tambahkan baris berikut:
1. dependencies {
2. ...
3. implementation "[Link]:work-runtime:2.5.0"
4. implementation '[Link]:android-async-http:1.4.10'
5. }
Lalu klik pilihan Sync Now di pojok kanan atas Android Studio seperti ini:
Ketika proses synchronize selesai, maka kedua library telah berhasil terpasang.
Selanjutnya, buat kelas baru dengan nama MyWorker dengan cara klik kanan di
package utama pada proyek aplikasi → New → Kotlin Class/Java Class, lalu
tambahkan extends dan constructor dengan cara menambahkan kode berikut:
Kotlin
Java
1. class MyWorker(context: Context, workerParams: WorkerParameters) :
Worker(context, workerParams) {
2.
3. }
Akan muncul garis merah pada kode di atas. Anda diharuskan untuk
menerapkan metode bawaan yang terdapat pada superclass Worker. Cukup klik
lampu warna merah atau tekan alt+enter untuk membuat metode yang
dibutuhkan.
Contohnya seperti ini:
Kotlin
Java
Seharusnya MyWorker yang Anda miliki sekarang menjadi seperti di bawah ini:
Kotlin
Java
1. class MyWorker(context: Context, workerParams: WorkerParameters) :
Worker(context, workerParams) {
2. override fun doWork(): Result {
3. TODO("Not yet implemented")
4. }
5. }
Ok, langkah selanjutnya Anda harus membuat akun di
website [Link] untuk mendapatkan api key. Anda dapat
registrasi menggunakan tautan ini.
Setelah proses registrasi selesai, Anda dapat masuk ke dalam
dashboard OpenWeatherMap. Selanjutnya Anda akan memperoleh application id
atau AppId. Nilai ini akan Anda gunakan untuk mengakses data pada
OpenWeatherMap. Sementara itu, API Key dapat ditemukan di tab API keys
seperti ini:
Kemudian cobalah untuk mengambil data cuaca pada browser dengan
memanggil url [Link]
q=Bandung&appid=YOUR_API_KEY. Ganti YOUR_API_KEY dengan api key
yang sudah Anda dapatkan di langkah sebelumnya. Sehingga, akan tampil data
JSON seperti berikut:
Supaya terlihat rapi, Anda bisa menambahkan extension JSON Formatter pada
Chrome melalui tautan ini sehingga tampilannya akan mudah dibaca seperti ini:
Perlu diketahui dari data di atas yaitu ada dua macam JSON, yaitu JSON Array
yang ditandai dengan tanda kurung siku "[]" dan JSON Object yang ditandai
dengan kurung kurawal "{}".
Okay sekarang saatnya mengimplementasikannya pada kelas MyWorker. Anda
akan menggunakan LoopJ untuk memanggil API OpenWeather, kemudian mem-
parsing data JSON-nya. Setelah itu tampilkan datanya di Notification seperti
berikut ini:
Kotlin
Java
1. ...
2. import [Link]
3. import [Link]
4.
5. class MyWorker(context: Context, workerParams: WorkerParameters) :
Worker(context, workerParams) {
6.
7. companion object {
8. private val TAG = MyWorker::[Link]
9. const val APP_ID = "YOUR_KEY_HERE"
10. const val EXTRA_CITY = "city"
11. const val NOTIFICATION_ID = 1
12. const val CHANNEL_ID = "channel_01"
13. const val CHANNEL_NAME = "dicoding channel"
14. }
15.
16. private var resultStatus: Result? = null
17.
18. override fun doWork(): Result {
19. val dataCity = [Link](EXTRA_CITY)
20. return getCurrentWeather(dataCity)
21. }
22.
23. private fun getCurrentWeather(city: String?): Result {
24. Log.d(TAG, "getCurrentWeather: Mulai.....")
25. [Link]()
26. val client = SyncHttpClient()
27. val url = "[Link]
q=$city&appid=$APP_ID"
28. Log.d(TAG, "getCurrentWeather: $url")
29. [Link](url, object : AsyncHttpResponseHandler() {
30. override fun onSuccess(statusCode: Int, headers: Array<Header?
>?, responseBody: ByteArray) {
31. val result = String(responseBody)
32. Log.d(TAG, result)
33. try {
34. val responseObject = JSONObject(result)
35. val currentWeather: String =
[Link]("weather").getJSONObject(0).getString("
main")
36. val description: String =
[Link]("weather").getJSONObject(0).getString("d
escription")
37. val tempInKelvin =
[Link]("main").getDouble("temp")
38. val tempInCelsius = tempInKelvin - 273
39. val temperature: String =
DecimalFormat("##.##").format(tempInCelsius)
40. val title = "Current Weather in $city"
41. val message = "$currentWeather, $description with
$temperature celsius"
42. showNotification(title, message)
43. Log.d(TAG, "onSuccess: Selesai.....")
44. resultStatus = [Link]()
45. } catch (e: Exception) {
46. showNotification("Get Current Weather Not Success",
[Link])
47. Log.d(TAG, "onSuccess: Gagal.....")
48. resultStatus = [Link]()
49. }
50. }
51.
52. override fun onFailure(statusCode: Int, headers: Array<Header?
>?, responseBody: ByteArray?, error: Throwable) {
53. Log.d(TAG, "onFailure: Gagal.....")
54. // ketika proses gagal, maka jobFinished diset dengan
parameter true. Yang artinya job perlu di reschedule
55. showNotification("Get Current Weather Failed",
[Link])
56. resultStatus = [Link]()
57. }
58. })
59. return resultStatus as Result
60. }
61.
62. private fun showNotification(title: String, description: String?) {
63. val notificationManager =
[Link](Context.NOTIFICATION_SERVICE)
as NotificationManager
64. val notification: [Link] =
[Link](applicationContext, CHANNEL_ID)
65. .setSmallIcon([Link].ic_notifications_black_24dp)
66. .setContentTitle(title)
67. .setContentText(description)
68. .setPriority(NotificationCompat.PRIORITY_HIGH)
69. .setDefaults(NotificationCompat.DEFAULT_ALL)
70. if ([Link].SDK_INT >= Build.VERSION_CODES.O) {
71. val channel = NotificationChannel(CHANNEL_ID, CHANNEL_NAME,
NotificationManager.IMPORTANCE_HIGH)
72. [Link](CHANNEL_ID)
73. [Link](channel)
74. }
75. [Link](NOTIFICATION_ID, [Link]())
76. }
77. }
Catatan:
Ganti teks pada YOUR_KEY_HERE dengan api key yang Anda dapatkan di
langkah sebelumnya.
Pada saat import pilihlah [Link]; dan
[Link].
Untuk kebutuhan asset gambar drawable, Anda bisa mendapatkannya
dengan cara klik kanan pada folder drawable → New → Vector Asset →
Pilih Clip Art dan cari dengan kata kunci "notifications" → OK → Next
→ Finish. Selain itu Anda juga bisa mengunduhnya secara manual di sini
dan menyalinnya ke direktori drawable pada project di Android Studio.
Berikutnya, lengkapi kode pada activity_main.xml sehingga menjadi seperti
ini:
1. <?xml version="1.0" encoding="utf-8"?>
2. <LinearLayout xmlns:android="[Link]
3. xmlns:app="[Link]
4. xmlns:tools="[Link]
5. android:layout_width="match_parent"
6. android:layout_height="match_parent"
7. android:padding="8dp"
8. android:orientation="vertical"
9. tools:context=".MainActivity">
10.
11. <TextView
12. android:layout_width="match_parent"
13. android:layout_height="wrap_content"
14. android:text="@string/enter_city_name" />
15.
16. <EditText
17. android:id="@+id/editCity"
18. android:layout_width="match_parent"
19. android:layout_height="wrap_content"
20. android:ems="10"
21. android:inputType="textPersonName" />
22.
23. <Button
24. android:id="@+id/btnOneTimeTask"
25. android:layout_width="match_parent"
26. android:layout_height="wrap_content"
27. android:layout_margin="4dp"
28. android:text="@string/run_one_time_task" />
29.
30. <TextView
31. android:id="@+id/textStatus"
32. android:layout_width="wrap_content"
33. android:layout_height="wrap_content"
34. android:text="@string/status" />
35.
36. </LinearLayout>
Jangan lupa menambahkan teks yang ada di layout ke dalam [Link].
1. <resources>
2. <string name="app_name">MyWorkManager</string>
3. <string name="enter_city_name">Enter city name</string>
4. <string name="run_one_time_task">Run One Time Task</string>
5. <string name="run_periodic_task">Run Periodic Task</string>
6. <string name="cancel_periodic_task">Cancel Periodic Task</string>
7. <string name="status">Status :</string>
8. </resources>
Resource string ini akan Anda gunakan selama latihan ini.
Pada kode tersebut, Anda menambahkan EditText untuk memasukkan nama
kota yang akan dipantau cuacanya. Kemudian ada satu button untuk
menjalankan WorkManager. Sehingga tampilan aplikasi Anda saat ini adalah
seperti berikut:
Selanjutnya, pada MainActivity tambahkan aksi klik pada button seperti ini:
Kotlin
Java
1. class MainActivity : AppCompatActivity(), OnClickListener {
2.
3. private lateinit var workManager: WorkManager
4. private lateinit var binding: ActivityMainBinding
5.
6. override fun onCreate(savedInstanceState: Bundle?) {
7. [Link](savedInstanceState)
8. binding = [Link](layoutInflater)
9. setContentView([Link])
10.
11. workManager = [Link](this)
12. [Link](this)
13. }
14.
15. override fun onClick(view: View) {
16.
17. }
18. }
Jangan lupa untuk menambahkan konfigurasi untuk mengaktifkan ViewBinding
pada [Link] (module: app) seperti berikut:
1. android {
2.
3. ...
4.
5. buildFeatures {
6. viewBinding = true
7. }
8. }
Kemudian sempurnakan kode untuk memulai WorkManager sehingga kelas
MainActivity menjadi seperti berikut:
Kotlin
Java
1. ...
2. import [Link]
3.
4. class MainActivity : AppCompatActivity(), OnClickListener {
5.
6. ...
7.
8. override fun onClick(view: View) {
9. when ([Link]) {
10. [Link] -> startOneTimeTask()
11. }
12. }
13.
14. private fun startOneTimeTask() {
15. [Link] = getString([Link])
16. val data = [Link]()
17. .putString(MyWorker.EXTRA_CITY,
[Link]())
18. .build()
19. val oneTimeWorkRequest =
[Link](MyWorker::[Link])
20. .setInputData(data)
21. .build()
22. [Link](oneTimeWorkRequest)
23. [Link]([Link])
24. .observe(this@MainActivity, { workInfo ->
25. val status = [Link]
26. [Link]("\n" + status)
27. })
28. }
29. }
Terakhir, tambahkan permission internet dan ijinkan clear text traffic di
dalam berkas [Link] seperti berikut:
1. <?xml version="1.0" encoding="utf-8"?>
2. <manifest xmlns:android="[Link]
3. package="[Link]">
4. <uses-permission android:name="[Link]"/>
5. <application
6. android:allowBackup="true"
7. android:icon="@mipmap/ic_launcher"
8. android:label="@string/app_name"
9. android:supportsRtl="true"
10. android:usesCleartextTraffic="true"
11. android:theme="@style/AppTheme">
12. <activity android:name=".MainActivity">
13. <intent-filter>
14. <action android:name="[Link]" />
15. <category android:name="[Link]" />
16. </intent-filter>
17. </activity>
18. </application>
19. </manifest>
android:usesCleartextTraffic="true" berfungsi supaya tidak terjadi error pada
perangkat dengan Android versi Pie ke atas. Hal ini karena Anda mengakses
API dengan awalan http, bukan https. Pada perangkat Android P ke atas
penggunaan ClearTextTraffic bernilai false secara default untuk keamanan.
Untuk itu, Anda perlu mengaktifkannya supaya proses pengambilan data dari API
berjalan lancar.
Selesai! Sekarang jalankan aplikasinya dan tekan tombol RUN ONE TIME TASK.
Sehingga, data cuaca akan ditampilkan pada panel notifikasi seperti contoh di
bawah ini:
Dengan menggunakan WorkManager Anda bisa mengatur kapan task Anda
dieksekusi, misal saat ada koneksi internet, saat sedang diisi dayanya, maupun
saat memori tidak penuh. Caranya yaitu dengan menambahkan constraints.
Keren bukan! Nah, sekarang coba tambahkan kode berikut pada saat
mengkonfigurasi WorkManager pada MainActivity:
Kotlin
Java
1. class MainActivity : AppCompatActivity(), OnClickListener {
2.
3. ...
4.
5. private fun startOneTimeTask() {
6. [Link] = getString([Link])
7. val data = [Link]()
8. .putString(MyWorker.EXTRA_CITY,
[Link]())
9. .build()
10. val constraints = [Link]()
11. .setRequiredNetworkType([Link])
12. .build()
13. val oneTimeWorkRequest =
[Link](MyWorker::[Link])
14. .setInputData(data)
15. .setConstraints(constraints)
16. .build()
17. [Link](oneTimeWorkRequest)
18. [Link]([Link])
19. .observe(this@MainActivity, { workInfo ->
20. val status = [Link]
21. [Link]("\n" + status)
22. })
23. }
24. }
Codelab Periodic Task
Sekarang Anda akan membuat periodic task yang berfungsi untuk
mengambil data cuaca setiap 15 menit, sehingga data akan selalu ter-
update. Pertanyaanya mengapa 15 menit? Hal itu karena waktu tersebut
adalah batas minimal interval yang diperbolehkan di dalam WorkManager.
Selain itu, Anda juga akan belajar membatalkan task yang sudah berjalan.
Yuk, lanjutkan belajarnya!
1. Pertama, tambahkan button pada activity_main.xml menggunakan kode
berikut:
1. <?xml version="1.0" encoding="utf-8"?>
2. <LinearLayout xmlns:android="[Link]
3. xmlns:app="[Link]
4. xmlns:tools="[Link]
5. android:layout_width="match_parent"
6. android:layout_height="match_parent"
7. android:padding="8dp"
8. android:orientation="vertical"
9. tools:context=".MainActivity">
10.
11. <TextView
12. android:layout_width="match_parent"
13. android:layout_height="wrap_content"
14. android:text="@string/enter_city_name" />
15.
16. <EditText
17. android:id="@+id/editCity"
18. android:layout_width="match_parent"
19. android:layout_height="wrap_content"
20. android:ems="10"
21. android:inputType="textPersonName" />
22.
23. <Button
24. android:id="@+id/btnOneTimeTask"
25. android:layout_width="match_parent"
26. android:layout_height="wrap_content"
27. android:layout_margin="4dp"
28. android:text="@string/run_one_time_task" />
29.
30. <LinearLayout
31. android:layout_width="match_parent"
32. android:layout_height="wrap_content"
33. android:orientation="horizontal">
34.
35. <Button
36. android:id="@+id/btnPeriodicTask"
37. android:layout_width="wrap_content"
38. android:layout_height="wrap_content"
39. android:layout_margin="4dp"
40. android:layout_weight="1"
41. android:text="@string/run_periodic_task" />
42.
43. <Button
44. android:id="@+id/btnCancelTask"
45. android:layout_width="wrap_content"
46. android:layout_height="wrap_content"
47. android:layout_margin="4dp"
48. android:layout_weight="1"
49. android:enabled="false"
50. android:text="@string/cancel_periodic_task" />
51. </LinearLayout>
52.
53. <TextView
54. android:id="@+id/textStatus"
55. android:layout_width="wrap_content"
56. android:layout_height="wrap_content"
57. android:text="@string/status" />
58.
59. </LinearLayout>
Kemudian tambahkan kode berikut di MainActivity untuk membuat periodic
task dan membatalkan task:
Kotlin
Java
1. class MainActivity : AppCompatActivity(), OnClickListener {
2.
3. private lateinit var workManager: WorkManager
4. private lateinit var periodicWorkRequest: PeriodicWorkRequest
5. private lateinit var binding: ActivityMainBinding
6.
7. override fun onCreate(savedInstanceState: Bundle?) {
8. [Link](savedInstanceState)
9. binding = [Link](layoutInflater)
10. setContentView([Link])
11.
12. workManager = [Link](this)
13.
14. [Link](this)
15. [Link](this)
16. [Link](this)
17. }
18.
19. override fun onClick(view: View) {
20. when ([Link]) {
21. [Link] -> startOneTimeTask()
22. [Link] -> startPeriodicTask()
23. [Link] -> cancelPeriodicTask()
24. }
25. }
26.
27. private fun startOneTimeTask() {
28. [Link] = getString([Link])
29. val data = [Link]()
30. .putString(MyWorker.EXTRA_CITY,
[Link]())
31. .build()
32. val constraints = [Link]()
33. .setRequiredNetworkType([Link])
34. .build()
35. val oneTimeWorkRequest =
[Link](MyWorker::[Link])
36. .setInputData(data)
37. .setConstraints(constraints)
38. .build()
39. [Link](oneTimeWorkRequest)
40. [Link]([Link])
41. .observe(this@MainActivity, { workInfo ->
42. val status = [Link]
43. [Link]("\n" + status)
44. })
45. }
46.
47. private fun startPeriodicTask() {
48. [Link] = getString([Link])
49. val data = [Link]()
50. .putString(MyWorker.EXTRA_CITY,
[Link]())
51. .build()
52. val constraints = [Link]()
53. .setRequiredNetworkType([Link])
54. .build()
55. periodicWorkRequest =
[Link](MyWorker::[Link], 15,
[Link])
56. .setInputData(data)
57. .setConstraints(constraints)
58. .build()
59. [Link](periodicWorkRequest)
60. [Link](periodicWorkReques
[Link])
61. .observe(this@MainActivity, { workInfo ->
62. val status = [Link]
63. [Link]("\n" + status)
64. [Link] = false
65. if ([Link] == [Link]) {
66. [Link] = true
67. }
68. })
69. }
70.
71. private fun cancelPeriodicTask() {
72. [Link]([Link])
73. }
74. }
2. Selanjutkan jalankan kembali aplikasi Anda. Cobalah untuk jalankan task
dengan menekan tombol RUN PERIODIC TASK dan kemudian
membatalkannya dengan menekan CANCEL PERIODIC TASK.
3. Dapat dilihat setelah aplikasi menjalankan task (RUNNING), ia akan
masuk ke state ENQUEUED untuk menunggu proses selanjutnya 15 menit
kemudian. Selanjutnya jika Anda membatalkan task, maka ia masuk ke
state CANCELLED.
Menggunakan Background Task Inspector
Pada Android Studio versi Arctic Fox ke atas, terdapat fitur bernama
Background Task Inspector untuk melihat task WorkManager yang sedang
berjalan. Anda dapat memanfaatkan fitur ini jika menggunakan
WorkManager versi 2.5.0 ke atas. Dengan menggunakan fitur ini, Anda
dapat memonitor data setiap task dan hubungan setiap WorkManager.
Untuk memunculkan fitur ini, pilih View → Tool Window → App
Inspection.
Setelah memilih menu tersebut, akan muncul panel baru di bawah dengan
nama App Inspection. Buka panel tersebut dan pilih tab Background
Task Inspector. Jalankan aplikasi dan isi nama kota serta klik Run One
Time Task untuk menjalankan WorkManager. Jika data aplikasi tidak
otomatis muncul, pilih nama aplikasi pada dropdown menu. Berikut
contoh tampilan Background Task Inspector dari latihan ini:
Anda dapat melihat detail task seperti status, waktu task dimulai, berapa
kali percobaan, dan output dari sebuah WorkManager. Anda juga bisa klik
baris tersebut tersebut untuk melihat detail setiap task seperti ini:
Panel ini memberikan informasi yang lebih detail terkait description,
execution, continuation, dan result. Pada bagian description terdapat
informasi nama package kelas Worker, tags, dan UUID. Kemudian pada
bagian execution terdapat informasi constraint (jika ada), frekuensi, dan
status task. Pada bagian continuation, menunjukkan lokasi task tersebut
pada sebuah chaining. Dan terakhir pada bagian Result, menunjukkan
waktu mulai task, berapa kali perobaaan, dan output dari sebuah
WorkManager.
Anda juga bisa klik Show in graph untuk melihat hubungan antar
WorkManager dalam bentuk grafik:
Namun, karena di sini hanya ada satu WorkManager, maka grafik tidak
terlalu terlihat. Untuk memberikan gambaran, berikut adalah contoh
grafik yang lebih kompleks dengan kode berikut:
Anda dapat melihat dan memonitor task pada WorkManager dengan lebih
mudah menggunakan Background Task Inspector. Mantap!
Bedah Kode
UsesClearTextTraffic
Pada AndroidManifest kita menambahkan kode berikut:
1. android:usesCleartextTraffic="true"
Kode di atas berfungsi supaya tidak terjadi error pada perangkat dengan
Android versi Pie ke atas. Hal ini karena kita mengakses API dengan
awalan http, bukan https, dan pada perangkat Android P ke atas
penggunaan CleartextTraffic bernilai false secara default untuk
keamanan. Untuk itulah kita perlu mengaktifkannya supaya proses
pengambilan data dari API berjalan lancar.
WorkManager
Worker
Kita mulai dengan melihat kode pada kelas MyWorker berikut:
Kotlin
Java
1. class MyWorker(context: Context, workerParams: WorkerParameters) :
Worker(context, workerParams) {
2.
3. override fun doWork(): Result {
4. val dataCity = [Link](EXTRA_CITY)
5. return getCurrentWeather(dataCity)
6. }
7. }
Metode doWork adalah metode yang akan dipanggil ketika WorkManager
berjalan. Kode di dalamnya akan dijalankan di background thread secara
otomatis. Metode ini juga mengembalikan nilai berupa Result yang
berfungsi untuk mengetahui status WorkManager yang berjalan. Ada
beberapa status yang bisa dikembalikan yaitu:
[Link](), result yang menandakan berhasil.
[Link](), result yang menandakan gagal.
[Link](), result yang menandakan untuk mengulang task lagi.
Di kode Anda, Anda membuat method getCurrentWeather yang
mengembalikan nilai Result. Perhatikan kode yang dicetak tebal berikut:
Kotlin
Java
1. private var resultStatus: Result? = null
2.
3. private fun getCurrentWeather(city: String?): Result {
4. ...
5. [Link](url, object : AsyncHttpResponseHandler() {
6. override fun onSuccess(statusCode: Int, headers: Array<Header?>?,
responseBody: ByteArray) {
7. val result = String(responseBody)
8. Log.d(TAG, result)
9. try {
10. ...
11. resultStatus = [Link]()
12. } catch (e: Exception) {
13. ...
14. resultStatus = [Link]()
15. }
16. }
17.
18. override fun onFailure(statusCode: Int, headers: Array<Header?>?,
responseBody: ByteArray?, error: Throwable) {
19. ...
20. resultStatus = [Link]()
21. }
22. })
23. return resultStatus as Result
24. }
Dengan begitu, kita bisa menentukan kembalian dari proses ini apakah
berhasil atau gagal dengan menggunakan kode tersebut.
WorkRequest
Selanjutnya, mari kita bedah kode untuk membuat WorkManager di kelas
MainActivity, Ada dua macam request yang bisa dibuat, yaitu:
OneTimeWorkRequest untuk menjalankan task sekali saja, untuk
membuatnya Anda menggunakan kode berikut:
o Kotlin
o Java
1. val oneTimeWorkRequest =
[Link](MyWorker::[Link]).build()
2. [Link](oneTimeWorkRequest)
PeriodicWorkRequest untuk menjalankan task secara periodic, untuk
membuatnya Anda menggunakan kode berikut:
Kotlin
Java
1. val periodicWorkRequest =
[Link](MyWorker::[Link], 15,
[Link]).build()
2. [Link](periodicWorkRequest)
Kode di atas digunakan untuk menjalankan PeriodicWorkRequest dengan
interval 15 menit. Anda bisa mengaturnya dengan mengganti parameter
kedua dan ketiga.
Catatan:
Batas minimal interval yang diperbolehkan yaitu 15 menit, sama dengan
JobScheduler.
Kirim Data ke Worker
Selanjutnya, mari kita bedah kode untuk mengirim data di WorkManager,
perhatikan kode di dalam MainActivity.
Kotlin
Java
1. val data = [Link]()
2. .putString(MyWorker.EXTRA_CITY, [Link]())
3. .build()
4.
5. val oneTimeWorkRequest = [Link](MyWorker::[Link])
6. .setInputData(data)
7. .build()
8. [Link](oneTimeWorkRequest)
Fungsi di atas digunakan untuk membuat one-time request. Saat
membuat request, Anda bisa menambahkan data untuk dikirimkan
dengan membuat object Data yang berisi data key-value, key yang
dipakai di sini yaitu MyWorker.EXTRA_CITY. Setelah itu dikirimkan melalui
setInputData. Kemudian untuk mendapatkan datanya di kelas Worker,
Anda perlu menggunakan kode berikut:
Kotlin
Java
1. val dataCity = [Link](EXTRA_CITY)
Yang perlu diperhatikan yaitu tipe data dan key yang dimasukkan harus
sama persis. Di sini tipe data yang digunakan yaitu String dan key yang
dipakai yaitu EXTRA_CITY.
Constraint
Constraint digunakan untuk memberikan syarat kapan task ini dieksekusi,
perhatikan kode di dalam MainActivity.
Kotlin
Java
1. val constraints = [Link]()
2. .setRequiredNetworkType([Link])
3. .build()
4. val oneTimeWorkRequest = [Link](MyWorker::[Link])
5. .setInputData(data)
6. .setConstraints(constraints)
7. .build()
Ada banyak kondisi yang bisa kita set di sini. Misalnya adalah:
setRequiredNetworkType, ketika bernilai CONNECTED berarti dia harus
terhubung ke koneksi internet, apa pun jenisnya. Bila kita ingin memasang
ketentuan bahwa job hanya akan berjalan ketika perangkat terhubung ke
network Wi-fi, maka kita perlu memberikan nilai UNMETERED.
setRequiresDeviceIdle, menentukan apakah task akan dijalankan ketika
perangkat dalam keadaan sedang digunakan atau tidak. Secara default,
parameter ini bernilai false. Bila kita ingin task dijalankan ketika
perangkat dalam kondisi tidak digunakan, maka kita beri nilai true.
setRequiresCharging, menentukan apakah task akan dijalankan ketika
baterai sedang diisi atau tidak. Nilai true akan mengindikasikan bahwa
task hanya berjalan ketika baterai sedang diisi. Kondisi ini dapat
digunakan bila task yang dijalankan akan memakan waktu yang lama.
setRequiresStorageNotLow, menentukan apakah task yang dijalankan
membutuhkan ruang storage yang tidak sedikit. Secara default, nilainya
bersifat false.
Dan ketentuan lainnya yang bisa kita gunakan.
WorkInfo
WorkInfo digunakan untuk mengetahui status task yang dieksekusi,
perhatikan kode di dalam MainActivity.
Kotlin
Java
1. [Link]([Link])
2. .observe(this@MainActivity, { workInfo ->
3. val status = [Link]
4. [Link]("\n" + status)
5. [Link] = false
6. if ([Link] == [Link]) {
7. [Link] = true
8. }
9. })
Anda dapat membaca status secara live dengan menggunakan
getWorkInfoByIdLiveData. Anda juga bisa memberikan aksi pada state
tertentu dengan mengambil data state dan membandingkannya dengan
konstanta yang bisa didapat di [Link]. Misalnya, pada kode di
atas kita mengatur tombol Cancel task aktif jika task dalam
state ENQUEUED.
Cancel Work
Anda dapat membatalkan task yang berjalan dengan cara berikut:
Kotlin
Java
1. [Link]([Link])
Kode di atas digunakan untuk membatalkan task berdasarkan id request.
Selain menggunakan id, Anda juga bisa menambahkan tag pada request.
Kelebihan dari penggunaan tag yaitu Anda bisa membatalkan task lebih
dari satu task sekaligus seperti ini:
Kotlin
Java
1. val oneTimeWorkRequest = [Link](MyWorker::[Link])
2. .setInputData(data)
3. .setConstraints(constraints)
4. .addTag("MY_TAG")
5. .build()
6. ...
7. periodicWorkRequest = [Link]([Link], 15,
[Link])
8. .setInputData(data)
9. .setConstraints(constraints)
10. .addTag("MY_TAG")
11. .build()
12. ...
13. [Link]("MY_TAG")
Work Chaining
Selain menjalankan single task seperti pada latihan, Anda juga bisa
membuat task-chaining, baik secara paralel maupun sekuensial. Berikut
adalah contohnya:
Kotlin
Java
1. workManager
2. .beginWith(workA1, workA2, workA3)
3. .then(workB)
4. .then(workC1, workC2)
5. .enqueue()
Anda dapat mendalami lebih lanjut topik ini dengan membaca tautan
berikut:
WorkManager
Codelab WorkManager
Introducing WorkManager
Parsing JSON API dengan LoopJ
LoopJ
LoopJ adalah salah library untuk menangani koneksi ke API. Selain LoopJ
ada Retrofit, Volley, FastAndroidNetworking dsb. Di sini kita menggunakan
LoopJ karena dinilai mudah untuk pemula dan cocok untuk memahami
parsing data JSON.
Kotlin
Java
1. private fun getCurrentWeather(job: JobParameters) {
2. ...
3. [Link]()
4. val client = SyncHttpClient()
5. val url = "[Link]
q=$CITY&appid=$YOUR_KEY_HERE"
6. [Link](url, object : AsyncHttpResponseHandler() {
7. override fun onSuccess(statusCode: Int, headers: Array<Header>,
responseBody: ByteArray) {
8. ...
9. }
10.
11. override fun onFailure(statusCode: Int, headers: Array<Header>,
responseBody: ByteArray, error: Throwable) {
12. ...
13. }
14. })
Catatan:
Di sini kita menggunakan SyncHttpClientuntuk koneksi data yang sifatnya
synchronous. Selain itu, ada AsyncHttpClient untuk koneksi secara
asynchronous. Khusus di WorkManager Anda perlu menjalankan proses
secara synchronous supaya bisa mendapatkan result success. Selain itu
WorkManager sudah berjalan di background thread, jadi aman saja
menjalankan secara langsung. Namun jika Anda ingin menggunakan LoopJ
di Activity, maka gunakanlah AsyncHttpClient supaya tidak terjadi
error NetworkOnMainThread.
Untuk terhubung dengan [Link], kita cukup memanggil
get() dengan url yang dibutuhkan. Metode-metode yang ada di
AsyncHttpClient merepresentasikan metode HTTP seperti POST, GET, PUT,
HEAD dan DELETE.
Target dari url webservice yang akan diakses adalah
[Link]
q="+CITY+"&appid="+YOUR_KEY_HERE.
Pada bagian CITY, kita beri nilai sesuai dari inputan EditText. Sementara
pada bagian YOUR_KEY_HERE, kita masukkan nilai API_KEY yang telah
disediakan oleh [Link].
Kita dapat memanfaatkan callback onSuccess() untuk menandakan
bahwa proses berhasil. Sementara itu, untuk mengindikasikan kegagalan,
kita dapat menggunakan callback onFailure().
JsonArray dan JsonObject
JSON adalah kepanjangan dari JavaScript Object Notation. Ia
merupakan format pertukaran data yang ringan, mudah dibaca dan ditulis
oleh manusia, serta mudah diterjemahkan dan dibuat (generate) oleh
komputer. Ada dua macam JSON, yaitu JSON Array yang ditandai dengan
tanda kurung siku / "[]" dan JSON Object yang ditandai dengan kurung
kurawal / "{}".
Misalnya Anda ingin mengambil data description maka Anda perlu
melewati JsonArray weather dulu, kemudian ambil urutan ke-0 dan Anda
baru bisa mengambil data description. Contoh untuk kodenya seperti ini:
.getJSONArray("weather").getJSONObject(0).getString("description") dan
jika Anda ingin mengambil data suhu maka kodenya seperti ini:
.getJSONObject("main").getDouble("temp"). Sehingga, untuk
mendapatkan data secara keseluruhan akan seperti ini:
Kotlin
Java
1. val result = String(responseBody)
2. Log.d(TAG, result)
3. try {
4. val responseObject = JSONObject(result)
5.
6. val currentWeather =
[Link]("weather").getJSONObject(0).getString("
main")
7. val description =
[Link]("weather").getJSONObject(0).getString("d
escription")
8. val tempInKelvin =
[Link]("main").getDouble("temp")
9.
10. val tempInCelsius = tempInKelvin - 273
11. val temperature = DecimalFormat("##.##").format(tempInCelsius)
12.
13. val title = "Current Weather"
14. val message = "$currentWeather, $description with $temperature celsius"
15. val notifId = 100
16.
17. showNotification(applicationContext, title, message, notifId)
18. ...
19. } catch (e: Exception) {
20. ...
21. [Link]()
22. }
Kode di atas memproses respons yang diterima dari webservice. Karena
nilai awal respons adalah data byte, maka kita harus mengubahnya
menjadi string. Hal ini akan memudahkan kita untuk mengubahnya
menjadi bentuk JSON.
Catatan:
Perhatikan saat getJsonArray atau getString, pastikan teks yang diambil
sesuai dengan yang berasal dari API dan harus sama persis.
Untuk lebih membuat kamu paham tentang JSON kamu bisa mempelajari
lebih lanjut pada url ini:
JSON
JSON Parsing
Selamat Anda sudah belajar tentang WorkManager dan Parsing JSON yang
merupakan skill dasar sebagai Android developer. Pahami baik-baik dan
baca ulang jika kurang mengerti. Hal ini karena akan sangat penting
untuk Anda. Semangat!
Bonus Codelab : Parsing JSON dengan Moshi
Sejauh ini, sejak Anda pertama kali belajar tentang koneksi ke web API,
Anda selalu melakukan parsing JSON secara manual, yaitu dengan
menggunakan JsonObject dan JsonArray. Nah, perlu Anda ketahui bahwa
sebenarnya Anda juga bisa mem-parsing JSON secara otomatis tanpa
perlu harus menulis secara manual key yang ingin didapat. Anda dapat
melakukannya dengan bantuan library pihak ketiga, seperti Moshi, GSON,
maupun Jackson. Sebenarnya ketiganya memiliki konsep yang sama,
namun pada codelab bonus ini kita akan menggunakan Moshi sebagai
contohnya. Yuk kita coba!
1. Pertama, tambahkan terlebih dahulu library Moshi pada [Link]
(module: app) seperti berikut:
o Kotlin
o Java
1. plugins {
2. id '[Link]'
3. id 'kotlin-android'
4. id 'kotlin-kapt'
5. }
6. ...
7. dependencies {
8. ...
9. implementation "[Link]:moshi-kotlin:1.11.0"
10. kapt '[Link]:moshi-kotlin-codegen:1.8.0'
11. }
Selanjutnya buatlah beberapa class yang merepresentasikan susunan pada
JSON. Sebagai contoh dengan bentuk JSON yang didapat dari API
OpenWeatherMapseperti ini:
1. {
2. "coord":{
3. "lon":107.6186,
4. "lat":-6.9039
5. },
6. "weather":[
7. {
8. "id":804,
9. "main":"Clouds",
10. "description":"overcast clouds",
11. "icon":"04d"
12. }
13. ],
14. "base":"stations",
15. "main":{
16. "temp":299.3,
17. "feels_like":301.74,
18. "temp_min":299.3,
19. "temp_max":299.3,
20. "pressure":1012,
21. "humidity":62,
22. "sea_level":1012,
23. "grnd_level":932
24. },
25. "visibility":10000,
26. "wind":{
27. "speed":0.68,
28. "deg":38,
29. "gust":0.87
30. },
31. "clouds":{
32. "all":99
33. },
34. "dt":1614909285,
35. "sys":{
36. "country":"ID",
37. "sunrise":1614898494,
38. "sunset":1614942453
39. },
40. "timezone":25200,
41. "id":1650357,
42. "name":"Bandung",
43. "cod":200
44. }
Karena Anda hanya akan mengambil data yang dibutuhkan saja (yang dicetak
tebal), buatlah bentuk class-nya seperti berikut:
Kotlin
Java
1. data class Response(
2. val id: Int,
3. val name: String,
4. @Json(name = "weather")
5. val weatherList: List<Weather>,
6. val main: Main,
7. )
8.
9. data class Weather(
10. val main: String,
11. val description: String
12. )
13.
14. data class Main(
15. @Json(name = "temp")
16. val temperature: Double
17. )
Kuncinya adalah apabila ia berupa JSON Array (ditandai dengan kurung siku/[ ]),
variabelnya berupa List. Jika ia berupa JSON Object (ditandai dengan kurung
kurawal/{ }), bentuknya adalah Object dari Class.
Pastikan juga bahwa nama variabel yang digunakan sama dengan key yang ada
pada API. Apabila Anda ingin menggunakan nama variabel yang berbeda, Anda
bisa menggunakan anotasi @Json seperti contoh di atas.
Setelah usai, Anda cukup menginisialisasi Moshi dan dapat mengganti kode
untuk parsing manual yang lama dengan Object yang bisa langsung didapatkan
dengan Moshi seperti berikut:
Kotlin
Java
1. private fun getCurrentWeather(city: String?): Result {
2. ...
3. [Link](url, object : AsyncHttpResponseHandler() {
4. override fun onSuccess(statusCode: Int, headers: Array<Header?
>?, responseBody: ByteArray) {
5. val result = String(responseBody)
6. Log.d(TAG, result)
7. try {
8. // Hapus kode ini
9. // val responseObject = JSONObject(result)
10. // val currentWeather: String =
[Link]("weather").getJSONObject(0).getStr
ing("main")
11. // val description: String =
[Link]("weather").getJSONObject(0).getStr
ing("description")
12. // val tempInKelvin =
[Link]("main").getDouble("temp")
13.
14. val moshi = [Link]()
15. .addLast(KotlinJsonAdapterFactory())
16. .build()
17.
18. val jsonAdapter =
[Link](Response::[Link])
19. val response = [Link](result)
20.
21. response?.let {
22. val currentWeather = [Link][0].main
23. val description = [Link][0].description
24. val tempInKelvin = [Link]
25.
26. val tempInCelsius = tempInKelvin - 273
27. val temperature : String =
DecimalFormat("##.##").format(tempInCelsius)
28. val title = "Current Weather in $city"
29. val message = "$currentWeather, $description with
$temperature celsius"
30. showNotification(title, message)
31. }
32. Log.d(TAG, "onSuccess: Selesai.....")
33. resultStatus = [Link]()
34. } catch (e: Exception) {
35. ...
36. }
37. }
38. ...
39. })
40. return resultStatus as Result
41. }
Yang perlu diperhatikan adalah pada kode [Link] pastikan Anda
memberikan class yang sesuai dengan hasil JSON yang diberikan. Dalam
kasus ini adalah kelas Response yang merupakan induk dari semua
kelas.
Catatan:
Apabila response JSON dimulai dengan kurung siku / [ ] yang berarti ia
berupa JSON Array. Maka pada [Link] argumen yang dimasukkan
adalah Array<Response>.
3. Jalankanlah kembali aplikasi Anda dan aplikasi tetap berhasil digunakan
secara sempurna. Yang membedakan adalah sekarang Anda tidak perlu
mem-parsing JSON secara manual satu per satu lagi. Namun Anda bisa
langsung mendapatkannya dalam bentuk Object. Bayangkan jika data
yang ingin Anda ambil puluhan tentu cara ini akan sangat membantu kan?
Kelebihan lainnya adalah Anda bisa menghindari eror yang bisa saja
terjadi karena salah menuliskan key atau pendefinisian object saat mem-
parsing secara manual. Keren kan! Selamat mencoba!
Selamat! Anda telah mempelajari sesuatu yang keren. Tidak hanya belajar
bagaimana menerapkan WorkManager, tetapi Anda juga telah belajar
bagaimana mengambil data dari sebuah layanan di Internet. Anda juga
telah belajar mengintegrasikan library ke dalam aplikasi melalui gradle.
Source code dapat diunduh di:
Source Code Latihan WorkManager
Teori Retrofit
Retrofit adalah satu library buatan Square yang populer digunakan untuk
melakukan Networking ke Web API. Tahukah mengapa? Dengan Retrofit,
mengatur endpoint API dan parsing JSON jadi jauh lebih mudah.
Untuk menggunakannya, Anda perlu menambahkan beberapa library
terlebih dahulu seperti berikut:
1. implementation "[Link].retrofit2:retrofit:$retrofitVersion"
2. implementation "[Link].retrofit2:converter-gson:
$retrofitVersion"
Catatan:
Untuk melihat versi terbaru Anda dapat melihatnya di sini.
Seperti yang Anda lihat, selain library Retrofit, Anda juga menambahkan
library untuk converter, dalam hal ini yaitu Gson. Alih-alih melakukan
parsing secara manual, melalui Gson Anda dapat melakukan parsing
secara otomatis hanya dengan menambahkan annotation pada class
model. Sebenarnya terdapat converter lain yang bisa Anda gunakan
seperti Moshi, Jackson, dsb yang dapat Anda lihat pada tautan ini. Namun,
pada latihan ini Anda hanya akan fokus menggunakan GSON.
Studi Kasus
Supaya lebih jelas mari kita buat contohnya dengan mengambil data list
users dari URL [Link] Respon dari request
pada URL [Link] akan menghaspilkan output seperti berikut:
1. {
2. "page":1,
3. "per_page":6,
4. "total":12,
5. "total_pages":2,
6. "data":[
7. {
8. "id":1,
9. "email":"[Link]@[Link]",
10. "first_name":"George",
11. "last_name":"Bluth",
12.
"avatar":"[Link]
n/[Link]"
13. },
14. {
15. "id":2,
16. "email":"[Link]@[Link]",
17. "first_name":"Janet",
18. "last_name":"Weaver",
19.
"avatar":"[Link]
n/[Link]"
20. },
21. {
22. "id":3,
23. "email":"[Link]@[Link]",
24. "first_name":"Emma",
25. "last_name":"Wong",
26.
"avatar":"[Link]
aev/[Link]"
27. },
28. {
29. "id":4,
30. "email":"[Link]@[Link]",
31. "first_name":"Eve",
32. "last_name":"Holt",
33.
"avatar":"[Link]
ires/[Link]"
34. },
35. {
36. "id":5,
37. "email":"[Link]@[Link]",
38. "first_name":"Charles",
39. "last_name":"Morris",
40.
"avatar":"[Link]
oon/[Link]"
41. },
42. {
43. "id":6,
44. "email":"[Link]@[Link]",
45. "first_name":"Tracey",
46. "last_name":"Ramos",
47.
"avatar":"[Link]
o/[Link]"
48. }
49. ]
50. }
Setelah tahu berbagai kata kunci dalam output tersebut, Anda perlu
membuat kelas model untuk data JSON di atas dan menambahkan
annotation seperti ini:
Kotlin
Java
1. data class ResponseUser(
2. @field:SerializedName("page")
3. val page: Int? = null,
4.
5. @field:SerializedName("per_page")
6. val perPage: Int? = null,
7.
8. @field:SerializedName("total")
9. val total: Int? = null,
10.
11. @field:SerializedName("total_pages")
12. val totalPages: Int? = null,
13.
14. @field:SerializedName("data")
15. val data: List<DataItem>? = null
16. )
Catatan:
Pastikan bahwa property yang di dalam SerializedName memiliki nilai
yang sama persis dengan key yang pada respon JSON.
Selanjutnya, buat juga model yang berisi data user dan tambahkan
annotation seperti ini:
Kotlin
Java
1. data class DataItem(
2. @field:SerializedName("id")
3. val id: Int? = null,
4.
5. @field:SerializedName("email")
6. val email: String? = null,
7.
8. @field:SerializedName("first_name")
9. val firstName: String? = null,
10.
11. @field:SerializedName("last_name")
12. val lastName: String? = null,
13.
14. @field:SerializedName("avatar")
15. val avatar: String? = null
16. )
SerializedName berfungsi untuk menandai suatu variabel untuk
dimasukkan data dengan key yang sesuai dari JSON. Misalnya variabel
firstName akan dimasukkan data dengan key "first_name".
Tips:
Anda bisa memanfaatkan plugin RoboPOJOGenerator pada Android Studio
untuk membuat kelas model secara otomatis.
Setelah usai, Anda perlu membuat interface berdasarkan URL API
[Link]
Kotlin
Java
1. interface ApiService {
2. @GET("api/users")
3. fun getListUsers(@Query("page") page: String): Call<ResponseUser>
4. }
Seperti yang Anda lihat, di sini URL yang digunakan hanya bagian
belakangnya saja. Hal ini dikarenakan URL dibagi menjadi 3 bagian yaitu:
Base URL : [Link] (URL dasar yang tetap yang akan diletakkan
di Api )
Endpoint : api/users (bagian akhir URL yang biasanya digunakan untuk
menentukan aksi)
Parameter : ?page=1 (ditandai dengan awalan “?” dan pemisah antar
parameter “&”)
Kemudian untuk membuat instance Retrofit diperlukan [Link]
seperti berikut:
Kotlin
Java
1. val retrofit = [Link]()
2. .baseUrl("[Link]
3. .addConverterFactory([Link]())
4. .build()
5. val apiService = [Link](ApiService::[Link])
Perhatikan bahwa base URL diletakkan di sini dan juga terdapat converter
factory yang berfungsi untuk menambahkan converter GSON.
Kemudian untuk mengeksekusi request secara asynchronous, Anda dapat
menggunakan fungsi enqueue seperti berikut:
Kotlin
Java
1. val client = [Link]("1")
2.
3. [Link](object : Callback<ResponseUser> {
4. override fun onResponse(call: Call<ResponseUser>, response:
Response<ResponseUser>) {
5. if ([Link]) {
6. val listUser = [Link]()?.data as List<DataItem>
7. ...
8. }
9. }
10.
11. override fun onFailure(call: Call<ResponseUser>, t: Throwable) {
12. // Terpanggil ketika tidak dapat menghubungi server
13. }
14. })
Wow! Anda langsung bisa mendapatkan List Object tanpa perlu
melakukan parsing manual. Mantap bukan?
Mari kita bandingkan jika tidak menggunakan library GSON Converter.
Untuk mendapatkan listUser, Anda perlu menuliskan kode seperti berikut:
Kotlin
Java
1. val listUser = arrayListOf<DataItem>()
2. val jsonObject = JSONObject([Link]().toString())
3. val jsonArray = [Link]("data")
4. for (i in 0 until [Link]()) {
5. val id = [Link](i).getInt("id")
6. val email = [Link](i).getString("email")
7. val firstName = [Link](i).getString("first_name")
8. val lastName = [Link](i).getString("last_name")
9. val avatar = [Link](i).getString("avatar")
10. val user = DataItem(id, email, firstName, lastName, avatar)
11. [Link](user)
12. }
Banyak sekali baris yang diperlukan bukan? Dengan Retrofit dan GSON
Converter, kode tersebut dapat diringkas hanya menjadi satu baris.
Mantap! Contoh ini juga masih menggunakan API yang simpel lho!
Bayangkan kalau API yang digunakan jauh lebih kompleks lagi. Tentu
menggunakan GSON Converter ini akan sangat berguna sekali.
Macam Method dan Annotation pada Retrofit
Selain request @GET seperti contoh di atas, Anda juga dapat melakukan
variasi request lainnya seperti berikut:
Kotlin
Java
1. //add information using Header
2. @Headers("Authorization: token <Personal Access Token>" )
3. @GET("api/users")
4. fun getListUsers(@Query("page") page: String): Call<ResponseUser>
5.
6. //get list user by id using path
7. @GET("api/users/{id}")
8. fun getUser(@Path("id") id: String): Call<ResponseUser>
9.
10. //post user using field x-www-form-urlencoded
11. @FormUrlEncoded
12. @POST("api/users")
13. fun createUser(
14. @Field("name") name: String,
15. @Field("job") job: String
16. ): Call<ResponseUser>
17.
18. //upload file using multipart
19. @Multipart
20. @PUT("api/uploadfile")
21. fun updateUser(
22. @Part("file") file: [Link],
23. @PartMap data: Map<String, RequestBody>
24. ): Call<ResponseUser>
Berikut adalah fungsi dari masing-masing kode annotation di atas:
@Headers : menambahkan informasi tambahan pada header request
seperti Authorization, jenis data dsb.
@Query : memasukkan parameter pada method @Get.
@Path : memasukkan variabel yang dapat diubah-ubah pada endpoint
Sebagai contoh id akan mengganti {id} yang ada di endpoint.
@FormUrlEncoded : menandai fungsi pada @Post sebagai form-url-
encoded.
@Field : memasukkan parameter pada method @Post.
@Multipart : menandai sebuah fungsi bahwa ia merupakan multi-part.
@Part : mengirimkan file dengan cara multi-part.
@PartMap : mengirimkan data lain selain file pada multi-part.
Mantap bukan? Anda tinggal memanggil fungsi yang di dalam ApiService
ini ketika perlu suatu data tanpa membuat dari awal.
Debug Response dari Retrofit.
Untuk mengetahui hasil respon saat melakukan request dengan
menggunakan Retrofit, Anda bisa menggunakan 2 cara berikut:
Logging Interceptor by OkHttp
Logging Interceptor merupakan library tambahan yang digunakan untuk
menampilkan hasil response pada Logcat tanpa menuliskannya secara
manual. Untuk menggunakannya, Anda perlu menambahkan library pada
[Link] seperti berikut:
1. implementation '[Link].okhttp3:logging-interceptor:
$loggingInterceptorVersion'
Untuk menggunakannya Anda perlu menambahkan client baru seperti
berikut:
Kotlin
Java
1. val loggingInterceptor =
2.
HttpLoggingInterceptor().setLevel([Link])
3. val client = [Link]()
4. .addInterceptor(loggingInterceptor)
5. .build()
6. val retrofit = [Link]()
7. .baseUrl("[Link]
8. .addConverterFactory([Link]())
9. .client(client)
10. .build()
Ketika request dijalankan, akan muncul hasil dari interceptor tersebut
yang terlihat seperti ini:
Dapat dilihat bahwa semua response body dapat tampil di logcat. Untuk
mencarinya, cukup dengan kata kunci “okhttp”.
Chucker
Berbeda dengan library sebelumnya, dengan chucker Anda dapat
menampilkan hasil response langsung pada device tanpa harus melihat
Logcat. Untuk menambahkan library tersebut, tambahkan library tersebut
pada [Link] seperti berikut:
1. debugImplementation "[Link]:library:
$chuckerVersion"
Untuk penggunaanya, cukup simpel yaitu dengan menambahkan
chuckerInterceptor seperti berikut:
Kotlin
Java
1. val client = [Link]()
2. .addInterceptor(ChuckerInterceptor(context))
3. .build()
4. val retrofit = [Link]()
5. .baseUrl("[Link]
6. .addConverterFactory([Link]())
7. .client(client)
8. .build()
Hasil interceptor dari Chucker di atas akan terlihat seperti
ini:
Yeay, Anda telah mempelajari berbagai macam tentang Retrofit, mulai
dari cara penggunaan sampai proses debugging. Mantap! Untuk
mempelajari lebih lanjut, Anda dapat melihat beberapa tautan berikut:
Documentation : Retrofit
Documentation : Chucker
Latihan Networking dengan Retrofit
Tujuan
Pada materi kali ini Anda akan belajar menggunakan Retrofit untuk
menampilkan dan mengirim data ke server. Aplikasi buatan Anda akan
terlihat seperti ini:
Alur Latihan
Berikut alur yang akan kita lakukan dalam latihan kali ini:
1. Membuat project baru dengan nama RestaurantReview.
2. Mengatur tampilan pada berkas layout XML.
3. Menambahkan library ke dalam project.
4. Membuat kelas RestaurantResponse untuk menampung sementara
data pengguna.
5. Membuat kelas ApiService dan ApiConfig untuk mengatur konfigurasi
Retrofit.
6. Mengimplementasikan Retrofit ke dalam Activity.
7. Menjalankan aplikasi.
Codelab Mengambil Data Review Restoran
Pada codelab latihan awal ini Anda akan menampilkan data restoran dan
review dengan menggunakan metode GET pada Retrofit.
1. Buat Project baru di Android Studio dengan kriteria sebagai berikut:
Nama Project RestaurantReview
Target & Minimum Target Phone and Tablet, API
SDK level 21
Tipe Activity Empty Activity
Activity Name MainActivity
Use AndroidX Artifacts True
Language Kotlin/Java
2. Mari buat terlebih dahulu desain tampilan aplikasi seperti berikut
pada activity_main.xml:
1. <?xml version="1.0" encoding="utf-8"?>
2. <[Link]
xmlns:android="[Link]
3. xmlns:app="[Link]
4. xmlns:tools="[Link]
5. android:layout_width="match_parent"
6. android:layout_height="match_parent"
7. tools:context=".MainActivity">
8.
9. <ImageView
10. android:id="@+id/ivPicture"
11. android:layout_width="0dp"
12. android:layout_height="200dp"
13. android:contentDescription="@string/title"
14. android:scaleType="centerCrop"
15. android:visibility="visible"
16. app:layout_constraintEnd_toEndOf="parent"
17. app:layout_constraintStart_toStartOf="parent"
18. app:layout_constraintTop_toTopOf="parent"
19. tools:srcCompat="@tools:sample/backgrounds/scenic" />
20.
21. <TextView
22. android:id="@+id/tvTitle"
23. android:layout_width="match_parent"
24. android:layout_height="wrap_content"
25. android:layout_marginStart="16dp"
26. android:layout_marginTop="16dp"
27. android:layout_marginEnd="16dp"
28. android:text="@string/title"
29. android:textSize="18sp"
30. android:textStyle="bold"
31. android:visibility="visible"
32. app:layout_constraintEnd_toEndOf="parent"
33. app:layout_constraintStart_toStartOf="parent"
34. app:layout_constraintTop_toBottomOf="@+id/ivPicture" />
35.
36. <TextView
37. android:id="@+id/tvDescription"
38. android:layout_width="match_parent"
39. android:layout_height="wrap_content"
40. android:layout_marginStart="16dp"
41. android:layout_marginTop="8dp"
42. android:layout_marginEnd="16dp"
43. android:ellipsize="end"
44. android:maxLines="2"
45. android:text="@string/description"
46. android:visibility="visible"
47. app:layout_constraintEnd_toEndOf="parent"
48. app:layout_constraintStart_toStartOf="@+id/tvTitle"
49. app:layout_constraintTop_toBottomOf="@+id/tvTitle" />
50.
51. <TextView
52. android:id="@+id/tvReview"
53. android:layout_width="match_parent"
54. android:layout_height="wrap_content"
55. android:layout_marginStart="16dp"
56. android:layout_marginTop="20dp"
57. android:layout_marginEnd="16dp"
58. android:text="@string/write_your_review_here"
59. android:textSize="18sp"
60. android:textStyle="bold"
61. app:layout_constraintEnd_toEndOf="parent"
62. app:layout_constraintStart_toStartOf="parent"
63. app:layout_constraintTop_toBottomOf="@+id/tvDescription" />
64.
65. <ProgressBar
66. android:id="@+id/progressBar"
67. style="?android:attr/progressBarStyle"
68. android:layout_width="wrap_content"
69. android:layout_height="wrap_content"
70. android:visibility="gone"
71. app:layout_constraintBottom_toBottomOf="parent"
72. app:layout_constraintEnd_toEndOf="parent"
73. app:layout_constraintStart_toStartOf="parent"
74. app:layout_constraintTop_toTopOf="parent"
75. tools:visibility="visible" />
76.
77. <[Link]
78. android:id="@+id/inputLayout"
79. style="@style/[Link]"
80. android:layout_width="0dp"
81. android:layout_height="wrap_content"
82. android:layout_margin="16dp"
83. app:layout_constraintEnd_toEndOf="parent"
84. app:layout_constraintStart_toStartOf="parent"
85. app:layout_constraintTop_toBottomOf="@+id/tvReview">
86.
87. <[Link]
88. android:id="@+id/edReview"
89. android:layout_width="match_parent"
90. android:layout_height="wrap_content"
91. android:ems="10"
92. android:hint="@string/review" />
93. </[Link]>
94.
95. <Button
96. android:id="@+id/btnSend"
97. android:layout_width="wrap_content"
98. android:layout_height="wrap_content"
99. android:layout_marginTop="16dp"
100. android:layout_marginEnd="16dp"
101. android:layout_marginRight="16dp"
102. android:text="@string/send"
103. app:layout_constraintStart_toStartOf="@id/inputLayout"
104. app:layout_constraintTop_toBottomOf="@id/inputLayout" />
105.
106. <[Link]
107. android:id="@+id/rvReview"
108. android:layout_width="0dp"
109. android:layout_height="0dp"
110. android:layout_marginStart="16dp"
111. android:layout_marginEnd="16dp"
112. android:overScrollMode="never"
113. android:scrollbarSize="0dp"
114. app:layout_constraintBottom_toBottomOf="parent"
115. app:layout_constraintEnd_toEndOf="parent"
116. app:layout_constraintStart_toStartOf="parent"
117. app:layout_constraintTop_toBottomOf="@+id/btnSend" />
118. </[Link]>
Tambahkan juga untuk resource teksnya pada [Link] seperti
berikut:
1. <resources>
2. <string name="app_name">Restaurant Review</string>
3. <string name="title">Judul</string>
4. <string name="description">Description</string>
5. <string name="review">Review</string>
6. <string name="write_your_review_here">Write your review here..</string>
7. <string name="send">Kirim</string>
8. </resources>
Jalankan dulu aplikasi Anda dan pastikan tampilan sesuai seperti desain di
atas.
Di awal telah dijelaskan bahwa Anda akan membuat sebuah aplikasi yang
menampilkan data restoran beserta reviewnya. Di sini Anda akan
menggunakan data API dari [Link] Endpoint
yang dipanggil yaitu:
[Link] dengan
uewq1zg2zlskfw1e867 adalah salah satu id restoran yang dapat dilihat di
[Link]
Selanjutnya Anda akan menggunakan library Retrofit untuk mengakses
API tersebut. Untuk menambahkannya, buka berkas
[Link](Module: app). Kemudian tambahkan baris berikut:
Kotlin
Java
1. dependencies {
2. ...
3. implementation '[Link]:glide:4.11.0'
4. implementation '[Link].retrofit2:retrofit:2.9.0'
5. implementation "[Link].retrofit2:converter-gson:2.9.0"
6. implementation "[Link].okhttp3:logging-interceptor:4.9.0"
7. }
Sebelum melakukan sync, aktifkan terlebih dahulu ViewBinding dengan
menambahkan konfigurasi berikut:
1. ...
2. android {
3. ...
4. buildFeatures {
5. viewBinding true
6. }
7. }
8. ...
Setelah selesai, langsung saja klik Sync Now di pojok kanan atas Android Studio
untuk mengunduh library.
Selanjutnya Anda akan membuat kelas model untuk menampung respon dari
API. Supaya lebih mudah, kali ini Anda akan menggunakan plugin bernama
RoboPojoGenerator yang dapat Anda unduh melalui menu File → Setting →
Plugins. Pada tab Marketplace cari RoboPojoGenerator dan klik Install.
Setelah selesai di-install, RoboPojoGenerator siap digunakan.
Kemudian klik kanan pada package utama dan pilih New → Generate POJO
from JSON. Sebenarnya Anda bisa saja copy-paste semua kode JSON pada
[Link]
Namun karena Anda hanya akan menampilkan sebagian informasi yang
dibutuhkan saja, Anda dapat menghapus beberapa baris yang berhubungan
dengan city, address, dan menusehingga data yang dimasukkan cukup seperti
ini:
1. {
2. "error":false,
3. "message":"success",
4. "restaurant":{
5. "id":"uewq1zg2zlskfw1e867",
6. "name":"Kafein",
7. "description":"Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel
augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus.
Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero,
sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus
pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec
vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci
eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec
sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit
cursus nunc,",
8. "pictureId":"15",
9. "rating":4.6,
10. "customerReviews":[
11. {
12. "name":"Gilang",
13. "review":"Saya sangat suka menu malamnya!",
14. "date":"13 November 2019"
15. },
16. {
17. "name":"Ahmad",
18. "review":"Tempatnya bagus namun menurut saya masih sedikit mahal." ,
19. "date":"14 Agustus 2018"
20. }
21. ]
22. }
23. }
Selanjutnya jangan lupa untuk atur language sesuai dengan bahasa Anda, pilih
GSON pada Framework, dan isi pada Root object name dengan
RestaurantResponse. Lalu klik Generate maka akan tercipta class dengan isi
seperti berikut:
Kotlin
Java
Pastikan untuk hapus centang pada nullable fields juga.
Kotlin
Java
[Link]
1. data class RestaurantResponse(
2.
3. @field:SerializedName("restaurant")
4. val restaurant: Restaurant,
5.
6. @field:SerializedName("error")
7. val error: Boolean,
8.
9. @field:SerializedName("message")
10. val message: String
11. )
12.
13. data class Restaurant(
14.
15. @field:SerializedName("customerReviews")
16. val customerReviews: List<CustomerReviewsItem>,
17.
18. @field:SerializedName("pictureId")
19. val pictureId: String,
20.
21. @field:SerializedName("name")
22. val name: String,
23.
24. @field:SerializedName("rating")
25. val rating: Double,
26.
27. @field:SerializedName("description")
28. val description: String,
29.
30. @field:SerializedName("id")
31. val id: String
32. )
33.
34. data class CustomerReviewsItem(
35.
36. @field:SerializedName("date")
37. val date: String,
38.
39. @field:SerializedName("review")
40. val review: String,
41.
42. @field:SerializedName("name")
43. val name: String
44. )
Voila! Class yang berisi berbagai macam model untuk menampung data dari
JSON sudah tercipta dengan cepat dan mudah. Menarik, bukan?
Selanjutnya untuk persiapan Retrofit, buatlah interface baru dengan cara klik
kanan di package utama pada proyek aplikasi → New → Kotlin Class/Java
Class dan beri nama ApiService.
Kotlin
Java
1. import [Link]
2. import [Link].*
3.
4. interface ApiService {
5. @GET("detail/{id}")
6. fun getRestaurant(
7. @Path("id") id: String
8. ): Call<RestaurantResponse>
9. }
Selanjutnya, buat kelas baru dengan nama ApiConfig dengan cara klik kanan di
package utama pada proyek aplikasi → New → Kotlin Class/Java Class, lalu
tambahkan kode berikut untuk membuat konfigurasi Retrofit.
Kotlin
Java
1. class ApiConfig {
2. companion object{
3. fun getApiService(): ApiService {
4. val loggingInterceptor =
5. HttpLoggingInterceptor().setLevel([Link])
6. val client = [Link]()
7. .addInterceptor(loggingInterceptor)
8. .build()
9. val retrofit = [Link]()
10. .baseUrl("[Link]
11. .addConverterFactory([Link]())
12. .client(client)
13. .build()
14. return [Link](ApiService::[Link])
15. }
16. }
17. }
Kelas ini akan membuat kode Anda menjadi lebih efektif karena Anda tidak perlu
membuat konfigurasi Retrofit baru setiap kali membutuhkannya, tetapi cukup
memanggil fungsi yang ada di dalam class ini saja.
Sebelum melangkah lebih jauh, buatlah terlebih dahulu layout untuk item
RecyclerView dengan cara klik kanan pada folder layout → New → Layout
resource file dan beri nama item_review. Lalu buat desain seperti berikut:
1. <?xml version="1.0" encoding="utf-8"?>
2. <TextView xmlns:android="[Link]
3. xmlns:tools="[Link]
4. android:id="@+id/tvItem"
5. android:layout_width="match_parent"
6. android:layout_height="wrap_content"
7. android:gravity="center_vertical"
8. android:paddingTop="8dp"
9. android:paddingBottom="8dp"
10. android:textAppearance="?android:attr/textAppearanceListItemSmall"
11. tools:text="Review" />
Kemudian buat class baru untuk adapter RecyclerView dengan nama
ReviewAdapter dan tuliskan kode berikut:
Kotlin
Java
1. class ReviewAdapter(private val listReview: List<String>) :
[Link]<[Link]>() {
2. override fun onCreateViewHolder(viewGroup: ViewGroup, viewType:
Int) =
3.
ViewHolder([Link]([Link]).inflate([Link]
m_review, viewGroup, false))
4.
5. override fun onBindViewHolder(viewHolder: ViewHolder, position:
Int) {
6. [Link] = listReview[position]
7. }
8.
9. override fun getItemCount() = [Link]
10.
11. class ViewHolder(view: View) : [Link](view) {
12. val tvItem: TextView = [Link]([Link])
13. }
14. }
Kemudian bagian utamanya adalah proses mengambil data dengan Retrofit dan
menampilkannya menggunakan RecyclerView di MainActivity. Berikut adalah
caranya:
Kotlin
Java
1. class MainActivity : AppCompatActivity() {
2.
3. private lateinit var binding: ActivityMainBinding
4.
5. companion object {
6. private const val TAG = "MainActivity"
7. private const val RESTAURANT_ID = "uewq1zg2zlskfw1e867"
8. }
9.
10. override fun onCreate(savedInstanceState: Bundle?) {
11. [Link](savedInstanceState)
12. binding = [Link](layoutInflater)
13. setContentView([Link])
14.
15. supportActionBar?.hide()
16.
17. val layoutManager = LinearLayoutManager(this)
18. [Link] = layoutManager
19. val itemDecoration = DividerItemDecoration(this,
[Link])
20. [Link](itemDecoration)
21.
22. findRestaurant()
23. }
24.
25. private fun findRestaurant() {
26. showLoading(true)
27. val client =
[Link]().getRestaurant(RESTAURANT_ID)
28. [Link](object : Callback<RestaurantResponse> {
29. override fun onResponse(
30. call: Call<RestaurantResponse>,
31. response: Response<RestaurantResponse>
32. ){
33. showLoading(false)
34. if ([Link]) {
35. val responseBody = [Link]()
36. if (responseBody != null) {
37. setRestaurantData([Link])
38.
setReviewData([Link])
39. }
40. } else {
41. Log.e(TAG, "onFailure: ${[Link]()}")
42. }
43. }
44.
45. override fun onFailure(call: Call<RestaurantResponse>, t:
Throwable) {
46. showLoading(false)
47. Log.e(TAG, "onFailure: ${[Link]}")
48. }
49. })
50. }
51.
52. private fun setRestaurantData(restaurant: Restaurant) {
53. [Link] = [Link]
54. [Link] = [Link]
55. [Link](this@MainActivity)
56. .load("[Link]
{[Link]}")
57. .into([Link])
58. }
59.
60. private fun setReviewData(consumerReviews:
List<CustomerReviewsItem>) {
61. val listReview = ArrayList<String>()
62. for (review in consumerReviews) {
63. [Link](
64. """
65. ${[Link]}
66. - ${[Link]}
67. """.trimIndent()
68. )
69. }
70. val adapter = ReviewAdapter(listReview)
71. [Link] = adapter
72. [Link]("")
73. }
74.
75. private fun showLoading(isLoading: Boolean) {
76. if (isLoading) {
77. [Link] = [Link]
78. } else {
79. [Link] = [Link]
80. }
81. }
82. }
Terakhir, tambahkan permission seperti berikut pada
[Link] karena Anda membutuhkan akses internet.
1. <?xml version="1.0" encoding="utf-8"?>
2. <manifest xmlns:android="[Link]
3. package="[Link]">
4.
5. <uses-permission
android:name="[Link]"/>
6.
7. <application
8. ...
9. </application>
10. </manifest>
12. Selesai! Sekarang jalankan aplikasinya. Maka data restoran dan
review akan tampil seperti contoh di bawah ini:
13. Yeay. Anda sudah dapat mengambil data dari API dengan
menggunakan Retrofit. Mantap!
Codelab Mengirim Data Review
Nah, pada latihan kali ini Anda akan mengirimkan data review dengan
menggunakan metode @POST pada Retrofit. Sudah tidak sabar kan? Yuk,
kita lanjut!
1. Untuk mengirim review, Anda dapat melihat dokumentasi pada
[Link] dan mencoba pada postman terlebih
dahulu sebelum mengimplementasikan ke Android. Berikut adalah
contohnya:
Dari sini kita tahu bagaimana model response setelah berhasil mengirim
review.
2. Selanjutnya, tambahkan dulu model untuk menerima respon ketika
mengirim data seperti yang kita lihat pada postman dengan cara seperti
ini:
o Kotlin
o Java
Tambahkan kode pada RestaurantResponse:
1. ...
2.
3. data class PostReviewResponse(
4.
5. @field:SerializedName("customerReviews")
6. val customerReviews: List<CustomerReviewsItem>,
7.
8. @field:SerializedName("error")
9. val error: Boolean,
10.
11. @field:SerializedName("message")
12. val message: String
13. )
Selanjutnya, tambahkan fungsi baru pada ApiService untuk mengirim data
dengan menggunakan kode berikut:
Kotlin
Java
1. interface ApiService {
2. @GET("detail/{id}")
3. fun getRestaurant(
4. @Path("id") id: String
5. ): Call<RestaurantResponse>
6.
7. @FormUrlEncoded
8. @Headers("Authorization: token 12345")
9. @POST("review")
10. fun postReview(
11. @Field("id") id: String,
12. @Field("name") name: String,
13. @Field("review") review: String
14. ): Call<PostReviewResponse>
15. }
Kemudian tambahkan fungsi untuk mengirim review ketika tombol kirim
diklik pada MainActivity seperti ini:
Kotlin
Java
1. class MainActivity : AppCompatActivity() {
2. ...
3. override fun onCreate(savedInstanceState: Bundle?) {
4. [Link](savedInstanceState)
5. binding = [Link](layoutInflater)
6. setContentView([Link])
7. ...
8. findRestaurant()
9.
10. [Link] { view ->
11. postReview([Link]())
12. val imm =
getSystemService(Context.INPUT_METHOD_SERVICE) as
InputMethodManager
13. [Link]([Link], 0)
14. }
15. }
16.
17. ...
18.
19. private fun postReview(review: String) {
20. showLoading(true)
21. val client =
[Link]().postReview(RESTAURANT_ID,
"Dicoding", review)
22. [Link](object : Callback<PostReviewResponse> {
23. override fun onResponse(
24. call: Call<PostReviewResponse>,
25. response: Response<PostReviewResponse>
26. ){
27. showLoading(false)
28. val responseBody = [Link]()
29. if ([Link] && responseBody != null) {
30. setReviewData([Link])
31. } else {
32. Log.e(TAG, "onFailure: ${[Link]()}")
33. }
34. }
35.
36. override fun onFailure(call: Call<PostReviewResponse>,
t: Throwable) {
37. showLoading(false)
38. Log.e(TAG, "onFailure: ${[Link]}")
39. }
40. })
41. }
42. }
4. Jalankan kembali aplikasi dan cobalah untuk menulis review. Setelahnya,
jangan lupa klik tombol Kirim ya!
5. Yeay, data review berhasil dikirim ke server dan berhasil ditampilkan.
Selamat! Anda telah mempelajari sesuatu yang keren. Tidak hanya belajar
bagaimana mengambil data dengan Retrofit, namun juga mengirimkan
data dengan Authentication ke server. Keren!
Bedah Kode
Retrofit Model
Dengan menggunakan RoboPojoGenerator, Anda telah membuat class
model yang berfungsi untuk menampung data dari JSON. 3 class yang
dibuat merepresentasikan kedalaman object yang diambil. Untuk lebih
jelasnya lihatlah gambar ini:
Class RestaurantResponse untuk mengambil data respon dari server
seperti eror dan message. Kemudian class Restaurant untuk mengambil
JSON Object restaurant; class CustomerReviewItem untuk mengambil
JSON Array customerReviews. Untuk membedakan antara JSONObject dan
JSONArray, cukup gunakan List<CustomerReviewsItem> untuk
JSONArray; dan Restaurant tanpa List untuk JSONObject.
Kemudian untuk menandai sebuah variabel terhubung dengan data JSON,
gunakan annotation @SerializedName seperti berikut:
Kotlin
Java
1. @field:SerializedName("rating")
2. val rating: Double,
Pastikan pula tipe data yang digunakan disesuaikan dengan data yang
diambil. Misal data 4.6 maka sebaiknya menggunakan tipe Double. Jika
Anda menggunakan tipe Integer maka akan mencetuskan eror saat
dikonversi.
Catatan:
Saat menggunakan RoboPojoGenerator, tipe data akan ditentukan
berdasarkan contoh response yang dimasukkan. Apabila pada contoh
tersebut rating berisi angka 4, maka RoboPojoGenerator akan
mendeteksinya sebagai Integer. Maka berhati-hatilah dan cek terlebih
dahulu karena bisa jadi pada rating restoran lain berisi 4.6 yang
seharusnya Double.
ApiConfig
Pada latihan ini, Anda membuat sebuah class yang berfungsi untuk
membuat dan mengkonfigurasi Retrofit dengan nama ApiConfig.
Kotlin
Java
1. class ApiConfig {
2. companion object{
3. fun getApiService(): ApiService {
4. val loggingInterceptor =
5. HttpLoggingInterceptor().setLevel([Link])
6. val client = [Link]()
7. .addInterceptor(loggingInterceptor)
8. .build()
9. val retrofit = [Link]()
10. .baseUrl("[Link]
11. .addConverterFactory([Link]())
12. .client(client)
13. .build()
14. return [Link](ApiService::[Link])
15. }
16. }
17. }
Dengan membuat class ini, Anda tidak perlu menyiapkan Retrofit pada
setiap class yang akan menggunakan Retrofit. Anda hanya cukup
memanggil [Link](). Cukup simpel daripada Anda harus
menulis berulang-ulang bukan? Mantap. Ingat selalu prinsip “DRY” ya!
Don’t repeat yourself.
Catatan:
Anda juga dapat menggunakan class ini untuk keperluan API lain dengan
mengganti kode pada base URL dan nama service-nya.
API Service
API Service merupakan interface yang berisi kumpulan endpoint yang
digunakan pada sebuah aplikasi.
Kotlin
Java
1. interface ApiService {
2. @GET("detail/{id}")
3. fun getRestaurant(
4. @Path("id") id: String
5. ): Call<RestaurantResponse>
6.
7. @FormUrlEncoded
8. @Headers("Authorization: token 12345")
9. @POST("review")
10. fun postReview(
11. @Field("id") id: String,
12. @Field("name") name: String,
13. @Field("review") review: String
14. ): Call<PostReviewResponse>
15. }
Pada aplikasi ini terdapat 2 endpoint, yakni fungsi getRestaurant dengan
annotation @GET untuk mengambil data. Anda dapat mengganti variable
{id} pada endpoint dengan menggunakan @Path sehingga dapat
mengakses detail suatu restoran dengan URL [Link]
[Link]/detail/uewq1zg2zlskfw1e867.
Lalu, yang kedua adalah fungsi postReview dengan annotation @POST
untuk mengirim data. Selain itu, Anda juga dapat menambahkan @Header
untuk menyematkan token jika API tersebut membutuhkan otorisasi.
Kemudian, Anda harus memakai anotasi @FormUrlEncoded untuk
mengirimkan data dengan menggunakan @Field. Pastikan key yang
dimasukkan pada @Field harus sama dengan field yang ada pada API. Jika
tidak sama, data tidak akan terkirim.
Request API
Setelah persiapan Rtrofit sudah selesai, Anda dapat memanggil endpoint
yang ada pada API Service seperti berikut:
Kotlin
Java
1. val client = [Link]().getRestaurant(RESTAURANT_ID)
2. [Link](object : Callback<RestaurantResponse> {
3. override fun onResponse(
4. call: Call<RestaurantResponse>,
5. response: Response<RestaurantResponse>
6. ){
7. showLoading(false)
8. if ([Link]) {
9. val responseBody = [Link]()
10. if (responseBody != null) {
11. setRestaurantData([Link])
12. setReviewData([Link])
13. }
14. } else {
15. Log.e(TAG, "onFailure: ${[Link]()}")
16. }
17. }
18.
19. override fun onFailure(call: Call<RestaurantResponse>, t: Throwable) {
20. showLoading(false)
21. Log.e(TAG, "onFailure: ${[Link]}")
22. }
23. })
Di sini kita menggunakan fungsi enqueue untuk menjalankan
request secara asynchronous di background. Sehingga aplikasi tidak
freeze/lag ketika melakukan request. Kemudian, hasilnya terdapat dua
callback, yakni onResponse ketika ada respon, dan onFailure ketika gagal.
Ketika terdapat respon, bisa jadi terjadi kegagalan seperti eror 404 atau
500, maka untuk mendapatkan data ketika berhasil, kita mengeceknya
melalui [Link]() untuk mengetahui apakah server
mengembalikan kode 200 (OK) atau tidak. Untuk datanya sendiri dapat
diambil di [Link]().
Seperti yang Anda lihat, di sini kita tidak perlu melakukan parsing lagi,
karena data yang didapat sudah berupa POJO. Proses parsing dilakukan
secara otomatis oleh Retrofit dengan menggunakan kode
.addConverterFactory([Link]()) di bagian ApiConfig
dan anotasi SerializedName pada masing-masing POJO.
Selamat Anda sudah belajar tentang bagaimana Retrofit bekerja, baik
untuk melakukan request data dan mengirim data ke API. Pahami baik-
baik dan baca ulang jika kurang mengerti ya. Poin bahasan materi kali ini
tak kalah pentingnya untuk Anda. Semangat!
Silakan unduh proyek di atas pada tautan berikut:
Source Code Latihan Retrofit