Surely the Android Architecture Components library set is no longer strange to Android Developer ?? For those who are new to it, I don’t say, but even with some players, it may still make some mistakes when using the Android Architecture Components. I am sure )). Or, if you haven’t had it, in this article, I would like to mention 5 common mistakes that we often make when using the Architecture Components.
1. Leaking LiveData observers in Fragment
Since Fragment has its own lifecycle, even if Fragment is detached and re-attached, it is still possible that k is destroyed, for example, if retained Fragment will not be destroyed when the configuration changes. This means that when observing LiveData in onCreateView () or in subsequent callbacks (usually onActivityCreated ()) and Fragment pass as LivecycleOwner as an example
1 | 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) }) // Risky: Passing Fragment as LifecycleOwner } ... } |
The above code shows that it will always create Observer when Fragment re-attached. However, LiveData will not remove the old observer for LifecycleOwner (here Fragment) k is destroyed. So to solve this problem we will use getViewLifecycleOwner () or getViewLifecycleOwnerLiveData () instead of direct Fragment instance.
2. Reloading data after every rotation
Usually we put logic in onCreate () – for Activity, onCreateView – for vs Fragment. This seems to be true in all cases, but if we look at it, it will not really work when using the same ViewModel. Because every time we rotate the screen, activity or fragment will recreate, that the ViewModel guy has the mechanism k is destroyed when rotating. Therefore, placing logic in this case can cause redundancy (since the old data must be reloaded). To handle this simply, we just need to put the data load logic while initializing the ViewModel or just load the data when doing a specific action. As the example below:
1 | class ProductViewModel( private val repository: ProductRepository ) : ViewModel() { private val productDetails = MutableLiveData<Resource<ProductDetails>>() private val specialOffers = MutableLiveData<Resource<SpecialOffers>>() init { loadProductsDetails() // ViewModel is created only once during Activity/Fragment lifetime } private fun loadProductsDetails() { // private, just utility method to be invoked in constructor repository.getProductDetails() // Loading ProductDetails from network/database ... // Getting ProductDetails from repository and updating productDetails LiveData } fun loadSpecialOffers() { // public, intended to be invoked by other classes when needed repository.getSpecialOffers() // Loading SpecialOffers from network/database ... // Getting SpecialOffers from repository and updating _specialOffers LiveData } fun getProductDetails(): LiveData<Resource<ProductDetails>> { // Simple getter return productDetails } fun getSpecialOffers(): LiveData<Resource<SpecialOffers>> { // Simple getter return specialOffers } } class ProductActivity : AppCompatActivity() { lateinit var productViewModelFactory: ProductViewModelFactory override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val viewModel = ViewModelProviders.of(this, productViewModelFactory).get(ProductViewModel::class.java) viewModel.getProductDetails().observe(this, Observer { /*...*/ }) // Just setting observer viewModel.getSpecialOffers().observe(this, Observer { /*...*/ }) // Just setting observer button_offers.setOnClickListener { viewModel.loadSpecialOffers() } } } |
3. Leaking ViewModel
As we all know, we should not pass the View reference to the ViewModel. However, we should not pass ViewModel reference to other classes. For example, the following case:
1 | @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 { // Risky: Passing listener (which holds reference to the MapViewModel) liveData.value = it // to singleton scoped LocationRepository } } } |
Here Repository is declared as Singleton and we transmit the listener to Repository in the ViewModel but not clear it after use. To solve this problem we can do the following:
1 | @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 { // Risky: Passing listener (which holds reference to the MapViewModel) liveData.value = it // to singleton scoped LocationRepository } } override onCleared() { // GOOD: Listener instance from above and MapViewModel repository.removeOnLocationChangedListener() // can now be garbage collected } } |
4. Exposing LiveData as mutable để xem xem xem
This means that the Views-Fragment or Activity should not manually update the LiveData, but let the ViewModel handle this. )) The mission of View-Fragment or Activity is only observe LiveData. For example:
1 | class CatalogueViewModel : ViewModel() { // BAD: Exposing mutable LiveData val products = MutableLiveData<Products>() // GOOD: Encapsulate access to mutable LiveData through getter private val promotions = MutableLiveData<Promotions>() fun getPromotions(): LiveData<Promotions> = promotions // GOOD: Encapsulate access to mutable LiveData using backing property private val _offers = MutableLiveData<Offers>() val offers: LiveData<Offers> = _offers fun loadData(){ products.value = loadProducts() // Other classes can also set products value promotions.value = loadPromotions() // Only CatalogueViewModel can set promotions value _offers.value = loadOffers() // Only CatalogueViewModel can set offers value } } |
5. Creating ViewModel’s dependencies after every configuration change
Because the ViewModel exists in the configuration change cases, the creation of dependencies each time that configuration changes is redundant, which may even cause unintended incidents. Especially when we leave logical processing in dependencies in the constructor. Let’s take a look at the example below:
1 | class MoviesViewModel( private val repository: MoviesRepository, private val stringProvider: StringProvider, private val authorisationService: AuthorisationService ) : ViewModel() { ... } class MoviesViewModelFactory( // We need to create instances of below dependencies to create instance of MoviesViewModelFactory private val repository: MoviesRepository, private val stringProvider: StringProvider, private val authorisationService: AuthorisationService ) : ViewModelProvider.Factory { override fun <T : ViewModel> create(modelClass: Class<T>): T { // but this method is called by ViewModelProvider only if ViewModel wasn't already created 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?) { // Called each time Activity is recreated super.onCreate(savedInstanceState) setContentView(R.layout.activity_movies) injectDependencies() // Creating new instance of MoviesViewModelFactory viewModel = ViewModelProviders.of(this, viewModelFactory).get(MoviesViewModel::class.java) } ... } |
Each time the configuration changes, we will create a new instance of ViewModelFactory so we do not need to create a new instance of all dependecies (ViewModelFactory can do this). The solution here is that we will postpone creating dependencies until the create () function is called, because it is only called in the Activity or Fragment lifecycle.
1 | class MoviesViewModel( private val repository: MoviesRepository, private val stringProvider: StringProvider, private val authorisationService: AuthorisationService ) : ViewModel() { ... } class MoviesViewModelFactory( private val repository: Provider<MoviesRepository>, // Passing Providers here private val stringProvider: Provider<StringProvider>, // instead of passing directly dependencies private val authorisationService: Provider<AuthorisationService> ) : ViewModelProvider.Factory { override fun <T : ViewModel> create(modelClass: Class<T>): T { // This method is called by ViewModelProvider only if ViewModel wasn't already created return MoviesViewModel(repository.get(), stringProvider.get(), // Deferred creating dependencies only if new insance of ViewModel is needed 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() // Creating new instance of MoviesViewModelFactory viewModel = ViewModelProviders.of(this, viewModelFactory).get(MoviesViewModel::class.java) } ... } |
Conclude
So, I have shown you some common mistakes when using the Architecture Components. Hopefully my article will be a bit helpful to you guys ). If you see anything that is not understood or unreasonable, please comment to contribute to me and everyone else !! Thanks for reading ))
Reference source: Link here!