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 da nossa equipe. Os níveis são os seguintes:

  • Altamente recomendável:implemente essa prática em qualquer caso que não entre em conflito direto com a 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 das lógicas 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 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. O Jetpack Compose é o kit de ferramentas moderno recomendado para criar a IU 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 IU, consulte Camada de IU.
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 incluem:

  • 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 as práticas recomendadas de corrotinas, consulte Práticas recomendadas para corrotinas no Android.

Usar uma camada de domínios. Use uma camada de domínios 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 buscar dados do app para expor o estado da 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.

Leia mais sobre 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 para 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

ViewModels são responsáveis por fornecer o estado da interface e o acesso à camada de dados. Confira algumas práticas recomendadas para ViewModels:

Recomendação Descrição
Manter 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 cuidadosamente se esse elemento está na camada certa.
Usar corrotinas e fluxos.

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

  • Fluxos do Kotlin para receber dados do app.
  • Funções suspend para executar ações com viewModelScope.
Usar ViewModels no nível da tela.

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

Para elementos combináveis mais complexos ou aqueles com comportamento dinâmico com base no estado, use rememberViewModelStoreOwner() para definir um ViewModel diretamente no local da chamada do elemento combinável.

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 conhecida como uiState. Se a IU mostrar diferentes dados não relacionados, a VM pode expor várias propriedades do estado da IU.
  • Defina uiState como um StateFlow.
  • Crie o uiState usando o stateIn operador com a WhileSubscribed(5000) política se os dados forem originados de outras camadas da hierarquia como um fluxo de dados. Consulte 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 elementos combináveis em vez de substituir callbacks de ciclo de vida de Activity.

Não substitua os métodos de ciclo de vida de 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 Hilt caso se trate de um projeto complexo, 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 seu 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 falsificações, 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. Veja 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, você pode ter um OfflineFirstNewsRepository ou InMemoryNewsRepository. Se não encontrar um nome melhor, use DefaultNewsRepository. Implementações falsas precisam incluir o prefixo Fake, como FakeAuthorsRepository.

Outros recursos

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

Documentação

Conteúdo de visualizações