Conceitos e implementação do Jetpack Compose
Os componentes com reconhecimento de ciclo de vida executam ações em resposta a uma mudança no status do ciclo de vida de outro componente, como atividades e fragmentos. Esses componentes ajudam você a produzir códigos mais organizados e, com frequência, mais leves e mais fáceis de manter.
Um padrão comum é implementar as ações dos componentes dependentes nos métodos de ciclo de vida de atividades e fragmentos. No entanto, esse padrão leva a um código desorganizado e à proliferação de erros. Usando componentes com reconhecimento de ciclo de vida, é possível mover o código de componentes dependentes dos métodos de ciclo de vida para os próprios componentes.
O pacote androidx.lifecycle fornece classes e interfaces que permitem
criar componentes compatíveis com o ciclo de vida, que podem
ajustar automaticamente o comportamento com base no estado atual do ciclo de vida de uma
atividade ou um fragmento.
A maioria dos componentes de app definidos no Android Framework tem ciclos de vida anexados a eles. Os ciclos de vida são gerenciados pelo sistema operacional ou pelo código da estrutura em execução no processo. Eles são fundamentais para o modo como o Android funciona e seu aplicativo precisa respeitá-los. Não fazer isso pode provocar vazamentos de memória ou até mesmo falhas no aplicativo.
Imagine que temos uma atividade que mostra o local do dispositivo na tela. Uma implementação comum pode ser semelhante a esta:
Kotlin
internal class MyLocationListener(
private val context: Context,
private val callback: (Location) -> Unit
) {
fun start() {
// connect to system location service
}
fun stop() {
// disconnect from system location service
}
}
class MyActivity : AppCompatActivity() {
private lateinit var myLocationListener: MyLocationListener
override fun onCreate(...) {
myLocationListener = MyLocationListener(this) { location ->
// update UI
}
}
public override fun onStart() {
super.onStart()
myLocationListener.start()
// manage other components that need to respond
// to the activity lifecycle
}
public override fun onStop() {
super.onStop()
myLocationListener.stop()
// manage other components that need to respond
// to the activity lifecycle
}
}
Java
class MyLocationListener {
public MyLocationListener(Context context, Callback callback) {
// ...
}
void start() {
// connect to system location service
}
void stop() {
// disconnect from system location service
}
}
class MyActivity extends AppCompatActivity {
private MyLocationListener myLocationListener;
@Override
public void onCreate(...) {
myLocationListener = new MyLocationListener(this, (location) -> {
// update UI
});
}
@Override
public void onStart() {
super.onStart();
myLocationListener.start();
// manage other components that need to respond
// to the activity lifecycle
}
@Override
public void onStop() {
super.onStop();
myLocationListener.stop();
// manage other components that need to respond
// to the activity lifecycle
}
}
Mesmo que esse exemplo pareça bom, em um app real, você acabaria tendo muitas chamadas que gerenciam a IU e outros componentes em resposta ao estado atual do ciclo de vida. O gerenciamento de vários componentes coloca uma quantidade considerável de
código em métodos de ciclo de vida, como onStart() e onStop, o que
dificulta a manutenção.
Além disso, não há qualquer garantia de que o componente será iniciado antes da atividade ou que o fragmento será interrompido. Isso é especialmente verdadeiro se precisarmos realizar uma
operação de longa duração, como uma verificação de configuração em onStart. Isso
pode causar uma disputa em que o método onStop() termina antes do
onStart, mantendo o componente ativo por mais tempo que o necessário.
Kotlin
class MyActivity : AppCompatActivity() {
private lateinit var myLocationListener: MyLocationListener
override fun onCreate(...) {
myLocationListener = MyLocationListener(this) { location ->
// update UI
}
}
public override fun onStart() {
super.onStart()
Util.checkUserStatus { result ->
// what if this callback is invoked AFTER activity is stopped?
if (result) {
myLocationListener.start()
}
}
}
public override fun onStop() {
super.onStop()
myLocationListener.stop()
}
}
Java
class MyActivity extends AppCompatActivity {
private MyLocationListener myLocationListener;
public void onCreate(...) {
myLocationListener = new MyLocationListener(this, location -> {
// update UI
});
}
@Override
public void onStart() {
super.onStart();
Util.checkUserStatus(result -> {
// what if this callback is invoked AFTER activity is stopped?
if (result) {
myLocationListener.start();
}
});
}
@Override
public void onStop() {
super.onStop();
myLocationListener.stop();
}
}
O pacote androidx.lifecycle fornece classes e interfaces que ajudam
a lidar com esses problemas de forma resiliente e isolada.
Ciclo de vida
Lifecycle é uma classe que contém as informações sobre o estado do ciclo de vida
de um componente (como uma atividade ou um fragmento) e permite que outros objetos
observem esse estado.
O Lifecycle usa duas enumerações principais para rastrear o status do ciclo de vida do
componente associado:
Evento
Os eventos de ciclo de vida que são enviados pelo framework e pela classe
Lifecycle. Esses eventos são mapeados para os eventos de callback em atividades
e fragmentos.
Estado
O estado atual do componente rastreado pelo objeto Lifecycle.
Pense nos estados como nós de um gráfico e nos eventos como as arestas entre esses nós.
Uma classe pode monitorar o status do ciclo de vida do componente implementando a
DefaultLifecycleObserver e substituindo métodos correspondentes, como
onCreate, onStart etc. Depois, você pode adicionar um observador chamando o método
addObserver() da classe Lifecycle e transmitindo uma
instância do seu observador, conforme mostrado no exemplo a seguir:
Kotlin
class MyObserver : DefaultLifecycleObserver {
override fun onResume(owner: LifecycleOwner) {
connect()
}
override fun onPause(owner: LifecycleOwner) {
disconnect()
}
}
myLifecycleOwner.getLifecycle().addObserver(MyObserver())
Java
public class MyObserver implements DefaultLifecycleObserver {
@Override
public void onResume(LifecycleOwner owner) {
connect()
}
@Override
public void onPause(LifecycleOwner owner) {
disconnect()
}
}
myLifecycleOwner.getLifecycle().addObserver(new MyObserver());
No exemplo acima, o objeto myLifecycleOwner implementa a interface
LifecycleOwner, que é explicada na seção a seguir.
LifecycleOwner
LifecycleOwner é uma interface de método único que indica que a classe
tem um Lifecycle. Ela tem um método, getLifecycle, que precisa ser
implementado pela classe. Se você estiver tentando gerenciar o ciclo de vida de um processo de aplicativo
inteiro, consulte ProcessLifecycleOwner.
Essa interface abstrai a propriedade de um Lifecycle de classes
individuais, como Fragment e AppCompatActivity, e permite
escrever componentes que funcionam com elas. Qualquer classe de aplicativo personalizada pode
implementar a interface LifecycleOwner.
Os componentes que implementam DefaultLifecycleObserver funcionam perfeitamente com
os que implementam LifecycleOwner, porque um proprietário pode fornecer um
ciclo de vida, que um observador pode registrar para monitorar.
No exemplo de rastreamento de local, podemos fazer com que a classe MyLocationListener
implemente DefaultLifecycleObserver e, em seguida, inicialize-a com o
Lifecycle da atividade no método onCreate(). Isso permite que a
classe MyLocationListener seja autossuficiente, ou seja, a lógica para
reagir a mudanças no status do ciclo de vida é declarada em MyLocationListener, e não
na atividade. Armazenar os componentes individuais na sua própria lógica torna
a lógica de atividades e fragmentos mais fácil de gerenciar.
Kotlin
class MyActivity : AppCompatActivity() {
private lateinit var myLocationListener: MyLocationListener
override fun onCreate(...) {
myLocationListener = MyLocationListener(this, lifecycle) { location ->
// update UI
}
Util.checkUserStatus { result ->
if (result) {
myLocationListener.enable()
}
}
}
}
Java
class MyActivity extends AppCompatActivity {
private MyLocationListener myLocationListener;
public void onCreate(...) {
myLocationListener = new MyLocationListener(this, getLifecycle(), location -> {
// update UI
});
Util.checkUserStatus(result -> {
if (result) {
myLocationListener.enable();
}
});
}
}
Um caso de uso comum é evitar invocar determinados callbacks se o Lifecycle
não estiver em um bom estado no momento. Por exemplo, se o callback executar uma transação de fragmento
depois que o estado da atividade for salvo, isso vai causar uma falha, então não
queremos invocar esse callback.
Para facilitar esse caso de uso, a classe Lifecycle permite que outros objetos
consultem o estado atual.
Kotlin
internal class MyLocationListener(
private val context: Context,
private val lifecycle: Lifecycle,
private val callback: (Location) -> Unit
): DefaultLifecycleObserver {
private var enabled = false
override fun onStart(owner: LifecycleOwner) {
if (enabled) {
// connect
}
}
fun enable() {
enabled = true
if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
// connect if not connected
}
}
override fun onStop(owner: LifecycleOwner) {
// disconnect if connected
}
}
Java
class MyLocationListener implements DefaultLifecycleObserver {
private boolean enabled = false;
public MyLocationListener(Context context, Lifecycle lifecycle, Callback callback) {
...
}
@Override
public void onStart(LifecycleOwner owner) {
if (enabled) {
// connect
}
}
public void enable() {
enabled = true;
if (lifecycle.getCurrentState().isAtLeast(STARTED)) {
// connect if not connected
}
}
@Override
public void onStop(LifecycleOwner owner) {
// disconnect if connected
}
}
Com essa implementação, a classe LocationListener tem reconhecimento
total do ciclo de vida. Se for necessário usar o LocationListener de outra atividade
ou fragmento, basta inicializá-lo. Todas as operações de configuração e desmontagem
são gerenciadas pela própria classe.
Se uma biblioteca fornece classes que precisam trabalhar com o ciclo de vida do Android, recomendamos que você use componentes com reconhecimento de ciclo de vida. Seus clientes de biblioteca podem integrar facilmente esses componentes sem o gerenciamento manual do ciclo de vida no lado do cliente.
Implementar um LifecycleOwner personalizado
Fragmentos e atividades na Biblioteca de Suporte 26.1.0 e versões mais recentes já implementam
a interface LifecycleOwner.
Se você tiver uma classe personalizada que queira transformar em um LifecycleOwner,
use a classe LifecycleRegistry, mas será necessário encaminhar eventos
para essa classe, conforme mostrado no exemplo de código a seguir:
Kotlin
class MyActivity : Activity(), LifecycleOwner {
private lateinit var lifecycleRegistry: LifecycleRegistry
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycleRegistry = LifecycleRegistry(this)
lifecycleRegistry.markState(Lifecycle.State.CREATED)
}
public override fun onStart() {
super.onStart()
lifecycleRegistry.markState(Lifecycle.State.STARTED)
}
override fun getLifecycle(): Lifecycle {
return lifecycleRegistry
}
}
Java
public class MyActivity extends Activity implements LifecycleOwner {
private LifecycleRegistry lifecycleRegistry;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
lifecycleRegistry = new LifecycleRegistry(this);
lifecycleRegistry.markState(Lifecycle.State.CREATED);
}
@Override
public void onStart() {
super.onStart();
lifecycleRegistry.markState(Lifecycle.State.STARTED);
}
@NonNull
@Override
public Lifecycle getLifecycle() {
return lifecycleRegistry;
}
}
Práticas recomendadas para componentes com reconhecimento de ciclo de vida
- Mantenha seus controladores de IU (atividades e fragmentos) o mais simples possível.
Eles não podem tentar adquirir os próprios dados. Em vez disso, use um
ViewModelpara fazer isso e observe um objetoLiveDatapara refletir as mudanças nas visualizações. - Tente escrever interfaces baseadas em dados, em que a responsabilidade do controlador de IU é
atualizar as visualizações conforme os dados mudam ou notificar as ações do usuário de volta ao
ViewModel. - Coloque a lógica de dados na classe
ViewModel.ViewModelservirá como o conector entre o controlador de IU e o restante do app. Entretanto, tenha cuidado, porque não é responsabilidade doViewModelbuscar dados (por exemplo, de uma rede). Em vez disso,ViewModelprecisa chamar o componente adequado para buscar os dados e, em seguida, fornecer o resultado para o controlador de IU. - Use a vinculação de dados para manter uma interface limpa entre as visualizações e o controlador de IU. Isso permite que você torne suas visualizações mais declarativas e minimize o código de atualização que precisa ser escrito nas atividades e nos fragmentos. Se você preferir fazer isso na linguagem de programação Java, use uma biblioteca como a Butter Knife para evitar código boilerplate e ter uma abstração melhor.
- Se a interface for complexa, crie uma classe presenter para processar as modificações. Essa pode ser uma tarefa trabalhosa, mas pode facilitar o teste dos componentes da interface.
- Evite referenciar um contexto
ViewouActivityno seuViewModel. Se oViewModeldurar mais que a atividade (em caso de mudanças de configuração), sua atividade vai vazar e não será descartada corretamente pelo coletor de lixo. - Use corrotinas do Kotlin para gerenciar tarefas de longa duração e outras operações que podem ser executadas de forma assíncrona.
Casos de uso para componentes com reconhecimento de ciclo de vida
Componentes com reconhecimento de ciclo de vida podem facilitar muito o gerenciamento de ciclos de vida em diversos casos. Vejas alguns exemplos:
- Alternar entre atualizações de localização aproximada ou detalhada. Use
componentes com reconhecimento de ciclo de vida para ativar atualizações de localização detalhadas enquanto
seu app de local estiver visível e mude para atualizações aproximadas quando o
app estiver em segundo plano. O
LiveData, um componente com reconhecimento de ciclo de vida, permite que o app atualize a IU automaticamente quando o usuário muda de local. - Parar e iniciar o armazenamento de vídeos em buffer. Use componentes com reconhecimento de ciclo de vida para iniciar o armazenamento em buffer de vídeo o mais rápido possível, mas adie a reprodução até que o app esteja totalmente iniciado. Também é possível usar componentes com reconhecimento de ciclo de vida para encerrar o armazenamento em buffer quando o app é destruído.
- Iniciar e interromper a conectividade de rede. Use componentes com reconhecimento de ciclo de vida para ativar a atualização em tempo real (streaming) de dados de rede enquanto um app estiver em primeiro plano e também para pausá-la automaticamente quando ele estiver em segundo plano.
- Pausar e retomar drawables animados. Use componentes com reconhecimento de ciclo de vida para pausar drawables animados quando o app estiver em segundo plano e retomar os drawables depois que ele voltar para o primeiro plano.
Gerenciar eventos "on stop"
Quando um Lifecycle pertence a um AppCompatActivity ou
Fragment, o estado do Lifecycle muda para CREATED e
o evento ON_STOP é enviado quando o onSaveInstanceState() do AppCompatActivity ou
Fragment é chamado.
Quando o estado de um Fragment ou AppCompatActivity é salvo usando
onSaveInstanceState, a interface é considerada imutável até que
ON_START seja chamado. Tentar modificar a IU depois que o estado é salvo
pode causar inconsistências no estado de navegação do aplicativo.
Por isso, o FragmentManager gera uma exceção se o app executar um
FragmentTransaction depois que o estado for salvo. Consulte commit() para mais detalhes.
LiveData evita esse caso extremo ao não
chamar o observador se o Lifecycle associado a ele não estiver pelo
menos em STARTED. Nos bastidores, ele chama isAtLeast() antes de decidir invocar o observador.
Infelizmente, o método onStop() da AppCompatActivity é chamado
depois de onSaveInstanceState. Isso gera uma lacuna em que as mudanças de estado da IU
não são permitidas, mas o Lifecycle ainda não foi passado para o
estado CREATED.
Para evitar esse problema, a classe do Lifecycle na versão beta2 e anteriores
marca o estado como CREATED sem despachar o evento para que qualquer código
que verificar o estado atual receba o valor real, mesmo que o evento não seja
despachado até onStop() ser chamado pelo sistema.
Essa solução tem dois grandes problemas:
- No nível de API 23 ou anterior, o sistema Android realmente salva o estado de uma atividade, mesmo que ele seja parcialmente coberto por outra atividade. Em outras
palavras, o sistema Android chama
onSaveInstanceState(), mas não necessariamenteonStop. Isso cria um intervalo potencialmente longo em que o observador ainda acha que o ciclo de vida está ativo, mesmo que o estado da IU não possa ser modificado. - Qualquer classe que queira expor um comportamento semelhante à classe
LiveDataprecisa implementar a solução alternativa fornecida pela versãobeta 2e anteriores doLifecycle.
Outros recursos
Para saber mais sobre como processar ciclos de vida com componentes que os reconhecem, consulte os recursos adicionais a seguir.
Codelabs
Blogs
- Apresentação do Android Sunflower (em inglês)