<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ana Coimbra</title>
    <description>The latest articles on DEV Community by Ana Coimbra (@anacoimbrag).</description>
    <link>https://dev.to/anacoimbrag</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F364886%2Fc80b2566-010a-46a1-9796-6d618633dc77.jpg</url>
      <title>DEV Community: Ana Coimbra</title>
      <link>https://dev.to/anacoimbrag</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/anacoimbrag"/>
    <language>en</language>
    <item>
      <title>Mostrando conteúdo rápido no Android: App Actions &amp; Slices</title>
      <dc:creator>Ana Coimbra</dc:creator>
      <pubDate>Mon, 27 Apr 2020 20:22:03 +0000</pubDate>
      <link>https://dev.to/anacoimbrag/mostrando-conteudo-rapido-no-android-app-actions-slices-4e0b</link>
      <guid>https://dev.to/anacoimbrag/mostrando-conteudo-rapido-no-android-app-actions-slices-4e0b</guid>
      <description>&lt;p&gt;Atualmente está cada vez mais frequente o uso de assistentes virtuais, principalmente em smartphones. Eu, por exemplo, sempre apago a luz do meu quarto por comando de voz, dentre outras inúmeras possibilidades. Neste cenário, os aplicativos Android conseguem obter ainda mais engajamento dos usuários através de respostas rápidas de seus conteúdos integrando com o Google Assistant. Para isso, a Google lançou o &lt;strong&gt;App Actions&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Antes de continuar, é necessário pontuar que essa ferramenta, no dia da publicação deste artigo, se encontra em Developer Preview e tem algumas limitações: apenas um conjunto limitado de ações está disponível e para algumas linguagens específicas (que varia de acordo com a ação). Veja a lista de ações reconhecidas (&lt;em&gt;Built-in intents&lt;/em&gt;) &lt;a href="https://developers.google.com/assistant/app/reference/built-in-intents"&gt;aqui&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Como funciona
&lt;/h2&gt;

&lt;p&gt;Na percepção do usuário, uma &lt;strong&gt;App Action&lt;/strong&gt; é como se fosse um “atalho” do &lt;em&gt;Google Assistant&lt;/em&gt; para o app. Na realidade, o que acontece é que o &lt;em&gt;Assistant&lt;/em&gt; faz o reconhecimento da requisição do usuário e interpreta esses dados como um &lt;em&gt;Intent&lt;/em&gt; (necessariamente precisam ser aqueles &lt;em&gt;Built-in&lt;/em&gt;, conforme mencionado anteriormente) com seus &lt;em&gt;fullfilments&lt;/em&gt; (url, parâmetros, etc.) relacionados. Após a interpretação, é feito um mapeamento com as ações correspondentes aos &lt;em&gt;Intents&lt;/em&gt; que estiverem registrados no arquivo de ações (&lt;code&gt;actions.xml&lt;/code&gt;) do app.&lt;/p&gt;

&lt;p&gt;Ao identificar um &lt;em&gt;Intent&lt;/em&gt; relacionado a uma(s) ação(ões), o &lt;em&gt;Assistant&lt;/em&gt; interpretra os parâmetros como uma entidade - que deve seguir o padrão &lt;a href="https://schema.org/"&gt;schema.org&lt;/a&gt; - e também a url (&lt;em&gt;deep link&lt;/em&gt;) mapeado no arquivo de ações do app. Por fim o sistema identifica e direciona o usuário direto para o conteúdo do app. No caso do uso de &lt;em&gt;Slice&lt;/em&gt;, a informação é mostrado diretamente no &lt;em&gt;Assistant&lt;/em&gt; (Veremos melhor mais pra frente).&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Comando no &lt;em&gt;assistant&lt;/em&gt;
&lt;/th&gt;
&lt;th&gt;Resultado no App&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TJa0Agf_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/512/1%2A-IJfzBx8MrW-fqTfHwIxUQ.png" alt=""&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SKBa8vPX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/512/1%2APBBSbysMyWjT_d2huzO9Jw.png" alt=""&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
Exemplo de funcionamento do App Actions



&lt;h2&gt;
  
  
  Projeto
&lt;/h2&gt;

&lt;p&gt;Como exemplo de código para este artigo, criei um projeto de assistente de receitas, em que você pergunta ao &lt;em&gt;Google Assistant&lt;/em&gt; coisas como “Olá Google, abrir receita de Pão de queijo” e assim é mostrado um &lt;em&gt;slice&lt;/em&gt; do app diretamente no &lt;em&gt;assistant&lt;/em&gt;. Para dispositivos em que os slices não estiverem disponíveis é apresentado uma &lt;em&gt;Activity&lt;/em&gt; com os detalhes da receita.&lt;/p&gt;

&lt;p&gt;Os dados das receitas foram alimentados por um json presente no &lt;a href="https://gist.github.com/lucasheriques/ed2214dba65b8903a5b62566f4439005"&gt;Gist&lt;/a&gt;. Para facilitar um eventual escalonamento nos dados, pensando numa possibilidade de adicionar novas receitas futuramente, importei esse json para um banco de dados no &lt;a href="https://firebase.google.com/docs/firestore"&gt;Firestore&lt;/a&gt; do &lt;a href="https://firebase.google.com/"&gt;Firebase&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Resumindo: o app recebe o nome da receita como parâmetro do &lt;em&gt;assistant&lt;/em&gt;, faz a busca no &lt;em&gt;Firestore&lt;/em&gt; e, assim que encontrado, mostra ao usuário o conteúdo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configurações iniciais
&lt;/h2&gt;

&lt;p&gt;Antes de começar, precisamos configurar o ambiente para conseguir testar as ações que implementarmos.&lt;/p&gt;

&lt;h3&gt;
  
  
  Criar um projeto na Playstore
&lt;/h3&gt;

&lt;p&gt;Para que as ações sejam reconhecidas pelo &lt;em&gt;Google Assistant&lt;/em&gt; (mesmo em teste) é necessário criar um projeto na &lt;em&gt;Playstore&lt;/em&gt; e subir pelo menos um build (APK ou aab) com o arquivo das ações do app registrado no &lt;em&gt;Manifest&lt;/em&gt;. Não é necessário publicar o app, basta ter um artefato salvo.&lt;/p&gt;

&lt;p&gt;Para mais informações sobre como inserir um app na Playstore, veja na &lt;a href="https://support.google.com/googleplay/android-developer/answer/113469"&gt;documentação&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Instalar plugin para testar as ações
&lt;/h3&gt;

&lt;p&gt;Para rodar os testes, antes de publicar o app, é necessário instalar o plugin &lt;strong&gt;App Actions Test Tool&lt;/strong&gt; no &lt;em&gt;Android Studio&lt;/em&gt;. Para saber como instalar veja &lt;a href="https://developers.google.com/assistant/app/test-tool#getting_the_tool"&gt;aqui&lt;/a&gt;. Essa ferramenta permite configurar os parâmetros da ação a ser testada.&lt;/p&gt;

&lt;p&gt;É importante pontuar que esse plugin tem algumas restrições:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Funciona apenas com dispositivos físicos&lt;/li&gt;
&lt;li&gt;Suporta apenas algumas localidades (&lt;em&gt;en-US, en-GB, en-CA, en-IN, en-BE, en-SG, e en-AU&lt;/em&gt;). Com exceção de alguns &lt;em&gt;built-in intents&lt;/em&gt; que aceitam algumas outras localidades, mas ainda bem limitado.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Após instalado, também é necessário fazer o login no &lt;em&gt;Android Studio&lt;/em&gt; com a mesma conta do dispositivo que você irá usar para os testes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Pb1qmXUE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/1400/1%2AFyk2dS-B0ZyOnHfzyTiILw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Pb1qmXUE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/1400/1%2AFyk2dS-B0ZyOnHfzyTiILw.png" alt="App Actions Test Tool"&gt;&lt;/a&gt;&lt;/p&gt;
App Actions Test Tool - Página de instalação do plugin no Android Studio.



&lt;h3&gt;
  
  
  Adicionar suporte a deep links no app
&lt;/h3&gt;

&lt;p&gt;Por fim, é necessário configurar quais telas do app devem tratar os &lt;em&gt;deep links&lt;/em&gt; transferidos do &lt;em&gt;assistant&lt;/em&gt;. No caso do projeto, foi configurado apenas que o link &lt;code&gt;recipes://detail&lt;/code&gt; abre a tela de detalhe da receita:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- AndroidManifest.xml --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;activity&lt;/span&gt; &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;".RecipeDetailActivity"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;intent-filter&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;action&lt;/span&gt; &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"android.intent.action.VIEW"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

        &lt;span class="nt"&gt;&amp;lt;category&lt;/span&gt; &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"android.intent.category.DEFAULT"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;category&lt;/span&gt; &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"android.intent.category.BROWSABLE"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

        &lt;span class="nt"&gt;&amp;lt;data&lt;/span&gt;
            &lt;span class="na"&gt;android:host=&lt;/span&gt;&lt;span class="s"&gt;"detail"&lt;/span&gt;
            &lt;span class="na"&gt;android:scheme=&lt;/span&gt;&lt;span class="s"&gt;"recipes"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/intent-filter&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/activity&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Para adicionar suporte a &lt;em&gt;deep link&lt;/em&gt;, basta adicionar dentro da tag &lt;code&gt;&amp;lt;activity&amp;gt;&lt;/code&gt; desejada no manifest as configurações de &lt;code&gt;&amp;lt;intent-filter&amp;gt;&lt;/code&gt; com &lt;code&gt;&amp;lt;action&amp;gt;&lt;/code&gt; do tipo &lt;code&gt;android.intent.action.VIEW&lt;/code&gt; e com as categorias &lt;code&gt;android.intent.category.DEFAULT&lt;/code&gt; e &lt;code&gt;android.intent.category.BROWSABLE&lt;/code&gt; (essa última é bem importante para o funcionamento das ações). Além disso é necessário definir o &lt;code&gt;schema&lt;/code&gt; (protocolo) e o &lt;code&gt;host&lt;/code&gt; da url que abrirá essa activity. É possível adicionar também definições de parâmetros esperados e &lt;em&gt;path&lt;/em&gt; da url, mas não é necessário no momento. Para mais informações sobre &lt;em&gt;deep links&lt;/em&gt; em Android, veja na &lt;a href="https://developer.android.com/training/app-links/deep-linking"&gt;documentação&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configurando as ações
&lt;/h2&gt;

&lt;p&gt;Com todas as configurações preliminares finalizadas, vamos inserir nossa primeira ação. O primeiro passo é adicionar o arquivo &lt;code&gt;actions.xml&lt;/code&gt; dentro da pasta &lt;code&gt;res/xml&lt;/code&gt;. É possível criar esse arquivo de forma automática no &lt;em&gt;Android Studio&lt;/em&gt;: &lt;code&gt;Arquivo &amp;gt; Novo &amp;gt; XML &amp;gt; Arquivo XML App Actions&lt;/code&gt;. Em seguida, determinar as ações possíveis do app nesse local:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- actions.xml --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;actions&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;action&lt;/span&gt; &lt;span class="na"&gt;intentName=&lt;/span&gt;&lt;span class="s"&gt;"actions.intent.OPEN_APP_FEATURE"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;fulfillment&lt;/span&gt; &lt;span class="na"&gt;urlTemplate=&lt;/span&gt;&lt;span class="s"&gt;"recipes://detail{?recipe}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;parameter-mapping&lt;/span&gt;
                &lt;span class="na"&gt;intentParameter=&lt;/span&gt;&lt;span class="s"&gt;"feature"&lt;/span&gt;
                &lt;span class="na"&gt;urlParameter=&lt;/span&gt;&lt;span class="s"&gt;"recipe"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/fulfillment&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/action&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/actions&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Inicialmente, configurei dessa forma a ação do app. O primeiro ponto é relacionado ao &lt;code&gt;intentName&lt;/code&gt; que precisa de ser um daqueles &lt;a href="https://developers.google.com/assistant/app/reference/built-in-intents"&gt;Built-in intents&lt;/a&gt; já mencionados anteriormente. No caso deste app de receitas não existe um &lt;em&gt;intent&lt;/em&gt; pré-definido de “ver receita” então usei o genérico que é “abrir funcionalidade” (&lt;code&gt;actions.intent.OPEN_APP_FEATURE&lt;/code&gt;). Na documentação desta ação, precisamos obter apenas um parâmetro do &lt;em&gt;intent&lt;/em&gt;, que é o &lt;em&gt;feature&lt;/em&gt;. Neste caso, os parâmetros acabam se resumindo em apenas uma &lt;em&gt;string&lt;/em&gt;, mas poderia ser qualquer estrutura de dados desde de que se encaixe no padrão &lt;a href="https://schema.org"&gt;schema.org&lt;/a&gt;. Por exemplo, outros &lt;em&gt;intents&lt;/em&gt; exigem uma estrutura fixa, como por exemplo &lt;code&gt;actions.intent.ORDER_MENU_ITEM&lt;/code&gt; que exige que os parâmetros sejam correspondentes ao schema de &lt;a href="https://schema.org/MenuItem"&gt;MenuItem&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Para facilitar a obtenção das receitas no &lt;em&gt;Firestore&lt;/em&gt;, configurei um padrão para o parâmetro de &lt;code&gt;feature&lt;/code&gt; em que o texto falado pelo usuário é mapeado para o id do documento no &lt;em&gt;Firestore&lt;/em&gt; relativo a receita escolhida.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- actions.xml --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;actions&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;action&lt;/span&gt; &lt;span class="na"&gt;intentName=&lt;/span&gt;&lt;span class="s"&gt;"actions.intent.OPEN_APP_FEATURE"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;fulfillment&lt;/span&gt; &lt;span class="na"&gt;urlTemplate=&lt;/span&gt;&lt;span class="s"&gt;"recipes://detail{?recipe}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;parameter-mapping&lt;/span&gt;
                &lt;span class="na"&gt;intentParameter=&lt;/span&gt;&lt;span class="s"&gt;"feature"&lt;/span&gt;
                &lt;span class="na"&gt;urlParameter=&lt;/span&gt;&lt;span class="s"&gt;"recipe"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/fulfillment&amp;gt;&lt;/span&gt;

        &lt;span class="nt"&gt;&amp;lt;parameter&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"feature"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;entity-set-reference&lt;/span&gt; &lt;span class="na"&gt;entitySetId=&lt;/span&gt;&lt;span class="s"&gt;"RecipeEntitySet"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/parameter&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/action&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;entity-set&lt;/span&gt; &lt;span class="na"&gt;entitySetId=&lt;/span&gt;&lt;span class="s"&gt;"RecipeEntitySet"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;entity&lt;/span&gt;
            &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"@string/frozen_yogurt"&lt;/span&gt;
            &lt;span class="na"&gt;alternateName=&lt;/span&gt;&lt;span class="s"&gt;"@array/frozen_yogurt"&lt;/span&gt;
            &lt;span class="na"&gt;identifier=&lt;/span&gt;&lt;span class="s"&gt;"2gbkANgwMJYMUnYgoRHn"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;entity&lt;/span&gt;
            &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"@string/cheese_bread"&lt;/span&gt;
            &lt;span class="na"&gt;alternateName=&lt;/span&gt;&lt;span class="s"&gt;"@array/cheese_bread"&lt;/span&gt;
            &lt;span class="na"&gt;identifier=&lt;/span&gt;&lt;span class="s"&gt;"6aRoFUbVuMp3EP48uO8M"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;entity&lt;/span&gt;
            &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"@string/mac_and_cheese"&lt;/span&gt;
            &lt;span class="na"&gt;alternateName=&lt;/span&gt;&lt;span class="s"&gt;"@array/mac_and_cheese"&lt;/span&gt;
            &lt;span class="na"&gt;identifier=&lt;/span&gt;&lt;span class="s"&gt;"7BnVOHJMxqHzUNxnYY5D"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        ...
    &lt;span class="nt"&gt;&amp;lt;/entity-set&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/actions&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Agora o parâmetro &lt;code&gt;feature&lt;/code&gt; está configurado para aceitar qualquer valor dentro do conjunto &lt;code&gt;RecipeEntitySet&lt;/code&gt;. Repare que cada &lt;code&gt;&amp;lt;entry&amp;gt;&lt;/code&gt; possui o &lt;code&gt;name&lt;/code&gt; que é um nome principal que o &lt;em&gt;assistant&lt;/em&gt; vai identificar a entidade; também tem &lt;code&gt;alternateName&lt;/code&gt; que são sinônimos ou nomes alternativos da entidade, por exemplo: pão de queijo, biscoito de queijo, etc. O &lt;code&gt;identifier&lt;/code&gt; é o identificador do documento desta entidade no &lt;em&gt;Firestore&lt;/em&gt;. Ao fazer o mapeamento dos parâmetros com as entidades suportadas, o &lt;em&gt;Assistant&lt;/em&gt; inicia o &lt;em&gt;intent&lt;/em&gt; substituindo o &lt;code&gt;identifier&lt;/code&gt; no lugar da &lt;code&gt;feature&lt;/code&gt; e aí cabe ao app fazer o tratamento da informação.&lt;/p&gt;

&lt;p&gt;Existem diversas possibilidades de configurações de parâmetros e entidades. Por exemplo, no lugar de um &lt;code&gt;identifier&lt;/code&gt;, é possível configurar uma url para cada entidade, e aí dentro de &lt;code&gt;fullfilment&lt;/code&gt; não será necessário tratar o &lt;code&gt;parameter-mapping&lt;/code&gt; pois já foi feito na entidade.&lt;/p&gt;

&lt;p&gt;Após configurar o arquivo &lt;code&gt;actions.xml&lt;/code&gt; é necessário registrá-lo no &lt;code&gt;AndroidManifest.xml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- AndroidManifest.xml --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;application&lt;/span&gt; &lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  ...
  &lt;span class="nt"&gt;&amp;lt;meta-data&lt;/span&gt;
    &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"com.google.android.actions"&lt;/span&gt;
    &lt;span class="na"&gt;android:resource=&lt;/span&gt;&lt;span class="s"&gt;"@xml/actions"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/application&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Testando as ações
&lt;/h3&gt;

&lt;p&gt;Para testar as novas ações, basta inicial o plugin do &lt;strong&gt;App Actions Test Tool&lt;/strong&gt; que foi instalado anteriormente. Ao iniciar, será pedido um nome e uma localidade para criar um preview. Ambos os campos são opcionais. Lembrando que a localidade precisa ser uma daquelas já mencionadas no início do artigo.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VWRZhlhp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/1312/1%2AmM0BaNgkmPkR41LEopBbow.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VWRZhlhp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/1312/1%2AmM0BaNgkmPkR41LEopBbow.png" alt="App Actions Test Tool - Start"&gt;&lt;/a&gt;&lt;/p&gt;
App Actions Test Tool — Create preview




&lt;p&gt;Após criar o &lt;em&gt;preview&lt;/em&gt;, a ferramenta já obtém todas as ações configuradas do app, que no nosso caso agora é apenas &lt;code&gt;actions.intent.OPEN_APP_FEATURE&lt;/code&gt;. No campo feature vamos inserir o nome da “funcionalidade” que ficou configurado como sendo o nome da receita. Repare que a localidade precisa ser em inglês, então coloquei como feature “cheese bread”.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;App Actions Test Tool Preview&lt;/th&gt;
&lt;th&gt;Resultado no App&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--W5IflgUN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/1024/1%2AkgSzaqYxGXrZinbeT0XsPA.png" alt=""&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SKBa8vPX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/512/1%2APBBSbysMyWjT_d2huzO9Jw.png" alt=""&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
Rodando uma ação através do Android App Action Test Tool



&lt;p&gt;Ao clicar em &lt;code&gt;run&lt;/code&gt; o plugin inicia o &lt;em&gt;assistant&lt;/em&gt; do dispositivo com o &lt;em&gt;intent&lt;/em&gt; de abrir funcionalidade e com o parâmetro &lt;code&gt;recipe=&amp;lt;id_recipe&amp;gt;&lt;/code&gt; pois foi o mapeamento que ele fez com as configurações que colocamos no &lt;em&gt;preview&lt;/em&gt;. Assim que é recebido pelo dispositivo, ele já reconhece que existe o app responsável por tratar essa ação e abre diretamente a tela de detalhe da receita.&lt;/p&gt;




&lt;h1&gt;
  
  
  Android Slices
&lt;/h1&gt;

&lt;p&gt;Aproveitando que já fizemos uma forma rápida do usuário acessar as funcionalidades do app através das ações, porque não mostrar o conteúdo de forma mais direta e simples? Para isso, precisamos configurar um &lt;strong&gt;Android Slice&lt;/strong&gt;. Que é uma forma dinâmica e interativa de mostrar o conteúdo do app diretamente no &lt;em&gt;assistant&lt;/em&gt;, sem a necessidade de abrir o app efetivamente. Também é possível tomar algumas ações rápidas sem estar dentro de todo o contexto da aplicação.&lt;/p&gt;

&lt;p&gt;Existem diversos templates de &lt;em&gt;slices&lt;/em&gt; que podem ser usados conforme a necessidade do aplicativo. Além disso também é possível inserir dados de forma dinâmica através de uma conexão com servidor, controladores de entrada do usuário, &lt;em&gt;live data&lt;/em&gt;, e outras diversas funcionalidades. Para saber mais sobre os &lt;em&gt;Slices&lt;/em&gt; do Android, veja na &lt;a href="https://developer.android.com/guide/slices"&gt;documentação&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Instalar visualizador de slices
&lt;/h2&gt;

&lt;p&gt;Antes de começar a criar os slices precisamos baixar um app que vai nos permitir visualizar esses componentes.&lt;/p&gt;

&lt;p&gt;Primeiro, é preciso baixar o app na página de releases do &lt;a href="https://github.com/android/user-interface-samples/releases"&gt;&lt;em&gt;Slice Viewer&lt;/em&gt;&lt;/a&gt;. Em seguida, basta instalar o APK no dispositivo. Se preferir use diretamente o comando do adb: &lt;code&gt;adb install -r -t slice-viewer.apk&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Criar provedor de slices
&lt;/h2&gt;

&lt;p&gt;O próximo passo agora é criar um &lt;code&gt;SliceProvider&lt;/code&gt; que pode ser feito diretamente pelo &lt;em&gt;Android Studio&lt;/em&gt;: &lt;code&gt;Arquivo &amp;gt; Novo &amp;gt; Outros &amp;gt; Slice Provider&lt;/code&gt;. Lembrando também que é necessário registrar esse provedor no &lt;code&gt;AndroidManifest.xml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- AndroidManifest.xml --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;application&lt;/span&gt; &lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
 ...
 &lt;span class="nt"&gt;&amp;lt;provider&lt;/span&gt;
     &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;".app.RecipeSliceProvider"&lt;/span&gt;
     &lt;span class="na"&gt;android:authorities=&lt;/span&gt;&lt;span class="s"&gt;"${applicationId}"&lt;/span&gt;
     &lt;span class="na"&gt;android:exported=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
     &lt;span class="nt"&gt;&amp;lt;intent-filter&amp;gt;&lt;/span&gt;
         &lt;span class="nt"&gt;&amp;lt;action&lt;/span&gt; &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"android.intent.action.VIEW"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

         &lt;span class="nt"&gt;&amp;lt;category&lt;/span&gt; &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"android.app.slice.category.SLICE"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

         &lt;span class="nt"&gt;&amp;lt;data&lt;/span&gt;
             &lt;span class="na"&gt;android:host=&lt;/span&gt;&lt;span class="s"&gt;"${applicationId}"&lt;/span&gt;
             &lt;span class="na"&gt;android:pathPattern=&lt;/span&gt;&lt;span class="s"&gt;"/detail"&lt;/span&gt;
             &lt;span class="na"&gt;android:scheme=&lt;/span&gt;&lt;span class="s"&gt;"content"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
     &lt;span class="nt"&gt;&amp;lt;/intent-filter&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;/provider&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/application&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;No caso, o &lt;code&gt;RecipeSliceProvider&lt;/code&gt; possui apenas um atributo que é uma lista mutável de receitas e, a cada nova ação acionada pelo usuário que mostra o &lt;em&gt;assistant&lt;/em&gt;, é feito uma busca no &lt;em&gt;Firestore&lt;/em&gt; com o id obtido na uri e, quando retornado sucesso é mostrado um conteúdo resumido da receita no &lt;em&gt;slice&lt;/em&gt; vinculado. A decisão de usar uma lista é porque podem existir inúmeros &lt;em&gt;slices&lt;/em&gt; aparecendo juntos em uma sessão do &lt;em&gt;assistant&lt;/em&gt;. Por isso ficamos com diversas referências que são associadas por seus identificadores de forma que cada &lt;em&gt;slice&lt;/em&gt; mostra o conteúdo correto da receita que corresponde.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="p"&gt;**&lt;/span&gt;&lt;span class="nc"&gt;RecipeSliceProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kt&lt;/span&gt;&lt;span class="p"&gt;**&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RecipeSliceProvider&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;SliceProvider&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onCreateSliceProvider&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onMapIntentToUri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Intent&lt;/span&gt;&lt;span class="p"&gt;?):&lt;/span&gt; &lt;span class="nc"&gt;Uri&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;uriBuilder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;scheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ContentResolver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SCHEME_CONTENT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;intent&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;uriBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;dataPath&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;dataPath&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;uriBuilder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;uriBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dataPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;uriBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;uriBuilder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;uriBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;authority&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;packageName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;uriBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onBindSlice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sliceUri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Slice&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;context&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sliceUri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="c1"&gt;//TODO("Return slice with content")&lt;/span&gt;

        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="c1"&gt;//TODO("Return slice with not found error")&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onSlicePinned&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sliceUri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;//TODO("get dynamic initial data, subscribe to any observers")&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onSliceUnpinned&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sliceUri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onSliceUnpinned&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sliceUri&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;//TODO("tear down every observer and remove unecessary resources")&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Neste provedor temos alguns pontos de atenção: &lt;code&gt;onCreateSliceProvider&lt;/code&gt; é usado para iniciar qualquer objeto que seja necessário e retornar &lt;code&gt;true&lt;/code&gt; caso o provedor tenha sido criado com sucesso, caso contrário, retorna &lt;code&gt;false&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A implementação &lt;code&gt;deonMapIntentToUri&lt;/code&gt; é necessária apenas no caso em que o app precisa obter e mapear as requisições de url e converter urls comuns em content uri. No caso desse app, o mapeamento do &lt;em&gt;slice&lt;/em&gt; foi feito diretamente via um &lt;em&gt;content uri&lt;/em&gt;, então não foi necessária toda essa implementação.&lt;/p&gt;

&lt;p&gt;O método &lt;code&gt;onBindSlice&lt;/code&gt; é responsável por construir o &lt;em&gt;slice&lt;/em&gt; e vincular os dados necessários. Também é neste método que fazemos o mapeamento do conteúdo, através do &lt;code&gt;path&lt;/code&gt; do &lt;code&gt;sliceUri&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="p"&gt;**&lt;/span&gt;&lt;span class="nc"&gt;RecipeSliceProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kt&lt;/span&gt;&lt;span class="p"&gt;**&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt;
&lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onBindSlice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sliceUri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Slice&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;context&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sliceUri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getQueryParameter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"recipe"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;resource&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getResource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sliceUri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"/detail"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nc"&gt;Resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Success&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;createContentSlice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sliceUri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nc"&gt;Resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Error&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;createErrorSlice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sliceUri&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;createLoadingSlice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sliceUri&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;createErrorSlice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sliceUri&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Em &lt;code&gt;onSlicePinned&lt;/code&gt; o &lt;em&gt;slice&lt;/em&gt; foi atrelado a um serviço e está prestes a ser exibido pro usuário. Neste momento é hora de buscar os dados do servidor, sobrescrever para algum &lt;em&gt;observable&lt;/em&gt;, ou ações semelhantes, quando necessário. Ao final, quando obter os dados necessários, basta chamar &lt;code&gt;context.contentResolver.notifyChanged(sliceUri, null)&lt;/code&gt; para que seja atualizado o &lt;code&gt;onBindSlice&lt;/code&gt; com o novo conteúdo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="p"&gt;**&lt;/span&gt;&lt;span class="nc"&gt;RecipeSliceProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kt&lt;/span&gt;&lt;span class="p"&gt;**&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt;
&lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onSlicePinned&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sliceUri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sliceUri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getQueryParameter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"recipe"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nc"&gt;FirestoreManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRecipeById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isSuccessful&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;doc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;
            &lt;span class="n"&gt;recipes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;toRecipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
            &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;contentResolver&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;notifyChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sliceUri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;recipes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;printStackTrace&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;contentResolver&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;notifyChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sliceUri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Para o caso exclusivo deste app e para facilitar o desenvolvimento, foi usado o SDK puro do Firestore para obtenção dos dados, de forma que foram usados apenas callbacks sem interação com &lt;em&gt;live data&lt;/em&gt;, e &lt;em&gt;observables&lt;/em&gt; por exemplo.&lt;/p&gt;

&lt;p&gt;Por fim, no &lt;code&gt;onSliceUnpinned&lt;/code&gt; é a hora de remover os &lt;em&gt;observers&lt;/em&gt; e fazer qualquer limpeza para evitar vazamentos de memória. No caso do app de receitas, é feito apenas a limpeza dos dados dentro da lista &lt;code&gt;recipes&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Usar templates para slices
&lt;/h3&gt;

&lt;p&gt;Os &lt;em&gt;Slices&lt;/em&gt; são construídos através de um sistema de lista (&lt;code&gt;ListBuilder&lt;/code&gt;) que pode ter diferentes tipos de formato de linhas. O primeiro é o cabeçalho (&lt;code&gt;HeaderBuilder&lt;/code&gt;) que possuem as informações principais do &lt;em&gt;slice&lt;/em&gt; e também é o conteúdo mostrado quando o &lt;em&gt;slice&lt;/em&gt; está no modo de visualização compacto. Outro formato é a linha em si (&lt;code&gt;RowBuilder&lt;/code&gt;) para um conteúdo mais simples, e podendo ter uma ou mais ações rápidas atreladas. De forma semelhante tem o formato de grid (&lt;code&gt;GridBuilder&lt;/code&gt;) que mostra o conteúdo em diferentes colunas dentro da mesma linha. Também é possível adicionar uma barra de progresso ou um &lt;em&gt;slider&lt;/em&gt; através do &lt;code&gt;RangeBuilder&lt;/code&gt;. Para mais informações sobre templates dos &lt;em&gt;slices&lt;/em&gt; veja na &lt;a href="https://developer.android.com/guide/slices/templates"&gt;documentação&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Para o app de receitas usei o &lt;code&gt;HeaderBuilder&lt;/code&gt; para mostrar o nome da receita no modo reduzido do &lt;em&gt;slice&lt;/em&gt; e nas outras partes, foi usado o &lt;code&gt;RowBuilder&lt;/code&gt; por ser um conteúdo mais simples e único, precisando apenas de linhas mesmo. Contudo, foi configurado três tipos de &lt;em&gt;slices&lt;/em&gt;: um para ser exibido enquanto os dados são carregados, outro para quando a receita foi retornada e outro para o caso de ter retornado algum erro ou não ter sido encontrada a receita.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="p"&gt;**&lt;/span&gt;&lt;span class="nc"&gt;RecipeSliceProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kt&lt;/span&gt;&lt;span class="p"&gt;**&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;createLoadingSlice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sliceUri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sliceUri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ListBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;INFINITY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;header&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nf"&gt;setTitle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loading_recipe&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Para os &lt;em&gt;slices&lt;/em&gt; é importante que apareça alguma informação instantânea para o usuário mesmo ainda não tendo todos os dados a serem mostrados .. por isso, foi criado o &lt;em&gt;slice&lt;/em&gt; para mostrar que o conteúdo está carregando.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="p"&gt;**&lt;/span&gt;&lt;span class="nc"&gt;RecipeSliceProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kt&lt;/span&gt;&lt;span class="p"&gt;**&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;createContentSlice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sliceUri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;recipe&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Recipe&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;recipe&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sliceUri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ListBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;INFINITY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;header&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nf"&gt;setTitle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;recipe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;orEmpty&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="n"&gt;subtitle&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;recipe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;baseIngredients&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;joinToString&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;orEmpty&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
          &lt;span class="n"&gt;summary&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;recipe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;baseIngredients&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;joinToString&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;orEmpty&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
          &lt;span class="n"&gt;primaryAction&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createDetailAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;recipe&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nf"&gt;gridRow&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nf"&gt;cell&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;bitmap&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Glide&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;asBitmap&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;recipe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
              &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;image&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
                  &lt;span class="nc"&gt;IconCompat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createWithBitmap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bitmap&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
              &lt;span class="nf"&gt;addImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ListBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LARGE_IMAGE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nf"&gt;row&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;directions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="n"&gt;subtitle&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;recipe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;directions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;orEmpty&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nf"&gt;createErrorSlice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sliceUri&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Para o carregamento da imagem da receita foi utilizada a biblioteca &lt;a href="https://bumptech.github.io/glide/"&gt;Glide&lt;/a&gt; que transforma uma url de imagem em um &lt;em&gt;bitmap&lt;/em&gt; no android e, com isso foi gerado o &lt;code&gt;IconImage&lt;/code&gt; necessário para ser inserido no &lt;em&gt;slice&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="p"&gt;**&lt;/span&gt;&lt;span class="nc"&gt;RecipeSliceProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kt&lt;/span&gt;&lt;span class="p"&gt;**&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;createErrorSlice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sliceUri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sliceUri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ListBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;INFINITY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;header&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error_recipe_not_found&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="n"&gt;primaryAction&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createAppAction&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Por fim, repare que em diversos momentos foi configurado também uma &lt;code&gt;primaryAction&lt;/code&gt; que é uma &lt;code&gt;SliceAction&lt;/code&gt; na qual possui informações de &lt;em&gt;intents&lt;/em&gt; e &lt;em&gt;pending intents&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="p"&gt;**&lt;/span&gt;&lt;span class="nc"&gt;RecipeSliceProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kt&lt;/span&gt;&lt;span class="p"&gt;**&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;createDetailAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;recipe&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Recipe&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="nf"&gt;createAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RecipeDetailActivity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;recipe&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;createAppAction&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; 
  &lt;span class="nf"&gt;createAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Intent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MainActivity&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;createAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Intent&lt;/span&gt;&lt;span class="p"&gt;?):&lt;/span&gt; &lt;span class="nc"&gt;SliceAction&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="nc"&gt;SliceAction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nc"&gt;PendingIntent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getActivity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;PendingIntent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;FLAG_UPDATE_CURRENT&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nc"&gt;IconCompat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createWithResource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;drawable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ic_launcher_foreground&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nc"&gt;ListBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ICON_IMAGE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;getString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open_app&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;orEmpty&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Testar slice no dispositivo
&lt;/h3&gt;

&lt;p&gt;Para testar no &lt;em&gt;Slice Viewer&lt;/em&gt; instalado anteriormente, é necessário criar uma nova configuração de execução no &lt;em&gt;Android Studio&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ETaU7IPk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/1400/1%2AzpYICuYOe377LzW4zVCRNA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ETaU7IPk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/1400/1%2AzpYICuYOe377LzW4zVCRNA.png" alt="Slice run configuration"&gt;&lt;/a&gt;&lt;/p&gt;
Adicionar nova configuração de execução para mostrar o Slice no Slice Viewer



&lt;p&gt;Nomeie-a como preferir e em nas opções de lançamento (&lt;em&gt;Launch Options&lt;/em&gt;) selecione o modo URL e na url coloque &lt;code&gt;slice-content://&amp;lt;application_id&amp;gt;/path&lt;/code&gt; de acordo com o que você configurou no &lt;em&gt;SliceProvider&lt;/em&gt; dentro do &lt;code&gt;AndroidManifest.xml.&lt;/code&gt; No caso do app de receitas, também foi necessário adicionar o parâmetro &lt;code&gt;recipe&lt;/code&gt; porque precisamos dele para saber qual a receita mostrar pro usuário. Após adicionado, basta rodar essa nova configuração.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;
&lt;em&gt;Slice&lt;/em&gt; carregando&lt;/th&gt;
&lt;th&gt;
&lt;em&gt;Slice&lt;/em&gt; de conteúdo&lt;/th&gt;
&lt;th&gt;
&lt;em&gt;Slice&lt;/em&gt; de erro&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YiOK4UbY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/512/1%2Aaq3VzEyFM2tVQIUluVKnqw.png" alt=""&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mVUBDyoD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/512/1%2A-8ssSMJNRoIGZsJvTmyUzQ.png" alt=""&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3ICcLce7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/512/1%2AYFuauITYBabEFO163WRJNg.png" alt=""&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
Slice Viewer — Exemplos de slice



&lt;h2&gt;
  
  
  Configurar ação para usar o slice
&lt;/h2&gt;

&lt;p&gt;Finalmente, vamos configurar o arquivo &lt;code&gt;actions.xml&lt;/code&gt; para mostrar o &lt;em&gt;slice&lt;/em&gt; criado. Para isso, basta adicionar um novo &lt;code&gt;fullfiment&lt;/code&gt; dentro da ação com &lt;code&gt;fullfilmentMode="actions.fullfilment.SLICE&lt;/code&gt; e o &lt;code&gt;urlTemplate&lt;/code&gt; de acordo com o que ficou no &lt;code&gt;AndroidManifest&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- actions.xml --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;actions&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;action&lt;/span&gt; &lt;span class="na"&gt;intentName=&lt;/span&gt;&lt;span class="s"&gt;"actions.intent.OPEN_APP_FEATURE"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;fulfillment&lt;/span&gt;
            &lt;span class="na"&gt;fulfillmentMode=&lt;/span&gt;&lt;span class="s"&gt;"actions.fulfillment.SLICE"&lt;/span&gt;
            &lt;span class="na"&gt;urlTemplate=&lt;/span&gt;&lt;span class="s"&gt;"content://com.anacoimbra.android.recipes/detail{?recipe}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;parameter-mapping&lt;/span&gt;
                &lt;span class="na"&gt;intentParameter=&lt;/span&gt;&lt;span class="s"&gt;"feature"&lt;/span&gt;
                &lt;span class="na"&gt;required=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;
                &lt;span class="na"&gt;urlParameter=&lt;/span&gt;&lt;span class="s"&gt;"recipe"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/fulfillment&amp;gt;&lt;/span&gt;
      ...
  &lt;span class="nt"&gt;&amp;lt;/action&amp;gt;&lt;/span&gt;
  ...
&lt;span class="nt"&gt;&amp;lt;/actions&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Pronto! Agora já está configurado para abrir o &lt;em&gt;slice&lt;/em&gt; direto no &lt;em&gt;Google Assistant&lt;/em&gt;. Para testar essa parte agora, basta abrir o &lt;strong&gt;Android App Actions Test Tool&lt;/strong&gt; novamente e, se tiver um &lt;em&gt;preview&lt;/em&gt; já criado, clique em &lt;em&gt;Update Preview&lt;/em&gt; para que a ferramenta possa atualizar suas informações com as alterações feitas em &lt;code&gt;actions.xml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1LyM7hIP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/400/1%2ASb4x8oFlG7y0yaI91o3gKQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1LyM7hIP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/400/1%2ASb4x8oFlG7y0yaI91o3gKQ.png" alt="Slice on assistant"&gt;&lt;/a&gt;&lt;/p&gt;
Slice no Google Assistant






&lt;h2&gt;
  
  
  Considerações finais
&lt;/h2&gt;

&lt;p&gt;Implementar ações e &lt;em&gt;slices&lt;/em&gt; para o app pode ser algo bem interessante que ajuda muito no engajamento e na interação do usuário. Contudo, muitas coisas ainda estão em fase experimental, por isso algumas questões ainda podem se alterar e tem muito a ser aprimorado.&lt;/p&gt;

&lt;p&gt;De qualquer forma, é uma funcionalidade incrível que vale a pena testar e aprender pois está cada vez mais comum obtermos conteúdo através de assistentes virtuais e ter isso a favor do nosso aplicativo pode ser um alto ganho de usabilidade e negócio.&lt;/p&gt;

&lt;p&gt;O código completo do projeto abordado neste artigo pode ser encontrado no &lt;a href="https://github.com/anacoimbrag/recipes-assistant"&gt;Github&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>android</category>
      <category>assistant</category>
      <category>androiddev</category>
    </item>
    <item>
      <title>Realidade Aumentada para Android: ARCore e Sceneform</title>
      <dc:creator>Ana Coimbra</dc:creator>
      <pubDate>Sun, 19 Apr 2020 20:31:52 +0000</pubDate>
      <link>https://dev.to/anacoimbrag/realidade-aumentada-para-android-arcore-e-sceneform-3ae</link>
      <guid>https://dev.to/anacoimbrag/realidade-aumentada-para-android-arcore-e-sceneform-3ae</guid>
      <description>&lt;p&gt;Com a quantidade de aplicativos e soluções digitais atualmente, os usuários estão exigindo cada vez mais interações mais imersivas e que sejam cada vez mais próxima da realidade. Com isso, vemos crescer o número de aplicações com o uso de realidade aumentada.&lt;/p&gt;

&lt;h2&gt;
  
  
  O que é Realidade Aumentada?
&lt;/h2&gt;

&lt;p&gt;Realidade Aumentada é uma experiência interativa em que objetos do mundo virtual sobrepõe o mundo real e, dessa forma, os dois coexistem e podem interagir entre si.&lt;/p&gt;

&lt;p&gt;Um aplicativo bem famoso que usa esse recurso é o Pokémon GO.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2htUYs0u--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/d6inpecl7sf8bcma2qqa.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2htUYs0u--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/d6inpecl7sf8bcma2qqa.jpg" alt="Pokemon Go"&gt;&lt;/a&gt;&lt;/p&gt;
Aplicativo Pókemon GO usando Realidade Aumentada. Fonte: Tumisu from Pixabay



&lt;p&gt;Também é usado em aplicativos de lojas de móveis e acessórios pra casa, para que se possa visualizar o objeto no ambiente real.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HoAxGtI_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://anacoimbra.dev/media/arcore-sceneform/furniture.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HoAxGtI_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://anacoimbra.dev/media/arcore-sceneform/furniture.gif" alt="Furniture in AR"&gt;&lt;/a&gt;&lt;/p&gt;
Posicionamento de móveis usando realidade aumentada



&lt;p&gt;Com o avanço dos hardwares disponíveis em diversos dispositivos do mercado, principalmente nos smartphones intermediários, essa tem se tornado uma tecnologia emergente que muitas empresas estão investindo.Se quiser saber mais informações, fiz também um &lt;a href="https://dev.to/anacoimbrag/realidade-aumentada-fundamentos-e-conceitos-1e84"&gt;texto&lt;/a&gt; mais teórico sobre realidade aumentada.&lt;/p&gt;




&lt;h2&gt;
  
  
  Google ARCore
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://developers.google.com/ar/"&gt;ARCore&lt;/a&gt; é a plataforma da Google pra construir experiências em realidade aumentada para Android, Unit, e até mesmo pra web. Para deixar a interação entre mundo real e virtual mais completa, faz uso de três características principais através da câmera do dispositivo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;O &lt;strong&gt;rastreamento de movimento&lt;/strong&gt; permite entender e rastrear a posição do dispositivo em relação ao espaço.&lt;/li&gt;
&lt;li&gt;O &lt;strong&gt;entendimento do ambiente&lt;/strong&gt; permite que o dispositivo reconheça o tamanho e localização de todos os tipos de superfícies disponíveis.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;estimativa de luz&lt;/strong&gt; permite que o dispositivo estime as condições atuais de luz ambiente.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Basicamente, o &lt;em&gt;ARCore&lt;/em&gt; rastreia posição do dispositivo conforme ele se move e ao mesmo tempo constrói seu entendimento do mundo real. Para fazer isso ele utiliza pontos chaves ( &lt;strong&gt;feature points&lt;/strong&gt; ) na imagem da câmera que ajudam a detectar diversos tipos de superfícies e também as mudanças de localização.&lt;/p&gt;

&lt;p&gt;Se tem vontade de aprender mais a fundo sobre o &lt;em&gt;ARCore&lt;/em&gt; e seus fundamentos, basta verificar a sua &lt;a href="https://developers.google.com/ar/discover/concepts"&gt;documentação&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Limitações
&lt;/h3&gt;

&lt;p&gt;Para uma boa experiência, é necessário demonstrar ao usuário o espaço necessário para o pleno funcionamento da aplicação. No caso de grandes espaços e lugares públicos, o grande desafio é, principalmente, a &lt;strong&gt;oclusão&lt;/strong&gt; , quando um objeto virtual sobrepõe objetos físicos mas na verdade não deveria. No caso do ARCore atualmente não é possível tratar esse problema, mas a Google já anunciou o desenvolvimento da &lt;a href="https://developers.google.com/ar/develop/call-for-collaborators#depth-api"&gt;ARCore Depth API&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Um dos fatores essenciais em algumas aplicações com ARCore é a &lt;strong&gt;detecção do(s) plano(s)&lt;/strong&gt; onde serão inseridos os objetos virtuais. Contudo alguns fatores podem limitar ou dificultar a detecção destes planos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Superfícies muito lisas e sem textura como uma parede branca&lt;/li&gt;
&lt;li&gt;Ambientes com pouca iluminação&lt;/li&gt;
&lt;li&gt;Ambientes com iluminação em excesso&lt;/li&gt;
&lt;li&gt;Superfícies refletivas ou transparentes como espelho ou vidro&lt;/li&gt;
&lt;li&gt;Superfícies em movimento como aqueles robôs que varrem a casa&lt;/li&gt;
&lt;li&gt;Superfícies verticais são mais difíceis de serem detectadas que as horizontais&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nestes casos, pode ser que o plano demore muito a ser detectado ou nem aconteça e para ajudar o usuário a entender o que está acontecendo, e ele não achar que é um “erro no sistema” sempre deixe claro as condições necessárias.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nu8ujJCn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://anacoimbra.dev/media/arcore-sceneform/plane-detection.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nu8ujJCn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://anacoimbra.dev/media/arcore-sceneform/plane-detection.gif" alt="Plane detection"&gt;&lt;/a&gt;&lt;/p&gt;
Visualização da detecção de plano - Fonte: Google



&lt;p&gt;Quando acionado, o ARCore está sempre ampliando seu entendimento do ambiente e, com isso, pode ser que as primeiras detecções de &lt;em&gt;feature points&lt;/em&gt; não sejam tão fidedignas ao mundo real mas, com o tempo isso é ajustado a medida que a aplicação entende melhor o ambiente. Esse ponto pode causar frustração no usuário, tendo em vista que os objetos a serem inseridos em um plano detectado precocemente podem estar fora de escala com o mundo real. Portanto, é recomendado que, após a primeira detecção do plano, o usuário saiba que precisa continuar movendo o dispositivo para o pleno entendimento do ambiente pela aplicação.&lt;/p&gt;

&lt;h3&gt;
  
  
  Boas práticas
&lt;/h3&gt;

&lt;p&gt;Diversas experiências com realidade aumentada acabam ocorrendo em ambientes não controlados e o usuário é livre para fazer o que bem entende durante sua interação. Portanto, é necessário deixar bem claro pra ele qual o espaço necessário para que os objetos virtuais tenham uma boa visualização dentro do mundo real.&lt;/p&gt;

&lt;p&gt;Além disso, essa relação entre mundo virtual e físico precisa ser coerente, por exemplo: não é estranho um guarda-roupa virtual aparecer em cima de um banco? Pois é, por isso é necessário que a pessoa interagindo com o aplicativo saiba qual o espaço ela deve usar: se é uma mesa, um cômodo ou um espaço ao ar livre, por exemplo. Lembrando que também é necessário ser responsivo tendo em vista que existem inúmeros tamanhos de mesa, de cômodos, etc.&lt;/p&gt;

&lt;p&gt;Durante a detecção de plano, informe na interface que é necessário mover o dispositivo com imagens e animações. Após o(s) plano(s) ser(em) detectado(s) informe o que o usuário deve fazer em seguida como, por exemplo, adicionar um objeto à cena ao clicar em um plano. Além disso, é necessário realçar a(s) superfície(s) encontrada(s) para que o usuário saiba o espaço que possui pra interagir, mas apenas uma superfície por vez e apenas se o usuário estiver apontando para ela.&lt;/p&gt;

&lt;p&gt;Usuários de smartphones estão acostumados a usarem aplicativos de forma estática ou seja, não saem do lugar ou de sua posição. Contudo, em uma aplicação com ARCore, é possível (e recomendado) que a pessoa se movimente e explore o ambiente e tudo que cerca o mundo virtual inserido no mundo físico. Por isso, é dar indicações para que ela se mova e tenha uma experiência mais imersiva (que é o principal propósito da realidade aumentada).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Q61iCntE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://anacoimbra.dev/media/arcore-sceneform/move-clues.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Q61iCntE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://anacoimbra.dev/media/arcore-sceneform/move-clues.gif" alt="Moving clues"&gt;&lt;/a&gt;&lt;/p&gt;
Pistas na cena para o usuário saber que pode se movimentar - Fonte: Google



&lt;p&gt;É claro que terão momentos em que o usuário não estará habilitado a se movimentar, para isso é recomendado dar alternativas para que ele possa imergir na experiência também, como opção de rotacionar, transladar e escalonar os objetos virtuais da cena.&lt;/p&gt;

&lt;p&gt;Os usuários quando estão interagindo com uma experiência de realidade aumentada tendem a ficar tão imersos nela que acabam esquecendo do (resto do) mundo real. Por isso, é imprescindível mantê-lo seguro:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nunca faça o usuário andar para trás, ele não verá se tem algum obstáculo e pode colidir e/ou sofrer uma queda.&lt;/li&gt;
&lt;li&gt;Evite experiências que duram um longo período de tempo pois o indivíduo pode se sentir cansado e ter dores após um uso intenso de uma aplicação com realidade aumentada. Proponha pontos de “parada” para que ele tire um tempo para descansar.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YrXaF17J--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://anacoimbra.dev/media/arcore-sceneform/backward-movement.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YrXaF17J--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://anacoimbra.dev/media/arcore-sceneform/backward-movement.gif" alt="Backwards movement"&gt;&lt;/a&gt;&lt;/p&gt;
Evite situações que o usuário tenha que andar para trás - Fonte: Google






&lt;h2&gt;
  
  
  Sceneform
&lt;/h2&gt;

&lt;p&gt;Sceneform é uma biblioteca &lt;em&gt;opensource&lt;/em&gt; que permite utilizar todos os recursos do &lt;strong&gt;ARCore no Android&lt;/strong&gt; sem a necessidade direta de manipulação das informações no &lt;strong&gt;OpenGL&lt;/strong&gt;. Ela já nos oferece diversas facilidades também se tratando de funcionalidades padrões de uma aplicação em realidade aumentada como: posicionamento de um objeto no ambiente, renderização de um objeto através de augmented image que utiliza uma imagem como padrão para reconhecer o espaço e determinar posicionamento e orientação, também chamado de &lt;em&gt;fiducial marker&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Histórico
&lt;/h3&gt;

&lt;p&gt;Até a versão &lt;a href="https://github.com/google-ar/sceneform-android-sdk/tree/v1.15.0"&gt;1.15.0&lt;/a&gt; do Sceneform, a bibilioteca não era totalmente &lt;em&gt;opensource&lt;/em&gt; e, portanto tínhamos total acesso apenas às funcionalidades diretamente relacionadas a interface com usuário e alguns exemplos de código. Agora, a partir da versão &lt;a href="https://github.com/google-ar/sceneform-android-sdk/tree/v1.16.0%C2%A0"&gt;1.16.0&lt;/a&gt; ela está 100% &lt;em&gt;opensource&lt;/em&gt; e os códigos de exemplos anteriores foram descontinuados. Antes, existiam duas formas de carregar um modelo: uma utilizando um arquivo  &lt;strong&gt;.sfb&lt;/strong&gt; , que era obtido pelo plugin da própria biblioteca no Android Studio e o outra é utilizando um arquivo  &lt;strong&gt;.glTF&lt;/strong&gt; ou &lt;strong&gt;.glb&lt;/strong&gt; que não necessita a utilização do plugin e pode ser carregado diretamente via url do arquivo. O suporte a utilização de arquivos .sfb foi descontinuada e agora só é possível através da segunda forma, por isso os exemplos anteriores foram removidos.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--X1f1HtlM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://anacoimbra.dev/media/arcore-sceneform/sceneform.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--X1f1HtlM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://anacoimbra.dev/media/arcore-sceneform/sceneform.gif" alt="Sceneform"&gt;&lt;/a&gt;&lt;/p&gt;
Exemplo Sceneform - Fonte: Google



&lt;h2&gt;
  
  
  Conceitos
&lt;/h2&gt;

&lt;p&gt;O &lt;em&gt;Sceneform&lt;/em&gt; facilita muito o desenvolvimento de aplicações com ARCore, mas mesmo assim, ainda trás alguns conceitos importantes que devem ser entendidos bem para um bom funcionamento da experiência.&lt;/p&gt;




&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Renderable&lt;/strong&gt; é a classe base para renderizar qualquer objeto dentro do mundo 3D. Pode ser dois tipos: &lt;strong&gt;ViewRenderable&lt;/strong&gt; que renderiza componentes visuais como &lt;em&gt;TextView&lt;/em&gt;, &lt;em&gt;Button&lt;/em&gt; dentro do espaço e o &lt;strong&gt;ModelRenderable&lt;/strong&gt; que renderiza objetos 3D em geral, como um cubo, esfera ou qualquer objeto no formato &lt;em&gt;.gLTF&lt;/em&gt; ou &lt;em&gt;.glb&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Node&lt;/strong&gt; representa um item de transformação dentro da cena. Pode ter um renderable atrelado a ele que será apresentado na cena. Um node pode ter inúmeros nodes “filhos” e um “pai”, que pode ser outro &lt;em&gt;node&lt;/em&gt; ou a cena(&lt;em&gt;scene&lt;/em&gt;).Além de ser possível personalizar o próprio &lt;em&gt;node&lt;/em&gt;, existem vários tipos já pré-determinados: &lt;em&gt;AnchorNode&lt;/em&gt;, que é automaticamente posicionado na cena de acordo com seu ponto âncora atrelado; &lt;em&gt;AugmentedFaceNode&lt;/em&gt;, usado para renderizar funcionalidades de reconhecimento de face e contém dois componentes: &lt;em&gt;face mash&lt;/em&gt; que utiliza imagens em 2D e coloca na face reconhecida e renderables para regiões da face e aí é possível detectar pontos como nariz, boca, olhos, etc.; &lt;em&gt;TransformableNode&lt;/em&gt; que é um node que pode ser manipulado no espaço com rotação, translação e escala; &lt;em&gt;Camera&lt;/em&gt; representa a câmera virtual e mostra a perspectiva pela qual a cena vai ser renderizada; &lt;em&gt;SkeletonNode&lt;/em&gt; representa as partes do “esqueleto” de um &lt;em&gt;ModelRenderable&lt;/em&gt;; &lt;em&gt;Sun&lt;/em&gt; representa o “sol” da cena que nada mais é que uma luz direcional.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scene&lt;/strong&gt; mantém a estrutura de dados dos componentes da cena, que nada mais é que um grafo, mais especificamente uma árvore, que tem como raíz a cena(&lt;em&gt;scene&lt;/em&gt;) e tem inúmeros filhos(&lt;em&gt;nodes&lt;/em&gt;) que por sua vez podem ter outros filhos &lt;em&gt;nodes&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Light&lt;/strong&gt; representa a luz aplicada a um node. Pode ser direcional ( &lt;strong&gt;DIRECTIONAL&lt;/strong&gt; ) que representa uma luz infinitamente longe; pontual ( &lt;strong&gt;POINT&lt;/strong&gt; ) que irradia raios de luz em todas as direções a partir de um único ponto de luz; holofote ( &lt;strong&gt;SPOTLIGHT&lt;/strong&gt; ) que é parecida com a luz pontual mas irradia os raios em um cone; holofote focado ( &lt;strong&gt;FOCUSE_SPOTLIGHT&lt;/strong&gt; ) é igual ao holofote normal mas a percepção da intensidade é a mesma independente do angulo do cone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plane&lt;/strong&gt; descreve os atributos de uma superfície planar identificada pela aplicação. É possível que mais de um plano sejam fundidos em um plano “pai” que por sua vez é mais amplo que seus “filhos”. Para visualizar os planos detectados, é usado o &lt;strong&gt;PlaneRenderer&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;Anchor&lt;/em&gt;&lt;/strong&gt; ou &lt;strong&gt;âncora&lt;/strong&gt; descreve uma posição fixa no mundo real, contento localização (coordenadas) e direção e também ajuda no entendimento do ambiente feito pelo &lt;em&gt;ARCore&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CollisionShape&lt;/strong&gt; possui a representação matemática de alguma forma geométrica que é usada em cálculo de colisões ou tamanho relativo entre os objetos (virtuais) da cena. Pode ser um cubo, uma esfera ou um raio.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Conclusão
&lt;/h2&gt;

&lt;p&gt;O &lt;strong&gt;ARCore&lt;/strong&gt; tem se tornado uma excelente ferramenta para criação de experiências com Realidade Aumentada para diversas plataformas, principalmente o Android. Apesar de ainda ter algumas limitações, tem resolvido de forma eficiente as demandas de aplicações em RA que tenho observado. Além disso, essas limitações conseguem ter menos impacto quando mantemos o usuário sempre informado do que está acontecendo e do que ele pode e/ou precisa fazer para melhorar a experiência.&lt;/p&gt;

&lt;p&gt;Além disso, com o &lt;strong&gt;Sceneform&lt;/strong&gt; é possível fazer excelentes experiências com realidade aumentada de forma simples e rápida. Contudo, fazer customizações de visualização e renderização pode ser um grande desafio, tendo em vista que a documentação tem certa limitação em relação aos componentes da cena e também não se encontra muito material avançado sobre o assunto.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Política de Cross-Origin</title>
      <dc:creator>Ana Coimbra</dc:creator>
      <pubDate>Sun, 19 Apr 2020 03:04:00 +0000</pubDate>
      <link>https://dev.to/portugues/politica-de-cross-origin-21pa</link>
      <guid>https://dev.to/portugues/politica-de-cross-origin-21pa</guid>
      <description>&lt;p&gt;É muito comum uma pessoas que desenvolvem Front-End se depararem com um erro frequente que é parecido com:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Origin &lt;a href="http://localhost"&gt;http://localhost&lt;/a&gt; is not allowed by Access-Control-Allow-Origin&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;E então vamos procurar na internet como resolver esse problema e nos deparamos com diversas soluções e cada uma funcionando de forma diferente e as vezes só copiamos e colamos e não entendemos muito bem.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ptb240B_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/pq26lhu9yzw6qwk1o6ym.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ptb240B_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/pq26lhu9yzw6qwk1o6ym.png" alt="Resposta do StackOverflow falando para adicionar um header no servidor"&gt;&lt;/a&gt;&lt;/p&gt;
Resposta do StackOverflow falando para adicionar um header no servidor



&lt;p&gt;Pensando neste exemplo de resposta do StackOverflow, a recomendação é adicionar um header no servidor e tudo será resolvido. Se alguém que passou por essa questão e fez apenas o que foi sugerido na imagem, acredito que não resolveu 100% do problema (sim, já aconteceu comigo). Além disso, nem sempre temos acesso a este servidor.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7ADBmWC9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ebphv65de3z1uj3tm5ig.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7ADBmWC9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ebphv65de3z1uj3tm5ig.png" alt="Resposta do StackOverflow para abrir o chrome em modo sem segurança"&gt;&lt;/a&gt;&lt;/p&gt;
Resposta do StackOverflow para abrir o chrome em modo sem segurança



&lt;p&gt;Neste outro caso, é recomendado que a pessoa abra o navegador com a segurança desativada que os requests vão funcionar. Já testei esse modo também e é incrível! Tudo funciona perfeitamente! Mas será que todos os usuários do meu site vão fazer isso também? É, acho que não.&lt;/p&gt;

&lt;p&gt;Neste artigo vou mostrar um pouco do que é essa política cross-origin, mais conhecida como CORS.&lt;/p&gt;




&lt;h2&gt;
  
  
  O que é CORS?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;C&lt;/strong&gt;ross-&lt;strong&gt;O&lt;/strong&gt;rigin &lt;strong&gt;R&lt;/strong&gt;esource &lt;strong&gt;S&lt;/strong&gt;haring é a sigla atribuída ao mecanismo dos navegadores que gerencia o compartilhamento de recursos entre diferentes origens.&lt;/p&gt;

&lt;p&gt;Por padrão, quando fazemos uma requisição através do &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest"&gt;XMLHttpRequest&lt;/a&gt; ou utilizando a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API"&gt;API Fetch&lt;/a&gt; do javascript, os navegadores utilizam a política &lt;em&gt;same-origin&lt;/em&gt; que só autoriza a troca de recursos entre as mesmas origens (domínios). Se for necessário essa troca entre diferentes origens, é necessário configurar as chamadas para que contenham os cabeçalhos CORS corretos.&lt;/p&gt;

&lt;p&gt;A utilização do CORS é importante porque além de poder restringir QUEM pode acessar os recursos de um servidor também pode especificar COMO esses recursos devem ser acessados. Mais para frente retornaremos nesta parte com exemplos.&lt;/p&gt;

&lt;h2&gt;
  
  
  O que é uma origem?
&lt;/h2&gt;

&lt;p&gt;Uma origem, ou domínio é composto por três partes: protocolo, host e porta.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fj85pozG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/yrz87dfb1xr4satxoj6x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fj85pozG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/yrz87dfb1xr4satxoj6x.png" alt="Definição de Origem ou Domínio: conjunto de protocolo, host e porta"&gt;&lt;/a&gt;&lt;/p&gt;
Definição de Origem ou Domínio: conjunto de protocolo, host e porta



&lt;p&gt;Pegando como exemplo o domínio &lt;em&gt;&lt;a href="http://exemplo.com"&gt;http://exemplo.com&lt;/a&gt;&lt;/em&gt; acima temos como protocolo HTTP, o host exemplo.com e a porta (oculta) 80. Se for necessário obter um recurso que mude qualquer um destes 3 itens, já não está dentro da mesma origem e, portanto a política same-origin não é mais válida e por isso precisamos configurar as chamadas para utilizarmos a política &lt;em&gt;cross-origin&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--WCudWGpr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/dn7eqlsio49fvj6xunef.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--WCudWGpr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/dn7eqlsio49fvj6xunef.png" alt="Políticas de compartilhamento de recursos"&gt;&lt;/a&gt;&lt;/p&gt;
Políticas de compartilhamento de recursos






&lt;h2&gt;
  
  
  Como funciona o CORS?
&lt;/h2&gt;

&lt;p&gt;O mecanismo funciona adicionando cabeçalhos HTTP nas respostas dos servidores para que estabeleçam um compartilhamento de recursos seguro entre diferentes origens. Com ele é possível configurar as origens permitidas para acessar o recurso e quais métodos (GET, POST, PUT, DELETE, etc.) elas podem utilizar.&lt;/p&gt;

&lt;p&gt;Além disso, quando o site faz uma requisição que pode causar &lt;strong&gt;efeitos colaterais&lt;/strong&gt; no servidor, o navegador realiza uma “pré-requisição” ou &lt;em&gt;preflight request&lt;/em&gt; para que o servidor retorne que é possível realizar a requisição que é pedida originalmente e só então ela é enviada ao servidor.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3EZqtcSe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/3118pmzuol3f1hx5wlpu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3EZqtcSe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/3118pmzuol3f1hx5wlpu.png" alt="Preflight — Simulação de uma conversa entre o Servidor A e o Servidor B"&gt;&lt;/a&gt;&lt;/p&gt;
Preflight — Simulação de uma conversa entre o Servidor A e o Servidor B



&lt;p&gt;A figura acima mostra como seria essa requisição se ela fosse uma conversa entre humanos. O Servidor A está precisando realizar uma ação no Servidor B que causa um efeito colateral nos dados do Servidor B e por isso, é necessário fazer a pré-chamada que é o mesmo que pedir autorização para fazer a ação 𝞪 no recurso 𝞫 sendo a origem 𝟃.&lt;/p&gt;

&lt;p&gt;Mas então, pra toda requisição que meu site faz, na verdade são feitas duas requisições? A preflight e a original? Bom, na verdade existem algumas condições que necessitam de fazer as pré-chamadas. Portanto existem dois tipos de requisições:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;as requisições simples&lt;/li&gt;
&lt;li&gt;as requisições que exigem preflight.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Requisições Simples
&lt;/h3&gt;

&lt;p&gt;Uma requisição simples é basicamente uma requisição que, em teoria não causa efeitos colaterais no servidor. Além disso tem que atender a todas as condições abaixo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Métodos permitidos: GET, HEAD, POST (com ressalvas relacionadas ao cabeçalho, como mostra os próximos tópicos)&lt;/li&gt;
&lt;li&gt;Além dos cabeçalhos pré-configurados pela conexão, apenas alguns outros são permitidos, como: Accept, Accept-Language, Content-Language, Content-Type (com ressalvas), DPR, Downlink, Save-Data, Viewport-Width, Width&lt;/li&gt;
&lt;li&gt;Valores permitidos para Content-Type: application/x-www-form-urlencoded, multipart/form-data, text/plain&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nos códigos abaixo podemos verificar exemplos de chamada de uma requisição simples e como ela deve ser tratada no servidor.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:8080&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Requisição simples — Cliente&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Access-Control-Allow-Origin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:3000&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello Cors Test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Requisição simples — Servidor&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Nesta chamada podemos verificar os cabeçalhos da requisição e não é necessário fazer a pré-requisição pois está dentro das condições anteriores. Contudo, mesmo nas chamadas simples, precisamos configurar o &lt;strong&gt;CORS&lt;/strong&gt; como podemos ver no cabeçalho &lt;em&gt;Access-Control-Allow-Origin&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--AHgApBW7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/jw4a7tqc9wliydquqqgy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AHgApBW7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/jw4a7tqc9wliydquqqgy.png" alt="Cabeçalhos — Requisição simples"&gt;&lt;/a&gt;&lt;/p&gt;
Cabeçalhos — Requisição simples



&lt;h3&gt;
  
  
  Requisições que exigem pré-requisições
&lt;/h3&gt;

&lt;p&gt;Quando a requisição não atende às condições citadas acima, ela precisa enviar uma &lt;strong&gt;pré-requisição&lt;/strong&gt; que nada mais é que um &lt;strong&gt;OPTIONS&lt;/strong&gt; com algumas informações sobre a requisição original.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:8080&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;value&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Requisição preflight — Cliente&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;No caso dessas requisições é necessário configurar os cabeçalhos para a chamada &lt;strong&gt;OPTIONS&lt;/strong&gt; porque ela que realiza a autorização da requisição original.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Access-Control-Allow-Origin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:3000&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Access-Control-Allow-Headers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POSTED on root&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Requisição preflight — Servidor&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;As imagens abaixo mostram os cabeçalhos da pré-chamada e da chamada efetiva respectivamente. É possível reparar que, na parte do &lt;em&gt;Request Headers&lt;/em&gt; o cliente envia ao servidor que deseja adicionar um cabeçalho não autorizado por padrão — &lt;em&gt;content-type&lt;/em&gt; — e também uma ação que pode causar um efeito colateral — &lt;em&gt;POST&lt;/em&gt; — e por isso é preciso enviar esses cabeçalhos requisitando esses recursos. Na parte de &lt;em&gt;Response Headers&lt;/em&gt; é possível observar que o servidor &lt;strong&gt;autoriza&lt;/strong&gt; a origem &lt;a href="http://localhost:3000"&gt;http://localhost:3000&lt;/a&gt; a acessar seu recurso e também autoriza o cliente a adicionar o cabeçalho content-type.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Preflight Headers&lt;/th&gt;
&lt;th&gt;Post Headers&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OrseR5CN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/wvwygd502tdeq56lhkdr.png" alt=""&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YngVuBxI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/jf9ffm78c1l5bz5w30ne.png" alt=""&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
Cabeçalhos — Preflight e Efetivo






&lt;h2&gt;
  
  
  Boas práticas utilizando CORS
&lt;/h2&gt;

&lt;p&gt;É claro que quando conhecemos mais sobre o que estamos usando, configurando e trabalhando consequentemente já teremos uma nova visão e um senso crítico para tal. Mas como tudo, as configurações de &lt;strong&gt;CORS&lt;/strong&gt; também exigem algumas boas práticas para manter a segurança e consistência da comunicação entre diferentes origens.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Na configuração de &lt;em&gt;Access-Control-Allow-Origin&lt;/em&gt;, ao invés de utilizar ”*” que libera qualquer origem de acessar o seu servidor, coloque exatamente a(s) origens que realmente devem ter acesso.&lt;/li&gt;
&lt;li&gt;O princípio do &lt;strong&gt;CORS&lt;/strong&gt; é manter sua conexão segura, e por isso se liberarmos qualquer origem, qualquer método e/ou qualquer cabeçalho, este princípio não será atingido. Portanto precisamos deixar nossas requisições seguras e apenas autorizar as origens, métodos e cabeçalhos necessários para a conexão.&lt;/li&gt;
&lt;li&gt;Existem alguns &lt;em&gt;workarounds&lt;/em&gt;/gambiarras, como vimos nos posts do stack overflow, ou resolver apenas no lado do cliente usando técnicas como jsonp do jquery e &lt;em&gt;no-cors&lt;/em&gt; na &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API"&gt;API Fetch&lt;/a&gt; (não é recomendável). O ideal é estar configurado com o mínimo de abertura, apenas o necessário.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusão
&lt;/h2&gt;

&lt;p&gt;Bom, o objetivo desse artigo é compartilhar alguns conhecimentos que adquiri ao me deparar com problemas relacionados a política cross-origin e, como sei que pra mim foi difícil entender o problema e encontrar soluções, imagino que outras pessoas também tenham essa dificuldade e por isso eu resolvi ajudar com esse texto. Espero que tenham gostado e comentários e/ou informações a acrescentar são sempre bem vindos!&lt;/p&gt;

&lt;h2&gt;
  
  
  Referências
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS"&gt;Cross-Origin Resource Sharing — Mozilla&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://enable-cors.org/index.html"&gt;Enable CORS&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Agradecimento especial a &lt;a href="https://medium.com/@vanessametonini"&gt;Vanessa&lt;/a&gt; e &lt;a href="https://medium.com/@drkbral"&gt;Diogo&lt;/a&gt; por revisarem esse texto ❤&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Realidade Aumentada Fundamentos e Conceitos</title>
      <dc:creator>Ana Coimbra</dc:creator>
      <pubDate>Sun, 19 Apr 2020 01:28:21 +0000</pubDate>
      <link>https://dev.to/anacoimbrag/realidade-aumentada-fundamentos-e-conceitos-1e84</link>
      <guid>https://dev.to/anacoimbrag/realidade-aumentada-fundamentos-e-conceitos-1e84</guid>
      <description>&lt;p&gt;Atualmente, com a o avanço cada vez mais rápido das tecnologias e da capacidade de processamento dos dispositivos tecnológicos, a realidade aumentada tem sido um recurso muito usado para diversas aplicações. Este artigo tem como objetivo apresentar de forma mais conceitual o que significa e o que contempla essa experiência.&lt;/p&gt;

&lt;p&gt;Frequentemente tentamos sempre fazer associações do mundo virtual com o mundo real mas isso ocasionalmente gera um certo esforço cognitivo. A realidade aumentada então chega com um propósito de ligar o mundo real com componentes digitais de forma direta e automática. O princípio básico da realidade aumentada é apresentar informações dentro do ambiente físico. &lt;strong&gt;É como se o conteúdo digital fizesse parte do mundo real na percepção do usuário.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Dando um passo atrás, precisamos definir primeiro o que nós percebemos como &lt;strong&gt;realidade&lt;/strong&gt;: um conjunto de sensações, seja auditiva, visual, palatável, olfativa ou gestual que associamos a memórias e modelos mentais pré adquiridos. Portanto, perceba que cada um tem uma percepção diferente de realidade, pois cada indivíduo tem um conjunto de modelos e memórias diferentes entre si, mesmo que as sensações sejam as mesmas. Com isso, quando uma realidade é "aumentada" - ou estendida - com objetos e/ou informações virtuais, é necessário que estes sejam coerentes ao mundo real em relação a posição e tamanho independente do dispositivo usado. Ou seja, quanto mais consistente for a aplicação dos objetos virtuais no mundo físico, mais transparente é essa experiência.&lt;/p&gt;




&lt;p&gt;Uma experiência de realidade aumentada precisa ter, necessariamente, três características:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Combinar o real e o virtual:&lt;/strong&gt; o usuário se mantém no mundo real, podendo sentir (ver, ouvir, cheirar, tocar) tudo que ele sentiria independente da experiência em RA. As informações adicionadas de forma digital devem fazer sentido para ele dentro daquela realidade no momento.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interativo em tempo real:&lt;/strong&gt; toda informação virtual tem uma localização ou espaço físico a ser inserido dentro do mundo real. E isto precisa se manter constante independente da movimentação ou ponto de vista do usuário.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feito em 3D:&lt;/strong&gt; como seres humanos com dois receptores de imagem (olhos), nós percebemos o mundo real em 3D, portanto as informações digitais que serão inseridos nele precisam estar compatíveis com esse modelo.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Repare que não existe nenhuma limitação de dispositivo e muito menos tem o requisito de ser uma interação visual. Podendo ser também auditiva, gestual ou até mesmo degustativa ou olfativa. Contudo é necessário que o usuário tenha controle em tempo real das informações e um registro do espaço. Portanto é preciso que, independente da movimentação do dispositivo utilizado, as informações estejam atreladas ao ambiente corretamente.&lt;/p&gt;




&lt;h2&gt;
  
  
  Componentes da Realidade Aumentada
&lt;/h2&gt;

&lt;p&gt;Em geral, duas coisas precisam acontecer em uma aplicação de realidade aumentada, de forma cíclica e contínua:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A aplicação precisa determinar o estado atual do mundo real e do mundo virtual.&lt;/li&gt;
&lt;li&gt;A aplicação precisa mostrar o conteúdo virtual sobreposto ao mundo real de forma que o usuário sinta o mundo virtual como parte do mundo físico.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Existem três componentes em uma aplicação de realidade aumentada:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sensores&lt;/strong&gt; que determinam o estado do mundo real onde a aplicação está rodando.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Processador&lt;/strong&gt; que avalia as informações adquiridas pelos sensores, aplica as leis da física e outras regras necessárias pelo mundo virtual para gerar os sinais necessários para o display das informações.&lt;/li&gt;
&lt;li&gt;Um &lt;strong&gt;display&lt;/strong&gt; - ou algum &lt;strong&gt;sistema interativo&lt;/strong&gt; adequado para criar a impressão que o mundo real e o virtual são coexistentes e "confundir" o usuário para que ele perceba tudo como uma realidade única.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Sensores
&lt;/h3&gt;

&lt;p&gt;Para responder corretamente ao ambiente, o mundo virtual precisa de dados atualizados em tempo real do mundo físico. Para isso, são usados diversos sensores, que podem ser divididos em três categorias:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sensores de rastreamento:&lt;/strong&gt; como a realidade aumentada tem como princípio ser registrada espacialmente, é necessário ter algum mecanismo que determine informações sobre a posição do participante, o mundo real e qualquer que seja o dispositivo usado na interação com RA. Neste caso, &lt;strong&gt;posição&lt;/strong&gt; inclui &lt;strong&gt;localização&lt;/strong&gt; e &lt;strong&gt;orientação&lt;/strong&gt;. Exemplos de sensores de rastreamento: Câmera, GPS. Giroscópio, Acelerômetro e Bússola. Para determinar de forma completa uma posição é necessário informação sobre &lt;strong&gt;seis graus de liberdade&lt;/strong&gt; de cada entidade que é rastreada, segundo a figura abaixo.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--i_EWtNRJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/1v4x1tlldxccjblda2xb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--i_EWtNRJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/1v4x1tlldxccjblda2xb.png" alt="Seis Graus de Liberdade"&gt;&lt;/a&gt;&lt;/p&gt;
Seis graus de liberdade incluem coordenadas X, Y e Z e também inclinação, direção e rotação do objeto no eixo em que ele se encontra.



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sensores de variáveis do ambiente:&lt;/strong&gt; Existem uma série de sensores que são menos utilizados em uma experiência de realidade aumentada, mas podem prover diversas informações relevantes sobre o mundo físico. Por exemplo: sensores de temperatura, umidade, pH, etc. Uma forma de aplicar esses sensores seria utilizar a aplicação como uma "lente mágica" que permite ao usuário visualizar as informações que seriam apenas "dados numéricos" em uma tela de computador.
Sensores de entrada do usuário: Enquanto nos outros sensores o usuário não tem controle sobre o que é rastreado ou medido, neste caso os sensores incluem componentes como botões, teclado, ou qualquer interface interativa em que o usuário possa tomar decisões sobre a experiência de realidade aumentada.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Processador
&lt;/h3&gt;

&lt;p&gt;O processador é o componente que obtém e analisa todos os dados dos sensores e coordena todas as tarefas de um sistema de realidade aumentada. Além de mandar todas a informações necessárias para o display mostrar as informações.&lt;br&gt;
O processador precisa ter poder computacional suficiente para fazer tudo isso citado em tempo real (na percepção do usuário). Para cada ação ou modificação nos dados dos sensores, as informações devem ser atualizadas sem nenhum atraso para o indivíduo.&lt;br&gt;
Por padrão, uma aplicação em realidade aumentada precisa sustentar, no mínimo, uma taxa de 15 frames por segundo - o ideal é que seja maior - para que o participante perceba o display como algo contínuo.&lt;/p&gt;

&lt;h3&gt;
  
  
  Display
&lt;/h3&gt;

&lt;p&gt;O display é o componente que vai causar uma "confusão" na percepção de realidade do usuário. Um display pode ser tanto o dispositivo que mostra os sinais computados no processador e vai misturar o mundo real e o mundo virtual como também podem ser os sinais apresentados na interação. Por exemplo, se tenho uma tela de smartphone mostrando uma cena de uma mesa, então essa tela é um display, assim como a mesa. Neste caso, a cena é um display que é apresentado por outro display.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conceitos relacionados a Realidade Aumentada
&lt;/h2&gt;

&lt;p&gt;Para entender plenamente o funcionamento da realidade aumentada, é necessário saber também de alguns conceitos relacionados.&lt;/p&gt;

&lt;h3&gt;
  
  
  Visão computacional
&lt;/h3&gt;

&lt;p&gt;Uma posição (localização e orientação) pode ser tanto absoluta: independe de qualquer outra coisa, ou seja, é relativo ao ambiente global com também pode ser relativa: determinada a partir da posição de outro objeto.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yJ-5JQRW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/da4escamqg1ox5m18vdk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yJ-5JQRW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/da4escamqg1ox5m18vdk.png" alt="Computer vision example"&gt;&lt;/a&gt;&lt;/p&gt;
Exemplo: Temos nas imagens um vaso virtual e uma mesa real. Na parte de cima, o vaso está posicionado em relação a mesa e, por isso, ao mover a mesa automaticamente o vaso é movido junto. Na parte de baixo, o vaso está posicionado de forma absoluta no ambiente, então, ao mover a mesa, o vaso permanece na mesma posição.



&lt;p&gt;O sensor (câmera) "enxerga" o mundo real e, baseado no que "vê", consegue determinar onde ela se encontra e sua orientação em relação a cena. Para trabalhar com visão computacional, é necessário analisar as imagens coletadas pela câmera para determinar o que é que ela "vê". Para isso, é necessário que existam pistas no ambiente que serão usados como pontos de referência para que ajude a determinar a localização e orientação em relação a esses pontos. Contudo, nem sempre é possível ou fácil (para o software) de detectar esses pontos.&lt;/p&gt;

&lt;p&gt;Para facilitar este problema, diversas aplicações de realidade aumentada utilizam imagens facilmente reconhecíveis que viram pontos de referência colocadas no ambiente de forma artificial. Essas imagens são chamadas de &lt;em&gt;fiducial markers&lt;/em&gt; ou &lt;em&gt;fiducial symbols&lt;/em&gt; (marcações ou símbolos impacientes). Quando o sistema encontra essas marcações consegue determinar mais facilmente onde a câmera está e qual sua orientação em relação ao símbolo. Uma marcação fiducial precisa ter um padrão único, um bom contraste para que seja muito fácil para um software de visão computacional reconhecer e também precisa ser assimétrico de forma que seja mais simples o cálculo da orientação da câmera sobre o marcador.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--T9RTESdX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/oh3dv5ih3sqjy22kadqo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--T9RTESdX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/oh3dv5ih3sqjy22kadqo.png" alt="Fiducial marker example"&gt;&lt;/a&gt;&lt;/p&gt;
Exemplo de uma Fiducial marker



&lt;h3&gt;
  
  
  Computação gráfica
&lt;/h3&gt;

&lt;p&gt;Conforme já mencionado, os objetos de uma experiência de uma realidade aumentada são descritos em 3D, mas esses objetos devem ser renderizados em uma imagem que será visualizada em um display 2D. E essa imagem deve ser gerada respeitando o ponto de vista, iluminação, propriedades do material, perspectiva, etc. Para isso usamos diversos conceitos de computação gráfica, como por exemplo descrever o objeto como uma malha de polígonos em um sistema de coordenadas tridimensional.&lt;/p&gt;

&lt;p&gt;Além disso, os objetos possuem propriedades que descrevem a aparência dele, como cor, refletividade, textura, etc. Com o mapeamento de textura uma imagem bidimensional pode ser aplicada a uma malha e mapeada como uma propriedade de superfície.&lt;/p&gt;

&lt;p&gt;Por fim, é possível também adicionar luzes na cena que serão incorporadas na renderização final da imagem e podem afetar as cores, sombra e outros aspectos gráficos da cena.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dimensionalidade
&lt;/h3&gt;

&lt;p&gt;Conforme visto anteriormente, o mundo físico é tridimensional enquanto o mundo virtual pode existir em inúmeras dimensões. Na realidade aumentada, o mundo real e o virtual são combinados em um display de apenas duas dimensões. Para resolver esse "dilema", além dos conceitos de computação gráfica, também é utilizado uma série de recursos que permitem ao usuário a percepção de profundidade (&lt;em&gt;deph cues&lt;/em&gt;). Esses recursos nos permitem determinar quão distante estão as coisas de nós e como entendemos o mundo tridimensional em que vivemos. Exemplos de &lt;em&gt;deph cues&lt;/em&gt;: tamanho - um objeto menor tende a estar mais longe que o objeto maior; iluminação - objetos com menos luz estão mais longe.&lt;/p&gt;

&lt;h3&gt;
  
  
  Registro e Latência
&lt;/h3&gt;

&lt;p&gt;Os conceitos de registro e latência se referem ao alinhamento do mundo virtual e real.&lt;/p&gt;

&lt;p&gt;O &lt;strong&gt;registro&lt;/strong&gt; se refere a quão preciso o mundo virtual se alinha fisicamente com o mundo físico. Quando os registros estão inadequados podem atrapalhar o efeito de realidade aumentada, principalmente se a aplicação é puramente uma "fusão" dos dois mundos. Um registro bem feito depende de diversas variáveis e a principal é a acurácia do sistema de rastreamento do ambiente.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;latência&lt;/strong&gt; é a quantidade de tempo que um componente do mundo virtual demora a ser atualizado em relação ao que deveria ocorrer idealmente (no mesmo instante). Assim, é possível dizer que a latência é um tipo de registro temporal.&lt;/p&gt;




&lt;p&gt;Embora ainda não vemos tantas aplicações em realidade aumentada frequentemente no nosso dia a dia, cada vez mais ela tem se difundido, principalmente nos aplicativos de jogos, varejo de móveis, além da sua utilização em eventos e campanhas promocionais (muitas vezes com o uso das &lt;em&gt;fiducial markers&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;Por isso acredito ser cada vez mais importante entender a essência dessa experiência que tem um grande potencial na tecnologia e nos negócios.&lt;/p&gt;




&lt;p&gt;Referências&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://books.google.com.br/books?id=7_O5LaIC0SwC&amp;amp;lpg=PP1&amp;amp;pg=PP1#v=onepage&amp;amp;q&amp;amp;f=false"&gt;Understanding Augmented Reality: Concepts and Applications - Alan B. Craig&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://books.google.com.br/books?id=qPU2DAAAQBAJ&amp;amp;lpg=PP1&amp;amp;pg=PP1#v=onepage&amp;amp;q&amp;amp;f=false"&gt;Augmented Reality: Principles and practices - Tobias Höllerer, Dieter Schmalstieg&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>realidadeaumentada</category>
      <category>3d</category>
      <category>portugues</category>
    </item>
  </channel>
</rss>
