Komponen Arsitektur Android: LiveData
() translation by (you can also view the original English article)
Kami telah membahas banyak hal dalam rangkaian Komponen Arsitektur Android kami. Kami mulai berbicara tentang ide dibalik arsitektur baru dan melihat komponen kunci yang dipresentasikan di Google I/O. Di posting kedua, kami memulai eksplorasi mendalam kami mengenai Komponen utama dari paket, melihat komponen Lifecycle
dan LiveModel
.. Di posting ini, kita akan terus mengeksplorasi Komponen Arsitektur, kali ini menganalisa komponen LiveData
yang mengagumkan
Saya berasumsi bahwa anda terbiasa dengan konsep dan komponen yang tercakup dalam tutorial terakhir, seperti Lifecycle
, LifecycleOwner
, dan LifecycleObserver
. Jika tidak, lihatlah posting pertama dalam seri ini, di mana saya membahas ide umum di dibalik Arsitektur Android baru dan komponen-komponennya.
Kami akan terus membangun aplikasi sampel yang kami mulai di bagian akhir dari seri ini. Anda bisa menemukannya di tutorial GitHub repo .
1. Komponen LiveData
LiveData
adalah pemegang data. Ia mampu diamati, bisa menampung data apapun, dan lebih dari itu, ia juga merupakan lifecycle-aware. Secara praktis, LiveData
dapat dikonfigurasi hanya untuk mengirim update data saat pengamatnya aktif. Berkat kesadaran lifecycle nya, bila diamati oleh LifecycleOwner
, Komponen LiveData
akan mengirim pembaruan hanya ketika pengamat Lifecycle
masih aktif, dan akan menghapus relasi yang diamati begitu pengamat Lifecycle
hancur
Komponen LiveData
memiliki banyak karakteristik menarik:
- mencegah kebocoran memori saat pengamat terikat ke
Lifecycle
- mencegah crash karena aktivitas berhenti
- data selalu up to date
- menangani perubahan konfigurasi dengan lancar
- memungkinkan untuk berbagi sumber daya
- secara otomatis menangani siklus hidup
2. Mengamati LiveData
Komponen LiveData
mengirimkan update data hanya jika pengamatnya "aktif". Saat diamati oleh LifecycleOwner
,komponen LiveData
menganggap pengamat hanya aktif saat Lifecycle
berada pada keadaan STARTED
atau RESUMED
, jika tidak maka ia akan menganggap pengamat sebagai tidak aktif. Selama keadaan pengamat tidak aktif, LiveData
akan menghentikan arus update data, sampai pengamatnya menjadi aktif sekali lagi. Jika pengamat hancur, LiveData
akan menghapus rujukannya kepada pengamat.



Untuk mencapai perilaku ini, LiveData
menciptakan hubungan yang erat dengan pengamat Lifecycle
saat diamati oleh LifecycleOwner
. Kapasitas ini memudahkannya untuk menghindari kebocoran memori ketika mengamati LiveData
. Namun, jika proses pengamatan disebut berlangsung tanpa ifecycleOwner
,komponen LiveData
tidak akan bereaksi terhadap keadaan Lifecycle
, dan status pengamat harus ditangani secara manual.
Untuk mengamati LiveData
, panggil observe(LifecycleOwner,Observer<T>) or observeForever(Observer<T>).
- observe(LifecycleOwner, Observer<T>): ini adalah cara standar untuk mengamati
LiveData
. Ini mengikat pengamat keLifecycle
, mengubah KeadaanLiveData
aktif dan tidak aktif sesuai dengan kondisiLifecycleOwner
saat ini. - observeForever(Observer<T>): Metode ini tidak menggunakan
LifecycleOwner
, sehinggaLiveData
tidak akan bisa meresponLifecycle
events . Bila menggunakan metode ini, maka sangat penting untuk memanggil removeObserver(Observer<T>), kalau tidak pengamat tidak menjadi sampah yang dikumpulkan, menyebabkan kebocoran memori.
1 |
// called from a LifecycleOwner |
2 |
location.observe( |
3 |
// LifecycleOwner |
4 |
this, |
5 |
// creating an observer |
6 |
Observer { |
7 |
location -> |
8 |
info("location: ${location!!.latitude}, |
9 |
${location.longitude}") |
10 |
}) |
11 |
} |
12 |
|
13 |
// Observing without LifecycleOwner |
14 |
val observer = Observer { |
15 |
location -> |
16 |
info("location: ${location!!.latitude}, |
17 |
${location.longitude}") |
18 |
}) |
19 |
location.observeForever(observer) |
20 |
|
21 |
// when observer without a LivecyleOwner |
22 |
// it is necessary to remove the observers at some point |
23 |
location.removeObserver( observer ) |
3. Menerapkan LiveData
Jenis generik di kelas (LiveData<T>)
mendefinisikan tipe data yang akan dipegang. Sebagai contoh, LiveData<Location>
memegang data Location
. atau LiveData<String>
memegang String
.
Ada dua metode utama yang harus diperhatikan dalam implementasi komponen: onActive()
dan onInactive()
. Kedua metode tersebut bereaksi terhadap keadaan pengamat.
Contoh Implementasi
Dalam contoh proyek kami banyak menggunakan objek LiveData
, tapi kami hanya menerapkan satu: LocationLiveData
. Kelas ini berhubungan dengan GPS location
, melewati posisi saat ini hanya untuk pengamat yang aktif. Perhatikan bahwa kelas memperbarui nilainya di metode onLocationChanged
, sampai ke pengamat aktif saat ini, Location
data refresh.
1 |
class LocationLiveData |
2 |
@Inject |
3 |
constructor( |
4 |
context: Context |
5 |
) : LiveData<Location>(), LocationListener, AnkoLogger { |
6 |
|
7 |
private val locationManager: LocationManager = |
8 |
context.getSystemService(Context.LOCATION_SERVICE) as LocationManager |
9 |
|
10 |
@SuppressLint("MissingPermission") |
11 |
override fun onInactive() { |
12 |
info("onInactive") |
13 |
locationManager.removeUpdates(this) |
14 |
} |
15 |
|
16 |
@SuppressLint("MissingPermission") |
17 |
fun refreshLocation() { |
18 |
info("refreshLocation") |
19 |
locationManager.requestSingleUpdate(LocationManager.GPS_PROVIDER, this, null ) |
20 |
} |
21 |
} |
4. Kelas pembantu MutableLiveData
MutableLiveData<T>
adalah kelas pembantu yang meluaskan LiveData
,dan memaparkan metode postValue
dan setValue
. Selain itu, ia berperilaku persis seperti induknya. Untuk menggunakannya, tentukan jenis data yang dimilikinya, seperti MutableLiveData<String>
untuk menahan String
, dan membuat instance baru.
1 |
val myData: MutableLiveData<String> = MutableLiveData() |
Untuk mengirim update ke pengamat, panggil postValue
atau setValue
. Perilaku metode ini sangat mirip; namun, setValue
akan langsung menetapkan nilai baru dan hanya bisa dipanggil dari thread utama, sementara postValue
membuat tugas baru di thread utama untuk mengatur nilai baru dan bisa dipanggil dari thread latar belakang.
1 |
fun updateData() { |
2 |
// must be called from the main thread |
3 |
myData.value = api.getUpdate |
4 |
} |
5 |
fun updateDataFromBG(){ |
6 |
// may be called from bg thread |
7 |
myData.postValue(api.getUpdate) |
8 |
} |
Penting untuk dipertimbangkan, karena metode postValue
menciptakan Task
baru dan memosting di thread utama, ia akan menjadi lebih lambat dari pada panggilan langsung ke setValue
.
5. Transformasi dari LiveData
Akhirnya, anda harus mengubah LiveData
dan menyebarkan nilai barunya kepada pengamatnya. Atau mungkin anda perlu membuat reaksi berantai antara dua objek LiveData
, membuat satu bereaksi terhadap perubahan pada yang lain. Untuk mengatasi kedua situasi tersebut,anda bisa menggunakan kelas Transformasi
.
Transformasi Peta
Transformasi.map
menerapkan fungsi pada instance LiveData
dan mengirimkan hasilnya kepada pengamatnya, memberi anda kesempatan untuk memanipulasi nilai data.



Sangat mudah untuk mengimplementasikan Transformations.map
. Yang harus anda lakukan adalah menyediakan LiveData
untuk diamati dan Fungtion
untuk dipanggil saat LiveData
yang diobservasi mengalami perubahan, mengingat bahwa Fungtion
harus mengembalikan nilai baru dari LiveData
yang ditransformasikan.
Misalkan anda punya LiveData
yang harus memanggil API saat nilai string
, seperti bidang pencarian, perubahan.
1 |
// LiveData that calls api |
2 |
// when 'searchLive' changes its value |
3 |
val apiLive: LiveData<Result> = Transformations.map( |
4 |
searchLive, |
5 |
{ |
6 |
query -> return@map api.call(query) |
7 |
} |
8 |
) |
9 |
|
10 |
// Every time that 'searchLive' have |
11 |
// its value updated, it will call |
12 |
// 'apiLive' Transformation.map |
13 |
fun updateSearch( query: String ) { |
14 |
searchLive.postValue( query ) |
15 |
} |
Transformasi SwitchMap
Transformations.switchMap
sangat mirip dengan Transformations.map
,tetapi ia harus mengembalikan objek LiveData
sebagai hasilnya. Ini sedikit sulit untuk digunakan, namun memungkinkan anda untuk membangun reaksi berantai yang kuat.
Dalam proyek kami, kami menggunakan Transformations.switchMap
untuk menciptakan reaksi antara LocationLiveData
dan ApiResponse<weatherResponse>
.
-
Transformation.switchMap
kami mengamati perubahanLocationLiveData
. - Nilai
LocationLiveData
yang diperbarui digunakan untuk memanggilMainRepository
untuk mendapatkan cuaca bagi lokasi yang ditentukan. - Repositori memanggil
OpenWeatherService
yang menghasilkanLiveData<ApiResponse<WeatherResponse>>
hasilnya. - Lalu,
LiveData
yang kembali diamati olehMediatorLiveData
, yang bertanggung jawab untuk memodifikasi nilai yang diterima dan memperbarui cuaca yang muncul di lapisan tampilan.
1 |
class MainViewModel |
2 |
@Inject |
3 |
constructor( |
4 |
private val repository: MainRepository |
5 |
) |
6 |
: ViewModel(), AnkoLogger { |
7 |
|
8 |
// Location |
9 |
private val location: LocationLiveData = |
10 |
repository.locationLiveDa() |
11 |
|
12 |
private var weatherByLocationResponse: |
13 |
LiveData<ApiResponse<WeatherResponse>> = |
14 |
Transformations.switchMap( |
15 |
location, |
16 |
{ |
17 |
l -> |
18 |
info("weatherByLocation: \nlocation: $l") |
19 |
return@switchMap repository.getWeatherByLocation(l) |
20 |
} |
21 |
) |
22 |
|
23 |
} |
Hati-hati dengan operasi yang memakan waktu dalam transformasi LiveData
anda. Dalam kode di atas, kedua metode Transformasi
berjalan di thread utama
6. MediatorLiveData
MediatorLiveData
adalah jenis LiveData
yang lebih maju. Ia memiliki kemampuan yang sangat mirip dengan kelas Transformasi
: ia mampu bereaksi terhadap objek LiveData
yang lain, memanggil Fungsi
ketika data yang diamati berubah. Namun, ia memiliki banyak keunggulan jika dibandingkan dengan Transformasi
, karena ia tidak perlu dijalankan pada thread utama dan dapat mengamati beberapa LiveData
sekaligus.
mengamati LiveData
, panggil addSource(LiveData,Observer<S>)
,membuat pengamat bereaksi terhadap metode onChanged
dari LiveData
yang diberikan. Untuk menghentikan pengamatan panggil removeSource(LiveData<S>)
.
1 |
val mediatorData: MediatorLiveData<String> = MediatorLiveData() |
2 |
mediatorData.addSource( |
3 |
dataA, |
4 |
{ |
5 |
value -> |
6 |
// react to value |
7 |
info("my value $value") |
8 |
} |
9 |
) |
10 |
mediatorData.addSource( |
11 |
dataB, |
12 |
{ |
13 |
value -> |
14 |
// react to value |
15 |
info("my value $value") |
16 |
// we can remove the source once used |
17 |
mediatorData.removeSource(dataB) |
18 |
} |
19 |
) |
Dalam proyek kami, data yang diamati oleh lapisan view yang berisi cuaca untuk dimunculkan adalah MediatorLiveData
. Komponen tersebut mengamati dua benda LiveData
lainnya: weatherByLocationResponse
, yang menerima informasi cuaca berdasarkan lokasi, dan weatherByCityResponse
, yang menerima informasi cuaca dengan nama kota. setiap kali benda tersebut diperbarui, weatherByCityResponse
akan memperbarui lapisan tampilan dengan cuaca saat ini yang diminta.
Dalam MainViewModel
, kami mengamati LiveData
dan memberikan objek weather
untuk dilihat
1 |
class MainViewModel |
2 |
@Inject |
3 |
constructor( |
4 |
private val repository: MainRepository |
5 |
) |
6 |
: ViewModel(), AnkoLogger { |
7 |
|
8 |
// ... |
9 |
// Value observed by View. |
10 |
// It transform a WeatherResponse to a WeatherMain. |
11 |
private val weather: |
12 |
MediatorLiveData<ApiResponse<WeatherMain>> = |
13 |
MediatorLiveData() |
14 |
|
15 |
// retrieve weather LiveData |
16 |
fun getWeather(): LiveData<ApiResponse<WeatherMain>> { |
17 |
info("getWeather") |
18 |
return weather |
19 |
} |
20 |
|
21 |
private fun addWeatherSources(){ |
22 |
info("addWeatherSources") |
23 |
weather.addSource( |
24 |
weatherByCityResponse, |
25 |
{ |
26 |
w -> |
27 |
info("addWeatherSources: \nweather: ${w!!.data!!}") |
28 |
updateWeather(w.data!!) |
29 |
} |
30 |
) |
31 |
weather.addSource( |
32 |
weatherByLocationResponse, |
33 |
{ |
34 |
w -> |
35 |
info("addWeatherSources: weatherByLocationResponse: \n${w!!.data!!}") |
36 |
updateWeather(w.data!!) |
37 |
} |
38 |
) |
39 |
|
40 |
} |
41 |
|
42 |
private fun updateWeather(w: WeatherResponse){ |
43 |
info("updateWeather") |
44 |
// getting weather from today |
45 |
val weatherMain = WeatherMain.factory(w) |
46 |
// save on shared preferences |
47 |
repository.saveWeatherMainOnPrefs(weatherMain) |
48 |
// update weather value |
49 |
weather.postValue(ApiResponse(data = weatherMain)) |
50 |
} |
51 |
|
52 |
init { |
53 |
// ... |
54 |
addWeatherSources() |
55 |
} |
56 |
} |
Dalam MainActivity
, cuaca diamati dan hasilnya ditampilkan kepada pengguna
1 |
private fun initModel() { |
2 |
// Get ViewModel |
3 |
viewModel = ViewModelProviders.of(this, viewModelFactory) |
4 |
.get(MainViewModel::class.java) |
5 |
|
6 |
if (viewModel != null) { |
7 |
|
8 |
// observe weather |
9 |
viewModel!!.getWeather().observe( |
10 |
this@MainActivity, |
11 |
Observer { |
12 |
r -> |
13 |
if ( r != null ) { |
14 |
info("Weather received on MainActivity:\n $r") |
15 |
if (!r.hasError()) { |
16 |
// Doesn't have any errors |
17 |
info("weather: ${r.data}") |
18 |
if (r.data != null) |
19 |
setUI(r.data) |
20 |
} else { |
21 |
// error |
22 |
error("error: ${r.error}") |
23 |
isLoading(false) |
24 |
if (r.error!!.statusCode != 0) { |
25 |
if (r.error!!.message != null) |
26 |
toast(r.error.message!!) |
27 |
else |
28 |
toast("An error occurred") |
29 |
} |
30 |
} |
31 |
} |
32 |
} |
33 |
) |
34 |
|
35 |
} |
36 |
} |
MediatorLiveData
juga digunakan sebagai basis objek yang menangani panggilan ke OpenWeatherMap API. Lihatlah implementasi ini; lebih maju daripada yang di atas, dan itu benar-benar layak dipelajari. Jika Anda tertarik, lihatlah OpenWeatherService
, perhatikan Mediator<T>
class
Kesimpulan
Kita hampir berada di ujung eksplorasi Komponen Arsitektur Android. Sekarang, anda harus cukup mengerti untuk membuat beberapa aplikasi hebat. Di postingan selanjutnya, kita akan jelajahi Room
, ORM yang membungkus SQLite
dan bisa berproduksi hasil LiveData
. Komponen Room
sangat sesuai dengan Arsitektur ini, dan ini adalah bagian terakhir dari teka-teki.
Sampai jumpa lagi! Dan sementara itu, lihat beberapa posting kami yang lain mengenai pengembangan aplikasi Android!
- Android SDKCara Menggunakan Google Cloud Vision API di Android AppsAshraff Hathibelagal
- JavaPola Desain Android: Pola PengamatChike Mgbemena
- KotlinKotlin Dari awal: Variabel, Jenis Dasar, dan ArrayChike Mgbemena
- KOTLINKotlin dari awal: Nullability, Loops, and ConditionsChike Mgbemena
- Android SDKApa itu Android Instant Apps?Jessica Thornsby