Recomendações para a arquitetura do Android

Nesta página, apresentamos várias práticas recomendadas e sugestões para a Arquitetura. Adote nossas dicas para melhorar a qualidade, robustez e escalonabilidade do app. Elas também facilitam os processos de manutenção e teste do app.

As práticas recomendadas abaixo estão agrupadas por assunto. Um nível de importância foi atribuído a cada uma delas, de acordo com a recomendação. Os níveis são os seguintes:

  • Altamente recomendável:implemente essa prática, a menos que ela entre em conflito direto com sua abordagem.
  • Recomendável: essa prática provavelmente vai melhorar seu app.
  • Opcional: essa prática pode melhorar seu app em algumas circunstâncias.

Arquitetura com camadas

A arquitetura com camadas recomendada favorece a separação de conceitos. Ela baseia a IU em modelos de dados, segue os princípios de fonte única de verdade e de fluxo de dados unidirecional. Confira algumas práticas recomendadas para arquitetura com camadas:

Recomendação Descrição
Usar uma camada de dados claramente definida. A camada de dados expõe os dados do aplicativo ao restante do app e contém a grande maioria da lógica de negócios dele.
  • Crie repositórios, mesmo que eles contenham somente uma fonte de dados.
  • Em apps pequenos, é possível colocar tipos de camada de dados em um pacote ou módulo de data.
Usar uma camada de IU claramente definida. A camada de IU mostra os dados do app na tela e atua como o ponto principal de interação do usuário. O Jetpack Compose é o kit de ferramentas moderno recomendado para criar a interface do seu app.
  • Em apps pequenos, é possível colocar tipos de camada de dados em um pacote ou módulo de ui.
Para mais informações sobre as práticas recomendadas da camada de UI, consulte Camada de UI.
Exponha os dados do aplicativo da camada de dados usando um repositório.

Verifique se os componentes na camada da interface, como elementos combináveis ou ViewModels, não interagem diretamente com uma fonte de dados. Exemplos de fontes de dados:

  • Bancos de dados, DataStore, SharedPreferences, APIs do Firebase.
  • Provedores de localização de GPS.
  • Provedores de dados Bluetooth.
  • Provedores de status da conectividade de rede.
Usar corrotinas e fluxos. Usar corrotinas e fluxos para estabelecer a comunicação entre as camadas.

Para mais informações sobre práticas recomendadas de corrotinas, consulte Práticas recomendadas para corrotinas no Android.

Usar uma camada de domínios. Use uma camada de domínio com casos de uso se precisar reutilizar a lógica de negócios que interage com a camada de dados em vários ViewModels ou se quiser simplificar a complexidade da lógica de negócios de um ViewModel específico.

Camada de IU

A função da camada de IU é mostrar os dados do app na tela e atuar como o ponto principal de interação do usuário. Confira algumas práticas recomendadas para a camada de IU:

Recomendação Descrição
Seguir o fluxo de dados unidirecional (UDF, na sigla em inglês). Siga os princípios do fluxo de dados unidirecional (UDF), em que os ViewModels expõem o estado da interface usando o padrão de observador e recebem ações da interface por chamadas de método.
Usar os ViewModels do AAC, caso sejam vantajosos para o app. Use ViewModels do AAC para processar a lógica de negócios e busque dados do app para expor o estado da interface à interface.

Para mais informações sobre as práticas recomendadas do ViewModel, consulte Recomendações de arquitetura.

Para mais informações sobre os benefícios dos ViewModels, consulte O ViewModel como um detentor de estado da lógica de negócios.

Usar a coleta de estado da IU com reconhecimento de ciclo de vida. Colete o estado da interface usando o builder adequado de corrotinas com reconhecimento de ciclo de vida, collectAsStateWithLifecycle.

Saiba mais sobre o collectAsStateWithLifecycle.

Não enviar eventos do ViewModel para a IU. Processe o evento imediatamente no ViewModel e cause uma atualização de estado com o resultado. Para mais informações sobre eventos da interface, consulte Processar eventos do ViewModel.
Usar um aplicativo de atividade única. Use a Navegação 3 para alternar entre as diferentes telas e links diretos para o app, caso ele tenha mais de uma tela.
Usar o Jetpack Compose. Use o Jetpack Compose para criar novos apps para smartphones, tablets, dispositivos dobráveis e Wear OS.

O snippet abaixo descreve como coletar o estado da IU de uma forma que reconhece o ciclo de vida:

  @Composable
  fun MyScreen(
      viewModel: MyViewModel = viewModel()
  ) {
      val uiState by viewModel.uiState.collectAsStateWithLifecycle()
  }

ViewModel

Os ViewModels são responsáveis por fornecer o estado da UI e acessar a camada de dados. Confira algumas práticas recomendadas para ViewModels:

Recomendação Descrição
Mantenha os ViewModels independentes do ciclo de vida do Android. Nos ViewModels, não mantenha uma referência a nenhum tipo relacionado ao ciclo de vida. Não transmita Activity, Context ou Resources como uma dependência. Caso um Context seja necessário no ViewModel, analise se esse elemento está na camada certa.
Usar corrotinas e fluxos.

O ViewModel interage com as camadas de dados ou de domínio usando o seguinte:

  • Fluxos do Kotlin para receber dados do app
  • Funções suspend para realizar ações usando viewModelScope
Usar ViewModels na tela.

Não use ViewModels em partes reutilizáveis da IU. Em vez disso, use-os em:

  • Elementos combináveis da tela.
  • Atividades ou fragmentos nas visualizações.
  • Destinos ou gráficos ao usar a navegação do Jetpack.
Use classes de detentores de estado simples em componentes de IU reutilizáveis. Use classes de detentores de estado simples para processar a complexidade em componentes de IU reutilizáveis. Quando você faz isso, o estado pode ser elevado e controlado externamente.
Não use o AndroidViewModel. Use a classe ViewModel, e não AndroidViewModel. Não use a classe Application no ViewModel. Em vez disso, mova a dependência para a IU ou para a camada de dados.
Expor um estado da IU. Faça com que seus ViewModels exponham dados à interface usando uma única propriedade chamada uiState. Se a IU mostrar diferentes dados não relacionados, a VM pode expor várias propriedades do estado da IU.
  • Transforme uiState em um StateFlow.
  • Crie o uiState usando o operador stateIn com a política WhileSubscribed(5000) se os dados forem originados de outras camadas da hierarquia como um fluxo de dados. Confira este exemplo de código.
  • Para casos mais simples sem fluxos de dados provenientes da camada de dados, é aceitável usar um MutableStateFlow exposto como um StateFlow imutável.
  • É possível optar por usar ${Screen}UiState como uma classe de dados que pode conter dados, erros e sinais de carregamento. Ela também pode ser uma classe selada, desde que os diferentes estados sejam exclusivos.

O snippet abaixo demonstra como expor o estado da IU de um ViewModel:

@HiltViewModel
class BookmarksViewModel @Inject constructor(
    newsRepository: NewsRepository
) : ViewModel() {

    val feedState: StateFlow<NewsFeedUiState> =
        newsRepository
            .getNewsResourcesStream()
            .mapToFeedState(savedNewsResourcesState)
            .stateIn(
                scope = viewModelScope,
                started = SharingStarted.WhileSubscribed(5_000),
                initialValue = NewsFeedUiState.Loading
            )

    // ...
}

Ciclo de vida

Siga as práticas recomendadas para trabalhar com o ciclo de vida da atividade:

Recomendação Descrição
Use efeitos com reconhecimento de ciclo de vida em combináveis em vez de substituir callbacks do ciclo de vida Activity.

Não substitua os métodos do ciclo de vida Activity, como onResume, para executar tarefas relacionadas à interface. Em vez disso, use LifecycleEffects do Compose ou escopos de corrotina com reconhecimento de ciclo de vida:

O snippet abaixo descreve como executar operações de acordo com um determinado estado do ciclo de vida:

  @Composable
  fun LocationChangedEffect(
    locationManager: LocationManager,
    onLocationChanged: (Location) -> Unit
  ) {
    val currentOnLocationChanged by rememberUpdatedState(onLocationChanged)

    LifecycleStartEffect(locationManager) {
        val listener = LocationListener { newLocation ->
            currentOnLocationChanged(newLocation)
        }

        try {
            locationManager.requestLocationUpdates(
                LocationManager.GPS_PROVIDER,
                1000L,
                1f,
                listener,
            )
        } catch (e: SecurityException) {
            // TODO: Handle missing permissions
        }

        onStopOrDispose {
            locationManager.removeUpdates(listener)
        }
    }
  }

Gerenciar dependências

Siga as práticas recomendadas ao gerenciar dependências entre componentes:

Recomendação Descrição
Usar a injeção de dependência. Quando possível, use as práticas recomendadas de injeção de dependência, principalmente a injeção de construtor.
Definir um componente como escopo quando necessário. Defina um contêiner de dependência como escopo caso o tipo contenha dados mutáveis que precisem ser compartilhados ou caso ele demande muita memória e bateria para inicializar e seja amplamente usado no app.
Usar o Hilt. Use o Hilt ou a injeção manual de dependência em apps simples. Use o Hilt se o projeto for complexo o suficiente, por exemplo, se ele incluir qualquer um dos seguintes itens:
  • Várias telas com ViewModels
  • Usa o WorkManager
  • Tem ViewModels com escopo para a backstack de navegação

Teste

Confira algumas práticas recomendadas para testes:

Recomendação Descrição
Saiba o que testar.

A menos que o projeto seja tão simples quanto um app "hello world", teste-o. No mínimo, inclua o seguinte:

  • Testes de unidade para ViewModels, incluindo fluxos
  • Testes de unidade para entidades da camada de dados, ou seja, repositórios e fontes de dados
  • Testes de navegação da interface, que são úteis como testes de regressão na CI.
Usar falsificações em vez de simulações. Para mais informações sobre o uso de fakes, consulte Usar testes duplos no Android.
Testar StateFlows. Ao testar StateFlow, faça o seguinte:

Para mais informações, consulte O que testar no Android e Testar o layout do Compose.

Modelos

Considere as práticas recomendadas abaixo ao desenvolver modelos nos seus apps:

Recomendação Descrição
Criar um modelo por camada em apps complexos.

Em apps complexos, crie novos modelos em camadas ou componentes diferentes quando apropriado. Confira estes exemplos:

  • Uma fonte de dados remota pode mapear o modelo recebido pela rede em uma classe mais simples, contendo apenas os dados de que o app precisa.
  • Os repositórios podem transformar modelos DAO em classes de dados mais simples, contendo apenas as informações de que a camada de interface precisa.
  • O ViewModel pode incluir modelos de camada de dados em classes UiState.

Convenções de nomenclatura

Ao definir a nomenclatura da base de código, é necessário considerar estas práticas recomendadas:

Recomendação Descrição
Nomear métodos.
Opcional
Use frases verbais para nomear métodos, por exemplo, makePayment().
Nomear propriedades.
Opcional
Use frases nominais para nomear propriedades, por exemplo, inProgressTopicSelection.
Nomear fluxos de dados.
Opcional
Quando uma classe expõe um fluxo de dados ou qualquer outro fluxo, a convenção de nomenclatura é get{model}Stream. Por exemplo, getAuthorStream(): Flow<Author>. Se a função retornar uma lista de modelos, use o nome do modelo no plural: getAuthorsStream(): Flow<List<Author>>.
Nomear implementações de interfaces.
Opcional
Use nomes significativos para as implementações de interfaces. Defina Default como prefixo, se não encontrar um nome mais adequado. Por exemplo, para uma interface NewsRepository, é possível usar OfflineFirstNewsRepository ou InMemoryNewsRepository. Se não encontrar um nome melhor, use DefaultNewsRepository. Adicione o prefixo Fake às implementações falsas, como em FakeAuthorsRepository.

Outros recursos

Para mais informações sobre a arquitetura do Android, consulte os seguintes recursos:

Documentação

Visualiza conteúdo