
Mesmo que você não cometa esses erros, vale a pena lembrá-los para não encontrar alguns problemas no futuro.
1. Vazamento de observadores do LiveData em fragmentos
Os fragmentos têm um ciclo de vida complexo e, quando um fragmento se desconecta e se une a uma Atividade, nem sempre é destruído. Por exemplo, fragmentos salvos não são destruídos durante as alterações na configuração. Quando isso acontece, a instância do fragmento permanece e apenas sua View é destruída, portanto onDestroy()
não onDestroy()
chamado e o estado DESTROYED não é atingido.
Isso significa que, se começarmos a observar o LiveData em onCreateView()
ou posterior (geralmente em onActivityCreated()
) e passar o fragmento como 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) })
transmitiremos uma nova instância idêntica do Observer sempre que um fragmento se unir novamente, mas o LiveData não excluirá os observadores anteriores porque o LifecycleOwner (fragmento) não atingiu o estado DESTRUÍDO. Isso leva a um número crescente de observadores idênticos e simultaneamente ativos, e o mesmo código de onChanged()
é executado várias vezes.
O problema foi originalmente relatado aqui e uma explicação mais detalhada pode ser encontrada aqui .
A solução recomendada é usar getViewLifecycleOwner () ou getViewLifecycleOwnerLiveData () do ciclo de vida do fragmento, que foram adicionados à biblioteca de suporte 28.0.0 e AndroidX 1.0.0, para que o LiveData exclua observadores toda vez que o fragmento View for destruído:
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. Reinicializando dados após cada rotação da tela
Estamos acostumados a colocar a lógica de inicialização e configuração em onCreate()
em Activity (e por analogia em onCreateView()
ou mais tarde em fragmentos), portanto, neste ponto, pode ser tentador iniciar o carregamento de alguns dados no ViewModels. No entanto, dependendo da sua lógica, isso pode levar ao recarregamento de dados após cada rotação da tela (mesmo que o ViewModel tenha sido usado), o que, na maioria dos casos, é simplesmente inútil.
Um exemplo:
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()
A decisão também depende da sua lógica. Se o repositório armazenar em cache os dados, o código acima provavelmente funcionará corretamente. Além disso, esse problema pode ser resolvido de outras maneiras:
- Use algo semelhante ao AbsentLiveData e inicie o download apenas se os dados não tiverem sido recebidos;
- Comece a baixar dados quando for realmente necessário. Por exemplo, no OnClickListener;
- E provavelmente o mais simples: coloque chamadas de carga no construtor ViewModel e use getters simples:
class ProductViewModel( private val repository: ProductRepository ) : ViewModel() { private val productDetails = MutableLiveData<Resource<ProductDetails>>() private val specialOffers = MutableLiveData<Resource<SpecialOffers>>() init { loadProductsDetails()
3. Modelos de exibição de vazamento
Os desenvolvedores de arquitetura deixaram claro que não devemos passar os links do View para o ViewModel.

mas também devemos ter cuidado ao passar referências ao ViewModels para outras classes. Após a conclusão da atividade (ou fragmento), o ViewModel não pode ser referenciado a nenhum objeto que a atividade possa sobreviver, para que o ViewModel possa ser destruído pelo coletor de lixo.
Neste exemplo de vazamento, o ouvinte de repositório, escrito no estilo Singleton, é passado para o ViewModel. Posteriormente, o ouvinte não é destruído:
@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 } } }
A solução aqui pode ser remover o ouvinte no método onCleared()
, salvá-lo como WeakReference
no repositório ou usar o LiveData para se comunicar entre o repositório e o 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. Permitir que o View modifique o LiveData
Isso não é um bug, mas contradiz a separação de interesses. O View - fragmentos e Activity - não deve poder atualizar o LiveData e, portanto, seu próprio estado, porque é de responsabilidade do ViewModels. A exibição deve poder assistir apenas ao LiveData.
Portanto, devemos encapsular o acesso ao MutableLiveData:
class CatalogueViewModel : ViewModel() {
5. Criando dependências do ViewModel após cada alteração na configuração
Os ViewModels suportam alterações de configuração, como rotação da tela, portanto, se você as criar toda vez que ocorrer uma alteração, algumas vezes poderá levar a um comportamento imprevisível, especialmente se alguma lógica for incorporada ao designer.
Embora isso possa parecer bastante óbvio, é algo fácil de ignorar ao usar o ViewModelFactory, que geralmente possui as mesmas dependências que o ViewModel que ele cria.
ViewModelProvider salva uma instância de ViewModel, mas não uma instância de ViewModelFactory, portanto, se tivermos este código:
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) } ... }
toda vez que ocorrer uma alteração na configuração, criaremos uma nova instância do ViewModelFactory e, portanto, sem necessidade especial de criar novas instâncias de todas as suas dependências (desde que não sejam limitadas de nenhuma maneira).
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) } ... }