Navegação e a backstack

Neste codelab, você concluirá a implementação do restante do app Cupcake, iniciado em um codelab anterior. O app Cupcake tem várias telas e mostra um fluxo de pedidos para cupcakes. Na versão final do app, o usuário precisa navegar nele e executar as seguintes ações:

  • Criar um pedido de cupcake
  • Usar os botões Para cima ou Voltar para ir a uma etapa anterior do fluxo do pedido
  • Cancelar um pedido
  • Enviar o pedido para outro app, como um app de e-mails

Ao longo deste tutorial, você aprenderá sobre como o Android processa tarefas e a backstack de um app. Com isso, você poderá manipular a backstack para certos casos, como cancelar um pedido, o que leva o usuário de volta à primeira tela do app (ao contrário da tela anterior do fluxo do pedido).

Pré-requisitos

  • Saber criar e usar um modelo de visualização compartilhada nos fragmentos de uma atividade.
  • Saber usar o componente de navegação do Jetpack.
  • Ter usado a vinculação de dados com o LiveData para manter a IU sincronizada com o modelo de visualização.
  • Saber criar uma intent para iniciar uma nova atividade.

O que você aprenderá

  • Como a navegação afeta a backstack de um app.
  • Como implementar o comportamento personalizado da backstack.

O que você criará

  • Um app de pedidos de cupcakes que permite ao usuário enviar o pedido para outro app e cancelar um pedido.

O que é necessário

  • Um computador com o Android Studio instalado.
  • O código para o app Cupcake criado no codelab anterior.

Esse codelab usa o app Cupcake do codelab anterior. Você pode usar seu código do codelab anterior ou fazer o download do código inicial no GitHub.

Fazer o download do código inicial para este codelab

Se você fizer o download do código inicial no GitHub, o nome da pasta do projeto é android-basics-kotlin-cupcake-app-viewmodel. 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.

Buscar 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 exibirá 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 funciona da forma esperada.
  5. Navegue pelos arquivos do projeto na janela de ferramentas Project para ver como o app está configurado.

Agora, execute o app. O resultado será semelhante a este:

45844688c0dc69a2.png

Neste codelab, você primeiro concluirá a implementação do botão Voltar no aplicativo, para que, ao tocar nele, o usuário seja levado à etapa anterior do fluxo de pedido.

fbdc1793f9fea6da.png

Em seguida, você adicionará um botão Cancel para que o usuário possa cancelar o pedido se ele mudar de ideia durante o processo de pedido.

d2b1aa0cfe686a09.gif

Depois, estenda o app para que tocar em Send Order to Another App compartilhe o pedido com outro app. Em seguida, o pedido pode ser enviado para uma loja de cupcakes por e-mail, por exemplo.

170d76b64ce78f56.png

Vamos terminar de criar o app Cupcake!

No app Cupcake, a barra de apps mostra uma seta para voltar à tela anterior. Ela é conhecida como o botão Para cima (ou Voltar), conforme você aprendeu nos codelabs anteriores. O botão Para cima não faz nada ainda. Portanto, primeiro, corrija esse bug de navegação no app.

fbdc1793f9fea6da.png

  1. Na MainActivity, você já precisa ter um código para configurar a barra de apps (também conhecida como barra de ações) usando o controlador de navegação. Defina navController como uma variável de classe para poder usá-la em outro método.
class MainActivity : AppCompatActivity(R.layout.activity_main) {

    private lateinit var navController: NavController

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val navHostFragment = supportFragmentManager
                .findFragmentById(R.id.nav_host_fragment) as NavHostFragment
        navController = navHostFragment.navController

        setupActionBarWithNavController(navController)
    }
}
  1. Na mesma classe, adicione o código para substituir a função onSupportNavigateUp(). Esse código solicitará que o navController processe a navegação no app. Caso contrário, o código volta à implementação da superclasse (em AppCompatActivity) para processar o botão Voltar.
override fun onSupportNavigateUp(): Boolean {
   return navController.navigateUp() || super.onSupportNavigateUp()
}
  1. Execute o app. O botão Voltar agora funcionará nos recursos FlavorFragment, PickupFragment e SummaryFragment. Conforme você navega para as etapas anteriores no fluxo de pedidos, os fragmentos mostrarão o sabor e a data de retirada corretas do modelo de visualização.

Agora, você criará um botão Cancel no fluxo de pedidos do app. O cancelamento de um pedido em qualquer ponto do processo enviará o usuário de volta para o StartFragment. Para lidar com esse comportamento, você aprenderá sobre tarefas e a backstack no Android.

Tarefas

As atividades no Android existem dentro de tarefas. Quando você abre um app pela primeira vez usando o ícone na tela de início, o Android cria uma nova tarefa com a atividade principal. Uma tarefa é um conjunto de atividades com as quais o usuário interage ao realizar uma ação específica (como verificar e-mails, criar um pedido de cupcake, tirar uma foto).

As atividades são organizadas em uma pilha, conhecida como a backstack. Cada nova atividade que o usuário acessa é colocada na backstack da tarefa. Pense nela como uma pilha de panquecas, em que cada nova panqueca é colocada no topo. A atividade na parte superior da pilha é a atividade atual com que o usuário está interagindo. As atividades na parte inferior da pilha foram colocadas em segundo plano e foram interrompidas.

517054e483795b46.png

A backstack é útil para quando o usuário quer navegar para uma etapa anterior. O Android pode remover a atividade atual do topo da pilha, destruí-la e reiniciar a atividade abaixo dela. Isso é conhecido como retirar uma atividade da pilha, o que leva a anterior ao primeiro plano para que o usuário possa interagir com ela. Se o usuário quiser voltar várias vezes, o Android continuará retirando as atividades da parte superior da pilha até que você esteja mais perto do final dela. Quando não houver mais atividades na backstack, o usuário voltará para a tela de início do dispositivo (ou para o app que iniciou esta pilha).

Vejamos a versão do app Words implementada com duas atividades: MainActivity e DetailActivity.

Quando o app é iniciado pela primeira vez, a MainActivity é aberta e adicionada à backstack da tarefa.

4bc8f5aff4d5ee7f.png

Quando você clica em uma letra, a DetailActivity é iniciada e colocada na backstack. Isso significa que a DetailActivity foi criada, iniciada e retomada para que o usuário possa interagir com ela. A MainActivity é colocada em segundo plano e exibida com a cor de fundo cinza no diagrama.

80f7c594ae844b84.png

Se você tocar no botão Voltar, a DetailActivity será retirada da backstack, e a instância DetailActivity será destruída e finalizada.

80f532af817191a4.png

Em seguida, o próximo item na parte de cima da backstack (a MainActivity) é colocada no primeiro plano.

85004712d2fbcdc1.png

Da mesma forma que a backstack pode monitorar as atividades que foram abertas pelo usuário, ela também pode monitorar os destinos do fragmento que o usuário visitou com a ajuda do componente de navegação do Jetpack.

fe417ac5cbca4ce7.png

A biblioteca de navegação permite exibir o destino de um fragmento da backstack sempre que o usuário pressionar o botão Voltar. Não é necessário implementar esse comportamento padrão Você só terá que escrever o código se precisar de um comportamento personalizado da backstack, que é o que será feito no app Cupcake.

Comportamento padrão do app Cupcake

Vejamos como a backstack funciona no app Cupcake. Há apenas uma atividade no app, mas há vários destinos de fragmento pelos quais o usuário pode navegar. Portanto, você quer que o botão Voltar retorne a um destino de fragmento anterior sempre que o usuário tocar nele.

Quando você abre o app pela primeira vez, o destino StartFragment é exibido. Esse destino é colocado no topo da pilha.

cf0e80b4907d80dd.png

Depois de selecionar uma quantidade de cupcakes para fazer o pedido, acesse o FlavorFragment para enviá-lo à backstack.

39081dcc3e537e1e.png

Quando você selecionar um sabor e tocar no botão Next, você navegará para o PickupFragment, que será enviado à backstack.

37dca487200f8f73.png

Por fim, depois de selecionar uma data de retirada e tocar em Next, você navegará para o SummaryFragment, que será adicionado ao topo da backstack.

d67689affdfae0dd.png

No SummaryFragment, vamos supor que você tenha tocado no botão Voltar ou Para cima. O SummaryFragment será retirado da pilha e destruído.

215b93fd65754017.png

O PickupFragment agora estará na parte superior da backstack e será exibido ao usuário.

37dca487200f8f73.png

Toque no botão Voltar ou Para cima novamente. O PickupFragment será retirado da pilha e o FlavorFragment será exibido.

Toque no botão Voltar ou Para cima novamente. O FlavorFragment será retirado da pilha e o StartFragment será exibido.

Conforme você navega para as etapas anteriores no fluxo de pedidos, apenas um destino é retirado da pilha por vez. Mas na próxima tarefa, você adicionará um recurso para cancelar o pedido no app. Poderá ser necessário retirar vários destinos da backstack de uma só vez para retornar o usuário ao StartFragment para iniciar um novo pedido.

e3dae0f492450207.png

Modificar a backstack no app Cupcake

Modifique as classes FlavorFragment, PickupFragment e SummaryFragment e os arquivos de layout para fornecer um botão "Cancel" para cancelar o pedido do usuário.

Adicionar uma ação de navegação

Primeiro, adicione ações de navegação ao gráfico de navegação do app para que o usuário possa voltar ao StartFragment dos outros destinos.

  1. Abra o Navigation Editor acessando o arquivo res > navigation > nav_graph.xml e selecionando a visualização Design.
  2. Haverá uma ação que navega do startFragment ao flavorFragment, uma ação que navega do flavorFragment ao pickupFragment e uma ação que navega do pickupFragment ao summaryFragment.
  3. Clique e arraste para criar uma nova ação de navegação do summaryFragment para o startFragment. Você pode ver estas instruções se quiser um resumo sobre como conectar destinos no gráfico de navegação.
  4. No pickupFragment, clique e arraste para criar uma nova ação para navegar até o startFragment.
  5. No flavorFragment, clique e arraste para criar uma nova ação para navegar até o startFragment.
  6. Ao terminar, o gráfico de navegação ficará assim.

dcbd27a08d24cfa0.png

Com essas mudanças, um usuário pode navegar de um dos fragmentos posteriores no fluxo do pedido para o início do fluxo. Agora você precisa criar o código para navegar com essas ações. O apropriado é fazer isso quando o usuário tocar no botão Cancel.

Adicionar o botão "Cancel" ao layout

Primeiro, adicione o botão Cancel aos arquivos de layout para todos os fragmentos, exceto o StartFragment. Não é necessário cancelar um pedido se você já está na primeira tela do fluxo de pedidos.

  1. Abra o arquivo de layout fragment_flavor.xml.
  2. Use a opção de visualização Split para editar o XML diretamente e visualizar a prévia lado a lado.
  3. Adicione o botão Cancel entre a visualização de texto do subtotal e o botão Next. Atribua a ele um ID de recurso @+id/cancel_button com texto para exibí-lo como @string/cancel.

O botão precisa ser posicionado horizontalmente ao lado do botão Next para que ele apareça como uma linha de botões. Para uma restrição vertical, restrinja o topo do botão Cancel à parte superior do botão Next. Para restrições horizontais, restrinja o início do botão Cancel ao contêiner pai e restrinja o final dele ao início do botão Next.

Também forneça uma altura de wrap_content e uma largura de 0dp ao botão Cancel. Assim, ele poderá dividir a largura da tela igualmente com o outro botão. O botão não estará visível no painel Preview até a próxima etapa.

...

<TextView
    android:id="@+id/subtotal" ... />

<Button
    android:id="@+id/cancel_button"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:text="@string/cancel"
    app:layout_constraintEnd_toStartOf="@id/next_button"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="@id/next_button" />

<Button
    android:id="@+id/next_button" ... />

...
  1. No arquivo fragment_flavor.xml, você também precisará mudar a restrição de início do botão Next de app:layout_constraintStart_toStartOf="parent para app:layout_constraintStart_toEndOf="@id/cancel_button". Adicione também uma margem final ao botão Cancel para que haja um espaço em branco entre os dois botões. Agora, o botão Cancel será exibido no painel Preview do Android Studio.
...

<Button
    android:id="@+id/cancel_button"
    android:layout_marginEnd="@dimen/side_margin" ... />

<Button
    android:id="@+id/next_button"
    app:layout_constraintStart_toEndOf="@id/cancel_button"... />

...
  1. Em relação ao estilo visual, use o atributo style="?attr/materialButtonOutlinedStyle" para aplicar o estilo de botão com contorno do Material Design (link em inglês) para que o botão Cancel não seja exibido de forma mais chamativa que o botão Next, que é a principal ação em que você quer que o usuário se concentre.
<Button
    android:id="@+id/cancel_button"
    style="?attr/materialButtonOutlinedStyle" ... />

O botão e o posicionamento estão ótimos agora!

1fb41763cc255c05.png

  1. Da mesma forma, adicione um botão Cancel ao arquivo de layout fragment_pickup.xml.
...

<TextView
    android:id="@+id/subtotal" ... />

<Button
    android:id="@+id/cancel_button"
    style="?attr/materialButtonOutlinedStyle"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:layout_marginEnd="@dimen/side_margin"
    android:text="@string/cancel"
    app:layout_constraintEnd_toStartOf="@id/next_button"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="@id/next_button" />

<Button
    android:id="@+id/next_button" ... />

...
  1. Atualize a restrição inicial no botão Next. O botão Cancel aparecerá na visualização.
<Button
    android:id="@+id/next_button"
    app:layout_constraintStart_toEndOf="@id/cancel_button" ... />
  1. Aplique uma mudança semelhante no arquivo fragment_summary.xml, embora o layout desse fragmento seja um pouco diferente. Você adicionará o botão Cancel abaixo do botão Send no LinearLayout vertical pai com uma margem entre eles.

741c0f034397795c.png

...

    <Button
        android:id="@+id/send_button" ... />

    <Button
        android:id="@+id/cancel_button"
        style="?attr/materialButtonOutlinedStyle"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/margin_between_elements"
        android:text="@string/cancel" />

</LinearLayout>
  1. Execute e teste o app. Você verá o botão Cancel nos layouts dos FlavorFragment, PickupFragment e SummaryFragment. No entanto, tocar no botão ainda não gera nenhuma ação. Você definirá os listeners de clique para esses botões na próxima etapa.

Adicionar um listener de clique ao botão "Cancel"

Em todas as classe de fragmento (exceto no StartFragment), adicione um método auxiliar que processa os eventos de clique do botão Cancel.

  1. Adicione este método cancelOrder() ao FlavorFragment. Quando as opções de sabor forem exibidas, se o usuário decidir cancelar o pedido, limpe o modelo de visualização chamando o método sharedViewModel.resetOrder(). e retorne ao StartFragment usando a ação de navegação com o ID R.id.action_flavorFragment_to_startFragment.
fun cancelOrder() {
    sharedViewModel.resetOrder()
    findNavController().navigate(R.id.action_flavorFragment_to_startFragment)
}

Se um erro relacionado ao código do recurso de ação for exibido, talvez seja necessário retornar ao arquivo nav_graph.xml para verificar se as ações de navegação também têm o mesmo nome (action_flavorFragment_to_startFragment).

  1. Use a vinculação do listener para configurar o listener de clique do botão Cancel no layout fragment_flavor.xml. Clicar nesse botão invocará o método cancelOrder() que você acabou de criar na classe FragmentFlavor.
<Button
    android:id="@+id/cancel_button"
    android:onClick="@{() -> flavorFragment.cancelOrder()}" ... />
  1. Repita o mesmo processo para o PickupFragment. Adicione um método cancelOrder() à classe de fragmento, que redefine a ordem e navega do PickupFragment ao StartFragment.
fun cancelOrder() {
    sharedViewModel.resetOrder()
    findNavController().navigate(R.id.action_pickupFragment_to_startFragment)
}
  1. No arquivo fragment_pickup.xml, defina o listener de clique do botão Cancel para chamar o método cancelOrder() quando o usuário clicar no botão.
<Button
    android:id="@+id/cancel_button"
    android:onClick="@{() -> pickupFragment.cancelOrder()}" ... />
  1. Adicione um código semelhante ao do botão Cancel no SummaryFragment, que levará o usuário de volta para o StartFragment. Talvez seja necessário importar androidx.navigation.fragment.findNavController se ele não for importado automaticamente para você.
fun cancelOrder() {
    sharedViewModel.resetOrder()
    findNavController().navigate(R.id.action_summaryFragment_to_startFragment)
}
  1. No arquivo fragment_summary.xml, chame o método cancelOrder() do SummaryFragment quando o usuário clicar no botão Cancel.
<Button
    android:id="@+id/cancel_button"
    android:onClick="@{() -> summaryFragment.cancelOrder()}" ... />
  1. Execute e teste o app para verificar a lógica que você acabou de adicionar em cada fragmento. Ao criar um pedido de cupcake, toque no botão Cancel no FlavorFragment, no PickupFragment ou no SummaryFragment para retornar ao StartFragment. Se você continuar para criar um novo pedido, verá que as informações do pedido anterior foram apagadas.

Parece que tudo está funcionando, mas, na verdade, há um bug com a navegação quando você retorna ao StartFragment. Siga as próximas etapas para reproduzir o bug.

  1. Navegue pelo fluxo do pedido para criar um novo pedido de cupcake até chegar à tela de resumo. Por exemplo, você pode pedir 12 cupcakes, sabor chocolate e escolher uma data futura para a retirada.
  2. Em seguida, toque em Cancel. Retorne para o StartFragment.
  3. Parece que tudo funciona, mas se você tocar no botão Voltar do sistema, voltará à tela de resumo do pedido que exibirá um resumo de pedido para 0 cupcakes e sem um sabor escolhido. Esse comportamento está incorreto e não pode ser exibido ao usuário.

1a9024cd58a0e643.png

É provável que o usuário não queira voltar às etapas anteriores do fluxo do pedido. Além disso, todos os dados do pedido no modelo de visualização foram apagados, então essas informações não são úteis. Em vez disso, tocar no botão Voltar no StartFragment precisa fechar o app Cupcake.

Vamos ver como a backstack está definida no momento e como corrigir o bug. Quando você cria um pedido e navega até a tela de resumo do pedido, cada destino é colocado na backstack.

fc88100cdf1bdd1.png

Você cancelou o pedido no SummaryFragment. Quando você navegou do SummaryFragment ao StartFragment, o Android adicionou outra instância do StartFragment como um novo destino da backstack.

5616cb0028b63602.png

É por isso que, quando você tocava no botão Voltar no StartFragment, o app exibiu o SummaryFragment novamente (com informações em branco).

Para corrigir esse bug de navegação, veja como o componente de navegação permite retirar outros destinos da backstack ao navegar usando uma ação.

Retirar mais destinos da backstack

Ao incluir um atributo app:popUpTo na ação de navegação do gráfico de navegação, mais de um destino pode ser retirado da backstack até que o destino especificado seja atingido. Se você especificar app:popUpTo="@id/startFragment", os destinos serão retirados da backstack até você alcançar o StartFragment, que permanecerá na pilha.

Ao adicionar essa mudança ao seu código e executar o app, perceberá que, ao cancelar um pedido, você retorna ao StartFragment. Desta vez, ao tocar no botão Voltar no StartFragment, você verá o StartFragment novamente (em vez de sair do app). Esse também não é o comportamento desejado. Conforme mencionado anteriormente, como você está navegando para o StartFragment, o Android adiciona o StartFragment como um novo destino na backstack. Portanto, agora você tem duas instâncias do StartFragment na backstack. Então, você precisará tocar duas vezes no botão Voltar para sair do app.

dd0fedc6e231e595.png

Para corrigir esse novo bug, solicite que todos os destinos sejam retirados da backstack até o StartFragment. Faça isso especificando app:popUpTo="@id/startFragment"

e app:popUpToInclusive="true" nas ações de navegação adequadas. Dessa forma, você terá apenas uma nova instância do StartFragment na backstack. Isso fará que, ao tocar no botão Voltar uma única vez no StartFragment, você saia do app. Faremos essa mudança agora.

cf0e80b4907d80dd.png

Modificar as ações de navegação

  1. Acesse o Navigation Editor abrindo o arquivo res > navigation > nav_graph.xml.
  2. Selecione a ação que vai do summaryFragment ao startFragment, para que fique destacada em azul.
  3. Clique em Attributes à direita, para expandir as opções, se ainda não estiverem abertas. Procure por Pop Behavior na lista de atributos que você pode modificar.

d762df0f167efd3a.png

  1. Nas opções suspensas, defina popUpTo como startFragment. Isso significa que todos os destinos da backstack serão retirados (começando do topo da pilha e movendo para baixo), até o startFragment.

a9a17493ed6bc27f.png

  1. Em seguida, clique na caixa de seleção popUpToInclusive até exibir uma marca de seleção e a etiqueta true. Isso indica que você quer retirar os destinos até chegar na instância do startFragment, e incluindo-a, que já está na backstack. Então, você não terá duas instâncias do startFragment na backstack.

4a403838a62ff487.png

  1. Repita essas mudanças para a ação que conecta o pickupFragment ao startFragment.

4a403838a62ff487.png

  1. Repita para a ação que conecta o flavorFragment ao startFragment.
  2. Quando terminar, confirme se você fez as mudanças corretas no app. Para isso, observe a visualização Code do arquivo do gráfico de navegação.
<navigation
    android:id="@+id/nav_graph" ...>
    <fragment
        android:id="@+id/startFragment" ...>
        ...
    </fragment>
    <fragment
        android:id="@+id/flavorFragment" ...>
        ...
        <action
            android:id="@+id/action_flavorFragment_to_startFragment"
            app:destination="@id/startFragment"
            app:popUpTo="@id/startFragment"
            app:popUpToInclusive="true" />
    </fragment>
    <fragment
        android:id="@+id/pickupFragment" ...>
        ...
        <action
            android:id="@+id/action_pickupFragment_to_startFragment"
            app:destination="@id/startFragment"
            app:popUpTo="@id/startFragment"
            app:popUpToInclusive="true" />
    </fragment>
    <fragment
        android:id="@+id/summaryFragment" ...>
        <action
            android:id="@+id/action_summaryFragment_to_startFragment"
            app:destination="@id/startFragment"
            app:popUpTo="@id/startFragment"
            app:popUpToInclusive="true" />
    </fragment>
</navigation>

Para cada uma das três ações (action_flavorFragment_to_startFragment, action_pickupFragment_to_startFragment e action_summaryFragment_to_startFragment), haverá atributos app:popUpTo="@id/startFragment" e app:popUpToInclusive="true" adicionados recentemente.

  1. Agora execute o app. Siga as etapas do fluxo do pedido e toque em Cancel. Quando retornar ao StartFragment, toque no botão Voltar (apenas uma vez) para sair do app.

Resumidamente, quando você cancela o pedido e volta para a primeira tela do aplicativo, todos os destinos de fragmento são retirados da backstack, incluindo a primeira instância do StartFragment. Ao concluir a ação de navegação, o StartFragment será adicionado como um novo destino na backstack. Ao tocar no botão Voltar, o StartFragment será removido da backstack e não haverá mais fragmentos nela. Portanto, o Android encerrará a atividade e o usuário sairá do app.

O app ficará assim: 5bf939ab1255fb0d.png

O app está ótimo até agora. Mas falta uma parte. Quando você toca no botão para enviar o pedido no SummaryFragment, uma mensagem Toast ainda é exibida.

90ed727c7b812fd6.png

Seria mais útil se o pedido pudesse ser enviado para outro app. Aproveite o que você aprendeu nos codelabs anteriores sobre como usar uma intent implícita para compartilhar informações do seu app com outro. Dessa forma, o usuário poderá compartilhar as informações do pedido de cupcake com um app de e-mails no dispositivo, permitindo que ele seja enviado por e-mail à loja de cupcakes.

170d76b64ce78f56.png

Para implementar esse recurso, veja como o assunto e o corpo do e-mail são estruturados na captura de tela acima.

Você usará essas strings que já estão no arquivo strings.xml.

<string name="new_cupcake_order">New Cupcake Order</string>
<string name="order_details">Quantity: %1$s cupcakes \n Flavor: %2$s \nPickup date: %3$s \n Total: %4$s \n\n Thank you!</string>

O order_details é um recurso de string com quatro argumentos de formato diferentes, que são marcadores para a quantidade real de cupcakes, o sabor e a data de retirada desejados e o preço total. Os argumentos são numerados de 1 a 4 usando a sintaxe %1 até %4. O tipo de argumento também é especificado ($s significa que uma string é esperada).

No código Kotlin, você poderá chamar o método getString() em R.string.order_details seguido dos quatro argumentos (a ordem é importante). Por exemplo, chamar getString(R.string.order_details, "12", "Chocolate", "Sat Dec 12", "$24.00") criará a string a seguir, que é exatamente o corpo de e-mail desejado.

Quantity: 12 cupcakes
Flavor: Chocolate
Pickup date: Sat Dec 12
Total: $24.00

Thank you!
  1. No arquivo SummaryFragment.kt, modifique o método sendOrder(). Remova a mensagem Toast existente.
fun sendOrder() {

}
  1. No método sendOrder(), construa o texto de resumo do pedido. Crie a string order_details formatada extraindo a quantidade, o sabor, a data de retirada e o preço do pedido usando o modelo de visualização compartilhada.
val orderSummary = getString(
    R.string.order_details,
    sharedViewModel.quantity.value.toString(),
    sharedViewModel.flavor.value.toString(),
    sharedViewModel.date.value.toString(),
    sharedViewModel.price.value.toString()
)
  1. Ainda no método sendOrder(), crie uma intent implícita para compartilhar o pedido com outro app. Consulte a documentação para ver como criar uma intent de e-mail. Especifique Intent.ACTION_SEND para a ação da intent, defina o tipo como "text/plain" e inclua extras de intent para o assunto do e-mail (Intent.EXTRA_SUBJECT) e para o corpo do e-mail (Intent.EXTRA_TEXT). Importe android.content.Intent, se necessário.
val intent = Intent(Intent.ACTION_SEND)
    .setType("text/plain")
    .putExtra(Intent.EXTRA_SUBJECT, getString(R.string.new_cupcake_order))
    .putExtra(Intent.EXTRA_TEXT, orderSummary)

Mais uma dica: se você adaptar este app ao seu caso de uso específico, poderá pré-preencher o destinatário do e-mail para ser o endereço de e-mail da loja de cupcakes. Na intent, você pode especificar o destinatário de e-mail usando o extra Intent.EXTRA_EMAIL.

  1. Como essa é uma intent implícita, não é necessário saber antecipadamente quais componentes ou apps específicos a processarão. O usuário decidirá qual app usar para realizar a intent. No entanto, antes de iniciar uma atividade usando a intent, verifique se há um app que possa processá-la instalado. Essa verificação evitará que o app Cupcake falhe se não houver um app que possa processar a intent, tornando seu código mais seguro.
if (activity?.packageManager?.resolveActivity(intent, 0) != null) {
    startActivity(intent)
}

Faça esta verificação acessando o PackageManager, que tem informações sobre quais pacotes de apps estão instalados no dispositivo. O PackageManager pode ser acessado pela activity do fragmento, desde que a activity e o packageManager não sejam nulos. Chame o método resolveActivity() do PackageManager usando a intent que você criou. Se o resultado não for nulo, é seguro chamar o método startActivity() usando a intent.

  1. Execute o app para testar o código. Crie um pedido de cupcake e toque em Send Order to Another App. Quando a caixa de diálogo de compartilhamento for exibida, você poderá selecionar o app Gmail, ou outro app, se preferir. Se você escolher o app Gmail, pode ser necessário configurar uma conta no dispositivo se você ainda não tiver feito isso (por exemplo, se estiver usando o emulador). Caso seu pedido de cupcake não seja exibido no corpo do e-mail, talvez seja necessário descartar o rascunho do e-mail atual primeiro.

170d76b64ce78f56.png

Ao testar diferentes cenários, você poderá perceber um bug se tiver selecionado apenas um cupcake. O resumo do pedido diz 1 cupcakes, mas isso está gramaticalmente incorreto.

ef046a100381bb07.png

Em vez disso, o texto precisa exibir 1 cupcake (sem plural). Se você quiser escolher se a palavra "cupcake" ou "cupcakes" será usada com base no valor da quantidade, pode usar um recurso conhecido com strings de quantidade no Android. Ao declarar um recurso plurals, você pode especificar recursos de string diferentes para usar com base na quantidade, por exemplo, no caso do singular ou plural.

  1. Adicione um recurso "plurals" para cupcakes no arquivo strings.xml.
<plurals name="cupcakes">
    <item quantity="one">%d cupcake</item>
    <item quantity="other">%d cupcakes</item>
</plurals>

No caso do singular (quantity="one"), a string será usada no singular. Em todos os outros casos (quantity="other"), a string será usada no plural. Em vez de %s, que espera um argumento de string, %d espera um argumento inteiro, que você transmitirá ao formatar a string.

No código Kotlin, chame:

getQuantityString(R.string.cupcakes, 1, 1) retorna a string 1 cupcake.

getQuantityString(R.string.cupcakes, 6, 6) retorna a string 6 cupcakes.

getQuantityString(R.string.cupcakes, 0, 0) retorna a string 0 cupcakes.

  1. Antes de acessar seu código Kotlin, atualize o recurso de string order_details no arquivo strings.xml para que a versão plural cupcakes não seja mais fixada no código.
<string name="order_details">Quantity: %1$s \n Flavor: %2$s \nPickup date: %3$s \n
        Total: %4$s \n\n Thank you!</string>
  1. Na classe SummaryFragment, atualize o método sendOrder() para usar a nova string de quantidade. Seria mais fácil descobrir primeiro a quantidade do modelo de visualização e armazená-la em uma variável. Como a quantity no modelo de visualização é do tipo LiveData<Int>, é possível que o sharedViewModel.quantity.value seja nulo. Se o valor for nulo, use 0 como o valor padrão para o numberOfCupcakes.

Adicione-o como a primeira linha de código no método sendOrder().

val numberOfCupcakes = sharedViewModel.quantity.value ?: 0

O operador elvis (link em inglês) , ?:, significa que, se a expressão à esquerda não for nula, ela será usada. Caso a expressão à esquerda seja nula, use a expressão à direita do operador elvis (0, neste caso).

  1. Depois, formate a string order_details como você fez anteriormente. Em vez de transmitir o numberOfCupcakes como o argumento de quantidade diretamente, crie a string de cupcakes formatada usando resources.getQuantityString(R.plurals.cupcakes, numberOfCupcakes, numberOfCupcakes).

O método sendOrder() completo ficará assim:

fun sendOrder() {
    val numberOfCupcakes = sharedViewModel.quantity.value ?: 0
    val orderSummary = getString(
        R.string.order_details,
        resources.getQuantityString(R.plurals.cupcakes, numberOfCupcakes, numberOfCupcakes),
        sharedViewModel.flavor.value.toString(),
        sharedViewModel.date.value.toString(),
        sharedViewModel.price.value.toString()
    )

    val intent = Intent(Intent.ACTION_SEND)
        .setType("text/plain")
        .putExtra(Intent.EXTRA_SUBJECT, getString(R.string.new_cupcake_order))
        .putExtra(Intent.EXTRA_TEXT, orderSummary)

    if (activity?.packageManager?.resolveActivity(intent, 0) != null) {
        startActivity(intent)
    }
}
  1. Execute e teste o código. Verifique se o resumo do pedido no corpo do e-mail mostra 1 cupcake, com 6 cupcakes ou 12 cupcakes.

Com isso, você concluiu toda a funcionalidade do app Cupcake! Parabéns! Este foi um app desafiador, e você fez um grande progresso na sua jornada para se tornar um desenvolvedor Android. Você conseguiu reunir todos os conceitos vistos até agora e, ao mesmo tempo, aprendeu algumas novas dicas de solução de problemas.

Etapas finais

Reserve um tempo para limpar o código, que é uma prática recomendada de programação que você aprendeu em codelabs anteriores.

  • Otimizar importações
  • Reformatar os arquivos
  • Remover o código não utilizado ou comentado
  • Adicionar comentários no código, quando necessário

Para tornar seu app mais acessível, teste com o Talkback ativado para garantir uma experiência do usuário tranquila. O feedback falado ajudará a transmitir o objetivo de cada elemento na tela, quando adequado. Verifique também se todos os elementos do app podem ser acessados usando os gestos de deslizar.

Verifique com atenção se os casos de uso implementados funcionam como esperado no app final. Exemplos:

  • Os dados precisam ser preservados após a rotação do dispositivo (graças ao modelo de visualização).
  • Se você tocar no botão Para cima ou Voltar, as informações do pedido precisam ser exibidas corretamente no FlavorFragment e no PickupFragment.
  • Enviar o pedido para outro app precisa compartilhar os detalhes corretos do pedido.
  • Cancelar um pedido precisa limpar todas as informações dele.

Se você encontrar algum bug, faça as correções necessárias.

Você fez um bom trabalho verificando o app.

O código da solução para este codelab está no projeto mostrado abaixo.

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

Buscar 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 exibirá 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 funciona da forma esperada.
  5. Navegue pelos arquivos do projeto na janela de ferramentas Project para ver como o app está configurado.
  • O Android mantém uma backstack com todos os destinos visitados, e cada novo destino é inserido na pilha.
  • Ao tocar no botão Para cima ou Voltar, é possível retirar os destinos da backstack.
  • O uso do componente de navegação do Jetpack ajuda você a enviar e retirar destinos de fragmentos da backstack para que o comportamento padrão do botão Voltar seja usado.
  • Especifique o atributo app:popUpTo em uma ação no gráfico de navegação para retirar os destinos da backstack até o especificado no valor do atributo.
  • Especifique app:popUpToInclusive="true" em uma ação quando o destino especificado em app:popUpTo também precisar ser retirado da backstack.
  • É possível criar uma intent implícita para compartilhar o conteúdo com um app de e-mails usando Intent.ACTION_SEND e preenchendo extras de intents, como Intent.EXTRA_EMAIL, Intent.EXTRA_SUBJECT e Intent.EXTRA_TEXT, entre outros.
  • Use um recurso plurals se você quiser usar recursos de string diferentes com base na quantidade, como singular ou plural.

Estenda o app Cupcake com suas próprias variações no fluxo de pedido de cupcake. Exemplos:

  • Ofereça um sabor especial que contenha algumas condições específicas, como indisponibilidade de retirada no mesmo dia.
  • Peça para o usuário inserir o nome para o pedido de cupcake.
  • Se o número for superior a 1 cupcake, ofereça a opção de selecionar vários sabores.

Quais áreas do app você precisaria atualizar para acomodar essa nova funcionalidade?

Confira seu trabalho:

Ele precisa ser executado sem erros.

Use o que você aprendeu na criação do app Cupcake para criar um app para seu caso de uso específico. Pode ser um app de pedidos de pizza, sanduíches ou qualquer outra coisa que você imaginar. Recomendamos que você esboce os diferentes destinos do app antes de começar a implementá-lo.

Para se inspirar em outras ideias de design, confira também o app Shrine (link em inglês), que é um estudo do Material Design que mostra como você pode adotar temas e componentes do Material Design na sua própria marca. O app Shrine é muito mais complexo do que o app Cupcake que você criou. Por isso, em vez de tentar criar um app muito desafiador logo no início, pense sobre como implementar recursos pequenos com os quais você pode lidar primeiro. Aumente sua confiança ao longo do tempo com vitórias incrementais e graduais.

Depois de criar seu próprio app, compartilhe o que você desenvolveu nas mídias sociais. Use a hashtag #LearningKotlin para que possamos vê-lo.