Advertisement
  1. Code
  2. Android SDK

Komponen Arsitektur Android: LiveData 

Scroll to top
Read Time: 8 min
This post is part of a series called Android Architecture Components.
Android Architecture Components: Lifecycle and LiveModel
Android Architecture Components: the Room Persistence Library

() 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   LifecycleLifecycleOwner, 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.

LiveData update flowLiveData update flowLiveData update flow

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 ke Lifecycle, mengubah Keadaan LiveData aktif dan tidak aktif  sesuai dengan kondisi LifecycleOwner saat ini.
  • observeForever(Observer<T>): Metode ini tidak menggunakan  LifecycleOwner, sehingga   LiveData tidak akan bisa merespon Lifecycle 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.

Transformationsmap flowTransformationsmap flowTransformationsmap flow

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>

  1. Transformation.switchMap kami mengamati perubahan LocationLiveData.
  2. Nilai LocationLiveData yang diperbarui digunakan untuk memanggil MainRepository untuk mendapatkan cuaca bagi lokasi yang ditentukan.
  3. Repositori memanggil OpenWeatherService yang menghasilkan LiveData<ApiResponse<WeatherResponse>>   hasilnya.
  4. Lalu, LiveData yang kembali diamati oleh MediatorLiveData, 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!

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.