Halo, Habr! Saya hadir untuk Anda terjemahan gratis "Panduan untuk arsitektur aplikasi" dari
JetPack . Saya meminta Anda untuk meninggalkan semua komentar pada terjemahan di komentar, dan mereka akan diperbaiki. Juga, komentar dari mereka yang menggunakan arsitektur yang disajikan dengan rekomendasi untuk penggunaannya akan bermanfaat bagi semua orang.
Panduan ini mencakup praktik terbaik dan arsitektur yang disarankan untuk membangun aplikasi yang kuat. Halaman ini mengasumsikan pengantar dasar untuk Kerangka Android. Jika Anda baru dalam pengembangan aplikasi Android, lihat
panduan pengembang kami untuk memulai dan mempelajari lebih lanjut tentang konsep yang disebutkan dalam panduan ini. Jika Anda tertarik pada arsitektur aplikasi dan ingin membiasakan diri dengan materi dalam panduan ini dalam hal pemrograman di Kotlin, lihat kursus Udacity,
"Mengembangkan Aplikasi untuk Android dengan Kotlin," .
Pengalaman Pengguna Aplikasi Seluler
Dalam kebanyakan kasus, aplikasi desktop memiliki titik masuk tunggal dari desktop atau peluncur, dan kemudian dijalankan sebagai proses monolitik tunggal. Aplikasi Android memiliki struktur yang jauh lebih kompleks. Aplikasi Android tipikal berisi beberapa
komponen aplikasi , termasuk
Aktivitas ,
Fragmen ,
Layanan ,
ContentProviders, dan
BroadcastReceivers .
Anda menyatakan semua atau beberapa komponen aplikasi ini dalam
manifes aplikasi. Android kemudian menggunakan file ini untuk memutuskan bagaimana mengintegrasikan aplikasi Anda ke dalam antarmuka pengguna umum perangkat. Mengingat bahwa aplikasi Android yang ditulis dengan baik berisi beberapa komponen, dan pengguna sering berinteraksi dengan beberapa aplikasi dalam waktu singkat, aplikasi harus beradaptasi dengan berbagai jenis alur kerja dan tugas yang digerakkan pengguna.
Misalnya, pertimbangkan apa yang terjadi ketika Anda berbagi foto di aplikasi media sosial favorit Anda:
- Aplikasi memicu niat kamera. Android meluncurkan aplikasi kamera untuk memproses permintaan. Saat ini, pengguna telah meninggalkan aplikasi untuk jejaring sosial, dan pengalamannya sebagai pengguna sangat sempurna.
- Aplikasi kamera dapat memicu niat lain, seperti meluncurkan pemilih file, yang dapat meluncurkan aplikasi lain.
- Pada akhirnya, pengguna kembali ke aplikasi jejaring sosial dan berbagi foto.
Setiap saat dalam proses, pengguna dapat terganggu oleh panggilan telepon atau pemberitahuan. Setelah tindakan yang terkait dengan interupsi ini, pengguna berharap dapat kembali dan melanjutkan proses berbagi foto ini. Perilaku beralih aplikasi ini biasa terjadi pada perangkat seluler, jadi aplikasi Anda harus menangani poin (tugas) ini dengan benar.
Ingatlah bahwa perangkat seluler juga terbatas sumber dayanya, jadi kapan saja, sistem operasi dapat menghancurkan beberapa proses aplikasi untuk membebaskan ruang untuk yang baru.
Dengan kondisi lingkungan ini, komponen aplikasi Anda dapat diluncurkan secara terpisah dan tidak berurutan, dan sistem operasi atau pengguna dapat menghancurkannya kapan saja. Karena peristiwa ini tidak di bawah kendali
Anda ,
Anda tidak boleh menyimpan data atau status apa pun dalam komponen aplikasi Anda, dan komponen aplikasi Anda tidak boleh saling bergantung.
Prinsip arsitektur umum
Jika Anda tidak boleh menggunakan komponen aplikasi untuk menyimpan data dan status aplikasi, bagaimana Anda harus mengembangkan aplikasi Anda?
Pembagian tanggung jawab
Prinsip yang paling penting untuk diikuti adalah
pembagian tanggung jawab . Kesalahan umum adalah ketika Anda menulis semua kode Anda di
Aktivitas atau
Fragmen . Ini adalah kelas antarmuka pengguna yang hanya boleh berisi logika yang memproses interaksi antarmuka pengguna dan sistem operasi. Dengan berbagi tanggung jawab sebanyak mungkin dalam kelas-kelas ini
(SRP) , Anda dapat menghindari banyak masalah yang terkait dengan siklus hidup aplikasi.
Kontrol Antarmuka Pengguna dari Model
Prinsip penting lainnya adalah Anda harus
mengontrol antarmuka pengguna Anda dari suatu model , lebih disukai dari model permanen. Model adalah komponen yang bertanggung jawab untuk memproses data untuk aplikasi. Mereka tidak tergantung pada objek
Lihat dan komponen aplikasi, oleh karena itu, mereka tidak terpengaruh oleh siklus hidup aplikasi dan masalah terkait.
Model permanen sangat ideal karena alasan berikut:
- Pengguna Anda tidak akan kehilangan data jika OS Android menghancurkan aplikasi Anda untuk membebaskan sumber daya.
- Aplikasi Anda terus berfungsi ketika koneksi jaringan tidak stabil atau tidak tersedia.
Dengan mengorganisasi fondasi aplikasi Anda ke dalam kelas model dengan tanggung jawab yang jelas untuk manajemen data, aplikasi Anda menjadi lebih teruji dan didukung.
Arsitektur Aplikasi yang Disarankan
Bagian ini menunjukkan bagaimana menyusun aplikasi menggunakan
komponen arsitektur , bekerja dalam
skenario penggunaan ujung ke
ujung .
Catatan Tidaklah mungkin untuk memiliki satu cara menulis aplikasi yang bekerja paling baik untuk setiap skenario. Namun, arsitektur yang direkomendasikan adalah titik awal yang baik untuk sebagian besar situasi dan alur kerja. Jika Anda sudah memiliki cara yang baik untuk menulis aplikasi Android yang memenuhi prinsip arsitektur umum, Anda tidak boleh mengubahnya.Bayangkan kami membuat antarmuka pengguna yang menampilkan profil pengguna. Kami menggunakan API pribadi dan API REST untuk mengambil data profil.
Ulasan
Untuk memulai, pertimbangkan skema interaksi modul arsitektur aplikasi yang sudah selesai:

Harap dicatat bahwa setiap komponen hanya bergantung pada komponen satu tingkat di bawahnya. Misalnya, Aktivitas dan Fragmen hanya bergantung pada model tampilan. Repositori adalah satu-satunya kelas yang bergantung pada banyak kelas lainnya; dalam contoh ini, penyimpanan tergantung pada model data yang persisten dan sumber data internal jarak jauh.
Pola desain ini menciptakan pengalaman pengguna yang konsisten dan menyenangkan. Terlepas dari apakah pengguna kembali ke aplikasi beberapa menit setelah menutupnya atau beberapa hari kemudian, ia akan langsung melihat informasi pengguna bahwa aplikasi tersebut disimpan secara lokal. Jika data ini kedaluwarsa, modul penyimpanan aplikasi mulai memperbarui data di latar belakang.
Buat antarmuka pengguna
Antarmuka pengguna terdiri dari fragmen
UserProfileFragment
dan
user_profile_layout.xml
layout
user_profile_layout.xml
sesuai.
Untuk mengelola antarmuka pengguna, model data kami harus berisi elemen data berikut:
- ID Pengguna: ID pengguna. Solusi terbaik adalah meneruskan informasi ini ke fragmen menggunakan argumen fragmen. Jika OS Android menghancurkan proses kami, informasi ini disimpan, sehingga pengidentifikasi akan tersedia saat berikutnya kami meluncurkan aplikasi kami.
- Objek pengguna: kelas data yang berisi informasi pengguna.
Kami menggunakan
UserProfileViewModel
berdasarkan pada komponen arsitektur ViewModel untuk menyimpan informasi ini.
Objek ViewModel menyediakan data untuk komponen antarmuka pengguna tertentu, seperti fragmen atau Kegiatan, dan berisi logika pemrosesan data bisnis untuk berinteraksi dengan model. Misalnya, ViewModel dapat memanggil komponen lain untuk memuat data dan dapat meneruskan permintaan pengguna untuk perubahan data. ViewModel tidak tahu tentang komponen-komponen antarmuka pengguna, sehingga tidak terpengaruh oleh perubahan konfigurasi, seperti menciptakan kembali Aktivitas saat perangkat diputar.Sekarang kami telah mengidentifikasi file-file berikut:
user_profile.xml
: tata letak antarmuka pengguna yang ditentukan.UserProfileFragment
: menggambarkan pengontrol antarmuka pengguna yang bertanggung jawab untuk menampilkan informasi kepada pengguna.UserProfileViewModel
: kelas yang bertanggung jawab untuk menyiapkan data untuk ditampilkan di UserProfileFragment
dan menanggapi interaksi pengguna.
Cuplikan kode berikut menunjukkan konten awal dari file-file ini. (File tata letak dihilangkan karena kesederhanaan.)
class UserProfileViewModel : ViewModel() { val userId : String = TODO() val user : User = TODO() } class UserProfileFragment : Fragment() { private val viewModel: UserProfileViewModel by viewModels() override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { return inflater.inflate(R.layout.main_fragment, container, false) } }
Sekarang kita memiliki modul kode ini, bagaimana kita menghubungkannya? Setelah bidang pengguna diatur dalam kelas UserProfileViewModel, kita perlu cara untuk memberi tahu antarmuka pengguna.
Catatan SavedStateHandle memungkinkan ViewModel untuk mengakses keadaan tersimpan dan argumen dari fragmen atau tindakan yang terkait.
Sekarang kita perlu memberi tahu Fragmen kita ketika objek pengguna diterima. Di sinilah komponen arsitektur LiveData muncul.
LiveData adalah pemegang data yang dapat diamati. Komponen lain dalam aplikasi Anda dapat melacak perubahan pada objek menggunakan dudukan ini, tanpa membuat jalur ketergantungan yang eksplisit dan sulit di antara mereka. Komponen LiveData juga memperhitungkan keadaan siklus hidup komponen aplikasi Anda, seperti Aktivitas, Fragmen, dan Layanan, dan termasuk logika pembersihan untuk mencegah kebocoran objek dan konsumsi memori yang berlebihan.
Catatan Jika Anda sudah menggunakan pustaka seperti RxJava atau Agera, Anda bisa terus menggunakannya daripada LiveData. Namun, saat menggunakan perpustakaan dan pendekatan serupa, pastikan Anda menangani siklus hidup aplikasi Anda dengan benar. Secara khusus, pastikan bahwa Anda menangguhkan aliran data Anda ketika LifecycleOwner terkait dihentikan, dan hancurkan aliran ini ketika LifecycleOwner terkait telah dihancurkan. Anda juga dapat menambahkan artifact android.arch.lifecycle: jet stream untuk menggunakan LiveData dengan pustaka aliran jet lain seperti RxJava2.Untuk memasukkan komponen LiveData dalam aplikasi kami, kami mengubah jenis bidang di
UserProfileViewModel
menjadi LiveData.
UserProfileFragment
sekarang diinformasikan tentang pembaruan data. Selain itu, karena bidang
LiveData ini mendukung siklus masa pakai, bidang ini secara otomatis menghapus tautan saat tidak diperlukan lagi.
class UserProfileViewModel( savedStateHandle: SavedStateHandle ) : ViewModel() { val userId : String = savedStateHandle["uid"] ?: throw IllegalArgumentException("missing user id") val user : LiveData<User> = TODO() }
Sekarang kami memodifikasi
UserProfileFragment
untuk mengamati data dalam
ViewModel
dan memperbarui antarmuka pengguna sesuai dengan perubahan:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) viewModel.user.observe(viewLifecycleOwner) {
Setiap kali data profil pengguna diperbarui, panggilan balik
onChanged () dipanggil dan antarmuka pengguna diperbarui.
Jika Anda terbiasa dengan perpustakaan lain yang menggunakan panggilan balik yang dapat diobservasi, Anda mungkin telah menyadari bahwa kami tidak mendefinisikan kembali metode
onStop () dari fragmen untuk berhenti mengamati data. Langkah ini opsional untuk LiveData karena mendukung siklus hidup, yang berarti tidak akan memanggil panggilan balik
onChanged()
jika fragmennya dalam keadaan tidak aktif; yaitu, ia menerima panggilan ke
onStart () , tetapi belum menerima
onStop()
). LiveData juga secara otomatis menghapus pengamat saat memanggil metode
onDestroy () pada fragmen.
Kami belum menambahkan logika apa pun untuk menangani perubahan konfigurasi, seperti memutar layar perangkat oleh pengguna.
UserProfileViewModel
secara otomatis dikembalikan ketika konfigurasi diubah, sehingga segera setelah sebuah fragmen baru dibuat, ia mendapat instance
ViewModel
sama, dan panggilan balik dipanggil segera menggunakan data saat ini. Mengingat bahwa objek
ViewModel
dirancang untuk bertahan dari objek
View
terkait yang mereka perbarui, Anda tidak boleh menyertakan referensi langsung ke objek
View
dalam implementasi ViewModel Anda. Untuk informasi lebih lanjut tentang masa pakai
ViewModel
sesuai dengan siklus hidup komponen antarmuka pengguna, lihat
Siklus hidup ViewModel.Pengambilan data
Sekarang kita telah menggunakan LiveData untuk menghubungkan
UserProfileViewModel
ke
UserProfileFragment
, bagaimana kita bisa mendapatkan data profil pengguna?
Dalam contoh ini, kami menganggap bahwa backend kami menyediakan API REST. Kami menggunakan pustaka Retrofit untuk mengakses backend kami, meskipun Anda dapat menggunakan pustaka yang berbeda yang memiliki tujuan yang sama.
Berikut adalah definisi kami tentang layanan Web yang menautkan ke backend kami:
interface Webservice { @GET("/users/{user}") fun getUser(@Path("user") userId: String): Call<User> }
Ide pertama untuk mengimplementasikan
ViewModel
mungkin melibatkan memanggil layanan Web
Webservice
untuk mengambil data dan menetapkan data itu ke objek
LiveData
kami. Desain ini berfungsi, tetapi menggunakannya membuat aplikasi kita lebih sulit untuk dipertahankan seiring pertumbuhannya. Ini memberikan terlalu banyak tanggung jawab kepada kelas
UserProfileViewModel
, yang melanggar prinsip
pemisahan kepentingan . Selain itu, ruang lingkup ViewModel dikaitkan dengan siklus hidup
Aktivitas atau
Fragmen , yang berarti bahwa data dari layanan Web hilang ketika siklus hidup objek antarmuka pengguna terkait berakhir. Perilaku ini menciptakan pengalaman pengguna yang tidak diinginkan.
Alih-alih,
ViewModel
kami mendelegasikan proses pengambilan data ke modul penyimpanan baru.
Modul repositori menangani operasi data. Mereka menyediakan API bersih sehingga sisa aplikasi dapat dengan mudah mendapatkan data ini. Mereka tahu di mana mendapatkan data dan panggilan API apa yang harus dilakukan saat memperbarui data. Anda dapat menganggap repositori sebagai perantara antara berbagai sumber data, seperti model persisten, layanan web, dan cache.Kelas
UserRepository
kami, ditampilkan dalam cuplikan kode berikut, menggunakan instance
WebService
untuk mengambil data pengguna:
class UserRepository { private val webservice: Webservice = TODO()
Meskipun modul penyimpanan tampaknya tidak perlu, itu melayani tujuan penting: itu abstrak sumber data dari sisa aplikasi. Sekarang
UserProfileViewModel
kami tidak tahu cara mengambil data, sehingga kami dapat menyediakan model presentasi dengan data yang diperoleh dari beberapa implementasi ekstraksi data yang berbeda.
Catatan Kami melewatkan kasus kesalahan jaringan karena kesederhanaan. Untuk implementasi alternatif yang mengekspos kesalahan dan status unduhan, lihat Lampiran: Pengungkapan Status Jaringan.
Mengelola Ketergantungan antar KomponenKelas
UserRepository
atas membutuhkan turunan dari layanan Web untuk mengambil data pengguna. Dia hanya bisa membuat contoh, tetapi untuk ini dia juga perlu tahu dependensi kelas layanan Web. Selain itu,
UserRepository
mungkin bukan satu-satunya kelas yang membutuhkan layanan web. Situasi ini mengharuskan kami untuk menduplikasi kode, karena setiap kelas yang membutuhkan tautan ke layanan Web perlu tahu cara membuatnya dan ketergantungannya. Jika setiap kelas membuat
WebService
baru, aplikasi kita bisa menjadi sangat padat sumber daya.
Untuk mengatasi masalah ini, Anda dapat menggunakan pola desain berikut:
- Ketergantungan Injeksi (DI) . Injeksi ketergantungan memungkinkan kelas untuk menentukan dependensi mereka tanpa membuatnya. Pada saat run time, kelas lain bertanggung jawab untuk menyediakan dependensi ini. Kami merekomendasikan perpustakaan Dagger 2 untuk menerapkan injeksi ketergantungan pada aplikasi Android. Belati 2 secara otomatis membuat objek, melewati pohon dependensi, dan memberikan jaminan waktu kompilasi untuk dependensi.
- (Lokasi layanan) Lokasi layanan : Templat lokasi layanan menyediakan registri di mana kelas bisa mendapatkan dependensi mereka alih-alih membangunnya.
Menerapkan registri layanan lebih mudah daripada menggunakan DI, jadi jika Anda baru mengenal DI, gunakan templat: lokasi layanan sebagai gantinya.
Templat ini memungkinkan Anda untuk menskalakan kode Anda karena mereka memberikan templat yang jelas untuk mengelola dependensi tanpa menduplikasi atau menyulitkan kode. Selain itu, template ini memungkinkan Anda untuk dengan cepat beralih antara pengujian dan implementasi pengambilan sampel data.
Aplikasi sampel kami menggunakan
Belati 2 untuk mengelola dependensi objek layanan Web.
Hubungkan ViewModel dan Penyimpanan
Sekarang kita memodifikasi
UserProfileViewModel
kita untuk menggunakan objek
UserRepository
:
class UserProfileViewModel @Inject constructor( savedStateHandle: SavedStateHandle, userRepository: UserRepository ) : ViewModel() { val userId : String = savedStateHandle["uid"] ?: throw IllegalArgumentException("missing user id") val user : LiveData<User> = userRepository.getUser(userId) }
Caching
Implementasi
UserRepository
mengabstraksi permohonan objek layanan Web, tetapi karena hanya bergantung pada satu sumber data, itu tidak terlalu fleksibel.
Masalah utama dengan implementasi
UserRepository
adalah bahwa setelah menerima data dari backend kami, data ini tidak disimpan di mana pun. Oleh karena itu, jika pengguna meninggalkan
UserProfileFragment
dan kemudian kembali ke sana, aplikasi kita harus mengambil data, bahkan jika mereka tidak berubah.
Desain ini tidak optimal karena alasan berikut:
- Ini menghabiskan sumber daya lalu lintas yang berharga.
- Hal ini membuat pengguna menunggu penyelesaian permintaan baru.
Untuk mengatasi kekurangan ini, kami menambahkan sumber data baru ke
UserRepository
kami, yang menyimpan benda-benda
User
dalam memori:
Data persisten
Menggunakan implementasi kami saat ini, jika pengguna memutar perangkat atau pergi dan segera kembali ke aplikasi, antarmuka pengguna yang ada menjadi segera terlihat, karena toko mengambil data dari cache kami di memori.
Namun, apa yang terjadi jika pengguna meninggalkan aplikasi dan kembali beberapa jam setelah OS Android menyelesaikan prosesnya? Mengandalkan implementasi kami saat ini dalam situasi ini, kami perlu mendapatkan data dari jaringan lagi. Proses peningkatan ini bukan hanya pengalaman pengguna yang buruk; itu juga boros karena mengkonsumsi data seluler yang berharga.
Anda dapat memecahkan masalah ini dengan melakukan caching permintaan web, tetapi ini menciptakan masalah baru yang utama: apa yang terjadi jika data pengguna yang sama ditampilkan dalam permintaan dari jenis yang berbeda, misalnya, saat menerima daftar teman? Aplikasi akan menampilkan data yang saling bertentangan, yang paling membingungkan. Misalnya, aplikasi kami dapat menampilkan dua versi data yang berbeda dari pengguna yang sama jika pengguna mengirim permintaan daftar teman dan permintaan satu pengguna pada waktu yang berbeda. Aplikasi kita harus mencari cara untuk menggabungkan data yang bertentangan ini.
Cara yang tepat untuk menghadapi situasi ini adalah dengan menggunakan model konstan.
Room Permanent Data Library (DB) siap membantu kami.
Room adalah perpustakaan pemetaan objek yang menyediakan penyimpanan data lokal dengan kode standar minimum. Pada waktu kompilasi, ia memeriksa setiap permintaan untuk kepatuhan dengan skema data Anda, sehingga permintaan SQL yang rusak menghasilkan kesalahan selama kompilasi, dan tidak crash saat runtime. Ruang abstrak dari beberapa detail implementasi dasar dari tabel dan kueri SQL mentah. Ini juga memungkinkan Anda untuk mengamati perubahan dalam data database, termasuk koleksi dan permintaan koneksi, mengekspos perubahan tersebut menggunakan objek LiveData. Itu bahkan secara eksplisit mendefinisikan kendala eksekusi yang memecahkan masalah threading umum, seperti akses ke penyimpanan di utas utama.
Catatan Jika aplikasi Anda sudah menggunakan solusi lain, seperti SQLite Object Relational Mapping (ORM), Anda tidak perlu mengganti solusi yang ada dengan Room. Namun, jika Anda menulis aplikasi baru atau mengatur ulang aplikasi yang sudah ada, sebaiknya gunakan Kamar untuk menyimpan data aplikasi Anda. Dengan demikian, Anda dapat memanfaatkan abstraksi perpustakaan dan verifikasi kueri.Untuk menggunakan Kamar, kita perlu mendefinisikan tata letak lokal kita. Pertama, kami menambahkan penjelasan @Entity ke kelas model data
User
kami dan penjelasan
@PrimaryKey
di bidang
id
kelas. Anotasi ini menandai
User
sebagai tabel dalam basis data kami, dan
id
sebagai kunci utama tabel:
@Entity data class User( @PrimaryKey private val id: String, private val name: String, private val lastName: String )
Kemudian kami membuat kelas database dengan mengimplementasikan
RoomDatabase
untuk aplikasi kami:
@Database(entities = [User::class], version = 1) abstract class UserDatabase : RoomDatabase()
Perhatikan bahwa
UserDatabase
abstrak. Pustaka Room secara otomatis menyediakan implementasi ini. Lihat dokumentasi untuk
Kamar untuk detailnya.
Sekarang kita membutuhkan cara untuk memasukkan data pengguna ke dalam basis data. Untuk tugas ini, kami membuat
objek akses data (DAO) .
@Dao interface UserDao { @Insert(onConflict = REPLACE) fun save(user: User) @Query("SELECT * FROM user WHERE id = :userId") fun load(userId: String): LiveData<User> }
Perhatikan bahwa metode
load
mengembalikan objek bertipe LiveData. Kamar tahu kapan database diubah, dan secara otomatis memberi tahu semua pengamat aktif tentang perubahan data. Karena Room menggunakan
LiveData , operasi ini efisien; itu memperbarui data hanya jika ada setidaknya satu pengamat aktif.
Catatan: Pemeriksaan kamar untuk pembatalan berdasarkan modifikasi tabel, yang berarti dapat mengirim pemberitahuan positif palsu.Setelah mendefinisikan kelas
UserDao
kami, kami kemudian merujuk DAO dari kelas basis data kami:
@Database(entities = [User::class], version = 1) abstract class UserDatabase : RoomDatabase() { abstract fun userDao(): UserDao }
Sekarang kita dapat mengubah
UserRepository
kita untuk memasukkan sumber data Room:
Harap perhatikan bahwa meskipun kami mengubah sumber data dalam
UserRepository
, kami tidak perlu mengubah
UserProfileViewModel
atau
UserProfileFragment
. Pembaruan kecil ini menunjukkan fleksibilitas yang disediakan arsitektur aplikasi kami. Ini juga bagus untuk pengujian karena kami dapat menyediakan
UserRepository
palsu dan menguji
UserProfileViewModel
produksi kami secara bersamaan.
Jika pengguna kembali dalam beberapa hari, maka aplikasi yang menggunakan arsitektur ini kemungkinan akan menampilkan informasi yang sudah ketinggalan zaman sampai repositori menerima informasi yang diperbarui. Tergantung pada kasus penggunaan Anda, Anda mungkin tidak menampilkan informasi yang ketinggalan zaman. Alih-alih, Anda bisa menampilkan data
placeholder , yang menunjukkan nilai dummy dan menunjukkan bahwa aplikasi Anda sedang memuat dan memuat informasi terbaru.
REST API . , , , API, , .
UserRepository
Webservice
, , , .
UserRepository
- .
LiveData. Menggunakan model ini, database berfungsi sebagai satu-satunya sumber kebenaran , dan bagian lain dari aplikasi mengaksesnya melalui kita UserRepository
. Terlepas dari apakah Anda menggunakan cache disk, kami menyarankan repositori Anda mengidentifikasi sumber data sebagai satu-satunya sumber kebenaran untuk sisa aplikasi Anda.Tampilkan kemajuan operasi
, pull-to-refresh, , , . , . , , , LiveData. , β ,
User
.
, :
, .
, :
- : Android UI . β Espresso .
UserProfileViewModel
. UserProfileViewModel
, () . - ViewModel:
UserProfileViewModel
JUnit . , UserRepository
. - UserRepository:
UserRepository
JUnit. Webservice
UserDao
. :
Webservice
, UserDao
, .- UserDao: DAO . - , . , , , β¦
: Room , DAO, JSQL SupportSQLiteOpenHelper . , SQLite SQLite . - -: . , -, . , MockWebServer , .
- : maven .
androidx.arch.core
: JUnit:
InstantTaskExecutorRule:
.CountingTaskExecutorRule:
. Espresso .
β , Android . , , , , .
, , , :
β , β ., , . , .
., , , . β β .
.Tahan godaan untuk membuat label βhanya satuβ yang mengungkapkan detail implementasi internal dari satu modul. Anda mungkin mendapatkan waktu dalam jangka pendek, tetapi kemudian Anda akan mengalami hutang teknis beberapa kali seiring basis kode Anda berkembang.Pikirkan tentang bagaimana membuat setiap modul dapat diuji secara terpisah.Misalnya, memiliki API yang terdefinisi dengan baik untuk mengambil data dari jaringan membuatnya mudah untuk menguji modul yang menyimpan data ini dalam database lokal. Jika sebaliknya Anda mencampur logika kedua modul ini di satu tempat atau mendistribusikan kode jaringan Anda di seluruh basis kode, pengujian menjadi jauh lebih sulit - dalam beberapa kasus bahkan tidak mustahil.Fokus pada inti unik aplikasi Anda untuk menonjol dari aplikasi lain., . , , Android .
., , . , .
., , .
Tambahan: pengungkapan status jaringan
Pada bagian di atas arsitektur aplikasi yang direkomendasikan, kami melewatkan kesalahan jaringan dan status boot untuk menyederhanakan cuplikan kode.Bagian ini menunjukkan cara menampilkan status jaringan menggunakan kelas Sumber Daya, yang merangkum data dan kondisinya.Cuplikan kode berikut memberikan contoh implementasiResource:
, , .
NetworkBoundResource
.
NetworkBoundResource
:

. ,
NetworkBoundResource
, , , . , , , , , .
, .
NetworkBoundResource
.
. . , .
, , , , , .
, , , . , , , . `SUCCESS` , .
API,
NetworkBoundResource
:
:
- ,
ResultType
RequestType
, , API, , . - Ini menggunakan kelas
ApiResponse
untuk permintaan jaringan. ApiResponse
Merupakan pembungkus sederhana untuk kelas Retrofit2.Call
yang mengubah respons menjadi instance LiveData
.
Implementasi penuh kelas NetworkBoundResource
muncul sebagai bagian dari proyek android-Architecture-komponen GitHub .Setelah dibuat, NetworkBoundResource
kita dapat menggunakannya untuk menulis disk dan implementasi yang terlampir User
pada jaringan di kelas UserRepository
: