
Sekalipun Anda tidak melakukan kesalahan ini, ada baiknya mengingatnya agar tidak menemui beberapa masalah di masa depan.
1. Kebocoran pengamat LiveData dalam fragmen
Fragmen memiliki siklus hidup yang kompleks, dan ketika sebuah fragmen memutuskan dan bergabung kembali dengan suatu Aktivitas, fragmen itu tidak selalu dihancurkan. Misalnya, fragmen yang disimpan tidak dihancurkan selama perubahan konfigurasi. Ketika ini terjadi, instance fragmen tetap, dan hanya View-nya yang dihancurkan, jadi onDestroy()
tidak dipanggil dan status DESTROYED tidak tercapai.
Ini berarti bahwa jika kita mulai mengamati LiveData di onCreateView()
atau lebih baru (biasanya di onActivityCreated()
) dan meneruskan fragmen sebagai LifecycleOwner:
class BooksFragment: Fragment() { private lateinit var viewModel: BooksViewModel override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater.inflate(R.layout.fragment_books, container) } override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) viewModel = ViewModelProviders.of(this).get(BooksViewModel::class.java) viewModel.liveData.observe(this, Observer { updateViews(it) })
maka kami akan mengirimkan instance Observer identik baru setiap kali sebuah fragmen bergabung kembali, tetapi LiveData tidak akan menghapus pengamat sebelumnya karena LifecycleOwner (fragmen) belum mencapai status DESTROYED. Ini pada akhirnya mengarah ke semakin banyak pengamat aktif yang identik dan secara bersamaan, dan kode yang sama dari onChanged()
dieksekusi beberapa kali.
Masalahnya awalnya dilaporkan di sini , dan penjelasan yang lebih rinci dapat ditemukan di sini .
Solusi yang disarankan adalah menggunakan getViewLifecycleOwner () atau getViewLifecycleOwnerLiveData () dari siklus fragmen, yang ditambahkan ke perpustakaan dukungan 28.0.0 dan AndroidX 1.0.0, sehingga LiveData akan menghapus pengamat setiap kali fragmen View dihancurkan:
class BooksFragment : Fragment() { ... override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) viewModel = ViewModelProviders.of(this).get(BooksViewModel::class.java) viewModel.liveData.observe(viewLifecycleOwner, Observer { updateViews(it) }) } ... }
2. Reboot data setelah setiap rotasi layar
Kami terbiasa meletakkan inisialisasi dan konfigurasi logika di onCreate()
di Activity (dan dengan analogi di onCreateView()
atau yang lebih baru dalam fragmen), jadi pada titik ini mungkin tergoda untuk memulai pemuatan beberapa data dalam ViewModels. Namun, tergantung pada logika Anda, ini dapat menyebabkan reload data setelah setiap rotasi layar (bahkan jika ViewModel digunakan), yang dalam kebanyakan kasus tidak ada gunanya.
Contoh:
class ProductViewModel( private val repository: ProductRepository ) : ViewModel() { private val productDetails = MutableLiveData<Resource<ProductDetails>>() private val specialOffers = MutableLiveData<Resource<SpecialOffers>>() fun getProductsDetails(): LiveData<Resource<ProductDetails>> { repository.getProductDetails()
Keputusan juga tergantung pada logika Anda. Jika repositori akan menyimpan data, maka kode di atas mungkin akan berfungsi dengan benar. Juga, masalah ini dapat diselesaikan dengan cara lain:
- Gunakan sesuatu yang mirip dengan AbsentLiveData dan mulai unduh hanya jika data belum diterima;
- Mulai mengunduh data saat itu benar-benar diperlukan. Misalnya, di OnClickListener;
- Dan mungkin yang paling sederhana: masukkan panggilan panggilan di konstruktor ViewModel dan gunakan getter sederhana:
class ProductViewModel( private val repository: ProductRepository ) : ViewModel() { private val productDetails = MutableLiveData<Resource<ProductDetails>>() private val specialOffers = MutableLiveData<Resource<SpecialOffers>>() init { loadProductsDetails()
3. Kebocoran ViewModels
Pengembang arsitektur menjelaskan bahwa kita tidak boleh melewatkan tautan Tautan ke ViewModel.

tetapi kita juga harus berhati-hati ketika meneruskan referensi ke ViewModels ke kelas lain. Setelah Activity (atau fragmen) selesai, ViewModel tidak dapat dirujuk ke objek apa pun yang Activity dapat bertahan, sehingga ViewModel dapat dihancurkan oleh pengumpul sampah.
Dalam contoh kebocoran ini, pendengar Repositori, yang ditulis dalam gaya Singleton, diteruskan ke ViewModel. Selanjutnya, pendengar tidak dimusnahkan:
@Singleton class LocationRepository() { private var listener: ((Location) -> Unit)? = null fun setOnLocationChangedListener(listener: (Location) -> Unit) { this.listener = listener } private fun onLocationUpdated(location: Location) { listener?.invoke(location) } } class MapViewModel: AutoClearViewModel() { private val liveData = MutableLiveData<LocationRepository.Location>() private val repository = LocationRepository() init { repository.setOnLocationChangedListener { liveData.value = it } } }
Solusi di sini bisa dengan menghapus pendengar dalam metode onCleared()
, menyimpannya sebagai WeakReference
di repositori, atau menggunakan LiveData untuk berkomunikasi antara repositori dan ViewModel:
@Singleton class LocationRepository() { private var listener: ((Location) -> Unit)? = null fun setOnLocationChangedListener(listener: (Location) -> Unit) { this.listener = listener } fun removeOnLocationChangedListener() { this.listener = null } private fun onLocationUpdated(location: Location) { listener?.invoke(location) } } class MapViewModel: AutoClearViewModel() { private val liveData = MutableLiveData<LocationRepository.Location>() private val repository = LocationRepository() init { repository.setOnLocationChangedListener { liveData.value = it } } override onCleared() { repository.removeOnLocationChangedListener() } }
4. Izinkan View untuk memodifikasi LiveData
Ini bukan bug, tetapi bertentangan dengan pemisahan kepentingan. View - fragmen dan Kegiatan - seharusnya tidak dapat memperbarui LiveData dan, oleh karena itu, keadaannya sendiri, karena itu adalah tanggung jawab ViewModels. Lihat seharusnya hanya dapat menonton LiveData.
Karena itu, kita harus merangkum akses ke MutableLiveData:
class CatalogueViewModel : ViewModel() {
5. Membuat dependensi ViewModel setelah setiap perubahan konfigurasi
ViewModels tahan terhadap perubahan konfigurasi, seperti rotasi layar, jadi jika Anda membuatnya setiap kali terjadi perubahan, kadang-kadang dapat menyebabkan perilaku yang tidak dapat diprediksi, terutama jika beberapa logika dibangun ke dalam perancang.
Meskipun ini mungkin tampak cukup jelas, itu adalah sesuatu yang mudah untuk diabaikan ketika menggunakan ViewModelFactory, yang biasanya memiliki dependensi yang sama dengan ViewModel yang dibuatnya.
ViewModelProvider menyimpan instance dari ViewModel, tetapi bukan instance dari ViewModelFactory, jadi jika kita memiliki kode ini:
class MoviesViewModel( private val repository: MoviesRepository, private val stringProvider: StringProvider, private val authorisationService: AuthorisationService ) : ViewModel() { ... } class MoviesViewModelFactory( private val repository: MoviesRepository, private val stringProvider: StringProvider, private val authorisationService: AuthorisationService ) : ViewModelProvider.Factory { override fun <T : ViewModel> create(modelClass: Class<T>): T { return MoviesViewModel(repository, stringProvider, authorisationService) as T } } class MoviesActivity : AppCompatActivity() { @Inject lateinit var viewModelFactory: MoviesViewModelFactory private lateinit var viewModel: MoviesViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_movies) injectDependencies() viewModel = ViewModelProviders.of(this, viewModelFactory).get(MoviesViewModel::class.java) } ... }
maka setiap kali perubahan konfigurasi terjadi, kami akan membuat instance baru dari ViewModelFactory dan, oleh karena itu, tanpa kebutuhan khusus untuk membuat instance baru dari semua dependensinya (asalkan mereka entah bagaimana tidak terbatas).
class MoviesViewModel( private val repository: MoviesRepository, private val stringProvider: StringProvider, private val authorisationService: AuthorisationService ) : ViewModel() { ... } class MoviesViewModelFactory( private val repository: Provider<MoviesRepository>, private val stringProvider: Provider<StringProvider>, private val authorisationService: Provider<AuthorisationService> ) : ViewModelProvider.Factory { override fun <T : ViewModel> create(modelClass: Class<T>): T { return MoviesViewModel(repository.get(), stringProvider.get(), authorisationService.get() ) as T } } class MoviesActivity : AppCompatActivity() { @Inject lateinit var viewModelFactory: MoviesViewModelFactory private lateinit var viewModel: MoviesViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_movies) injectDependencies() viewModel = ViewModelProviders.of(this, viewModelFactory).get(MoviesViewModel::class.java) } ... }