<?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>Forem: Ismael González Trujillo</title>
    <description>The latest articles on Forem by Ismael González Trujillo (@ismaelgt).</description>
    <link>https://forem.com/ismaelgt</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%2F253157%2F07253dce-cc80-45c1-8f72-5f6f9657bffb.png</url>
      <title>Forem: Ismael González Trujillo</title>
      <link>https://forem.com/ismaelgt</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/ismaelgt"/>
    <language>en</language>
    <item>
      <title>Software development: 5 career lessons from running 🏃🧑‍💻</title>
      <dc:creator>Ismael González Trujillo</dc:creator>
      <pubDate>Wed, 04 Mar 2020 16:41:52 +0000</pubDate>
      <link>https://forem.com/ismaelgt/software-development-5-career-lessons-from-running-1eig</link>
      <guid>https://forem.com/ismaelgt/software-development-5-career-lessons-from-running-1eig</guid>
      <description>&lt;p&gt;Last year I took up running and I wanted to share some things I learned along the way. Basic lessons, bordering on the self-evident, that can be applied to running, other sports or, why not, career development.&lt;/p&gt;

&lt;h2&gt;
  
  
  #1 - Starting is the hardest part
&lt;/h2&gt;

&lt;p&gt;At first I couldn't run five minutes without taking a walk break. After years of being a couch potato my body and my mind weren't prepared for this. Starting was going to be hard and I had no other choice than accepting it. But, as it usually happens, I'm not a special case.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/R2_Mn-qRKjA"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Every day it gets a little easier. You just have to do it every day. That's the hard part.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Couch to 5K (C25k)&lt;/strong&gt; is a beginners 8-week program that starts with a mix of running and walking, gradually building up strength and stamina to fully running 5K.&lt;/p&gt;

&lt;p&gt;So I went through the pain of completing the full program and, imagine what... I saw progress! Who would have imagined? Developing a new skill (like running) is a process made of different stages.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;four stages of competence&lt;/strong&gt; are the psychological states involved in the process of progressing from incompetence to competence in a skill:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Unconscious incompetence&lt;/strong&gt;: The learner isn't aware that a skill or knowledge gap exists.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conscious incompetence&lt;/strong&gt;: The learner is aware of a skill or knowledge gap and understands the importance of acquiring the new skill. It's in this stage that learning can begin.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conscious competence&lt;/strong&gt;: The learner knows how to use the skill or perform the task, but doing so requires practice, conscious thought and hard work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unconscious competence&lt;/strong&gt;: The individual has enough experience with the skill that they can perform it so easily they do it unconsciously.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As software developers we go through these stages, not once, but multiple times in our careers. It's a cycle that starts over and over again at different levels. Starting a new job in a new position, new project with a different team, new programming language or paradigm, new framework... They all require us to master new skills.&lt;/p&gt;

&lt;h2&gt;
  
  
  #2 - Don't compare to others
&lt;/h2&gt;

&lt;p&gt;Once I finished the 8-week C25k program, I started looking at my times and comparing them to my running friends'. Then I started wondering. Why am I so slow?&lt;/p&gt;

&lt;p&gt;There's an immediate and easy answer to that question. I had just started running. But very rarely do we just accept this obvious truth. Because... other people who have also just started running are faster than us. Well, the truth is that genetics also plays an essential role in running performance, along with age, exercising history and a myriad of other factors. It would be impossible for you to know how all of them are affecting other runners.&lt;/p&gt;

&lt;p&gt;We tend to make a lot of assumptions. But the truth is that reality is way too complex to make these direct comparisons.&lt;/p&gt;

&lt;p&gt;But the main reason why you shouldn't compare yourself to others is this one:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Comparison is the thief of joy.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A much more positive approach is to compare with your previous self and focus on your progress.&lt;/p&gt;

&lt;p&gt;Career comparison can also be dangerous, even if the original intention is positive. Pay inequality is a big problem for underrepresented groups, not only in tech. Making it visible might seem like a good idea. Trying to get everyone to go public with their salaries, however, starts an infinite loop of misleading comparisons where all the individual circumstances are ignored.&lt;/p&gt;

&lt;h2&gt;
  
  
  #3 - Set achievable goals
&lt;/h2&gt;

&lt;p&gt;Being able to run 5k was my first goal. Running a marathon wouldn't have been realistic given my fitness level. Setting long-term goals is a good way to improve motivation, but I prefer working towards short-term ones. Some short-term goals I've already achieved include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Completing C25K.&lt;/li&gt;
&lt;li&gt;Joining a running club.&lt;/li&gt;
&lt;li&gt;Doing a &lt;a href="http://parkrun.org.uk/" rel="noopener noreferrer"&gt;parkrun&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Running a 5k race.&lt;/li&gt;
&lt;li&gt;Running 10k in under 1 hour.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are &lt;strong&gt;S.M.A.R.T.&lt;/strong&gt; goals, as defined by &lt;em&gt;George T. Doran&lt;/em&gt; in a paper included in the November 1981 issue of &lt;em&gt;Management Review&lt;/em&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;S&lt;/strong&gt;pecific (simple, sensible, significant).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;M&lt;/strong&gt;easurable (meaningful, motivating).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A&lt;/strong&gt;chievable (agreed, attainable).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;R&lt;/strong&gt;elevant (reasonable, realistic and resourced, results-based).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;T&lt;/strong&gt;ime bound (time-based, time limited, time/cost limited, timely, time-sensitive).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1207854448086835203-163" src="https://platform.twitter.com/embed/Tweet.html?id=1207854448086835203"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1207854448086835203-163');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1207854448086835203&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;Need inspiration for your programming goals? Have a look at the comments on this post.&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/devmount" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F31184%2Fa0a1eef3-2293-4154-814c-995d552374d7.png" alt="devmount"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/devmount/what-are-your-goals-as-developer-for-2k20-4g95" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;What are your goals as developer for 2k20+?&lt;/h2&gt;
      &lt;h3&gt;Andreas ・ Jan 4 '20&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#watercooler&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#discuss&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#question&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  #4 - Slow down to avoid burnout
&lt;/h2&gt;

&lt;p&gt;Ok, I can run. But I am very slow so I set a (SMART) goal to run 5k in under 25 minutes by the end of the year. So every time I go for a run, I push really hard to try to do a new PB. This, obviously, doesn't work. Your body and your mind need recovery.&lt;/p&gt;

&lt;p&gt;Multiple studies have shown that runners of all ability and experience levels seem to improve the most when they do approximately 80 percent of their training at low intensity and 20 percent at moderate and high intensity.&lt;/p&gt;

&lt;p&gt;Feeling drained from pushing too hard is not alien to software developers.&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1213136318139699200-592" src="https://platform.twitter.com/embed/Tweet.html?id=1213136318139699200"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1213136318139699200-592');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1213136318139699200&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;Burnout is, unfortunately, a common phenomenon in our career. It can be defined as a state of physical or emotional exhaustion that also involves a sense of reduced accomplishment and loss of personal identity and is often due to long-term and unresolvable job stress.&lt;/p&gt;

&lt;p&gt;Want some tips to avoid burnout? &lt;a href="https://twitter.com/addyosmani" rel="noopener noreferrer"&gt;Addy Osmani&lt;/a&gt; has you covered.&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1207395911405453312-140" src="https://platform.twitter.com/embed/Tweet.html?id=1207395911405453312"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1207395911405453312-140');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1207395911405453312&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;h2&gt;
  
  
  #5 - Learn from others
&lt;/h2&gt;

&lt;p&gt;Running is a sport that suits introverts and extroverts. A long run is perfect for those who enjoy having me-time. But it's also great as a social activity. You can do it with friends, colleagues or a running club. Even though I lean towards the introverted side of the spectrum and I do a majority of my runs solo, I've enjoyed running with my colleagues every Thursday or at my local &lt;a href="https://www.parkrun.org.uk/hackney-marshes/" rel="noopener noreferrer"&gt;parkrun&lt;/a&gt; (where I also volunteer). Sharing my experience with others has increased my motivation and taught me a lot about running.&lt;/p&gt;

&lt;p&gt;If you enjoy the social component of software development there are a lot of ways to keep yourself up-to-date with new technologies, get to know new interesting people and work on interesting projects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Meetups&lt;/li&gt;
&lt;li&gt;Hackathons&lt;/li&gt;
&lt;li&gt;Tech talks&lt;/li&gt;
&lt;li&gt;Open Source community&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some of these social gatherings can even push your performance to unimaginable limits...&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1199794084669345792-986" src="https://platform.twitter.com/embed/Tweet.html?id=1199794084669345792"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1199794084669345792-986');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1199794084669345792&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Taking up running has made me reflect on basic aspects of career development like the four stages of skill competence, setting SMART goals, the importance of avoiding burnout, or the social aspect of learning.&lt;/p&gt;

&lt;p&gt;Think about them. They will help your career in the long run.&lt;/p&gt;

</description>
      <category>career</category>
      <category>motivation</category>
      <category>productivity</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Building cross-platform apps for mobile, web and voice (part II)</title>
      <dc:creator>Ismael González Trujillo</dc:creator>
      <pubDate>Sun, 08 Dec 2019 07:55:45 +0000</pubDate>
      <link>https://forem.com/ismaelgt/building-cross-platform-apps-for-mobile-web-and-voice-part-ii-e2i</link>
      <guid>https://forem.com/ismaelgt/building-cross-platform-apps-for-mobile-web-and-voice-part-ii-e2i</guid>
      <description>&lt;p&gt;&lt;em&gt;In this article and its &lt;a href="https://dev.to/ismaelgt/building-cross-platform-apps-for-mobile-web-and-voice-part-i-1ib2"&gt;part I&lt;/a&gt; we explore the technologies available to build a cross-platform experience which includes voice assistants. Assuming we want to build a small and simple to-do list app for Android, iOS, web and the Google Assistant, how can we provide the best experience for each platform while making sure our app can scale?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;To bring to life the tech stack defined in the previous article, we’ve created VOXE (VOice X-platform Experience). VOXE is a simple proof-of-concept to-do list app for Android, iOS, web and the Google Assistant. One single repository, which contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A NativeScript (Angular) app that compiles to Android, iOS and web.&lt;/li&gt;
&lt;li&gt;A Dialogflow agent.&lt;/li&gt;
&lt;li&gt;A Cloud Function for the Google Action fulfillment.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The app makes use of Cloud Firestore to store the data.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vbtp5_uV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/5iycenqwkbvh34vafj8x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vbtp5_uV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/5iycenqwkbvh34vafj8x.png" alt="VOXE architecture" width="880" height="455"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;VOXE architecture&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The repository is publicly available &lt;a href="https://github.com/potatolondon/voxe"&gt;on GitHub&lt;/a&gt;.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--566lAguM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/potatolondon"&gt;
        potatolondon
      &lt;/a&gt; / &lt;a href="https://github.com/potatolondon/voxe"&gt;
        voxe
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      VOice X-platform Experience. A to-do list app for Android, iOS, web and Google Assistant
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Firebase: Infrastructure and services
&lt;/h2&gt;

&lt;p&gt;VOXE makes use of three Firebase services:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://firebase.google.com/docs/hosting"&gt;Hosting&lt;/a&gt;: for the web app.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://firebase.google.com/docs/auth"&gt;Authentication&lt;/a&gt;: we will use Google here for simplicity but Firebase supports many other authentication methods such as passwords, phone numbers and other popular federated identity providers like Facebook, Twitter, etc.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://firebase.google.com/docs/firestore"&gt;Cloud Firestore&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first two services are easily configurable via the &lt;a href="https://console.firebase.google.com/"&gt;Firebase Console&lt;/a&gt;. Let’s dig into our data model and security rules.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cloud Firestore data model
&lt;/h3&gt;

&lt;p&gt;Cloud Firestore is a NoSQL, document-oriented database. Unlike a SQL database, there are no tables or rows. Instead, the data is stored in &lt;em&gt;documents&lt;/em&gt;, which are organised into &lt;em&gt;collections&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;We’ll need, then, a collection of to-do documents (&lt;em&gt;todos&lt;/em&gt;), each of which will define the following fields: &lt;code&gt;content&lt;/code&gt; (string), &lt;code&gt;createdAt&lt;/code&gt; timestamp (number) and &lt;code&gt;userId&lt;/code&gt; (string).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uJYVYWKA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/eh78pp4vmq44auuye4a9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uJYVYWKA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/eh78pp4vmq44auuye4a9.png" alt="" width="880" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Security rules
&lt;/h3&gt;

&lt;p&gt;Since we’re relying on Firebase client SDKs and not on a bespoke API, how do we make sure users only have access to to-do items created by themselves?&lt;/p&gt;

&lt;p&gt;Every database request from a Cloud Firestore mobile/web client library is evaluated against a set of &lt;a href="https://firebase.google.com/docs/firestore/security/get-started"&gt;security rules&lt;/a&gt; before reading or writing any data. These rules define the level of access for a specific path in the database using conditions based on authentication and data fields.&lt;/p&gt;

&lt;p&gt;Let’s have a look at the security rules for our database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;service cloud.firestore {
  match /databases/{database}/documents {
    match /todos/{todo} {
      allow read, delete: if request.auth.uid == resource.data.userId;
      allow create: if request.auth.uid == request.resource.data.userId;
      allow update: if request.auth.uid == resource.data.userId &amp;amp;&amp;amp; request.auth.uid == request.resource.data.userId;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;resource.data&lt;/code&gt; represents the document accessed by the query.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;request.auth.uid&lt;/code&gt; is the &lt;code&gt;id&lt;/code&gt; of the authenticated user.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;request.resource.data&lt;/code&gt; represents a new or updated document.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So this set of security rules will make sure that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Users can only read and delete documents whose &lt;code&gt;userId&lt;/code&gt; field matches their user &lt;code&gt;uid&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;User can only create documents whose &lt;code&gt;userId&lt;/code&gt; field matches their user &lt;code&gt;uid&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;User can only update documents whose &lt;code&gt;userId&lt;/code&gt; field matches their user &lt;code&gt;uid&lt;/code&gt; and this field can’t be changed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our database is now ready to roll.&lt;/p&gt;
&lt;h2&gt;
  
  
  NativeScript: A cross-platform native and web app
&lt;/h2&gt;

&lt;p&gt;In our previous article we discussed some of the benefits of using &lt;a href="https://www.nativescript.org"&gt;NativeScript&lt;/a&gt; for building cross-platform apps, focusing on its &lt;a href="https://docs.nativescript.org/angular/code-sharing/intro"&gt;code-sharing&lt;/a&gt; capabilities using Angular.&lt;/p&gt;

&lt;p&gt;A code-sharing project is one where we keep the code for the web and mobile apps in one place. Let’s have a look at some examples of how we have benefited from this architecture on VOXE.&lt;/p&gt;

&lt;p&gt;At &lt;strong&gt;component&lt;/strong&gt; level, these files define the app homepage:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/potatolondon/voxe/blob/master/src/app/home/home.component.ts"&gt;&lt;code&gt;home.component.ts&lt;/code&gt;&lt;/a&gt;: Angular component definition (web and native).&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/potatolondon/voxe/blob/master/src/app/home/home.component.html"&gt;&lt;code&gt;home.component.html&lt;/code&gt;&lt;/a&gt;: web template file.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/potatolondon/voxe/blob/master/src/app/home/home.component.tns.html"&gt;&lt;code&gt;home.component.tns.html&lt;/code&gt;&lt;/a&gt;: mobile template file.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--u289Bgtc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/gquw6138o0lwvjz9pzz9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--u289Bgtc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/gquw6138o0lwvjz9pzz9.png" alt="" width="880" height="345"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It is also possible to use the naming convention for platform-specific &lt;strong&gt;styling&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/potatolondon/voxe/blob/master/src/app/home/home.component.scss"&gt;&lt;code&gt;home.component.scss&lt;/code&gt;&lt;/a&gt;: web styles.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/potatolondon/voxe/blob/master/src/app/home/home.component.android.scss"&gt;&lt;code&gt;home.component.android.scss&lt;/code&gt;&lt;/a&gt;: Android styles.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/potatolondon/voxe/blob/master/src/app/home/home.component.ios.scss"&gt;&lt;code&gt;home.component.ios.scss&lt;/code&gt;&lt;/a&gt;: iOS styles.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We’ve been able to write an implementation for the Home component (&lt;a href="https://github.com/potatolondon/voxe/blob/master/src/app/home/home.component.ts"&gt;&lt;code&gt;home.component.ts&lt;/code&gt;&lt;/a&gt;) that works both for web and mobile but this might not always be possible. In fact, for the Authentication and Firestore &lt;strong&gt;services&lt;/strong&gt;, this isn’t possible since we’re forced to use the Firebase native client SDK for each platform. For this to work, again, we use the naming convention to define two different services. For example, the Firestore service is defined by these files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/potatolondon/voxe/blob/master/src/app/firestore/firestore.service.ts"&gt;&lt;code&gt;firestore.service.ts&lt;/code&gt;&lt;/a&gt;: web implementation using the &lt;a href="https://github.com/angular/angularfire"&gt;&lt;code&gt;angularfire&lt;/code&gt;&lt;/a&gt; library.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/potatolondon/voxe/blob/master/src/app/firestore/firestore.service.tns.ts"&gt;&lt;code&gt;firestore.service.tns.ts&lt;/code&gt;&lt;/a&gt;: mobile implementation using &lt;a href="https://github.com/EddyVerbruggen/nativescript-plugin-firebase"&gt;&lt;code&gt;nativescript-plugin-firebase&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/potatolondon/voxe/blob/master/src/app/firestore/firestore.service.interface.ts"&gt;&lt;code&gt;firestore.service.interface.ts&lt;/code&gt;&lt;/a&gt;: interface used by both implementations of the service.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Actions on Google: The voice experience
&lt;/h2&gt;

&lt;p&gt;As previously discussed, some of our app features could be triggered via voice (or text) commands using the Google Assistant. Let’s assume we want to be able to read our to-do list, add and remove items. Here’s a diagram of the conversation flow:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OAfpVYrk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/hn8d99053bahxdtpwl8o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OAfpVYrk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/hn8d99053bahxdtpwl8o.png" alt="" width="880" height="1226"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In order to achieve this integration we’ll need to follow these three steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create an Action in the &lt;a href="https://console.actions.google.com/"&gt;Google Actions Console&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Create a &lt;a href="https://dialogflow.cloud.google.com"&gt;Dialogflow Agent&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Deploy a &lt;a href="https://developers.google.com/assistant/actions/dialogflow#build_and_deploy_fulfillment"&gt;Fulfillment&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  Dialogflow
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://cloud.google.com/dialogflow/"&gt;Dialogflow&lt;/a&gt; is a natural language understanding platform that allows us to design and build conversational user interfaces. A &lt;a href="https://cloud.google.com/dialogflow/docs/agents-overview"&gt;Dialogflow agent&lt;/a&gt; is a virtual agent that handles these conversations with your end-users. Although the focus of this article is on the Google Assistant, it’s important to mention that Dialogflow can also be used to build text chatbots that can be integrated with different platforms.&lt;/p&gt;

&lt;p&gt;To describe how to do something we use &lt;a href="https://cloud.google.com/dialogflow/docs/intents-overview"&gt;intents&lt;/a&gt;. We want our Action to respond to requests like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Read my list&lt;/li&gt;
&lt;li&gt;Add a new item to my list&lt;/li&gt;
&lt;li&gt;Remove an item from my list&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can create these intents in Dialogflow. Here’s the list of Intents in VOXE:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dr0Dvpem--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/ckl7h0hov9j6uavc0yfw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dr0Dvpem--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/ckl7h0hov9j6uavc0yfw.png" alt="List of intents in VOXE" width="880" height="764"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;List of intents in VOXE&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For each of the intents we need to provide &lt;a href="https://cloud.google.com/dialogflow/docs/intents-training-phrases"&gt;training phrases&lt;/a&gt;. These are example phrases for what end-users might type or say, referred to as &lt;em&gt;end-user expressions&lt;/em&gt;. For each intent, we create many training phrases. When an &lt;em&gt;end-user expression&lt;/em&gt; resembles one of these phrases, Dialogflow matches the intent. “Please read my list”, “Remove an item from my list” or “Add an item to my list” are examples of training phrases for intents in VOXE.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HyxL8qfP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/ppa1cbgz80yvd54swaqf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HyxL8qfP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/ppa1cbgz80yvd54swaqf.png" alt="Training phrases for “Add item request” intent" width="880" height="406"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Training phrases for “Add item request” intent&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Training phrases can contain &lt;a href="https://cloud.google.com/dialogflow/docs/intents-actions-parameters"&gt;parameters&lt;/a&gt; of different entity types, from which we can extract values. For example, when the user mentions the item to be added to, or removed from the list, this list item is extracted as a parameter.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Nan9WgHs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/d8da1kbh8e5okhrf9o51.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Nan9WgHs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/d8da1kbh8e5okhrf9o51.png" alt="“item” parameter for “Add item request” intent" width="880" height="358"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;“item” parameter for “Add item request” intent&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;But how does Dialogflow know in which point of the conversation we are, so only the right intent for our invocation phrase can be triggered? &lt;a href="https://cloud.google.com/dialogflow/docs/contexts-overview"&gt;Contexts&lt;/a&gt; are the key to controlling the flow of the conversation and are used to define &lt;a href="https://cloud.google.com/dialogflow/docs/contexts-follow-up-intents"&gt;follow-up intents&lt;/a&gt;. When an intent is matched, any configured &lt;em&gt;output contexts&lt;/em&gt; for that intent become active. While any contexts are active, Dialogflow will only match intents that are configured with &lt;em&gt;input contexts&lt;/em&gt; that match the currently active contexts.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DOytEW8f--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/jl2ek7aiuk6hrz403q1w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DOytEW8f--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/jl2ek7aiuk6hrz403q1w.png" alt="Input and output contexts for “Add item request” intent" width="880" height="232"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Input and output contexts for “Add item request” intent&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Fulfillment
&lt;/h3&gt;

&lt;p&gt;We’ve discussed so far how intents represent the input of the conversational action. But what about the output? In Dialogflow, intents have a built-in &lt;a href="https://cloud.google.com/dialogflow/docs/intents-responses"&gt;response&lt;/a&gt; handler that can return &lt;strong&gt;static responses&lt;/strong&gt; after the intent is matched. Different variations of this response can be specified, and one of them will be selected randomly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FMyRqqVu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/44gvu10x4f31k83onna6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FMyRqqVu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/44gvu10x4f31k83onna6.png" alt="Responses to “Add item request” intent" width="880" height="423"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Responses to “Add item request” intent&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Most of the time we will need &lt;strong&gt;dynamic responses&lt;/strong&gt; for our intents. The “Read list” intent is a good example. We need some code to access the database and read our list. For this to happen we need to use &lt;a href="https://cloud.google.com/dialogflow/docs/fulfillment-overview"&gt;fulfillment&lt;/a&gt; for the intent. This tells Dialogflow to use an HTTP request to a webhook to determine the response.&lt;/p&gt;

&lt;p&gt;In order to build the fulfillment for VOXE we’ve used the Node.js® &lt;a href="https://github.com/actions-on-google/actions-on-google-nodejs"&gt;client library&lt;/a&gt; for Actions on Google. We have deployed our code to a &lt;a href="https://firebase.google.com/docs/functions"&gt;Cloud Function&lt;/a&gt; using Firebase.&lt;/p&gt;

&lt;p&gt;This snippet shows the implementation for the “Read list” intent handler:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use strict&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Import the Dialogflow module and response creation dependencies&lt;/span&gt;
&lt;span class="c1"&gt;// from the Actions on Google client library.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;dialogflow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;BasicCard&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;List&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;SignIn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Suggestions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;actions-on-google&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Import the firebase-functions package for deployment.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;functions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;firebase-functions&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Instantiate the Dialogflow client.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dialogflow&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;debug&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="na"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_CLIENT_ID_HERE&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="c1"&gt;// Init firebase&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;admin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;firebase-admin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;initializeApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;functions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;firebase&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firestore&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;auth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;todosCollection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;todos&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Middleware to store user uid&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;middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;conv&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;conv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getUserByEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;conv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Handle the Read List intent&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;intent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;read list&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;conv&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;snapshot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;todosCollection&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;userId&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="s1"&gt;==&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;conv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;)&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="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;err&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;conv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Sorry. It seems we are having some technical difficulties.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error reading Firestore&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;ssml&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;speak&amp;gt;This is your to-do list: &amp;lt;break time="1" /&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;doc&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;ssml&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;break time="500ms" /&amp;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;ssml&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;break time="500ms" /&amp;gt;Would you like me to do anything else?&amp;lt;/speak&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;conv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ssml&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;conv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Suggestions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Yes&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="s1"&gt;No&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="nx"&gt;conv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contexts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;restart-input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&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="nx"&gt;conv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;conv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;given_name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, your list is empty. Do you want to add a new item?`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;conv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Suggestions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Yes&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="s1"&gt;No&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="nx"&gt;conv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contexts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;read-empty-list-followup&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&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="c1"&gt;// The rest of your intents handlers here&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;

&lt;span class="c1"&gt;// Set the DialogflowApp object to handle the HTTPS POST request.&lt;/span&gt;
&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dialogflowFirebaseFulfillment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;functions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;https&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onRequest&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The full version of the fulfillment can be found &lt;a href="https://github.com/potatolondon/voxe/blob/master/functions/index.js"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Multimodal interaction
&lt;/h3&gt;

&lt;p&gt;The Google Assistant is available to users on multiple devices like smartphones, smart speakers and smart displays. This means we’re not limited to voice and text as the only method for input and output, but we can also use visuals. This is known as multimodal interaction, and takes full advantage of these devices capabilities. This makes it easy for users to quickly scan lists or make selections.&lt;/p&gt;

&lt;p&gt;A good example of multimodal interaction happens when we need to specify an item to be removed from the list. One option is using voice (or text) to indicate the item. However, if we’re using a touchscreen device, it feels more natural to select it from the list directly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zxkrkkvb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/5sxtnn50ro89toma0a3g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zxkrkkvb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/5sxtnn50ro89toma0a3g.png" alt="VOXE: Multimodal interaction to remove list item" width="880" height="505"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;VOXE: Multimodal interaction to remove list item&lt;/em&gt;&lt;/p&gt;



&lt;p&gt;&lt;em&gt;In this two-part article we discussed how to build a cross-platform experience which includes voice assistants. The &lt;a href="https://dev.to/ismaelgt/building-cross-platform-apps-for-mobile-web-and-voice-part-i-1ib2"&gt;first part&lt;/a&gt; focused on exploring the technologies available and choosing a tech stack. In this second part we explained how we built a simple to-do list prototype app using NativeScript, Firebase and Actions on Google.&lt;/em&gt;&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/ismaelgt" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RjZV6ZqF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/practicaldev/image/fetch/s--8_LHeOZI--/c_fill%2Cf_auto%2Cfl_progressive%2Ch_150%2Cq_auto%2Cw_150/https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/253157/07253dce-cc80-45c1-8f72-5f6f9657bffb.png" alt="ismaelgt"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/ismaelgt/building-cross-platform-apps-for-mobile-web-and-voice-part-i-1ib2" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Building cross-platform apps for mobile, web and voice (part I)&lt;/h2&gt;
      &lt;h3&gt;Ismael González Trujillo ・ Dec 8 '19 ・ 6 min read&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#crossplatform&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#webdev&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#serverless&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#googleassistant&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;



</description>
      <category>javascript</category>
      <category>nativescript</category>
      <category>firebase</category>
      <category>googleassistant</category>
    </item>
    <item>
      <title>Building cross-platform apps for mobile, web and voice (part I)</title>
      <dc:creator>Ismael González Trujillo</dc:creator>
      <pubDate>Sun, 08 Dec 2019 07:46:43 +0000</pubDate>
      <link>https://forem.com/ismaelgt/building-cross-platform-apps-for-mobile-web-and-voice-part-i-1ib2</link>
      <guid>https://forem.com/ismaelgt/building-cross-platform-apps-for-mobile-web-and-voice-part-i-1ib2</guid>
      <description>&lt;p&gt;&lt;em&gt;In this article and its &lt;a href="https://dev.to/ismaelgt/building-cross-platform-apps-for-mobile-web-and-voice-part-ii-e2i"&gt;part II&lt;/a&gt; we explore the technologies available to build a cross-platform experience including voice assistants. Assuming we want to build a small and simple to-do list app for Android, iOS, web and the Google Assistant, how can we provide the best experience for each platform while making sure our app can scale?&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Native vs hybrid vs cross-platform
&lt;/h2&gt;

&lt;p&gt;It’s been discussed already, a million times, and will be discussed over and over again. Should you build two dedicated native apps or a hybrid solution? What are the differences? Let’s try to summarise the different approaches we can take.&lt;/p&gt;

&lt;p&gt;Going fully native while targeting Android and iOS means developing two separate apps. Two different codebases, in two different programming languages (Java/Kotlin vs Swift/Objective C). In exchange, this approach offers best-in-class performance, a consistent look and feel and better access to low level platform specific features.&lt;/p&gt;

&lt;p&gt;A hybrid app is developed using standard web technologies (HTML, CSS and JavaScript) and runs as a web-view inside a native app. This means we can reuse the same codebase and target both Android and iOS. Although we can still access some platform specific functionality, the performance is compromised when compared to a native experience. This is the approach frameworks like &lt;a href="https://ionicframework.com/"&gt;Ionic&lt;/a&gt; and &lt;a href="https://cordova.apache.org/"&gt;Cordova&lt;/a&gt; use.&lt;/p&gt;

&lt;p&gt;Using frameworks like &lt;a href="https://facebook.github.io/react-native/"&gt;React Native&lt;/a&gt; or &lt;a href="https://www.nativescript.org/"&gt;NativeScript&lt;/a&gt;, it’s possible to use JavaScript to build truly native apps. Both frameworks also provide mechanisms to access native APIs to take full advantage of the platforms. Going with one or the other will depend on many variables but, mainly, the JavaScript flavour you want to taste: React (React Native) or Angular/Vue/TypeScript (NativeScript).&lt;/p&gt;

&lt;p&gt;Other cross-platform solutions that allow developers to create native apps reusing the same language and codebase are &lt;a href="https://flutter.dev/"&gt;Flutter&lt;/a&gt; (Dart) and &lt;a href="https://dotnet.microsoft.com/apps/xamarin"&gt;Xamarin&lt;/a&gt; (.NET/C#).&lt;/p&gt;

&lt;p&gt;An additional solution to consider would be building a &lt;a href="https://developers.google.com/web/progressive-web-apps"&gt;Progressive Web App&lt;/a&gt;. PWAs provide an installable, app-like experience on desktop and mobile that is built and delivered directly via the web. Since we’re exploring purely native solutions here (let’s put the web experience aside for now as we will come back to it in the next section) I’ll leave PWAs out of the scope of this article.&lt;/p&gt;

&lt;p&gt;Which of these approaches is best for your app? Well, there’s no easy answer to this question. A number of factors need to be taken into account here: budget, deadlines, language of preference, performance, etc.&lt;/p&gt;

&lt;p&gt;Since we’re aiming for a cross-platform experience and want to set ourselves up to be able to scale in complexity, to avoid exponentially increasing costs and timelines, it’s desirable to be able to reuse as much code as possible. Therefore we’re going to continue exploring the cross-platform path using JavaScript to compile to native code (React Native/NativeScript).&lt;/p&gt;

&lt;h2&gt;
  
  
  From web to mobile and back again
&lt;/h2&gt;

&lt;p&gt;Since we’ll be building our app using web technologies, do we expect our code to render in a browser? Well… no. At least not directly.&lt;/p&gt;

&lt;p&gt;React Native components compile to native Android and iOS components, not to DOM elements. But worry not, someone thought about this already. &lt;a href="https://github.com/necolas/react-native-web"&gt;React Native for Web&lt;/a&gt; unifies both worlds, bringing web rendering to React Native components.&lt;/p&gt;

&lt;p&gt;Now we can reuse our UI for mobile and web. However, this might not be what we are trying to achieve. After all, mobile and web are different platforms and often we want to take advantage of this to offer different user experiences. React Native for web doesn’t allow us to do this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.nativescript.org/angular/code-sharing/intro"&gt;NativeScript and Angular&lt;/a&gt; make it possible to use the same codebase to build both web and mobile apps, reusing the core business logic, while being flexible enough to include platform-specific code where necessary, by using a simple file name convention.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XPeSTvyd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ismael.dev/static/articles/voxe/nativescript-project-structure.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XPeSTvyd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ismael.dev/static/articles/voxe/nativescript-project-structure.png" alt="Code sharing with NativeScript" width="880" height="305"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Code sharing with NativeScript&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Going “serverless”
&lt;/h2&gt;

&lt;p&gt;So far we’ve discussed the technology stack for our app from the client(s) perspective. What about the backend? Again, it depends on what you’re building. You might already have a backend with an API deployed somewhere or you might need to build it from scratch.&lt;/p&gt;

&lt;p&gt;The example to-do app we aim to build will require a database and user authentication (plus hosting for the web version). Let’s assume we want to continue using JavaScript. One of the options would be building a backend based on &lt;a href="https://nodejs.org"&gt;Node.js®&lt;/a&gt;. But we might not need a fully fledged backend.&lt;/p&gt;

&lt;p&gt;An alternative would be using a platform that provides us with the services we need, like &lt;a href="https://firebase.google.com/"&gt;Firebase&lt;/a&gt;. Using Firebase makes it easy for our app(s) to benefit from a scalable infrastructure and cloud services like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Firestore: cloud-hosted NoSQL database with live synchronization and offline support.&lt;/li&gt;
&lt;li&gt;Authentication: multiple methods including email and password, third-party providers, etc.&lt;/li&gt;
&lt;li&gt;Hosting: for web assets including global CDN and free SSL certificate.&lt;/li&gt;
&lt;li&gt;Cloud Functions: custom backend code without needing to manage and scale your own servers.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://firebase.google.com/products"&gt;And lots more…&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zeCP2rfI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ismael.dev/static/articles/voxe/firebase-services.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zeCP2rfI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ismael.dev/static/articles/voxe/firebase-services.png" alt="Firebase services" width="880" height="461"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Firebase services&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Firebase seems to offer all we need for our to-do list app but, again, it might not be the right option for your app. The aim of these types of “serverless” cloud platforms (they actually run on servers, by the way) is accelerating apps and web development by removing the need to write backend code (most of the time) or manage server infrastructure.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://firebase.google.com/products/firestore/"&gt;Cloud Firestore&lt;/a&gt;, for example, is a cloud-hosted NoSQL database that can be accessed directly via native client SDKs. This raises, at least, an immediate concern. If the client can access the data directly, does this mean access control is non-existent? Not exactly. Firebase offers &lt;a href="https://firebase.google.com/docs/rules"&gt;Security Rules&lt;/a&gt; based on user identity to secure the data for mobile/web development. These rules might not be sufficient for your use case but this doesn’t stop you from accessing the Firestore from a server (e.g. &lt;a href="https://medium.com/firebase-developers/should-i-query-my-firebase-database-directly-or-use-cloud-functions-fbb3cd14118c"&gt;through a Cloud Function&lt;/a&gt;) using the &lt;a href="https://firebase.google.com/docs/admin/setup"&gt;Admin SDK&lt;/a&gt;. This way you could even build your own API on top.&lt;/p&gt;
&lt;h2&gt;
  
  
  The rise of voice assistants
&lt;/h2&gt;

&lt;p&gt;Smart speakers and voice assistants have become ubiquitous and your app could benefit from them. If you think this is the case and some of your app features could be triggered by voice commands, then you should consider one of these assistants as an additional client. But, which one?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.amazon.com/alexa/"&gt;Amazon Alexa&lt;/a&gt; and &lt;a href="https://developers.google.com/assistant"&gt;Google Assistant&lt;/a&gt; are the two most popular options here and their functionality can be extended via &lt;a href="https://developer.amazon.com/alexa-skills-kit/"&gt;Alexa Skills&lt;/a&gt; and &lt;a href="https://developers.google.com/assistant/conversational/"&gt;Google Actions&lt;/a&gt;, respectively. Both assistants are now built-in in an increasing number of first and third-party smart speakers, and are available as Android and iOS apps. Although Alexa is ahead in number of skills, some people would argue that the Google Assistant is more accurate, feels more fluid and natural in conversation and provides a better integration with the host platform.&lt;/p&gt;

&lt;p&gt;Assuming we want to integrate our app with the &lt;strong&gt;Google Assistant&lt;/strong&gt;, we would need three things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;An Actions on Google project:&lt;/strong&gt; This will allow you to access the developer console to manage and distribute your Action.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A Dialogflow Agent:&lt;/strong&gt; This will contain all the Intents that define your Action. Intents are used to categorise a user’s intentions. They have Training Phrases, which are examples of what a user might say to your agent. For instance: “Read my to-do list” or “Add ‘Buy broccoli’ to my to-do list”.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A Fulfillment:&lt;/strong&gt; This will receive requests from the Assistant, process the request and respond.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The fulfillment can be implemented using the &lt;a href="https://developers.google.com/assistant/actions/actions-sdk/fulfillment"&gt;Actions SDK&lt;/a&gt; with the Node.js® or the Java/Kotlin client library. If we use Node.js®, the fulfillment can then be deployed to a Cloud Function using Cloud Functions for Firebase. In order to integrate our Google Action with the rest of our ecosystem we will need this function to access our Firestore. This is possible thanks to the Firebase Admin SDK, as explained in the previous section.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VeuSfir6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ismael.dev/static/articles/voxe/actions-on-google-export.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VeuSfir6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ismael.dev/static/articles/voxe/actions-on-google-export.png" alt="" width="880" height="167"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;&lt;em&gt;In the &lt;a href="https://dev.to/ismaelgt/building-cross-platform-apps-for-mobile-web-and-voice-part-ii-e2i"&gt;second part&lt;/a&gt; of this article we will use the technology stack described here to build a simple proof of concept cross platform to-do list app, integrated with the Google Assistant.&lt;/em&gt;&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/ismaelgt" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RjZV6ZqF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/practicaldev/image/fetch/s--8_LHeOZI--/c_fill%2Cf_auto%2Cfl_progressive%2Ch_150%2Cq_auto%2Cw_150/https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/253157/07253dce-cc80-45c1-8f72-5f6f9657bffb.png" alt="ismaelgt"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/ismaelgt/building-cross-platform-apps-for-mobile-web-and-voice-part-ii-e2i" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Building cross-platform apps for mobile, web and voice (part II)&lt;/h2&gt;
      &lt;h3&gt;Ismael González Trujillo ・ Dec 8 '19 ・ 9 min read&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#javascript&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#nativescript&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#firebase&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#googleassistant&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>crossplatform</category>
      <category>webdev</category>
      <category>serverless</category>
      <category>googleassistant</category>
    </item>
  </channel>
</rss>
