Interoperabilitas View di Compose

1. Sebelum memulai

Pengantar

Pada tahap kursus ini, Anda telah berpengalaman dalam mem-build aplikasi dengan Compose dan memiliki pengetahuan cara membangun aplikasi dengan XML, View, View Binding, dan Fragment. Setelah mem-build aplikasi dengan View, Anda mungkin jadi menghargai kemudahan mem-build aplikasi dengan UI deklaratif seperti Compose. Namun, mungkin ada beberapa kasus yang wajar jika menggunakan View, bukan Compose. Dalam codelab ini, Anda akan mempelajari cara menggunakan Interop View untuk menambahkan komponen View ke dalam aplikasi Compose modern.

Pada saat menulis codelab ini, komponen UI yang Anda tetapkan untuk dibuat belum tersedia di Compose. Ini adalah peluang bagus untuk menggunakan Interop View.

Prasyarat:

Yang akan Anda butuhkan

  • Komputer yang memiliki akses internet dan Android Studio
  • Perangkat atau emulator
  • Kode awal untuk aplikasi Juice Tracker

Yang akan Anda bangun

Dalam codelab ini, Anda perlu mengintegrasikan tiga View ke dalam UI Compose untuk menyelesaikan UI aplikasi Juice Tracker. Ketiganya adalah Spinner, RatingBar, dan AdView. Untuk membangun komponen ini, Anda akan menggunakan Interoperabilitas View, atau disingkat Interop View. Dengan Interop View, Anda sebenarnya dapat menambahkan View ke aplikasi dengan menggabungkannya ke dalam Composable.

a02177f6b6277edc.png afc4551fde8c3113.png 5dab7f58a3649c04.png

Panduan kode

Dalam codelab ini, Anda akan menggunakan aplikasi JuiceTracker yang sama dari codelab Mem-build Aplikasi Android dengan View dan Menambahkan Compose ke aplikasi berbasis View. Perbedaan dengan versi ini adalah kode awal yang diberikan sepenuhnya dalam Compose. Aplikasi saat ini tidak memiliki input warna dan rating di sheet dialog entri dan banner iklan di bagian atas layar daftar.

Direktori bottomsheet berisi semua komponen UI yang terkait dengan dialog entri. Paket ini harus berisi komponen UI untuk input warna dan rating ketika dibuat.

homescreen berisi komponen UI yang dihosting oleh layar utama, termasuk daftar JuiceTracker. Paket ini nantinya harus berisi banner iklan ketika dibuat.

Komponen UI utama, seperti sheet bawah dan daftar jus akan dihosting di file JuiceTrackerApp.kt.

2. Mendapatkan kode awal

Untuk memulai, download kode awal:

Atau, Anda dapat membuat clone repositori GitHub untuk kode tersebut:

$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-juice-tracker.git
$ cd basic-android-kotlin-compose-training-juice-tracker
$ git checkout compose-starter
  1. Di Android Studio, buka folder basic-android-kotlin-compose-training-juice-tracker.
  2. Buka kode aplikasi Juice Tracker di Android Studio.

3. Konfigurasi Gradle

Tambahkan dependensi iklan layanan Play ke file build.gradle.kts aplikasi.

app/build.gradle.kts

android {
   ...
   dependencies {
      ...
      implementation("com.google.android.gms:play-services-ads:22.2.0")
   }
}

4. Penyiapan

Tambahkan nilai berikut ke manifes Android, di atas tag activity, guna mengaktifkan banner iklan untuk pengujian:

AndroidManifest.xml

...
<meta-data
   android:name="com.google.android.gms.ads.APPLICATION_ID"
   android:value="ca-app-pub-3940256099942544~3347511713" />

...

5. Menyelesaikan dialog entri

Di bagian ini, Anda akan menyelesaikan dialog entri dengan membuat indikator lingkaran berputar warna dan batang rating. Indikator lingkaran berputar warna adalah komponen untuk memilih warna, sedangkan batang rating dapat digunakan untuk memilih rating jus. Lihat desain di bawah ini:

Indikator lingkaran berputar warna yang mencantumkan beberapa warna

Batang rating dengan 4 dari 5 bintang dipilih

Membuat indikator lingkaran berputar warna

Untuk menerapkan indikator lingkaran berputar di Compose, class Spinner harus digunakan. Spinner adalah komponen View, bukan Composable, sehingga harus diimplementasikan menggunakan interop.

  1. Di direktori bottomsheet, buat file baru yang bernama ColorSpinnerRow.kt.
  2. Buat class baru di dalam file yang bernama SpinnerAdapter.
  3. Dalam konstruktor untuk SpinnerAdapter, tentukan parameter callback yang disebut onColorChange yang menggunakan parameter Int. SpinnerAdapter menangani fungsi callback untuk Spinner.

bottomsheet/ColorSpinnerRow.kt

class SpinnerAdapter(val onColorChange: (Int) -> Unit){
}
  1. Implementasikan antarmuka AdapterView.OnItemSelectedListener.

Dengan menerapkan antarmuka ini, Anda dapat menentukan perilaku klik untuk indikator lingkaran berputar. Anda dapat menyiapkan adaptor ini nanti di Composable.

bottomsheet/ColorSpinnerRow.kt

class SpinnerAdapter(val onColorChange: (Int) -> Unit): AdapterView.OnItemSelectedListener {
}
  1. Implementasikan fungsi anggota AdapterView.OnItemSelectedListener: onItemSelected() dan onNothingSelected().

bottomsheet/ColorSpinnerRow.kt

class SpinnerAdapter(val onColorChange: (Int) -> Unit): AdapterView.OnItemSelectedListener {
   override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
        TODO("Not yet implemented")
    }

    override fun onNothingSelected(parent: AdapterView<*>?) {
        TODO("Not yet implemented")
    }
}
  1. Ubah fungsi onItemSelected() untuk memanggil fungsi callback onColorChange() sehingga saat Anda memilih warna, aplikasi akan memperbarui nilai yang dipilih di UI.

bottomsheet/ColorSpinnerRow.kt

class SpinnerAdapter(val onColorChange: (Int) -> Unit): AdapterView.OnItemSelectedListener {
   override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
        onColorChange(position)
    }

    override fun onNothingSelected(parent: AdapterView<*>?) {
        TODO("Not yet implemented")
    }
}
  1. Ubah fungsi onNothingSelected() untuk menetapkan warna ke 0 sehingga saat Anda tidak memilih apa pun, warna default adalah warna pertama, yaitu merah.

bottomsheet/ColorSpinnerRow.kt

class SpinnerAdapter(val onColorChange: (Int) -> Unit): AdapterView.OnItemSelectedListener {
   override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
        onColorChange(position)
    }

    override fun onNothingSelected(parent: AdapterView<*>?) {
        onColorChange(0)
    }
}

SpinnerAdapter, yang menentukan perilaku indikator lingkaran berputar melalui fungsi callback, telah dibuat. Sekarang Anda harus membuat konten indikator lingkaran berputar dan mengisinya dengan data.

  1. Di dalam file ColorSpinnerRow.kt, tetapi di luar class SpinnerAdapter, buat Composable baru yang bernama ColorSpinnerRow.
  2. Dalam tanda tangan metode ColorSpinnerRow(), tambahkan parameter Int untuk posisi indikator lingkaran berputar, fungsi callback yang menggunakan parameter Int dan pengubah.

bottomsheet/ColorSpinnerRow.kt

...
@Composable
fun ColorSpinnerRow(
    colorSpinnerPosition: Int,
    onColorChange: (Int) -> Unit,
    modifier: Modifier = Modifier
) {
}
  1. Di dalam fungsi, buat array resource string warna jus menggunakan enum JuiceColor. Array ini berfungsi sebagai konten yang akan mengisi indikator lingkaran berputar.

bottomsheet/ColorSpinnerRow.kt

...
@Composable
fun ColorSpinnerRow(
    colorSpinnerPosition: Int,
    onColorChange: (Int) -> Unit,
    modifier: Modifier = Modifier
) {
   val juiceColorArray =
        JuiceColor.values().map { juiceColor -> stringResource(juiceColor.label) }

}
  1. Tambahkan Composable InputRow() dan teruskan resource string warna untuk label input dan pengubah, yang menentukan baris input tempat Spinner muncul.

bottomsheet/ColorSpinnerRow.kt

...
@Composable
fun ColorSpinnerRow(
    colorSpinnerPosition: Int,
    onColorChange: (Int) -> Unit,
    modifier: Modifier = Modifier
) {
   val juiceColorArray =
        JuiceColor.values().map { juiceColor -> stringResource(juiceColor.label) }
   InputRow(inputLabel = stringResource(R.string.color), modifier = modifier) {
   }
}

Berikutnya, Anda akan membuat Spinner. Karena Spinner adalah class View, API interoperabilitas View dari Compose harus digunakan untuk menggabungkannya ke dalam Composable. Hal ini dapat dilakukan dengan Composable AndroidView.

  1. Untuk menggunakan Spinner di Compose, buat Composable AndroidView() dalam isi lambda InputRow. Composable AndroidView() membuat elemen atau hierarki View dalam Composable.

bottomsheet/ColorSpinnerRow.kt

...
@Composable
fun ColorSpinnerRow(
    colorSpinnerPosition: Int,
    onColorChange: (Int) -> Unit,
    modifier: Modifier = Modifier
) {
   val juiceColorArray =
        JuiceColor.values().map { juiceColor -> stringResource(juiceColor.label) }
   InputRow(inputLabel = stringResource(R.string.color), modifier = modifier) {
      AndroidView()
   }
}

Composable AndroidView menggunakan tiga parameter:

  • factory lambda, yang merupakan fungsi untuk membuat View.
  • Callback update, yang dipanggil saat View yang dibuat di factory di-inflate.
  • modifier composable.

3bb9f605719b173.png

  1. Untuk menerapkan AndroidView, mulailah dengan meneruskan pengubah dan mengisi lebar maksimum layar.
  2. Teruskan lambda untuk parameter factory.
  3. Lambda factory menggunakan Context sebagai parameter. Buat class Spinner lalu teruskan konteksnya.

bottomsheet/ColorSpinnerRow.kt

...
@Composable
fun ColorSpinnerRow(
    colorSpinnerPosition: Int,
    onColorChange: (Int) -> Unit,
    modifier: Modifier = Modifier
) {
   ...
   InputRow(...) {
      AndroidView(
         modifier = Modifier.fillMaxWidth(),
         factory = { context ->
            Spinner(context)
         }
      )
   }
}

Sama seperti RecyclerView.Adapter yang menyediakan data ke RecyclerView, ArrayAdapter juga menyediakan data ke Spinner. Spinner memerlukan adaptor untuk menyimpan array warna.

  1. Setel adaptor menggunakan ArrayAdapter. ArrayAdapter memerlukan konteks, tata letak XML, dan array. Teruskan simple_spinner_dropdown_item untuk tata letak; tata letak ini disediakan sebagai default dengan Android.

bottomsheet/ColorSpinnerRow.kt

...
@Composable
fun ColorSpinnerRow(
    colorSpinnerPosition: Int,
    onColorChange: (Int) -> Unit,
    modifier: Modifier = Modifier
) {
   ...
   InputRow(...) {
      AndroidView(
         ​​modifier = Modifier.fillMaxWidth(),
         factory = { context ->
             Spinner(context).apply {
                 adapter =
                     ArrayAdapter(
                         context,
                         android.R.layout.simple_spinner_dropdown_item,
                         juiceColorArray
                     )
             }
         }
      )
   }
}

Callback factory menampilkan instance View yang dibuat di dalamnya. update adalah callback yang menggunakan parameter dari jenis sama yang ditampilkan oleh callback factory. Parameter ini adalah instance View yang di-inflate oleh factory. Dalam hal ini, karena Spinner dibuat di factory, instance Spinner tersebut dapat diakses dalam isi lambda update.

  1. Tambahkan callback update yang meneruskan spinner. Gunakan callback yang disediakan di update untuk memanggil metode setSelection().

bottomsheet/ColorSpinnerRow.kt

...
@Composable
fun ColorSpinnerRow(
    colorSpinnerPosition: Int,
    onColorChange: (Int) -> Unit,
    modifier: Modifier = Modifier
) {
   ...
   InputRow(...) {
      //...
         },
         update = { spinner ->
             spinner.setSelection(colorSpinnerPosition)
             spinner.onItemSelectedListener = SpinnerAdapter(onColorChange)
         }
      )
   }
}
  1. Gunakan SpinnerAdapter yang Anda buat sebelumnya untuk menetapkan callback onItemSelectedListener() di update.

bottomsheet/ColorSpinnerRow.kt

...
@Composable
fun ColorSpinnerRow(
    colorSpinnerPosition: Int,
    onColorChange: (Int) -> Unit,
    modifier: Modifier = Modifier
) {
   ...
   InputRow(...) {
      AndroidView(
         // ...
         },
         update = { spinner ->
             spinner.setSelection(colorSpinnerPosition)
             spinner.onItemSelectedListener = SpinnerAdapter(onColorChange)
         }
      )
   }
}

Kode untuk komponen indikator lingkaran berputar warna sekarang sudah selesai.

  1. Tambahkan fungsi utilitas berikut untuk mendapatkan indeks enum JuiceColor. Anda akan menggunakannya di langkah berikutnya.
private fun findColorIndex(color: String): Int {
   val juiceColor = JuiceColor.valueOf(color)
   return JuiceColor.values().indexOf(juiceColor)
}
  1. Implementasikan ColorSpinnerRow di Composable SheetForm pada file EntryBottomSheet.kt. Letakkan indikator lingkaran berputar warna setelah teks "Deskripsi", dan di atas tombol.

bottomsheet/EntryBottomSheet.kt

...
@Composable
fun SheetForm(
   juice: Juice,
   onUpdateJuice: (Juice) -> Unit,
   onCancel: () -> Unit,
   onSubmit: () -> Unit,
   modifier: Modifier = Modifier,
) {
   ...
   TextInputRow(
            inputLabel = stringResource(R.string.juice_description),
            fieldValue = juice.description,
            onValueChange = { description -> onUpdateJuice(juice.copy(description = description)) },
            modifier = Modifier.fillMaxWidth()
        )
        ColorSpinnerRow(
            colorSpinnerPosition = findColorIndex(juice.color),
            onColorChange = { color ->
                onUpdateJuice(juice.copy(color = JuiceColor.values()[color].name))
            }
        )
   ButtonRow(
            modifier = Modifier
                .align(Alignment.End)
                .padding(bottom = dimensionResource(R.dimen.padding_medium)),
            onCancel = onCancel,
            onSubmit = onSubmit,
            submitButtonEnabled = juice.name.isNotEmpty()
        )
    }
}

Membuat input rating

  1. Buat file baru di direktori bottomsheet yang bernama RatingInputRow.kt.
  2. Dalam file RatingInputRow.kt, buat Composable baru yang bernama RatingInputRow().
  3. Dalam tanda tangan metode, teruskan Int untuk rating, callback dengan parameter Int untuk menangani perubahan pemilihan, dan pengubah.

bottomsheet/RatingInputRow.kt

@Composable
fun RatingInputRow(rating:Int, onRatingChange: (Int) -> Unit, modifier: Modifier = Modifier){
}
  1. Seperti ColorSpinnerRow, tambahkan InputRow ke Composable yang berisi AndroidView, seperti yang ditunjukkan dalam kode contoh berikut.

bottomsheet/RatingInputRow.kt

@Composable
fun RatingInputRow(rating:Int, onRatingChange: (Int) -> Unit, modifier: Modifier = Modifier){
    InputRow(inputLabel = stringResource(R.string.rating), modifier = modifier) {
        AndroidView(
            factory = {},
            update = {}
        )
    }
}
  1. Dalam isi lambda factory, buat instance class RatingBar yang menyediakan jenis batang rating yang diperlukan untuk desain ini. Setel stepSize ke 1f untuk menerapkan rating agar hanya berupa bilangan bulat.

bottomsheet/RatingInputRow.kt

@Composable
fun RatingInputRow(rating:Int, onRatingChange: (Int) -> Unit, modifier: Modifier = Modifier){
    InputRow(inputLabel = stringResource(R.string.rating), modifier = modifier) {
        AndroidView(
            factory = { context ->
                RatingBar(context).apply {
                    stepSize = 1f
                }
            },
            update = {}
        )
    }
}

Jika View di-inflate, rating akan ditetapkan. Ingat kembali bahwa factory akan menampilkan instance RatingBar ke callback update.

  1. Gunakan rating yang diteruskan ke Composable untuk menetapkan rating bagi instance RatingBar di isi lambda update.
  2. Saat rating baru ditetapkan, gunakan callback RatingBar untuk memanggil fungsi callback onRatingChange() guna memperbarui rating di UI.

bottomsheet/RatingInputRow.kt

@Composable
fun RatingInputRow(rating:Int, onRatingChange: (Int) -> Unit, modifier: Modifier = Modifier){
    InputRow(inputLabel = stringResource(R.string.rating), modifier = modifier) {
        AndroidView(
            factory = { context ->
                RatingBar(context).apply {
                    stepSize = 1f
                }
            },
            update = { ratingBar ->
                ratingBar.rating = rating.toFloat()
                ratingBar.setOnRatingBarChangeListener { _, _, _ ->
                    onRatingChange(ratingBar.rating.toInt())
                }
            }
        )
    }
}

Sekarang, Composable input rating telah selesai.

  1. Gunakan composable RatingInputRow() di EntryBottomSheet. Tempatkan setelah indikator lingkaran berputar warna dan di atas tombol.

bottomsheet/EntryBottomSheet.kt

@Composable
fun SheetForm(
    juice: Juice,
    onUpdateJuice: (Juice) -> Unit,
    onCancel: () -> Unit,
    onSubmit: () -> Unit,
    modifier: Modifier = Modifier,
) {
    Column(
        modifier = modifier,
        verticalArrangement = Arrangement.spacedBy(4.dp)
    ) {
        ...
        ColorSpinnerRow(
            colorSpinnerPosition = findColorIndex(juice.color),
            onColorChange = { color ->
                onUpdateJuice(juice.copy(color = JuiceColor.values()[color].name))
            }
        )
        RatingInputRow(
            rating = juice.rating,
            onRatingChange = { rating -> onUpdateJuice(juice.copy(rating = rating)) }
        )
        ButtonRow(
            modifier = Modifier.align(Alignment.CenterHorizontally),
            onCancel = onCancel,
            onSubmit = onSubmit,
            submitButtonEnabled = juice.name.isNotEmpty()
        )
    }
}

Membuat banner iklan

  1. Dalam paket homescreen, buat file baru yang bernama AdBanner.kt.
  2. Dalam file AdBanner.kt, buat Composable baru yang bernama AdBanner().

Tidak seperti Composable sebelumnya yang telah dibuat, AdBanner tidak memerlukan input. Oleh karena itu, Anda tidak perlu menggabungkannya dalam Composable InputRow. Namun, metode ini memerlukan AndroidView.

  1. Coba Anda membuat banner sendiri menggunakan class AdView. Pastikan Anda menetapkan ukuran iklan ke AdSize.BANNER dan ID unit iklan ke "ca-app-pub-3940256099942544/6300978111".
  2. Saat AdView di-inflate, muat iklan menggunakan AdRequest Builder.

homescreen/AdBanner.kt

@Composable
fun AdBanner(modifier: Modifier = Modifier) {
    AndroidView(
        modifier = modifier,
        factory = { context ->
            AdView(context).apply {
                setAdSize(AdSize.BANNER)
                // Use test ad unit ID
                adUnitId = "ca-app-pub-3940256099942544/6300978111"
            }
        },
        update = { adView ->
            adView.loadAd(AdRequest.Builder().build())
        }
    )
}
  1. Tempatkan AdBanner sebelum JuiceTrackerList di JuiceTrackerApp. JuiceTrackerList dideklarasikan di baris 83.

ui/JuiceTrackerApp.kt

...
AdBanner(
   Modifier
       .fillMaxWidth()
       .padding(
           top = dimensionResource(R.dimen.padding_medium),
           bottom = dimensionResource(R.dimen.padding_small)
       )
)

JuiceTrackerList(
    juices = trackerState,
    onDelete = { juice -> juiceTrackerViewModel.deleteJuice(juice) },
    onUpdate = { juice ->
        juiceTrackerViewModel.updateCurrentJuice(juice)
        scope.launch {
            bottomSheetScaffoldState.bottomSheetState.expand()
        }
     },
)

6. Mendapatkan kode solusi

Untuk mendownload kode codelab yang sudah selesai, Anda dapat menggunakan perintah git berikut:

$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-juice-tracker.git
$ cd basic-android-kotlin-compose-training-juice-tracker
$ git checkout compose-with-views

Atau, Anda dapat mendownload repositori sebagai file ZIP, lalu mengekstraknya, dan membukanya di Android Studio.

Jika Anda ingin melihat kode solusi, lihat di GitHub.

7. Mempelajari lebih lanjut

8. Selesai!

Kursus ini mungkin berakhir di sini, tetapi ini hanyalah awal dari perjalanan Anda dalam pengembangan aplikasi Android.

Dalam kursus ini, Anda telah mempelajari cara mem-build aplikasi menggunakan Jetpack Compose, toolkit UI modern untuk mem-build aplikasi Android native. Sepanjang kursus ini, Anda telah mem-build aplikasi dengan daftar, satu atau beberapa layar, dan menavigasi di antara layar. Anda telah mempelajari cara membuat aplikasi interaktif, membuat aplikasi merespons input pengguna, dan mengupdate UI. Anda telah menerapkan Desain Material dan menggunakan warna, bentuk, dan tipografi untuk tema aplikasi. Anda juga menggunakan Jetpack dan library pihak ketiga lainnya untuk menjadwalkan tugas, mengambil data dari server jarak jauh, mempertahankan data secara lokal, dan lainnya.

Dengan menyelesaikan kursus ini, Anda tidak hanya memiliki pemahaman yang baik tentang cara membuat aplikasi yang menarik dan responsif menggunakan Jetpack Compose, tetapi juga dibekali dengan pengetahuan dan keterampilan yang Anda butuhkan untuk membuat aplikasi Android yang efisien, mudah dikelola, dan menarik secara visual. Fondasi ini akan membantu Anda untuk terus belajar dan mengembangkan keterampilan dalam pengembangan Android Modern dan Compose.

Kami ingin berterima kasih kepada Anda semua karena telah berpartisipasi dan menyelesaikan kursus ini. Kami mendorong Anda semua untuk terus belajar dan mengembangkan keterampilan melalui berbagai referensi tambahan, seperti Dokumentasi Developer Android, Kursus Jetpack Compose untuk Android Developers, Arsitektur Aplikasi Android Modern, Blog Developer Android, codelab, dan project contoh lainnya.

Terakhir, jangan lupa untuk membagikan hashtag yang Anda buat di media sosial dan gunakan hashtag #AndroidBasics agar kami dan komunitas developer Android lainnya juga dapat mengikuti perjalanan pembelajaran Anda.

Selamat belajar menulis kode!!