Projeto: app Lunch Tray

1. Antes de começar

Este codelab apresenta um novo app, chamado Lunch Tray, que você criará por conta própria. Aqui, você encontra as etapas para concluir o projeto do app Lunch Tray, incluindo a configuração e os testes no Android Studio.

Este codelab é diferente dos outros do curso. Ao contrário dos codelabs anteriores, o objetivo deste codelab não é fornecer um tutorial passo a passo sobre como criar um app. A ideia é configurar um projeto que será concluído de forma independente, com instruções sobre como concluir o app e verificar seu trabalho por conta própria.

Em vez do código da solução, fornecemos um conjunto de testes como parte do download do app. Você executará esses testes no Android Studio (mostraremos como fazer isso posteriormente neste codelab) e verá se o código é aprovado. Isso pode levar algumas tentativas. Até mesmo desenvolvedores profissionais raramente passam em todos os testes na primeira vez. Depois que seu código passar em todos os testes, considere o projeto como concluído.

Entendemos que talvez você queira apenas uma solução para conferir seu trabalho. Não fornecemos o código da solução deliberadamente, porque queremos que você pratique como é ser um desenvolvedor profissional. Isso pode exigir o uso de diferentes habilidades que você ainda não tem prática, como:

  • pesquisar no Google os termos, mensagens de erro e códigos do app que você não reconhece;
  • testar o código, ler erros e fazer mudanças no código para testá-lo novamente;
  • voltar ao conteúdo anterior nas Noções básicas do Android para relembrar o que você aprendeu;
  • comparar um código conhecido (ou seja, o fornecido no projeto ou o anterior da solução em outros apps da Unidade 3) com o que você está escrevendo.

Isso pode parecer complicado no início, mas temos 100% de certeza de que, se você concluiu a Unidade 3, já pode começar o projeto. Leve o tempo que precisar e não desista. Você consegue.

Pré-requisitos

  • Este projeto é destinado a usuários que concluíram a Unidade 3 do curso Noções básicas do Android no Kotlin.

O que você criará

  • Você vai usar um app de pedidos de comida chamado Lunch Tray, implementar um ViewModel com vinculação de dados e adicionar a navegação entre fragmentos.

O que é necessário

  • Um computador com o Android Studio instalado.

2. Visão geral do app final

Este é o projeto do app Lunch Tray.

Como você já deve saber, a navegação é uma parte fundamental do desenvolvimento para Android. Não importa se você está usando um app para procurar receitas, encontrar rotas até seu restaurante favorito ou, principalmente, pedir comida, é provável que você navegue por várias telas de conteúdo. Neste projeto, você usará as habilidades aprendidas na Unidade 3 para criar um app de pedidos de comida chamado Lunch Tray, implementando um modelo de visualização, a vinculação de dados e a navegação entre telas.

Veja abaixo as capturas de tela do app final. Ao iniciar o app Lunch Tray, o usuário é direcionado a uma tela com um único botão com a mensagem "Start Order".

9e25340c8f139f7a.png

Depois de clicar em Start Order, o usuário poderá selecionar um prato principal entre as opções disponíveis. Quando a seleção muda, o Subtotal mostrado na parte de baixo da tela é atualizado.

11d4a4c77e7e5fc6.png

A tela seguinte permite que o usuário adicione um acompanhamento.

a21a5d4ad1d674f5.png

Na próxima tela, o usuário pode adicionar uma guarnição.

3785f4a1ab074cf4.png

Por fim, será mostrado um resumo do valor do pedido, que é dividido em subtotal, tributos e custo total. Nessa tela, o usuário pode enviar ou cancelar o pedido.

f8916d76265c0da9.png

As duas opções retornam a navegação para a primeira tela. Se o usuário enviar o pedido, um aviso será mostrado na parte de baixo da tela informando que o pedido foi enviado.

80699b621adda4d8.png

3. Primeiros passos

Fazer o download do código do projeto

O nome da pasta é android-basics-kotlin-lunch-tray-app. Selecione essa pasta ao abrir o projeto no Android Studio.

Para encontrar o código deste codelab e abri-lo no Android Studio, faça o seguinte.

Acessar o código

  1. Clique no URL fornecido. Isso abrirá a página do GitHub referente ao projeto em um navegador.
  2. Na página do GitHub do projeto, clique no botão Code, que vai mostrar uma caixa de diálogo.

5b0a76c50478a73f.png

  1. Na caixa de diálogo, clique no botão Download ZIP para salvar o projeto no seu computador. Aguarde a conclusão do download.
  2. Localize o arquivo no computador, que provavelmente está na pasta Downloads.
  3. Clique duas vezes no arquivo ZIP para descompactá-lo. Isso criará uma nova pasta com os arquivos do projeto.

Abrir o projeto no Android Studio

  1. Inicie o Android Studio.
  2. Na janela Welcome to Android Studio, clique em Open an existing Android Studio project.

36cc44fcf0f89a1d.png

Observação: caso o Android Studio já esteja aberto, selecione a opção File > New > Import Project.

21f3eec988dcfbe9.png

  1. Na caixa de diálogo Import Project, vá até a pasta do projeto descompactada, que provavelmente está na pasta Downloads.
  2. Clique duas vezes nessa pasta do projeto.
  3. Aguarde o Android Studio abrir o projeto.
  4. Clique no botão Run 11c34fc5e516fb1c.png para criar e executar o app. Confira se ele é compilado da forma esperada.
  5. Procure os arquivos do projeto na janela de ferramentas Project para ver como o app está configurado.

Antes de começar a implementar o ViewModel e a navegação, confira se o projeto foi criado corretamente e se familiarize com ele. Ao executar o app pela primeira vez, você verá uma tela vazia. A MainActivity não apresenta nenhum fragmento, porque você ainda não configurou o gráfico de navegação.

A estrutura desse projeto é semelhante à de outros com os quais você já trabalhou. Os dados, o modelo e a IU são fornecidos em pacotes separados, e os recursos em diretórios separados.

881a368144f828a0.png

Todas as opções de pratos que o usuário pode pedir (pratos principais, acompanhamentos e guarnições) são representadas pela classe MenuItem no pacote model. Os objetos MenuItem incluem um nome, uma descrição, um preço e um tipo.

data class MenuItem(
    val name: String,
    val description: String,
    val price: Double,
    val type: Int
) {
    fun getFormattedPrice(): String = NumberFormat.getCurrencyInstance().format(price)
}

O tipo é representado por um número inteiro derivado do objeto ItemType no pacote constants.

object ItemType {
    val ENTREE = 1
    val SIDE_DISH = 2
    val ACCOMPANIMENT = 3
}

Objetos MenuItem individuais podem ser encontrados no DataSource.kt no pacote de dados.

object DataSource {
    val menuItems = mapOf(
        "cauliflower" to
        MenuItem(
            name = "Cauliflower",
            description = "Whole cauliflower, brined, roasted, and deep fried",
            price = 7.00,
            type = ItemType.ENTREE
        ),
    ...
}

Esse objeto contém um mapa que consiste em uma chave e uma classe MenuItem correspondente. Acesse o DataSource em ObjectViewModel, que você implementará primeiro.

Definir o ViewModel

Como vimos nas capturas de tela na página anterior, o app pede para o usuário selecionar três itens: um prato principal, um acompanhamento e uma guarnição. A tela de resumo do pedido mostra um subtotal e calcula os tributos de acordo com os itens selecionados. Esses valores são usados para calcular o total do pedido.

No pacote model, abra o OrderViewModel.kt. Você perceberá que algumas variáveis já estão definidas. A propriedade menuItems apenas permite que você acesse o DataSource no ViewModel.

val menuItems = DataSource.menuItems

Primeiramente, há algumas variáveis para previousEntreePrice, previousSidePrice e previousAccompanimentPrice. Como o subtotal é atualizado à medida que o usuário seleciona uma opção, e não é calculado apenas no final, essas variáveis são usadas para rastrear o item selecionado anteriormente caso ele seja trocado antes de passar para a próxima tela. Você usará essas variáveis para garantir que o subtotal considere as diferenças entre os preços dos itens anteriores e os que estão selecionados no momento.

private var previousEntreePrice = 0.0
private var previousSidePrice = 0.0
private var previousAccompanimentPrice = 0.0

Também existem variáveis particulares (_entree, _side e _accompaniment) para armazenar a opção selecionada no momento. Elas são do tipo MutableLiveData<MenuItem?>. Cada uma é acompanhada de uma propriedade de apoio pública (entree, side e accompaniment), do tipo imutável LiveData<MenuItem?>. Essas são acessadas pelos layouts dos fragmentos para mostrar na tela o item selecionado. A classe MenuItem contida no objeto LiveData também é anulável, já que o usuário tem a opção de não selecionar um prato principal, um acompanhamento e/ou uma guarnição.

// Entree for the order
private val _entree = MutableLiveData<MenuItem?>()
val entree: LiveData<MenuItem?> = _entree

// Side for the order
private val _side = MutableLiveData<MenuItem?>()
val side: LiveData<MenuItem?> = _side

// Accompaniment for the order.
private val _accompaniment = MutableLiveData<MenuItem?>()
val accompaniment: LiveData<MenuItem?> = _accompaniment

Também há variáveis LiveData para o subtotal, o total e os tributos, que usam a formatação de números para que eles sejam mostrados como moeda.

// Subtotal for the order
private val _subtotal = MutableLiveData(0.0)
val subtotal: LiveData<String> = Transformations.map(_subtotal) {
    NumberFormat.getCurrencyInstance().format(it)
}

// Total cost of the order
private val _total = MutableLiveData(0.0)
val total: LiveData<String> = Transformations.map(_total) {
    NumberFormat.getCurrencyInstance().format(it)
}

// Tax for the order
private val _tax = MutableLiveData(0.0)
val tax: LiveData<String> = Transformations.map(_tax) {
    NumberFormat.getCurrencyInstance().format(it)
}

Por fim, a alíquota é um valor de 0,08 (8%) fixado no código.

private val taxRate = 0.08

Há seis métodos no OrderViewModel que precisam ser implementados.

setEntree(), setSide() e setAccompaniment()

Todos esses métodos vão funcionar da mesma forma para o prato principal, o acompanhamento e a guarnição, respectivamente. Por exemplo, o setEntree() precisa fazer o seguinte:

  1. Definir a variável previousEntreePrice como o preço de current _entree se o valor de _entree não for null, ou seja, se o usuário selecionou um prato principal, mas mudou de ideia.
  2. Subtrair a variável previousEntreePrice do subtotal se o valor de _subtotal for null.
  3. Atualizar o valor de _entree de acordo com o prato principal transmitido para a função (acesso da classe MenuItem usando a propriedade menuItems).
  4. Chamar o updateSubtotal() e transmitir o preço do último prato principal selecionado.

A lógica para setSide() e setAccompaniment() é idêntica à implementação do método setEntree().

updateSubtotal()

O updateSubtotal() é chamado com um argumento para o novo preço que será adicionado ao subtotal. Esse método precisa realizar as três ações a seguir:

  1. Adicionar o itemPrice ao _subtotal se o valor do _subtotal não for null.
  2. Definir o _subtotal como o itemPrice se o _subtotal for null.
  3. Chamar calculateTaxAndTotal() depois que o _subtotal tiver sido definido ou atualizado, para que os valores sejam atualizados de modo a refletir o novo subtotal.

calculateTaxAndTotal()

O calculateTaxAndTotal() precisa atualizar as variáveis dos tributos e do total com base no subtotal. Implemente o método da seguinte maneira:

  1. Defina _tax como a alíquota multiplicada pelo subtotal.
  2. Defina _total como o subtotal mais os tributos.

resetOrder()

O resetOrder() será chamado quando o usuário enviar ou cancelar um pedido. É importante conferir se o app não tem dados pendentes quando o usuário inicia um novo pedido.

Implemente o resetOrder() redefinindo todas as variáveis modificadas no OrderViewModel para o valor original (0,0 ou nulo).

Criar variáveis de vinculação de dados

Implemente a vinculação de dados nos arquivos de layout. Abra os arquivos de layout e adicione variáveis de vinculação de dados do tipo OrderViewModel e/ou a classe de fragmento correspondente.

É necessário implementar todos os comentários TODO para definir o texto e os listeners de clique em quatro arquivos de layout:

  1. fragment_entree_menu.xml
  2. fragment_side_menu.xml
  3. fragment_accompaniment_menu.xml
  4. fragment_checkout.xml

Cada tarefa específica é acompanhada de um comentário TODO nos arquivos de layout, mas as etapas estão resumidas abaixo.

  1. Em fragment_entry_menu.xml, na tag <data>, adicione uma variável de vinculação para o EntreeMenuFragment. Para cada botão de opção, será necessário definir o prato principal no ViewModel quando ele for selecionado. O texto da visualização do subtotal precisa ser atualizado corretamente. Você também precisa definir o atributo onClick para cancel_button e next_button para cancelar o pedido ou navegar até a próxima tela, respectivamente.
  2. Faça o mesmo no fragment_side_menu.xml, adicionando uma variável de vinculação para o SideMenuFragment, exceto para definir o acompanhamento no modelo de visualização quando cada botão de opção estiver selecionado. O texto do subtotal também precisará ser atualizado, e será necessário definir o atributo onClick para os botões "Cancel" e "Next".
  3. Faça isso mais uma vez, mas agora no fragment_accompaniment_menu.xml com uma variável de vinculação para o AccompanimentMenuFragment, definindo a guarnição quando cada botão de opção for selecionado. Também será necessário definir atributos para o texto do subtotal, o botão "Cancel" e o botão "Next".
  4. No fragment_checkout.xml, você precisará adicionar uma tag <data> para definir variáveis de vinculação. Na tag <data>, adicione duas variáveis de vinculação, uma de OrderViewModel e outra de CheckoutFragment. Nas visualizações de texto, será necessário definir os nomes e preços do prato principal, do acompanhamento e da guarnição selecionados no OrderViewModel. Você também precisará definir o subtotal, os tributos e o total do OrderViewModel. Em seguida, defina onClickAttributes como o horário em que o pedido foi enviado e, caso o pedido seja cancelado, use as funções adequadas do CheckoutFragment.

.

Inicializar as variáveis de vinculação de dados nos fragmentos

Inicialize as variáveis de vinculação de dados nos arquivos de fragmento correspondentes no método onViewCreated().

  1. EntreeMenuFragment
  2. SideMenuFragment
  3. AccompanimentMenuFragment
  4. CheckoutFragment

Criar o gráfico de navegação

No pacote res > navigation, você encontrará um arquivo chamado mobile_navigation.xml.

76df37040ce88193.png

Esse é o gráfico de navegação do app, mas o arquivo está vazio no momento. Sua tarefa é adicionar destinos ao gráfico de navegação e definir a navegação entre telas a seguir.

  1. Navegação do StartOrderFragment ao EntreeMenuFragment.
  2. Navegação do EntreeMenuFragment ao SideMenuFragment.
  3. Navegação do SideMenuFragment ao AccompanimentMenuFragment.
  4. Navegação do AccompanimentMenuFragment ao CheckoutFragment.
  5. Navegação do CheckoutFragment ao StartOrderFragment.
  6. Navegação do EntreeMenuFragment ao StartOrderFragment.
  7. Navegação do SideMenuFragment ao StartOrderFragment.

Navegação do AccompanimentMenuFragment ao StartOrderFragment.

Depois de configurar o gráfico de navegação, você precisará executar a navegação nas classes de fragmentos. Implemente os comentários TODO restantes nos fragmentos, assim como MainActivity.kt.

  1. Para o método goToNextScreen() no EntreeMenuFragment, SideMenuFragment e AccompanimentMenuFragment, navegue até a próxima tela no app.
  2. Para o método cancelOrder() no EntreeMenuFragment, SideMenuFragment, AccompanimentMenuFragment e CheckoutFragment, chame resetOrder() no sharedViewModel e depois navegue até o StartOrderFragment.
  3. No StartOrderFragment, implemente setOnClickListener() para navegar até o EntreeMenuFragment.
  4. No CheckoutFragment, implemente o método submitOrder(). Chame resetOrder() no sharedViewModel e depois navegue até o StartOrderFragment.
  5. Por fim, no MainActivity.kt, defina navController como o navController do NavHostFragment.

4. Testar o app

O projeto Lunch Tray contém um destino "androidTest" com vários casos de teste: MenuContentTests, NavigationTests e OrderFunctionalityTests.

Como executar os testes

Para executar os testes, escolha uma das seguintes opções:

Para um único caso de teste, abra uma classe de caso de teste e clique na seta verde à esquerda da declaração da classe. Em seguida, selecione a opção "Run" no menu. Isso executará todos os testes do caso.

3f8f963fd6b3e894.png

Na maioria das vezes, convém executar apenas um teste, por exemplo, se apenas um teste for reprovado e os outros forem aprovados. Você pode executar um único teste da mesma forma que faria com todo o caso de teste. Use a seta verde e selecione a opção Run.

90a602537504ef83.png

Se você tem vários casos de teste, também pode executar todo o conjunto de testes. Assim como na execução do app, você encontra essa opção no menu Run.

4ab70aa8207f469.png

O Android Studio usará como padrão o último destino que você executou (app, destinos de teste etc.). Portanto, se o menu ainda mostrar Run > Run 'app', você poderá executar o destino de teste selecionando Run > Run.

8a3d33e6b635dac2.png

Em seguida, escolha o destino do teste no menu pop-up.

9154bfbaa26d1c91.png

5. Opcional: envie seu feedback

Gostaríamos de saber sua opinião sobre este projeto. Responda a esta pesquisa rápida: seu feedback nos ajudará a orientar projetos futuros neste curso.