<?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: Jonathan P</title>
    <description>The latest articles on Forem by Jonathan P (@johnnymakestuff).</description>
    <link>https://forem.com/johnnymakestuff</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%2F131023%2Fdacfbbd9-4ff5-4ec6-ac64-2ebcd1f0451c.png</url>
      <title>Forem: Jonathan P</title>
      <link>https://forem.com/johnnymakestuff</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/johnnymakestuff"/>
    <language>en</language>
    <item>
      <title>How to make a cross-platform serverless video sharing app with Flutter, Firebase and Publitio</title>
      <dc:creator>Jonathan P</dc:creator>
      <pubDate>Tue, 26 Nov 2019 00:00:00 +0000</pubDate>
      <link>https://forem.com/johnnymakestuff/how-to-make-a-cross-platform-serverless-video-sharing-app-with-flutter-firebase-and-publitio-3lei</link>
      <guid>https://forem.com/johnnymakestuff/how-to-make-a-cross-platform-serverless-video-sharing-app-with-flutter-firebase-and-publitio-3lei</guid>
      <description>&lt;h2&gt;
  
  
  What we're building
&lt;/h2&gt;

&lt;p&gt;We'll see how to build a cross-platform mobile app that allows users to upload and share videos.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZnRDBv4l--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://www.learningsomethingnew.com/flutter-video/final.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZnRDBv4l--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://www.learningsomethingnew.com/flutter-video/final.gif" alt="final"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The stack
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://flutter.dev/"&gt;Flutter&lt;/a&gt; - A new cross-platform mobile app development framework by Google, using the Dart language.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://firebase.google.com/"&gt;Firebase&lt;/a&gt; - The serverless real time database, for storing and syncing video metadata between clients.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://publit.io?fpr=jonathan43"&gt;Publitio&lt;/a&gt; - The hosting platform we'll use for storing and delivering the videos.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Flutter
&lt;/h2&gt;

&lt;p&gt;Flutter gives us a way of writing one code base for both iOS and Android, without having to duplicate the logic or the UI. &lt;br&gt;
The advantage over solutions like Cordova is that it's optimized for mobile performance, giving native-like response. The advantage over the likes of React Native is that you can write the UI once, as it circumvents the OS native UI components entirely. Flutter also has a first rate development experience, including hot reload and debugging in VSCode, which is awesome.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why Firebase
&lt;/h2&gt;

&lt;p&gt;Keeping things serverless has huge benefits for the time of development and scalability of the app. We can focus on our app instead of server devops. Firebase let's us sync data between clients without server side code, including offline syncing features (which are quite hard to implement). Flutter also has a set of firebase plugins that make it play nicely with Flutter widgets, so we get realtime syncing of the client UI when another client changes the data.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why Publitio
&lt;/h2&gt;

&lt;p&gt;We'll use Publitio as our Media Asset Management API. Publitio will be responsible for hosting our files, delivering them to clients using it's CDN, thumbnail/cover image generation, transformations/cropping, and other video processing features.&lt;/p&gt;

&lt;p&gt;By using an API for this, we can keep the app serverless (and therefore more easily scalable and maintainable), and not reinvent video processing in-house.&lt;/p&gt;
&lt;h2&gt;
  
  
  Let's start
&lt;/h2&gt;

&lt;p&gt;First, make sure you have a working flutter environment set up, using Flutter's &lt;a href="https://flutter.dev/docs/get-started/install"&gt;Getting Started&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now let's create a new project named &lt;code&gt;flutter_video_sharing&lt;/code&gt;. In terminal run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;flutter create &lt;span class="nt"&gt;--org&lt;/span&gt; com.learningsomethingnew.fluttervideo &lt;span class="nt"&gt;--project-name&lt;/span&gt; flutter_video_sharing &lt;span class="nt"&gt;-a&lt;/span&gt; kotlin &lt;span class="nt"&gt;-i&lt;/span&gt; swift flutter_video_sharing
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;To check the project has created successfully, run &lt;code&gt;flutter doctor&lt;/code&gt;, and see if there are any issues.&lt;/p&gt;

&lt;p&gt;Now run the basic project using &lt;code&gt;flutter run&lt;/code&gt; for a little sanity check (you can use an emulator or a real device, Android or iOS).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This is a good time to init a git repository and make the first commit.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Taking a video
&lt;/h2&gt;

&lt;p&gt;In order to take a video using the device camera, we'll use the &lt;a href="https://pub.dev/packages/image_picker"&gt;Image Picker plugin for Flutter&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;pubspec.yaml&lt;/code&gt; dependencies section, add the line (change to latest version of the plugin):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;image_picker&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;^0.6.1+10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;You might notice that there is also a &lt;a href="https://pub.dev/packages/camera"&gt;Camera Plugin&lt;/a&gt;. I found this plugin to be quite unstable for now, and has serious video quality limitations on many android devices, as it supports only camera2 API.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  iOS configuration
&lt;/h3&gt;

&lt;p&gt;For iOS, we'll have to add a camera and mic usage description in &lt;code&gt;ios/Runner/Info.plist&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="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;NSMicrophoneUsageDescription&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;Need access to mic&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;NSCameraUsageDescription&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;Need access to camera&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;In order to test the camera on iOS you'll have to use a real device as the simulator doesn't have a camera&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Using the plugin
&lt;/h3&gt;

&lt;p&gt;In &lt;code&gt;lib/main.dart&lt;/code&gt; edit &lt;code&gt;_MyHomePageState&lt;/code&gt; class with the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;_MyHomePageState&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;MyHomePage&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_videos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;[];&lt;/span&gt;

  &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;_imagePickerActive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

  &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;_takeVideo&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="n"&gt;async&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_imagePickerActive&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;_imagePickerActive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="n"&gt;videoFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="n"&gt;await&lt;/span&gt; &lt;span class="n"&gt;ImagePicker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;pickVideo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kn"&gt;source&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ImageSource&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;camera&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;_imagePickerActive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;videoFile&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;setState&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;_videos&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;videoFile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;});&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;)&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;Scaffold&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;appBar:&lt;/span&gt; &lt;span class="n"&gt;AppBar&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;title:&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;widget&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
      &lt;span class="o"&gt;),&lt;/span&gt;
      &lt;span class="nl"&gt;body:&lt;/span&gt; &lt;span class="n"&gt;Center&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
          &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;ListView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
              &lt;span class="nl"&gt;padding:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;EdgeInsets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;all&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
              &lt;span class="nl"&gt;itemCount:&lt;/span&gt; &lt;span class="n"&gt;_videos&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
              &lt;span class="nl"&gt;itemBuilder:&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;)&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;Card&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                  &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Container&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                    &lt;span class="nl"&gt;padding:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;EdgeInsets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;all&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
                    &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Center&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_videos&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;])),&lt;/span&gt;
                  &lt;span class="o"&gt;),&lt;/span&gt;
                &lt;span class="o"&gt;);&lt;/span&gt;
              &lt;span class="o"&gt;})),&lt;/span&gt;
      &lt;span class="nl"&gt;floatingActionButton:&lt;/span&gt; &lt;span class="n"&gt;FloatingActionButton&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;onPressed:&lt;/span&gt; &lt;span class="n"&gt;_takeVideo&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;tooltip:&lt;/span&gt; &lt;span class="s"&gt;'Take Video'&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Icon&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Icons&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
      &lt;span class="o"&gt;),&lt;/span&gt;
    &lt;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;What's going on here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We're replacing the &lt;code&gt;_incrementCounter&lt;/code&gt; method with &lt;code&gt;_takeVideo&lt;/code&gt;, and also making it &lt;code&gt;async&lt;/code&gt;. This is what happens when we click the Floating Action Button.&lt;/li&gt;
&lt;li&gt;We're taking a video with &lt;code&gt;await ImagePicker.pickVideo(source: ImageSource.camera);&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;We're saving the video file paths in the &lt;code&gt;_videos&lt;/code&gt; list in our widget's state. We call &lt;code&gt;setState&lt;/code&gt; so that the widget will rebuild and the changes reflect in the UI.&lt;/li&gt;
&lt;li&gt;In order to get some visual feedback, we replace the scaffold's main &lt;code&gt;Column&lt;/code&gt; with a &lt;code&gt;ListView&lt;/code&gt; using &lt;code&gt;ListView.builder&lt;/code&gt; which will render our dynamic list efficiently.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Running at this stage will look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--He8Zco95--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.learningsomethingnew.com/flutter-video/image-picker.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--He8Zco95--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.learningsomethingnew.com/flutter-video/image-picker.png" alt="image-picker"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Add Publitio
&lt;/h2&gt;

&lt;p&gt;Create a free account at &lt;a href="https://publit.io?fpr=jonathan43"&gt;Publit.io&lt;/a&gt;, and get your credentials from the dashboard.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: The link above has my referral code. If my writing is valuable to you, you can support it by using this link. Of course, you can just create an account without me 😭&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Add the package
&lt;/h3&gt;

&lt;p&gt;Add the &lt;a href="https://pub.dev/packages/flutter_publitio"&gt;flutter_publitio&lt;/a&gt; plugin to your &lt;code&gt;pubspec.yaml&lt;/code&gt; dependencies section:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;dependencies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;flutter_publitio&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;^1.0.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Create an env file
&lt;/h3&gt;

&lt;p&gt;A good way to store app configuration is by using &lt;code&gt;.env&lt;/code&gt; files, and loading them with &lt;a href="https://pub.dev/packages/flutter_dotenv"&gt;flutter_dotenv&lt;/a&gt;, which in turn implements a guideline from &lt;a href="http://www.12factor.net/"&gt;The Twelve-Factor App&lt;/a&gt;. This file will contain our API key and secret, and therefore should not be committed to source-control.&lt;br&gt;
So create an &lt;code&gt;.env&lt;/code&gt; file in the project's root dir, and put your credentials in it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PUBLITIO_KEY=12345abcd
PUBLITIO_SECRET=abc123
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now add &lt;code&gt;flutter_dotenv&lt;/code&gt; as a dependency, and also add the &lt;code&gt;.env&lt;/code&gt; file as an asset in &lt;code&gt;pubspec.yaml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;dependencies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;flutter_dotenv&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;^2.0.3&lt;/span&gt;

  &lt;span class="s"&gt;...&lt;/span&gt;

&lt;span class="na"&gt;flutter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;assets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.env&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  iOS configuration
&lt;/h3&gt;

&lt;p&gt;For iOS, the keys are loaded from &lt;code&gt;Info.plist&lt;/code&gt;. In order to keep our configuration in our environment and not commit it into our repository, we'll load the keys from an &lt;code&gt;xcconfig&lt;/code&gt; file:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In XCode, open &lt;code&gt;ios/Runner.xworkspace&lt;/code&gt; (this is the XCode project Flutter has generated), right click on Runner -&amp;gt; New File -&amp;gt; Other -&amp;gt; Configuration Settings File -&amp;gt; Config.xcconfig&lt;/li&gt;
&lt;li&gt;In &lt;code&gt;Config.xcconfig&lt;/code&gt;, add the keys like you did in the &lt;code&gt;.env&lt;/code&gt; file:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PUBLITIO_KEY = 12345abcd
PUBLITIO_SECRET = abc123
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now we'll import this config file from &lt;code&gt;ios/Flutter/Debug.xcconfig&lt;/code&gt; and &lt;code&gt;ios/Flutter/Release.xcconfig&lt;/code&gt; by adding this line to the bottom of both of them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#include "../Runner/Config.xcconfig"
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Add Config.xcconfig to .gitignore so you don't have your keys in git&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The last step is to add the config keys to &lt;code&gt;ios/Runner/Info.plist&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="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;PublitioAPIKey&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;$(PUBLITIO_KEY)&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;PublitioAPISecret&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;$(PUBLITIO_SECRET)&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Android configuration
&lt;/h3&gt;

&lt;p&gt;In Android, all we have to do is change &lt;code&gt;minSdkVersion&lt;/code&gt; from 16 to 19 in &lt;code&gt;android/app/build.gradle&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Is it just me or is everything always simpler with Android?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Upload the file
&lt;/h2&gt;

&lt;p&gt;Now that we have publitio added, let's upload the file we got from ImagePicker.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;main.dart&lt;/code&gt;, in the &lt;code&gt;_MyHomePageState&lt;/code&gt; class, we'll add a field keeping the status of the upload:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;_uploading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now we'll override &lt;code&gt;initState&lt;/code&gt; and call an async function &lt;code&gt;configurePublitio&lt;/code&gt; that will load the API keys:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="nd"&gt;@override&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;initState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;configurePublitio&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;initState&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nf"&gt;configurePublitio&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;async&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;await&lt;/span&gt; &lt;span class="n"&gt;DotEnv&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;load&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'.env'&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;await&lt;/span&gt; &lt;span class="n"&gt;FlutterPublitio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;configure&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;DotEnv&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'PUBLITIO_KEY'&lt;/span&gt;&lt;span class="o"&gt;],&lt;/span&gt; &lt;span class="n"&gt;DotEnv&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'PUBLITIO_SECRET'&lt;/span&gt;&lt;span class="o"&gt;]);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We'll add a function &lt;code&gt;_uploadVideo&lt;/code&gt; that calls publitio API's &lt;code&gt;uploadFile&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nf"&gt;_uploadVideo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;videoFile&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;async&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'starting upload'&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;uploadOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="s"&gt;"privacy"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"option_download"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"option_transform"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"1"&lt;/span&gt;
  &lt;span class="o"&gt;};&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="n"&gt;await&lt;/span&gt; &lt;span class="n"&gt;FlutterPublitio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;uploadFile&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;videoFile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uploadOptions&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;response&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We'll add the calling code to our &lt;code&gt;_takeVideo&lt;/code&gt; function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;setState&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;_uploading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_uploadVideo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;videoFile&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;setState&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;_videos&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"url_preview"&lt;/span&gt;&lt;span class="o"&gt;]);&lt;/span&gt;
  &lt;span class="o"&gt;});&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt; &lt;span class="n"&gt;PlatformException&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="si"&gt;${e.code}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;${e.message}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;//result = 'Platform Exception: ${e.code} ${e.details}';&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;setState&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;_uploading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="o"&gt;});&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Notice that the response from publitio API will come as a key-value map, from which we're only saving the &lt;code&gt;url_preview&lt;/code&gt;, which is the url for viewing the hosted video. We're saving that to our &lt;code&gt;_videos&lt;/code&gt; collection, and returning &lt;code&gt;_uploading&lt;/code&gt; to false after the upload is done. &lt;/p&gt;

&lt;p&gt;And finally we'll change the floating action button to a spinner whenever &lt;code&gt;_uploading&lt;/code&gt; is true:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="nl"&gt;floatingActionButton:&lt;/span&gt; &lt;span class="n"&gt;FloatingActionButton&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;_uploading&lt;/span&gt;
      &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;CircularProgressIndicator&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
          &lt;span class="nl"&gt;valueColor:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;AlwaysStoppedAnimation&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;Colors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;white&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
        &lt;span class="o"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Icon&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Icons&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
  &lt;span class="nl"&gt;onPressed:&lt;/span&gt; &lt;span class="n"&gt;_takeVideo&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Add thumbnails
&lt;/h3&gt;

&lt;p&gt;One of the things publitio makes easy is server-side video thumbnail extraction. You can use it's URL transformation features to get a thumbnail of any size, but for this we'll use the default thumbnail that is received in the upload response.&lt;/p&gt;

&lt;p&gt;Now that we want every list item to have a url and a thumbnail, it makes sense to extract a simple POCO class for each video entry. Create a new file &lt;code&gt;lib/video_info.dart&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;VideoInfo&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;videoUrl&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;thumbUrl&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

  &lt;span class="n"&gt;VideoInfo&lt;/span&gt;&lt;span class="o"&gt;({&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;videoUrl&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;thumbUrl&lt;/span&gt;&lt;span class="o"&gt;});&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We'll change the &lt;code&gt;_videos&lt;/code&gt; collection from &lt;code&gt;String&lt;/code&gt; to &lt;code&gt;VideoInfo&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;VideoInfo&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_videos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;VideoInfo&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;[];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;And after getting the upload response, we'll add a VideoInfo object with the url and the thumbnail url:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_uploadVideo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;videoFile&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;setState&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;_videos&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;VideoInfo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;videoUrl:&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"url_preview"&lt;/span&gt;&lt;span class="o"&gt;],&lt;/span&gt;
      &lt;span class="nl"&gt;thumbUrl:&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"url_thumbnail"&lt;/span&gt;&lt;span class="o"&gt;]));&lt;/span&gt;
&lt;span class="o"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Finally we'll add the thumbnail display to the list builder item:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Container&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;padding:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;EdgeInsets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;all&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;10.0&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
  &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="nl"&gt;crossAxisAlignment:&lt;/span&gt; &lt;span class="n"&gt;CrossAxisAlignment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;children:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Widget&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;[&lt;/span&gt;
      &lt;span class="n"&gt;Stack&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;alignment:&lt;/span&gt; &lt;span class="n"&gt;Alignment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;center&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;children:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Widget&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;[&lt;/span&gt;
          &lt;span class="n"&gt;Center&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;CircularProgressIndicator&lt;/span&gt;&lt;span class="o"&gt;()),&lt;/span&gt;
          &lt;span class="n"&gt;Center&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;ClipRRect&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
              &lt;span class="nl"&gt;borderRadius:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;BorderRadius&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;circular&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;8.0&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
              &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;FadeInImage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;memoryNetwork&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="nl"&gt;placeholder:&lt;/span&gt; &lt;span class="n"&gt;kTransparentImage&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                &lt;span class="nl"&gt;image:&lt;/span&gt; &lt;span class="n"&gt;_videos&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;].&lt;/span&gt;&lt;span class="na"&gt;thumbUrl&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
              &lt;span class="o"&gt;),&lt;/span&gt;
            &lt;span class="o"&gt;),&lt;/span&gt;
          &lt;span class="o"&gt;),&lt;/span&gt;
        &lt;span class="o"&gt;],&lt;/span&gt;
      &lt;span class="o"&gt;),&lt;/span&gt;
      &lt;span class="n"&gt;Padding&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;padding:&lt;/span&gt; &lt;span class="n"&gt;EdgeInsets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;only&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;top:&lt;/span&gt; &lt;span class="mf"&gt;20.0&lt;/span&gt;&lt;span class="o"&gt;)),&lt;/span&gt;
      &lt;span class="n"&gt;ListTile&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;title:&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_videos&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;].&lt;/span&gt;&lt;span class="na"&gt;videoUrl&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
      &lt;span class="o"&gt;),&lt;/span&gt;
    &lt;span class="o"&gt;],&lt;/span&gt;
  &lt;span class="o"&gt;),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;A few things here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We're giving the thumbnail nice round borders using &lt;code&gt;ClipRRect&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;We're displaying a &lt;code&gt;CircularProgressIndicator&lt;/code&gt; that displays while the thumbnail is loading (it will be hidden by the image after loading)&lt;/li&gt;
&lt;li&gt;For a nice fade effect, we're using &lt;code&gt;kTransparentImage&lt;/code&gt; from the package &lt;code&gt;transparent_image&lt;/code&gt; (which needs to be added to &lt;code&gt;pubspec.yaml&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And now we have nice thumbnails in the list:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1sM4iqGl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.learningsomethingnew.com/flutter-video/thumbnail.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1sM4iqGl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.learningsomethingnew.com/flutter-video/thumbnail.png" alt="thumbnail"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Play the video
&lt;/h2&gt;

&lt;p&gt;Now that we have the list of videos, we want to play each video when tapping on the list card.&lt;/p&gt;

&lt;p&gt;We'll use the &lt;a href="https://pub.dev/packages/chewie"&gt;Chewie plugin&lt;/a&gt; as our player. Chewie wraps the &lt;a href="https://pub.dev/packages/video_player"&gt;video_player plugin&lt;/a&gt; with native looking UI for playing, skipping, and full screening.&lt;br&gt;
It also supports auto rotating the video according to device orientation.&lt;br&gt;
What it can't do (odly), is figure out the aspect ratio of the video automatically. So we'll get that from the publitio result.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: Flutter's video_player package doesn't yet support caching, so replaying the video will cause it to re-download. This should be solved soon: &lt;a href="https://github.com/flutter/flutter/issues/28094"&gt;https://github.com/flutter/flutter/issues/28094&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So add to &lt;code&gt;pubspec.yaml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;video_player&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;^0.10.2+5&lt;/span&gt;
&lt;span class="na"&gt;chewie&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;^0.9.8+1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;For iOS, we'll also need to add to the following to &lt;code&gt;Info.plist&lt;/code&gt; to allow loading remote videos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;NSAppTransportSecurity&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;dict&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;NSAllowsArbitraryLoads&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;true/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dict&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now we'll add a new widget that will hold chewie. Create a new file &lt;code&gt;chewie_player.dart&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:chewie/chewie.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:flutter/material.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:video_player/video_player.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'video_info.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ChewiePlayer&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatefulWidget&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;VideoInfo&lt;/span&gt; &lt;span class="n"&gt;video&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;ChewiePlayer&lt;/span&gt;&lt;span class="o"&gt;({&lt;/span&gt;&lt;span class="n"&gt;Key&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nd"&gt;@required&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;video&lt;/span&gt;&lt;span class="o"&gt;})&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;key:&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;StatefulWidget&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;createState&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_ChewiePlayerState&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;_ChewiePlayerState&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ChewiePlayer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;ChewieController&lt;/span&gt; &lt;span class="n"&gt;chewieCtrl&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;VideoPlayerController&lt;/span&gt; &lt;span class="n"&gt;videoPlayerCtrl&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;initState&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;initState&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;videoPlayerCtrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;VideoPlayerController&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;network&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;widget&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;video&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;videoUrl&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;chewieCtrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ChewieController&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;videoPlayerController:&lt;/span&gt; &lt;span class="n"&gt;videoPlayerCtrl&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;autoPlay:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;autoInitialize:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;aspectRatio:&lt;/span&gt; &lt;span class="n"&gt;widget&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;video&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;aspectRatio&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;placeholder:&lt;/span&gt; &lt;span class="n"&gt;Center&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;network&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;widget&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;video&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;coverUrl&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
      &lt;span class="o"&gt;),&lt;/span&gt;
    &lt;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;dispose&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chewieCtrl&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;chewieCtrl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;dispose&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;videoPlayerCtrl&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;videoPlayerCtrl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;dispose&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;dispose&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;)&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;Scaffold&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;backgroundColor:&lt;/span&gt; &lt;span class="n"&gt;Colors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;black&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;body:&lt;/span&gt; &lt;span class="n"&gt;Stack&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;children:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Widget&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;[&lt;/span&gt;
          &lt;span class="n"&gt;Chewie&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="nl"&gt;controller:&lt;/span&gt; &lt;span class="n"&gt;chewieCtrl&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
          &lt;span class="o"&gt;),&lt;/span&gt;
          &lt;span class="n"&gt;Container&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="nl"&gt;padding:&lt;/span&gt; &lt;span class="n"&gt;EdgeInsets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;all&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;30.0&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
            &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;IconButton&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
              &lt;span class="nl"&gt;icon:&lt;/span&gt; &lt;span class="n"&gt;Icon&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Icons&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;close&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;color:&lt;/span&gt; &lt;span class="n"&gt;Colors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;white&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
              &lt;span class="nl"&gt;onPressed:&lt;/span&gt; &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Navigator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;pop&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
            &lt;span class="o"&gt;),&lt;/span&gt;
          &lt;span class="o"&gt;),&lt;/span&gt;
        &lt;span class="o"&gt;],&lt;/span&gt;
      &lt;span class="o"&gt;),&lt;/span&gt;
    &lt;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;A few things to note:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ChewiePlayer&lt;/code&gt; expects to get &lt;code&gt;VideoInfo&lt;/code&gt; which is the video to be played.&lt;/li&gt;
&lt;li&gt;The aspect ratio is initialized from the input &lt;code&gt;VideoInfo&lt;/code&gt;. We'll add this field soon.&lt;/li&gt;
&lt;li&gt;We show a placeholder image while the video is loading. We'll use publitio API to generate the cover image.&lt;/li&gt;
&lt;li&gt;We have an &lt;code&gt;IconButton&lt;/code&gt; that will close this widget by calling &lt;code&gt;Navigator.pop(context)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;We have to take care of disposing both the &lt;code&gt;VideoPlayerController&lt;/code&gt; and the &lt;code&gt;ChewieController&lt;/code&gt; by overriding the &lt;code&gt;dispose()&lt;/code&gt; method&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In &lt;code&gt;video_info.dart&lt;/code&gt; we'll add the &lt;code&gt;aspectRatio&lt;/code&gt; and &lt;code&gt;coverUrl&lt;/code&gt; fields:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;coverUrl&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;aspectRatio&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;And now in &lt;code&gt;main.dart&lt;/code&gt;, we'll first import our new &lt;code&gt;chewie_player&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'chewie_player.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Add a calculation of &lt;code&gt;aspectRatio&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight dart"&gt;&lt;code&gt;  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_uploadVideo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;videoFile&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"width"&lt;/span&gt;&lt;span class="o"&gt;];&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"height"&lt;/span&gt;&lt;span class="o"&gt;];&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;aspectRatio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;setState&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;_videos&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;VideoInfo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
          &lt;span class="nl"&gt;videoUrl:&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"url_preview"&lt;/span&gt;&lt;span class="o"&gt;],&lt;/span&gt;
        &lt;span class="nl"&gt;thumbUrl:&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"url_thumbnail"&lt;/span&gt;&lt;span class="o"&gt;]));&lt;/span&gt;
        &lt;span class="nl"&gt;thumbUrl:&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"url_thumbnail"&lt;/span&gt;&lt;span class="o"&gt;],&lt;/span&gt;
        &lt;span class="nl"&gt;aspectRatio:&lt;/span&gt; &lt;span class="n"&gt;aspectRatio&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;coverUrl:&lt;/span&gt; &lt;span class="n"&gt;getCoverUrl&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
        &lt;span class="o"&gt;));&lt;/span&gt;
    &lt;span class="o"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Add a method to get the cover image from publitio API (this is just replacing the extension of the video to &lt;code&gt;jpg&lt;/code&gt; - publitio does all the work):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;PUBLITIO_PREFIX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https://media.publit.io/file"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nf"&gt;getCoverUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;publicId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"public_id"&lt;/span&gt;&lt;span class="o"&gt;];&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$PUBLITIO_PREFIX&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;$publicId&lt;/span&gt;&lt;span class="s"&gt;.jpg"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;And wrap our list item &lt;code&gt;Card&lt;/code&gt; with a &lt;code&gt;GestureDetector&lt;/code&gt; to respond to tapping on the card, and call &lt;code&gt;Navigator.push&lt;/code&gt; that will route to our new &lt;code&gt;ChewiePlayer&lt;/code&gt; widget:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;GestureDetector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nl"&gt;onTap:&lt;/span&gt; &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;Navigator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="o"&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;MaterialPageRoute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
          &lt;span class="nl"&gt;builder:&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;)&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;ChewiePlayer&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
              &lt;span class="nl"&gt;video:&lt;/span&gt; &lt;span class="n"&gt;_videos&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;],&lt;/span&gt;
            &lt;span class="o"&gt;);&lt;/span&gt;
          &lt;span class="o"&gt;},&lt;/span&gt;
        &lt;span class="o"&gt;),&lt;/span&gt;
      &lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;},&lt;/span&gt;
    &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Card&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Container&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;padding:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;EdgeInsets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;all&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;10.0&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
        &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
          &lt;span class="nl"&gt;crossAxisAlignment:&lt;/span&gt; &lt;span class="n"&gt;CrossAxisAlignment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
          &lt;span class="nl"&gt;children:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Widget&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;[&lt;/span&gt;
            &lt;span class="n"&gt;Stack&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
              &lt;span class="nl"&gt;alignment:&lt;/span&gt; &lt;span class="n"&gt;Alignment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;center&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
              &lt;span class="nl"&gt;children:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Widget&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;[&lt;/span&gt;
                &lt;span class="n"&gt;Center&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;CircularProgressIndicator&lt;/span&gt;&lt;span class="o"&gt;()),&lt;/span&gt;
                &lt;span class="n"&gt;Center&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                  &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;ClipRRect&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                    &lt;span class="nl"&gt;borderRadius:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;BorderRadius&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;circular&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;8.0&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
                    &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;FadeInImage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;memoryNetwork&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                      &lt;span class="nl"&gt;placeholder:&lt;/span&gt; &lt;span class="n"&gt;kTransparentImage&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                      &lt;span class="nl"&gt;image:&lt;/span&gt; &lt;span class="n"&gt;_videos&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;].&lt;/span&gt;&lt;span class="na"&gt;thumbUrl&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                    &lt;span class="o"&gt;),&lt;/span&gt;
                  &lt;span class="o"&gt;),&lt;/span&gt;
                &lt;span class="o"&gt;),&lt;/span&gt;
              &lt;span class="o"&gt;],&lt;/span&gt;
            &lt;span class="o"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;Padding&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;padding:&lt;/span&gt; &lt;span class="n"&gt;EdgeInsets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;only&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;top:&lt;/span&gt; &lt;span class="mf"&gt;20.0&lt;/span&gt;&lt;span class="o"&gt;)),&lt;/span&gt;
            &lt;span class="n"&gt;ListTile&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
              &lt;span class="nl"&gt;title:&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_videos&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;].&lt;/span&gt;&lt;span class="na"&gt;videoUrl&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
            &lt;span class="o"&gt;),&lt;/span&gt;
          &lt;span class="o"&gt;],&lt;/span&gt;
        &lt;span class="o"&gt;),&lt;/span&gt;
      &lt;span class="o"&gt;),&lt;/span&gt;
    &lt;span class="o"&gt;),&lt;/span&gt;
  &lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Add Firebase
&lt;/h2&gt;

&lt;p&gt;Now that we can upload and playback videos, we want users of the app to view the videos other users posted. To do that (and keep the app serverless) we'll use Firebase.&lt;/p&gt;

&lt;h3&gt;
  
  
  Firebase Setup
&lt;/h3&gt;

&lt;p&gt;First, setup Firebase as described &lt;a href="https://firebase.google.com/docs/flutter/setup"&gt;here&lt;/a&gt;. This includes creating a firebase project, registering your mobile apps (Android and iOS) in the project, and configuring the credentials for both the Android and iOS projects. Then we'll add the Flutter packages &lt;a href="https://pub.dev/packages/firebase_core"&gt;firebase_core&lt;/a&gt; and &lt;a href="https://pub.dev/packages/cloud_firestore"&gt;cloud_firestore&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Cloud Firestore is the new version of the Firebase Realtime Database.&lt;br&gt;
You'll probably need to set &lt;code&gt;multiDexEnabled true&lt;/code&gt; in your &lt;code&gt;build.gradle&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Save video info to Firebase
&lt;/h3&gt;

&lt;p&gt;Instead of saving the video info to our own state, we'll save it to a new Firebase document in the &lt;code&gt;videos&lt;/code&gt; collection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;video&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;VideoInfo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;videoUrl:&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"url_preview"&lt;/span&gt;&lt;span class="o"&gt;],&lt;/span&gt;
  &lt;span class="nl"&gt;thumbUrl:&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"url_thumbnail"&lt;/span&gt;&lt;span class="o"&gt;],&lt;/span&gt;
  &lt;span class="nl"&gt;coverUrl:&lt;/span&gt; &lt;span class="n"&gt;getCoverUrl&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
  &lt;span class="nl"&gt;aspectRatio:&lt;/span&gt; &lt;span class="n"&gt;getAspectRatio&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Firestore&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;instance&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;collection&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'videos'&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;document&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;setData&lt;/span&gt;&lt;span class="o"&gt;({&lt;/span&gt;
  &lt;span class="s"&gt;"videoUrl"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;video&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;videoUrl&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;"thumbUrl"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;video&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;thumbUrl&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;"coverUrl"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;video&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;coverUrl&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;"aspectRatio"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;video&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;aspectRatio&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;});&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;document()&lt;/code&gt; method will create a new randomly named document inside the &lt;code&gt;videos&lt;/code&gt; collection.&lt;/p&gt;

&lt;p&gt;This is how the documents look in the Firebase Console:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5hRUvgOO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.learningsomethingnew.com/flutter-video/firestore.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5hRUvgOO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.learningsomethingnew.com/flutter-video/firestore.png" alt="firestore"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Show video list from Firebase
&lt;/h3&gt;

&lt;p&gt;Now in our &lt;code&gt;initState&lt;/code&gt; method we'll want to start a Firebase query that will listen to the &lt;code&gt;videos&lt;/code&gt; collection. Whenever the Firebase SDK triggers a change in the data, it will invoke the &lt;code&gt;updateVideos&lt;/code&gt; method, which will update our &lt;code&gt;_videos&lt;/code&gt; state (and Flutter will rebuild the UI):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="nd"&gt;@override&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;initState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;configurePublitio&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
  &lt;span class="n"&gt;listenToVideos&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;initState&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;listenToVideos&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="n"&gt;async&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;Firestore&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;instance&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;collection&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'videos'&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;snapshots&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;listen&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;updateVideos&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;updateVideos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;QuerySnapshot&lt;/span&gt; &lt;span class="n"&gt;documentList&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;async&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;newVideos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mapQueryToVideoInfo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;documentList&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;setState&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;_videos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;newVideos&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="o"&gt;});&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nf"&gt;mapQueryToVideoInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;QuerySnapshot&lt;/span&gt; &lt;span class="n"&gt;documentList&lt;/span&gt;&lt;span class="o"&gt;)&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;documentList&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;documents&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="n"&gt;DocumentSnapshot&lt;/span&gt; &lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="o"&gt;)&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;VideoInfo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;videoUrl:&lt;/span&gt; &lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"videoUrl"&lt;/span&gt;&lt;span class="o"&gt;],&lt;/span&gt;
      &lt;span class="nl"&gt;thumbUrl:&lt;/span&gt; &lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"thumbUrl"&lt;/span&gt;&lt;span class="o"&gt;],&lt;/span&gt;
      &lt;span class="nl"&gt;coverUrl:&lt;/span&gt; &lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"coverUrl"&lt;/span&gt;&lt;span class="o"&gt;],&lt;/span&gt;
      &lt;span class="nl"&gt;aspectRatio:&lt;/span&gt; &lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"aspectRatio"&lt;/span&gt;&lt;span class="o"&gt;],&lt;/span&gt;
    &lt;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="o"&gt;}).&lt;/span&gt;&lt;span class="na"&gt;toList&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now all the videos are shared!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZnRDBv4l--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://www.learningsomethingnew.com/flutter-video/final.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZnRDBv4l--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://www.learningsomethingnew.com/flutter-video/final.gif" alt="final"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Refactoring
&lt;/h2&gt;

&lt;p&gt;Now that everything's working, it's a good time for some refactoring. This is a really small app, but still some obvious things stand out.&lt;br&gt;
We have our data access and business logic all sitting in the same place - never a good idea. It's better to have our API/data access in separate modules, providing the business logic layer with the required services, while it can stay agnostic to (and &lt;a href="https://en.wikipedia.org/wiki/Loose_coupling"&gt;Loosely Coupled&lt;/a&gt; from) the implementation details.&lt;/p&gt;

&lt;p&gt;In order to keep this post short(ish) I won't include these changes here, but you can see them in the final code on &lt;a href="https://github.com/syonip/flutter_video_sharing"&gt;GitHub&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Future improvement: client side encoding
&lt;/h2&gt;

&lt;p&gt;We can use FFMpeg to encode the video on the client before uploading.&lt;br&gt;
This will save storage and make delivery faster, but will require a patient uploading user.&lt;br&gt;
If you want to see how to do that, write a comment below.&lt;/p&gt;

&lt;p&gt;Thanks for reading!&lt;br&gt;
Full code can be found on &lt;a href="https://github.com/syonip/flutter_video_sharing"&gt;GitHub&lt;/a&gt;.&lt;br&gt;
If you have any questions, please leave a comment!&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>firebase</category>
      <category>publitio</category>
      <category>videos</category>
    </item>
    <item>
      <title>How to make an image uploading app with Vue, Quasar, Firebase Storage and Cordova - Part 2</title>
      <dc:creator>Jonathan P</dc:creator>
      <pubDate>Sun, 13 Oct 2019 00:00:00 +0000</pubDate>
      <link>https://forem.com/johnnymakestuff/how-to-make-an-image-uploading-app-with-vue-quasar-firebase-storage-and-cordova-part-2-4489</link>
      <guid>https://forem.com/johnnymakestuff/how-to-make-an-image-uploading-app-with-vue-quasar-firebase-storage-and-cordova-part-2-4489</guid>
      <description>&lt;h2&gt;
  
  
  What we're building
&lt;/h2&gt;

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

&lt;p&gt;We'll build a cross-platform mobile app for taking photos and uploading to firebase. &lt;/p&gt;

&lt;p&gt;In &lt;a href="https://www.learningsomethingnew.com/how-to-make-an-image-uploading-app-with-vue-quasar-firebase-storage-and-cordova-part-1"&gt;Part 1&lt;/a&gt;, we saw how to take a picture and save it to Firebase Cloud Storage.&lt;br&gt;
In this post we'll move the uploading to a separate thread via web worker, and use the blueimp library to generate a thumbnail locally and show it while uploading.&lt;/p&gt;
&lt;h2&gt;
  
  
  Web Workers
&lt;/h2&gt;
&lt;h3&gt;
  
  
  What is a web worker
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Web Workers are a simple means for web content to run scripts in background threads. The worker thread can perform tasks without interfering with the user interface. (&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers"&gt;mozilla.org&lt;/a&gt;)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So we'll use a web worker to offload code that can potentially block our UI thread - in our case the Firebase Storage code - to another thread.&lt;/p&gt;
&lt;h3&gt;
  
  
  Configuring workerize-loader
&lt;/h3&gt;

&lt;p&gt;We'll use &lt;a href="https://github.com/developit/workerize-loader"&gt;workerize-loader&lt;/a&gt; to make using web workers a little easier (web workers interface is a little weird).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn add workerize-loader
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We need to add some webpack configuration to tell webpack to use &lt;code&gt;workerize-loader&lt;/code&gt;. In &lt;code&gt;quasar.conf.js&lt;/code&gt; in the &lt;code&gt;build&lt;/code&gt; section add:&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;extendWebpack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;worker&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;js$/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;workerize-loader&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="p"&gt;},&lt;/span&gt;

&lt;span class="nx"&gt;chainWebpack&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;isServer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isClient&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;globalObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;self&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;This tells webpack to load the file &lt;code&gt;worker.js&lt;/code&gt; using &lt;code&gt;workerize-loader&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Moving code to the worker
&lt;/h3&gt;

&lt;p&gt;Let's add the file &lt;code&gt;src/services/worker.js&lt;/code&gt; and move our uploading code into it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;cloudStorage&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./cloud-storage&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;uploadPicture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;imageData&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;storageId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;getTime&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;toString&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;downloadURL&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;cloudStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uploadBase64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;imageData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;storageId&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;storageId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;downloadURL&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;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;initFB&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;cloudStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;deletePic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;storageId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;cloudStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deleteFromStorage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;storageId&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;We've also added a &lt;code&gt;deletePic&lt;/code&gt; call which we'll see later in &lt;code&gt;cloudStorage.js&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding simple state management
&lt;/h2&gt;

&lt;p&gt;We'll want to create an instance of the worker and save it in the app's state. We don't have state management yet, so let's add a simple state management pattern using a &lt;code&gt;store.js&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;worker&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;workerize-loader!./worker.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt; &lt;span class="o"&gt;=&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;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;pics&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
        &lt;span class="na"&gt;uploading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nx"&gt;initWorker&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;workerInstance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;workerInstance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;initFB&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;loadPictures&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;debug&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;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;loadPictures triggered&lt;/span&gt;&lt;span class="dl"&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;picsJson&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;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pics&lt;/span&gt;&lt;span class="dl"&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;picsJson&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;picsJson&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pic&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;pic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uploading&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;addPic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pic&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;debug&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;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;addPic triggered with&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="nx"&gt;pic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;failed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;splice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pic&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pics&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pics&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;deletePic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;idx&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;debug&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;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;deletePic triggered with&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pics&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;uploading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pics&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;storageId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;workerInstance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deletePic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pics&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;storageId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;splice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pics&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pics&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;updatePicUploaded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;oldPic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;newPic&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;debug&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;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;updatePicUploaded triggered with&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;oldPic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;newPic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="nx"&gt;oldPic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uploading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
        &lt;span class="nx"&gt;oldPic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;newPic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;downloadURL&lt;/span&gt;
        &lt;span class="nx"&gt;oldPic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storageId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;newPic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storageId&lt;/span&gt;
        &lt;span class="nx"&gt;oldPic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;newPic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;
        &lt;span class="nx"&gt;oldPic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;newPic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;

        &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pics&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pics&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;updatePicFailed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pic&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;debug&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;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;updatePicFailed triggered with&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="nx"&gt;pic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;failed&lt;/span&gt; &lt;span class="o"&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="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;A few things going on here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We're importing our worker using the prefix &lt;code&gt;workerize-loader!&lt;/code&gt; which tells webpack to use the loader we configured earlier.&lt;/li&gt;
&lt;li&gt;We moved the &lt;code&gt;pics&lt;/code&gt; collection to the &lt;code&gt;state&lt;/code&gt; object from &lt;code&gt;Index.vue&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;We exposed the method &lt;code&gt;initWorker&lt;/code&gt; which initializes the worker instance.&lt;/li&gt;
&lt;li&gt;We added some CRUD methods for persisting the &lt;code&gt;pics&lt;/code&gt; collection in &lt;code&gt;localStorage&lt;/code&gt;: &lt;code&gt;addPic&lt;/code&gt;, &lt;code&gt;loadPictures&lt;/code&gt; and &lt;code&gt;delete&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;updatePicUploaded&lt;/code&gt; and &lt;code&gt;updatePicFailed&lt;/code&gt; changes the &lt;code&gt;loading&lt;/code&gt; property of the picture. We'll use this to show the spinner.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Generating thumbnails
&lt;/h2&gt;

&lt;p&gt;We're going to generate a thumbnail (on the client) to show while the image is uploading. We'll use the &lt;a href="https://www.npmjs.com/package/blueimp-load-image"&gt;blueimp&lt;/a&gt; library for this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn add blueimp-load-image
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Let's add another service for manipulating images: &lt;code&gt;src/services/image-ops.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;loadImage&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;blueimp-load-image&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;base64JpegPrefix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data:image/jpeg;base64,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;removeBase64Prefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;base64Str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;base64Str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;substr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;base64Str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;indexOf&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="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;addBase64Prefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;imageData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;base64JpegPrefix&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;imageData&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;generateThumbnail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;imageData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;maxWidth&lt;/span&gt;&lt;span class="p"&gt;)&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;new&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;resolve&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;let&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;base64JpegPrefix&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;imageData&lt;/span&gt;
        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;res&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&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;blob&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="nx"&gt;loadImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canvas&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;let&lt;/span&gt; &lt;span class="nx"&gt;dataURL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toDataURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;image/jpeg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;removeBase64Prefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dataURL&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="na"&gt;maxWidth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;maxWidth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;canvas&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="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;removeBase64Prefix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;generateThumbnail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;addBase64Prefix&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;Notice we've moved the &lt;code&gt;removeBase64Prefix&lt;/code&gt; and &lt;code&gt;addBase64Prefix&lt;/code&gt; methods here.&lt;br&gt;
The &lt;code&gt;generateThumbnail&lt;/code&gt; function takes imageData - a base64 string, uses the &lt;code&gt;fetch&lt;/code&gt; API, converts it to a &lt;code&gt;blob&lt;/code&gt;, and then uses blueimp's &lt;code&gt;loadImage&lt;/code&gt; to change it's size to &lt;code&gt;maxWidth&lt;/code&gt;.&lt;br&gt;
We're loading &lt;code&gt;maxWidth&lt;/code&gt; from a new config file we've added to make things tidy - &lt;code&gt;src/services/config.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;photos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;maxWidth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;thumbnailMaxWidth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&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;h2&gt;
  
  
  Adding the image-uploader service
&lt;/h2&gt;

&lt;p&gt;We'll extract all the image uploading flow to a service to keep our UI component clean. Add the file &lt;code&gt;src/services/image-uploader.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./store&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;cordovaCamera&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./cordova-camera&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;imageOps&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./image-ops&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./config&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="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;uploadImageFromCamera&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;base64&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;cordovaCamera&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getBase64FromCamera&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;imageData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;imageOps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;removeBase64Prefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;base64&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;thumbnailImageData&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;imageOps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;generateThumbnail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;imageData&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;photos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;thumbnailMaxWidth&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;localPic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;imageOps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addBase64Prefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;thumbnailImageData&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;uploading&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="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addPic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;localPic&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;uploadedPic&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;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;workerInstance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uploadPicture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;imageData&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;updatePicUploaded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;localPic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;uploadedPic&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;uploadImageFromCamera&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;What we're doing in the &lt;code&gt;uploadImageFromCamera&lt;/code&gt; method is: getting base64 from the cordova camera -&amp;gt; generating thumbnails using our &lt;code&gt;imageOps&lt;/code&gt; -&amp;gt; generating a new &lt;code&gt;pic&lt;/code&gt; object in our &lt;code&gt;state&lt;/code&gt; with &lt;code&gt;uploading=true&lt;/code&gt; -&amp;gt; uploading the picture to Firebase using the web worker -&amp;gt; updating the pic's &lt;code&gt;uploading&lt;/code&gt; state property when uploading is finished.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting it together
&lt;/h2&gt;

&lt;p&gt;Now we've added all these services, we need to call them from our components.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;App.vue&lt;/code&gt;, we'll use the &lt;code&gt;mounted&lt;/code&gt; lifecycle hook to initialize the worker and load the saved picture urls from the saved state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;q-app&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;view&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/template&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./services/store.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;App&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="nx"&gt;mounted&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;initWorker&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;loadPictures&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/script&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/style&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Finally, we'll add the interface for viewing all this in &lt;code&gt;Index.vue&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;q-page&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"q-pa-md"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"row justify-center q-ma-md"&lt;/span&gt; &lt;span class="na"&gt;v-for=&lt;/span&gt;&lt;span class="s"&gt;"(pic, idx) in pics"&lt;/span&gt; &lt;span class="na"&gt;:key=&lt;/span&gt;&lt;span class="s"&gt;"idx"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"col"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;q-card&lt;/span&gt; &lt;span class="na"&gt;v-if=&lt;/span&gt;&lt;span class="s"&gt;"pic"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;q-img&lt;/span&gt; &lt;span class="na"&gt;spinner-color=&lt;/span&gt;&lt;span class="s"&gt;"white"&lt;/span&gt; &lt;span class="na"&gt;:src=&lt;/span&gt;&lt;span class="s"&gt;"pic.url"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"spinner-container"&lt;/span&gt; &lt;span class="na"&gt;v-if=&lt;/span&gt;&lt;span class="s"&gt;"pic.uploading &amp;amp;&amp;amp; !pic.failed"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;q-spinner&lt;/span&gt; &lt;span class="na"&gt;color=&lt;/span&gt;&lt;span class="s"&gt;"white"&lt;/span&gt; &lt;span class="na"&gt;size=&lt;/span&gt;&lt;span class="s"&gt;"4em"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
              &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
              &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"spinner-container"&lt;/span&gt; &lt;span class="na"&gt;v-if=&lt;/span&gt;&lt;span class="s"&gt;"pic.failed"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;q-icon&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"cloud_off"&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"font-size: 48px;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/q-icon&amp;gt;&lt;/span&gt;
              &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/q-img&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;q-card-actions&lt;/span&gt; &lt;span class="na"&gt;align=&lt;/span&gt;&lt;span class="s"&gt;"around"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="nt"&gt;&amp;lt;q-btn&lt;/span&gt; &lt;span class="na"&gt;flat&lt;/span&gt; &lt;span class="na"&gt;round&lt;/span&gt; &lt;span class="na"&gt;color=&lt;/span&gt;&lt;span class="s"&gt;"red"&lt;/span&gt; &lt;span class="na"&gt;icon=&lt;/span&gt;&lt;span class="s"&gt;"favorite"&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;click=&lt;/span&gt;&lt;span class="s"&gt;"notifyNotImplemented()"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
              &lt;span class="nt"&gt;&amp;lt;q-btn&lt;/span&gt; &lt;span class="na"&gt;flat&lt;/span&gt; &lt;span class="na"&gt;round&lt;/span&gt; &lt;span class="na"&gt;color=&lt;/span&gt;&lt;span class="s"&gt;"teal"&lt;/span&gt; &lt;span class="na"&gt;icon=&lt;/span&gt;&lt;span class="s"&gt;"bookmark"&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;click=&lt;/span&gt;&lt;span class="s"&gt;"notifyNotImplemented()"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
              &lt;span class="nt"&gt;&amp;lt;q-btn&lt;/span&gt;
                &lt;span class="na"&gt;flat&lt;/span&gt;
                &lt;span class="na"&gt;round&lt;/span&gt;
                &lt;span class="na"&gt;color=&lt;/span&gt;&lt;span class="s"&gt;"primary"&lt;/span&gt;
                &lt;span class="na"&gt;icon=&lt;/span&gt;&lt;span class="s"&gt;"delete"&lt;/span&gt;
                &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;click=&lt;/span&gt;&lt;span class="s"&gt;"deletePic(idx)"&lt;/span&gt;
                &lt;span class="na"&gt;:disable=&lt;/span&gt;&lt;span class="s"&gt;"pic.uploading"&lt;/span&gt;
              &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/q-card-actions&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;/q-card&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/q-page&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;style &lt;/span&gt;&lt;span class="na"&gt;scoped&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nc"&gt;.spinner-container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;align-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;justify-content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../services/store&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;EventBus&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../services/event-bus&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;imageUploader&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../services/image-uploader&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PageIndex&lt;/span&gt;&lt;span class="dl"&gt;"&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="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nx"&gt;mounted&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;EventBus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$off&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;takePicture&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;EventBus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;takePicture&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uploadImageFromCamera&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;computed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;pics&lt;/span&gt;&lt;span class="p"&gt;()&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pics&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="na"&gt;methods&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;notifyNotImplemented&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Not implemented yet :/&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="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;deletePic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deletePic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Picture deleted.&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="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="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="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Delete failed. Check log.&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="p"&gt;},&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;uploadImageFromCamera&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;imageUploader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uploadImageFromCamera&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="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="s2"&gt;Uploading failed&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;dir&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="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;updatePicFailed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;localPic&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Uploading failed. Check log.&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="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;What's going on here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We've added a &lt;code&gt;q-spinner&lt;/code&gt; to show when the &lt;code&gt;uploading&lt;/code&gt; property of the picture is true.&lt;/li&gt;
&lt;li&gt;We're calling &lt;code&gt;imageUploader.uploadImageFromCamera&lt;/code&gt; when clicking on the photo button, which handles our uploading.&lt;/li&gt;
&lt;li&gt;We've added some actions to the &lt;code&gt;q-card&lt;/code&gt; of the picture (only delete is implemented for now)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final app
&lt;/h2&gt;

&lt;p&gt;That's it! We have an image uploading app that shows a blurred thumbnails with a spinner, a little like WhatsApp's image upload.&lt;br&gt;
Running all this using &lt;code&gt;quasar dev -m android/ios&lt;/code&gt; will show the final result:&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Further improvements
&lt;/h2&gt;

&lt;p&gt;Some ideas for improving this app further would be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Offload thumbnail creation to another web worker&lt;/li&gt;
&lt;li&gt;Handle long lists of images with a virtual list component like &lt;a href="https://github.com/tangbc/vue-virtual-scroll-list"&gt;this one&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Add a carousel / gallery component for viewing images full-size&lt;/li&gt;
&lt;li&gt;Add a retry mechanism for failed uploads&lt;/li&gt;
&lt;li&gt;Use &lt;a href="https://firebase.google.com/docs/auth"&gt;firebase auth&lt;/a&gt; - so that users can only delete their own photos&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Source code
&lt;/h2&gt;

&lt;p&gt;The full code is on GutHub here: &lt;a href="https://github.com/syonip/vue-firebase-image-upload"&gt;vue-firebase-image-upload&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Enjoy :)&lt;/p&gt;

</description>
      <category>vue</category>
      <category>cordova</category>
      <category>firebase</category>
      <category>images</category>
    </item>
    <item>
      <title>How to make an image uploading app with Vue, Quasar, Firebase Storage and Cordova - Part 1</title>
      <dc:creator>Jonathan P</dc:creator>
      <pubDate>Thu, 10 Oct 2019 00:00:00 +0000</pubDate>
      <link>https://forem.com/johnnymakestuff/how-to-make-an-image-uploading-app-with-vue-quasar-firebase-storage-and-cordova-part-1-2cf0</link>
      <guid>https://forem.com/johnnymakestuff/how-to-make-an-image-uploading-app-with-vue-quasar-firebase-storage-and-cordova-part-1-2cf0</guid>
      <description>&lt;h2&gt;
  
  
  What we're building
&lt;/h2&gt;

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

&lt;p&gt;We'll build a cross-platform mobile app for taking photos and uploading to firebase. &lt;/p&gt;

&lt;p&gt;In Part 1, we'll take a picture and save it to Firebase Cloud Storage, and then show it in our app.&lt;br&gt;
In Part 2, we'll offload the uploading work, and use the blueimp library to generate a thumbnail locally and show it while uploading.&lt;/p&gt;
&lt;h2&gt;
  
  
  The stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Vue JS - Component framework&lt;/li&gt;
&lt;li&gt;Cordova - Cross platform mobile framework&lt;/li&gt;
&lt;li&gt;Quasar - UI framework (and CLI)&lt;/li&gt;
&lt;li&gt;Firebase Cloud Storage - For storing the photos&lt;/li&gt;
&lt;li&gt;Web Workers - For offloading the uploading to a separate thread&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Scaffolding
&lt;/h2&gt;

&lt;p&gt;We'll use &lt;a href="https://quasar.dev/quasar-cli/developing-cordova-apps/preparation"&gt;Quasar CLI&lt;/a&gt; to initialize a new project, and run the cordova mode (android or ios) to see the app running on your connected device.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;quasar create vue-firebase-image-upload
&lt;span class="nb"&gt;cd &lt;/span&gt;vue-firebase-image-upload
quasar dev &lt;span class="nt"&gt;-m&lt;/span&gt; android
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;You'll have to add &lt;code&gt;https: true&lt;/code&gt; in the &lt;code&gt;devServer&lt;/code&gt; section of &lt;code&gt;quasar.conf.js&lt;/code&gt; if running on android &amp;gt; 9&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You should get a basic working version that looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--44KKwFfl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.learningsomethingnew.com/vue-fb-image-upload/quasar-dev.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--44KKwFfl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.learningsomethingnew.com/vue-fb-image-upload/quasar-dev.png" alt="quasar-dev"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is a good time to make your first commit.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For more details on how to install and setup quasar, see &lt;a href="https://www.learningsomethingnew.com/how-to-build-a-sound-cloud-like-audio-player-app-with-vue-js-quasar-and-wave-surfer#scaffolding"&gt;this post&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Taking a picture and getting base64
&lt;/h2&gt;

&lt;p&gt;The way to store images in Firebase Cloud Storage is by saving the base-64 string of the image, using Firebase's &lt;code&gt;putString&lt;/code&gt; method.&lt;br&gt;
Notice that you have to remove the base64 prefix from the string before uploading, or Firebase will reject the string.&lt;br&gt;
To begin, we'll add a button that takes a picture using Cordova's camera plugin, and print the base64 string.&lt;/p&gt;
&lt;h3&gt;
  
  
  Adding plugins
&lt;/h3&gt;

&lt;p&gt;First, add cordova camera plugin and file plugin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;src-cordova
cordova plugin add cordova-plugin-file
cordova plugin add cordova-plugin-camera
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Taking the picture
&lt;/h3&gt;

&lt;p&gt;In order to take a picture, we'll add some code in a new service file &lt;code&gt;src/services/cordova-camera.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;getCameraFileObject&lt;/span&gt;&lt;span class="p"&gt;()&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;new&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&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;let&lt;/span&gt; &lt;span class="nx"&gt;camera&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;camera&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;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;quality&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;destinationType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;camera&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DestinationType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FILE_URI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;encodingType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;camera&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;EncodingType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;JPG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;mediaType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;camera&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MediaType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PICTURE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;saveToPhotoAlbum&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;correctOrientation&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="nx"&gt;camera&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getPicture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;imageURI&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resolveLocalFileSystemURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;imageURI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fileEntry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;fileEntry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fileObject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fileObject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="kd"&gt;function&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="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="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
              &lt;span class="nx"&gt;reject&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="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="kd"&gt;function&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;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="nx"&gt;options&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;getBase64FromFileObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fileObject&lt;/span&gt;&lt;span class="p"&gt;)&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;new&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&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;var&lt;/span&gt; &lt;span class="nx"&gt;reader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;FileReader&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onloadend&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;evt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;evt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;evt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readAsDataURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fileObject&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;getBase64FromCamera&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;fileObject&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;getCameraFileObject&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;base64&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;getBase64FromFileObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fileObject&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;getBase64FromCamera&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This service is doing several steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Takes a picture using Cordova's camera plugin. This returns an image URI.&lt;/li&gt;
&lt;li&gt;Gets a file &lt;em&gt;entry&lt;/em&gt; using Cordova's file plugin, with &lt;code&gt;resolveLocalFileSystemURL&lt;/code&gt; function.&lt;/li&gt;
&lt;li&gt;Gets a File &lt;em&gt;object&lt;/em&gt; using the &lt;code&gt;file&lt;/code&gt; method.&lt;/li&gt;
&lt;li&gt;Uses &lt;code&gt;FileReader&lt;/code&gt; to get the base64 representation of the file.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now let's add a simple event bus. Add the file &lt;code&gt;src/services/event-bus.js&lt;/code&gt; with content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Vue&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;EventBus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Vue&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now in &lt;code&gt;src/layouts/MyLayout.vue&lt;/code&gt;, we'll add a button in the toolbar for taking a picture, and use our event bus to send the handling to &lt;code&gt;Index.vue&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  ...

  &lt;span class="nt"&gt;&amp;lt;q-btn&lt;/span&gt; &lt;span class="na"&gt;flat&lt;/span&gt; &lt;span class="na"&gt;dense&lt;/span&gt; &lt;span class="na"&gt;round&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;click=&lt;/span&gt;&lt;span class="s"&gt;"takePicture"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;q-icon&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"camera"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/q-btn&amp;gt;&lt;/span&gt;

  ...
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;EventBus&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../services/event-bus.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;

    &lt;span class="na"&gt;methods&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;takePicture&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;EventBus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;takePicture&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="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Finally we'll catch the &lt;code&gt;takePicture&lt;/code&gt; event in our main component: &lt;code&gt;Index.vue&lt;/code&gt;, call the &lt;code&gt;cordova-camera.js&lt;/code&gt; service function and print the base64 result (which we'll later upload to firebase):&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;Index.vue&lt;/code&gt; file in the &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; section:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;EventBus&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../services/event-bus&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;cordovaCamera&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../services/cordova-camera&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PageIndex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;mounted&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;EventBus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$off&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;takePicture&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;EventBus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;takePicture&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uploadImageFromCamera&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;methods&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;uploadImageFromCamera&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;base64&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;cordovaCamera&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getBase64FromCamera&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;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;base64&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;Running with &lt;code&gt;quasar dev -m android&lt;/code&gt;, and looking at the chrome dev tools, we can see the base64 output as a long string printed in the console:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---OP5sNtW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.learningsomethingnew.com/vue-fb-image-upload/base64.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---OP5sNtW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.learningsomethingnew.com/vue-fb-image-upload/base64.png" alt="base64"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Adding firebase
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Web SDK or native SDK?
&lt;/h3&gt;

&lt;p&gt;When using Firebase in a Cordova app, you can choose between using the Web SDK and a cordova plugin the wraps the Native SDKs. The state of the current Firebase cordova plugins seems a little unstable to me, not fully supporting Firebase Cloud Storage yet. And with the ability to offload work to a web worker, we can use the full-featured official Web SDK, and not take the performance hit.&lt;br&gt;
Add the firebase sdk using yarn:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn add firebase
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Firebase setup
&lt;/h3&gt;

&lt;p&gt;Follow the instructions in the Firebase Cloud Storage &lt;a href="https://firebase.google.com/docs/storage/web/start"&gt;setup page&lt;/a&gt;.&lt;br&gt;
After the setup you should have a firebase project with Storage enabled. We'll save the settings in a separate file: &lt;code&gt;firebase-config.js&lt;/code&gt; (this file won't be committed to our repo). The contents should look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;firebase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;your-api-key&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;authDomain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;your-auth-domain&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;databaseURL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;your-database-url&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;storageBucket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;your-storage-bucket-url&amp;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;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Uploading the image
&lt;/h2&gt;

&lt;p&gt;Now that we have Firebase settings in place, we'll add another service for handling Firebase app initialization and uploading.&lt;br&gt;
Add the file &lt;code&gt;src/services/cloud-storage.js&lt;/code&gt; with content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;firebase&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;firebase/app&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;firebase/storage&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;firebaseConfig&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./firebase-config&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="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;uploadBase64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;imageData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;storageId&lt;/span&gt;&lt;span class="p"&gt;)&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;new&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&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;let&lt;/span&gt; &lt;span class="nx"&gt;uploadTask&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;firebase&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;ref&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;child&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;storageId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;putString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;imageData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;uploadTask&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;state_changed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="kd"&gt;function&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="p"&gt;{},&lt;/span&gt;
            &lt;span class="kd"&gt;function&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="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;reject&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="p"&gt;},&lt;/span&gt;
            &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;uploadTask&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;ref&lt;/span&gt;
                    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getDownloadURL&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;downloadURL&lt;/span&gt;&lt;span class="p"&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;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Uploaded a blob or file!&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;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;got downloadURL: &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;downloadURL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                        &lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;downloadURL&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;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;()&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="nx"&gt;initializeApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;firebaseConfig&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="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;uploadBase64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;initialize&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;uploadBase64&lt;/code&gt; function uploads &lt;code&gt;imageData&lt;/code&gt; (the base64 string) using the &lt;code&gt;putString&lt;/code&gt; method, under the Firebase child of &lt;code&gt;storageId&lt;/code&gt;. You can have any ID you want, here I've chosen to use the unix datetime number as the child ID. After the uploading is done, we return the image URL using &lt;code&gt;getDownloadURL&lt;/code&gt; function of the uploading task.&lt;/p&gt;

&lt;p&gt;In our main &lt;code&gt;App.vue&lt;/code&gt; file we'll call the initialize function when the app is mounted:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;cloudStorage&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./services/cloud-storage&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;App&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;mounted&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;cloudStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;initialize&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;Finally we'll call the uploading function from &lt;code&gt;Index.vue&lt;/code&gt;. This is now the content of &lt;code&gt;Index.vue&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;q-page&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"row justify-center q-ma-md"&lt;/span&gt; &lt;span class="na"&gt;v-for=&lt;/span&gt;&lt;span class="s"&gt;"(pic, idx) in pics"&lt;/span&gt; &lt;span class="na"&gt;:key=&lt;/span&gt;&lt;span class="s"&gt;"idx"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"col"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;q-card&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;q-img&lt;/span&gt; &lt;span class="na"&gt;spinner-color=&lt;/span&gt;&lt;span class="s"&gt;"white"&lt;/span&gt; &lt;span class="na"&gt;:src=&lt;/span&gt;&lt;span class="s"&gt;"pic"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/q-card&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/q-page&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;EventBus&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../services/event-bus&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;cordovaCamera&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../services/cordova-camera&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;cloudStorage&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../services/cloud-storage&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PageIndex&lt;/span&gt;&lt;span class="dl"&gt;"&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="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;pics&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;span class="nx"&gt;mounted&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;EventBus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$off&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;takePicture&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;EventBus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;takePicture&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uploadImageFromCamera&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;methods&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;removeBase64Prefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;base64Str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;base64Str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;substr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;base64Str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;indexOf&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="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;uploadImageFromCamera&lt;/span&gt;&lt;span class="p"&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;base64&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;cordovaCamera&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getBase64FromCamera&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;imageData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;removeBase64Prefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;base64&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;storageId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;getTime&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;toString&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;uploadedPic&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;cloudStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uploadBase64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;imageData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;storageId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;uploadedPic&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;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We've added the &lt;code&gt;pics&lt;/code&gt; array to our component's &lt;code&gt;data&lt;/code&gt;, containing URLs of all the pictures we uploaded. We've added a &lt;code&gt;v-for&lt;/code&gt; that will show each picture in a quasar &lt;code&gt;q-img&lt;/code&gt; component inside a &lt;code&gt;q-card&lt;/code&gt; component.&lt;br&gt;
We've also changes &lt;code&gt;uploadImageFromCamera&lt;/code&gt; to take a picture, get the &lt;code&gt;base64&lt;/code&gt; string (stripping the prefix so Firebase won't freak out), calculated the &lt;code&gt;storageId&lt;/code&gt; using the current datetime, and called the uploading function. After it's uploaded, we add the resulting URL to the &lt;code&gt;pics&lt;/code&gt; array.&lt;/p&gt;

&lt;p&gt;And we can now see the uploaded image: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dx7hZxF6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.learningsomethingnew.com/vue-fb-image-upload/image-upload.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dx7hZxF6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.learningsomethingnew.com/vue-fb-image-upload/image-upload.png" alt="uploaded-image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The full code is on &lt;a href="https://github.com/syonip/vue-firebase-image-upload"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In the next part of the series we'll move tasks to a web worker, add a loading spinner, and save the URLs in local storage. Stay tuned!&lt;/p&gt;

</description>
      <category>vue</category>
      <category>cordova</category>
      <category>firebase</category>
      <category>images</category>
    </item>
    <item>
      <title>How do you create charts / diagrams for your blog posts?</title>
      <dc:creator>Jonathan P</dc:creator>
      <pubDate>Mon, 09 Sep 2019 17:40:09 +0000</pubDate>
      <link>https://forem.com/johnnymakestuff/how-do-you-create-charts-diagrams-for-your-blog-posts-3o6d</link>
      <guid>https://forem.com/johnnymakestuff/how-do-you-create-charts-diagrams-for-your-blog-posts-3o6d</guid>
      <description>&lt;p&gt;Anyone know of a good library / site for creating sleek looking diagrams for technical blog posts?&lt;/p&gt;

&lt;p&gt;I was impressed by the charts on this &lt;a href="https://davidea.st/articles/firebase-bundle-size"&gt;blog post&lt;/a&gt; but wasn't able to find out what he's using (he didn't answer me on twitter :) )&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>blogging</category>
    </item>
    <item>
      <title>How to make your Svelte 3 Cordova app work on old phones using Babel 7 and Rollup</title>
      <dc:creator>Jonathan P</dc:creator>
      <pubDate>Thu, 22 Aug 2019 00:00:00 +0000</pubDate>
      <link>https://forem.com/johnnymakestuff/how-to-make-your-svelte-3-cordova-app-work-on-old-phones-using-babel-7-and-rollup-2h38</link>
      <guid>https://forem.com/johnnymakestuff/how-to-make-your-svelte-3-cordova-app-work-on-old-phones-using-babel-7-and-rollup-2h38</guid>
      <description>&lt;p&gt;When working with a relatively new framework like svelte you come across issues that more "battle tested" frameworks have solved already.&lt;br&gt;
One such issue is transpiling your code to work on older browsers. This is meaningful especially for a cordova app, because it runs in a system WebView, which version depends on various factors, and is not always updated.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I wrote more about WebView versions in the post: &lt;a href="https://www.learningsomethingnew.com/should-you-transpile-your-cordova-app"&gt;should you transpile your cordova app&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  What should be transpiled?
&lt;/h2&gt;

&lt;p&gt;We're going to use babel of course, in order to transpile.&lt;/p&gt;

&lt;p&gt;From the babel &lt;a href="https://babeljs.io/docs/en/"&gt;docs&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Babel is a toolchain that is mainly used to convert ECMAScript 2015+ code into a backwards compatible version of JavaScript in current and older browsers or environments.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But which code needs to be transpiled? We need to consider two levels of transpilation:&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Our app's code
&lt;/h3&gt;

&lt;p&gt;If you're using ES6 and above in your app (and you should), you need to transpile that. This includes you &lt;code&gt;.svelte&lt;/code&gt; files and &lt;code&gt;.js&lt;/code&gt; files.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Dependencies
&lt;/h3&gt;

&lt;p&gt;Your dependencies are also code that's bundled along with your app. If they were published with ES6 syntax, then they'll cause your &lt;code&gt;bundle.js&lt;/code&gt; to contain untranspiled code.&lt;br&gt;
By the way, here is &lt;a href="https://github.com/rollup/rollup-plugin-babel#external-dependencies"&gt;babel's take on external dependencies&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Ideally, you should only be transforming your source code, rather than running all of your external dependencies through Babel – hence the exclude: 'node_modules/**'&lt;br&gt;
We encourage library authors not to distribute code that uses untranspiled ES6 features (other than modules) for this reason. Consumers of your library should not have to transpile your ES6 code, any more than they should have to transpile your CoffeeScript, ClojureScript or TypeScript.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Unfortunately, not all libraries abide by this rule. &lt;br&gt;
One such library is &lt;a href="https://github.com/axios/axios/blob/master/UPGRADE_GUIDE.md#es6-promise-polyfill"&gt;axios&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Up until the 0.6.0 release ES6 Promise was being polyfilled using es6-promise. With this release, the polyfill has been removed, and you will need to supply it yourself if your environment needs it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Another is svelte. According to this &lt;a href="https://github.com/sveltejs/svelte/issues/558"&gt;svelte GitHub issue&lt;/a&gt;, it's still a WIP to list exactly what needs transpiling / polyfills.&lt;/p&gt;
&lt;h2&gt;
  
  
  The solution
&lt;/h2&gt;

&lt;p&gt;The following configuration has worked for me, successfully transpiling my cordova app.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Install dependencies
&lt;/h3&gt;

&lt;p&gt;First let's install the required babel packages, and the rollup plugin.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i &lt;span class="nt"&gt;-D&lt;/span&gt; @babel/core @babel/preset-env rollup-plugin-babel core-js
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Configure babel
&lt;/h3&gt;

&lt;p&gt;Now add the following configuration to &lt;code&gt;babel.config.js&lt;/code&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;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cache&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;presets&lt;/span&gt; &lt;span class="o"&gt;=&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;@babel/preset-env&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;// "debug": true,&lt;/span&gt;
            &lt;span class="na"&gt;useBuiltIns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;usage&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;corejs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// or 2,&lt;/span&gt;
            &lt;span class="na"&gt;targets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;browsers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;last 2 versions&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="p"&gt;}]&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;presets&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;Notice the &lt;code&gt;browsers: "last 2 versions"&lt;/code&gt; line. It's a feature of &lt;code&gt;@babel/preset-env&lt;/code&gt; that makes it transpile to last 2 major versions of all browsers.&lt;br&gt;
Also, the &lt;code&gt;debug&lt;/code&gt; field can come in handy if you want to see exactly what files are being transpiled.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. Configure rollup
&lt;/h3&gt;

&lt;p&gt;Add the following babel section &lt;em&gt;at the end&lt;/em&gt; of the plugins section of &lt;code&gt;rollup.config.js&lt;/code&gt;, assuming you're using the same directory structure as in the &lt;a href="https://github.com/syonip/svelte-cordova-template"&gt;svelte cordova template&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;svelte&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rollup-plugin-svelte&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;resolve&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rollup-plugin-node-resolve&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;commonjs&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rollup-plugin-commonjs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;livereload&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rollup-plugin-livereload&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;replace&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rollup-plugin-replace&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;babel&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rollup-plugin-babel&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="na"&gt;plugins&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="nx"&gt;production&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;babel&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;exclude&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="c1"&gt;// 'node_modules/**',&lt;/span&gt;
                &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sr"&gt;core-js&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sr"&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;extensions&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="s1"&gt;.svelte&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;.js&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;.jsx&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;.es6&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;.es&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;.mjs&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;.ts&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="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;Several things to notice here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The line &lt;code&gt;production &amp;amp;&amp;amp; babel&lt;/code&gt; is a js trick to make the babel plugin not execute in dev mode. You can turn this on or off as you please, if you're testing how the transpiled code works, it might be handy to let it run in dev mode too.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;exclude&lt;/code&gt; section does &lt;strong&gt;not&lt;/strong&gt; contain &lt;code&gt;node_modules&lt;/code&gt;. We're transpiling our dependencies, because some of them are untranspiled. If this is not the case for you, you can ignore them, but you still need to transpile the svelte module itself.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;extensions&lt;/code&gt; section must contain the &lt;code&gt;.svelte&lt;/code&gt; extension, or else your own app's code won't go through babel.&lt;/li&gt;
&lt;li&gt;We &lt;strong&gt;are&lt;/strong&gt; excluding &lt;code&gt;core-js&lt;/code&gt; from transpilation, because of a &lt;a href="https://github.com/rollup/rollup-plugin-babel/issues/254"&gt;circular reference issue with rollup-plugin-babel&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Credits
&lt;/h2&gt;

&lt;p&gt;I've used this &lt;a href="http://simey.me/svelte3-rollup-and-babel7/"&gt;post by simey.me&lt;/a&gt; as a basis for the configuration described here.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>cordova</category>
      <category>svelte</category>
      <category>babel</category>
    </item>
    <item>
      <title>How (and why) to change your Android Cordova app's minSdkVersion</title>
      <dc:creator>Jonathan P</dc:creator>
      <pubDate>Mon, 19 Aug 2019 00:00:00 +0000</pubDate>
      <link>https://forem.com/johnnymakestuff/how-and-why-to-change-your-android-cordova-app-s-minsdkversion-f1p</link>
      <guid>https://forem.com/johnnymakestuff/how-and-why-to-change-your-android-cordova-app-s-minsdkversion-f1p</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;For example, for setting minSdkVersion to 24, in &lt;code&gt;config.xml&lt;/code&gt; add in the Android platform section:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;platform&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"android"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;preference&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"android-minSdkVersion"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"24"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    ...
&lt;span class="nt"&gt;&amp;lt;/platform&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Make sure to &lt;strong&gt;remove and re-add the platform&lt;/strong&gt; or it won't work!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;cordova platform &lt;span class="nb"&gt;rm &lt;/span&gt;android
cordova platform add android
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;For considerations, and a way to test that it works, read on.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is minSdkVersion
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;minSdkVersion&lt;/code&gt; is a property of the &lt;code&gt;&amp;lt;uses-sdk&amp;gt;&lt;/code&gt; tag in the &lt;code&gt;AndroidManifest.xml&lt;/code&gt; file. From the &lt;a href="https://developer.android.com/guide/topics/manifest/uses-sdk-element"&gt;docs&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Google Play uses the  attributes declared in your app manifest to filter your app from devices that do not meet it's platform version requirements.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So if you want your app to be available for download in the Play Store only for devices with Android Nougat (7.0) and above, you'd want to set &lt;code&gt;&amp;lt;uses-sdk android:minSdkVersion="24"&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why should I change the minSdkVersion
&lt;/h2&gt;

&lt;p&gt;If you're building a production cordova app, most chances are you're using the latest JS / HTML / CSS in order to make your app smooth and pretty. &lt;br&gt;
Thing is, many Android devices are still using an old OS version. Old OS versions == old WebView which won't support many of the new web features. &lt;/p&gt;

&lt;p&gt;Some active users of my apps are still using Android 4.0 - ICS. The latest release of ICS was June 2012, 7 years ago! It's no longer supported by Google, and is almost guaranteed to have your new cordova app perform very badly (if it works at all). Bad performance / crashes == bad reviews. You don't want that. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--R1xiHhZg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.learningsomethingnew.com/cordova-babel/bad-review2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--R1xiHhZg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.learningsomethingnew.com/cordova-babel/bad-review2.png" alt="bad-review2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The default minSdkVersion in cordova apps is currently API level 19 - Android 4.4 KitKat. Your cordova app might &lt;em&gt;work&lt;/em&gt; on Android 4.4, but it probably won't work &lt;em&gt;well&lt;/em&gt;. So you have two options:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Build your app so that it works on Android 4.4, which has a WebView with Chrome version 30 (ancient!)&lt;br&gt;
This will include a lot of transpiling, debugging, and completely giving up on things like css &lt;code&gt;grid&lt;/code&gt;, which just isn't supported on Chrome 30, and never will be. You can use &lt;a href="https://modernizr.com"&gt;modernizr&lt;/a&gt; to detect whether features are available, and provide a different version of the app according to features, but that makes for quite a lot of extra work.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Decide on a higher minimum version, and block all users with a lower version from downloading your app. In my new app I've decided to go for API Level 24 - Android 7 Nougat. &lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;I've written more about the current market share of Android / iOS versions and the corresponding WebView browser versions in &lt;a href="https://www.learningsomethingnew.com/should-you-transpile-your-cordova-app"&gt;this post&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;
  
  
  How to set minSdkVersion
&lt;/h1&gt;

&lt;p&gt;You don't want to do anything directly in the Android project, as it will be overridden every time you build your cordova app! So setting &lt;code&gt;android:minSdkVersion&lt;/code&gt; in &lt;code&gt;AndroidManifest.xml&lt;/code&gt; is out of the question!&lt;/p&gt;

&lt;p&gt;One of the main advantages of using cordova over React react-native is that you never have to edit the native project manually. You have only the cordova project, and the native versions are generated according to configuration. This makes your project update-able to new cordova versions, and doesn't require native adjustments for each new plugin / update you install.&lt;/p&gt;

&lt;p&gt;According to the cordova &lt;a href="https://cordova.apache.org/docs/en/latest/config_ref/"&gt;docs&lt;/a&gt;, you can set this value&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;platform&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"android"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;preference&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"android-minSdkVersion"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"24"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    ...
&lt;span class="nt"&gt;&amp;lt;/platform&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Alas, if you add this to you config, generate an apk and upload to the store, you'll notice (maybe after a few negative reviews) that it didn't change the minSdkLevel!&lt;/p&gt;

&lt;p&gt;After wasting some time on this I found a hint in this GitHub &lt;a href="https://github.com/apache/cordova-android/issues/686"&gt;issue&lt;/a&gt;, saying you have to remove and re-add the platform for it to take effect. &lt;/p&gt;

&lt;p&gt;So after changing &lt;code&gt;config.xml&lt;/code&gt;, you must also remove and re-add the platform, for the changes to take effect in the generated Android project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;cordova platform &lt;span class="nb"&gt;rm &lt;/span&gt;android
cordova platform add android
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Checking it worked
&lt;/h2&gt;

&lt;p&gt;One way to see that it worked is in the Play Console, after uploading a new apk:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QXbW2KP9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.learningsomethingnew.com/cordova-change-minsdk/store.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QXbW2KP9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.learningsomethingnew.com/cordova-change-minsdk/store.png" alt="console"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Preferably, you'd want a way to test your generated apk locally without having to bump version and upload to the store for every test.&lt;/p&gt;

&lt;p&gt;It seems like there should be an easier way to do this, but it seems like the only option now is to de-compile the apk file. I use the open source apktool by Connor Tumbleson for this. You can get the latest version &lt;a href="https://bitbucket.org/iBotPeaches/apktool/downloads/"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;After downloading, build your cordova app using &lt;code&gt;cordova build android&lt;/code&gt; and run this in a shell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;java &lt;span class="nt"&gt;-jar&lt;/span&gt; &amp;lt;path-to-apktool.jar&amp;gt; d &amp;lt;path-to-cordova-project&amp;gt;/android/app/build/outputs/apk/debug/app-debug.apk
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;It will generate a folder named &lt;code&gt;app-debug&lt;/code&gt; &lt;em&gt;in the folder from which you ran the command&lt;/em&gt;. Go into that folder and open &lt;code&gt;apktool.yml&lt;/code&gt;. You should see something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sdkInfo:
  minSdkVersion: '24'
  targetSdkVersion: '28'
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;That way you can be sure your changes are in effect before uploading to the store.&lt;/p&gt;

</description>
      <category>cordova</category>
      <category>android</category>
    </item>
    <item>
      <title>Should you transpile your cordova app?</title>
      <dc:creator>Jonathan P</dc:creator>
      <pubDate>Thu, 15 Aug 2019 00:00:00 +0000</pubDate>
      <link>https://forem.com/johnnymakestuff/should-you-transpile-your-cordova-app-1d2c</link>
      <guid>https://forem.com/johnnymakestuff/should-you-transpile-your-cordova-app-1d2c</guid>
      <description>&lt;p&gt;The following is based on a true story.&lt;/p&gt;

&lt;h2&gt;
  
  
  Just worst
&lt;/h2&gt;

&lt;p&gt;You've finished your amazing cross-platform mobile app, you publish it on the stores, and then you get this review:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.learningsomethingnew.com%2Fcordova-babel%2Fbad-review1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.learningsomethingnew.com%2Fcordova-babel%2Fbad-review1.png" alt="bad-review1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hmm.. But it opens on my phone! So you take a closer look:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.learningsomethingnew.com%2Fcordova-babel%2Fbad-review2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.learningsomethingnew.com%2Fcordova-babel%2Fbad-review2.png" alt="bad-review2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Oh right, we never checked it on an old phone. Darn. Let's try it on an Android 4.4 emulator:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.learningsomethingnew.com%2Fcordova-babel%2Fbundle-js-error.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.learningsomethingnew.com%2Fcordova-babel%2Fbundle-js-error.png" alt="error"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So a quick search of some of the errors points to the problem. We used all the cool new ES6 syntax, but the WebView is too old to support ES6.&lt;/p&gt;

&lt;h2&gt;
  
  
  How likely is this to happen? What's the impact?
&lt;/h2&gt;

&lt;p&gt;So your app that uses ES2015+ syntax doesn't work on old devices. But how old? And how many?&lt;/p&gt;

&lt;p&gt;In order to assess the damage, we need some data. We're basically asking what's the adoption rate of new browsers &lt;strong&gt;within the system WebView components of Android and iOS&lt;/strong&gt;. But what browser does the WebView actually run? This differs by the OS and the OS version, so let's look at both major mobile operating systems.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Android
&lt;/h3&gt;

&lt;p&gt;The WebView cordova apps use in Android is the system WebView component. There have been two major changes in how the Android WebView works in recent years:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Since Android Lollipop, the WebView &lt;a href="https://developer.chrome.com/multidevice/webview/overview#will_the_new_webview_auto-update_" rel="noopener noreferrer"&gt;auto updates through the app store&lt;/a&gt;. So a device running Android 5.1 will support new ES features (as long as the user actually approves updates of the WebView app).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Starting from Android 7 (Nougat), &lt;a href="https://developer.android.com/about/versions/nougat/android-7.0#webview" rel="noopener noreferrer"&gt;Chrome and WebView are the same&lt;/a&gt;. That means the WebView cordova uses is the same version as the Chrome app, and there is no need to update a "System WebView" app. &lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;If you're bored, check out some of the reviews of the "System WebView" app on the play store:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.learningsomethingnew.com%2Fcordova-babel%2Fwebview-review.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.learningsomethingnew.com%2Fcordova-babel%2Fwebview-review.png" alt="webview-review"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For Android versions &lt;strong&gt;below L&lt;/strong&gt;, the WebView is part of the OS and won't update without updating the OS.&lt;br&gt;
Looking at the market share numbers of Android versions, we see that we don't really need to worry about Android versions below L, as they make about ~3% of all devices:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.learningsomethingnew.com%2Fcordova-babel%2Fandroid-versions.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.learningsomethingnew.com%2Fcordova-babel%2Fandroid-versions.png" alt="android-versions"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you still want to cater to this ~3%, or your app runs on specific proprietary devices that can't be upgraded, then you have two options:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Transpile all the way down to chrome version 30 (or lower if you need to support below Android 4.4)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use the deprecated crosswalk plugin which ships the WebView &lt;em&gt;within your apk&lt;/em&gt;. It's no longer maintained (because the changes mentioned above render it quite useless), and doesn't install at all if you use the new cordova CLI (version 9). If you really have to support old devices, I've managed to find one &lt;a href="https://github.com/waitaction/cordova-plugin-crosswalk-webview.git" rel="noopener noreferrer"&gt;fork&lt;/a&gt; of the project that actually works with new cordova versions, but it will probably stop working with the next version :)&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  2. iOS
&lt;/h3&gt;

&lt;p&gt;iOS 8 and Yosemite introduced the new WKWebView, which now deprecates the old UIWebView (read more about it in this very detailed &lt;a href="https://nshipster.com/wkwebview/" rel="noopener noreferrer"&gt;post on nshipster&lt;/a&gt;). The new WKWebView runs the same JS engine as Safari, so that's basically the same change that Android 7 did with Chrome and the system WebView.&lt;/p&gt;

&lt;p&gt;Notice though that in new cordova projects (version 9 currently), cordova still uses the old and deprecated UIWebView, so it's recommended to upgrade to WKWebView in new applications. More about this in this &lt;a href="https://cordova.apache.org/news/2018/08/01/future-cordova-ios-webview.html" rel="noopener noreferrer"&gt;announcement by cordova&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So if we're using WKWebView which was introduced in iOS 8, how many users are we losing? According to this, less that 2.3% (version 8 isn't even listed):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.learningsomethingnew.com%2Fcordova-babel%2Fios-versions.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.learningsomethingnew.com%2Fcordova-babel%2Fios-versions.png" alt="ios-versions"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, the problem is less severe with iOS than with Android. This is a perk of having the same company that makes the OS also make the hardware.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to transpile
&lt;/h3&gt;

&lt;p&gt;We can't talk about transpiling without talking about babel.&lt;br&gt;
From the babel &lt;a href="https://babeljs.io/docs/en/" rel="noopener noreferrer"&gt;docs&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Babel is a toolchain that is mainly used to convert ECMAScript 2015+ code into a backwards compatible version of JavaScript in current and older browsers or environments.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's a pretty cool project, which lets us use the newest language features before they get adopted into all browsers. But you'll notice that the babel configuration is non-trivial to say the least.&lt;/p&gt;

&lt;p&gt;Luckily, most frameworks come pre-configured with babel these days, so if you generate a Vue CLI 3 app for example, it will come with a configuration of webpack to use babel.&lt;/p&gt;

&lt;p&gt;If you're messing around with a newer / less known framework, try to find a sample project on github that has the babel configuration.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you're using Svelte, you can check out my post on how to configure babel with rollup for a svelte cordova app &lt;a href="https://www.learningsomethingnew.com/how-to-make-your-svelte-cordova-app-work-on-old-phones-using-babel" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  So, should I transpile my cordova app?
&lt;/h2&gt;

&lt;p&gt;Yes you should. It's the same argument for why you want your web app to be transpiled (down to the last two versions of major browsers is a good standard, but it depends on your app's customers).&lt;/p&gt;

&lt;p&gt;As a developer I always like to use the latest language features, and I can never be sure what my end customer's browser will support. I don't think it's a good idea to stay behind and just use the language features that are already widely supported. Language improvements make development faster and our code better. Luckily, you can use babel's &lt;a href="https://babeljs.io/docs/en/babel-preset-env#targetsbrowsers" rel="noopener noreferrer"&gt;preset-env&lt;/a&gt; and define your target browsers generically using a query to select browsers (ex: last 2 versions, &amp;gt; 5%, etc).&lt;/p&gt;

&lt;p&gt;If babel ensures our code runs on the last two versions of all major browsers, and we know the vast majority of Android and iOS WebViews are updated automatically as part of the OS, then we're pretty much 99% covered. &lt;/p&gt;

&lt;p&gt;Finally, here is a decision tree to help you decide:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.learningsomethingnew.com%2Fcordova-babel%2Fdecision-tree.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.learningsomethingnew.com%2Fcordova-babel%2Fdecision-tree.jpg" alt="decision-tree"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>cordova</category>
      <category>mobile</category>
      <category>babel</category>
    </item>
    <item>
      <title>What are good reasons to open source your project?</title>
      <dc:creator>Jonathan P</dc:creator>
      <pubDate>Mon, 29 Jul 2019 08:24:34 +0000</pubDate>
      <link>https://forem.com/johnnymakestuff/what-are-good-reasons-to-open-source-your-project-aji</link>
      <guid>https://forem.com/johnnymakestuff/what-are-good-reasons-to-open-source-your-project-aji</guid>
      <description>&lt;p&gt;I've been thinking about open sourcing some of my side projects, and was wondering how to approach this.&lt;/p&gt;

&lt;p&gt;I don't think that open sourcing is necessarily a good way to &lt;em&gt;gain traction&lt;/em&gt;, as it's not much easier to gain users on GitHub than on the web in general. &lt;/p&gt;

&lt;p&gt;And it seems like you're giving up the chances of making profit from the product.&lt;/p&gt;

&lt;p&gt;Has anyone faced a similar dilemma?&lt;br&gt;
How do you decide?&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>opensource</category>
    </item>
    <item>
      <title>How to build a SoundCloud-like audio player app with VueJS, Quasar and WaveSurfer</title>
      <dc:creator>Jonathan P</dc:creator>
      <pubDate>Fri, 26 Jul 2019 00:00:00 +0000</pubDate>
      <link>https://forem.com/johnnymakestuff/how-to-build-a-soundcloud-like-audio-player-app-with-vuejs-quasar-and-wavesurfer-5bic</link>
      <guid>https://forem.com/johnnymakestuff/how-to-build-a-soundcloud-like-audio-player-app-with-vuejs-quasar-and-wavesurfer-5bic</guid>
      <description>&lt;h2&gt;
  
  
  What we're building
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.learningsomethingnew.com%2Fquasar-sc%2Fdevice_mockup.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.learningsomethingnew.com%2Fquasar-sc%2Fdevice_mockup.png" alt="final"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We'll use Quasar and WaveSurfer to build a SoundCloud like cross-platform mobile audio player app. We'll load a local audio file from the device using html file input, render a waveform and add controls to play the audio.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quasar?
&lt;/h2&gt;

&lt;p&gt;Quasar is a UI framework built on Vue.js. It seems more mobile-oriented than Vuetify, and also has an excellent CLI that works great for generating Cordova apps. You get debugging on actual device with hot reload out-of-the-box. This is a crucial part of an efficient dev process, and one of the determining factors for me when choosing a framework /stack.&lt;/p&gt;

&lt;p&gt;More points that made me want to try it out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Vibrant and active community (11K GitHub stars)&lt;/li&gt;
&lt;li&gt;Great CLI&lt;/li&gt;
&lt;li&gt;Beautiful UI components and grid system built in (I like to write as little CSS as possible)&lt;/li&gt;
&lt;li&gt;Use the Vue ecosystem you know and love&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Scaffolding
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;I assume you already have a working Cordova setup with android studio / xcode configured. Just follow &lt;a href="https://quasar.dev/quasar-cli/developing-cordova-apps/preparation" rel="noopener noreferrer"&gt;quasar's tutorial&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's start by installing the Quasar CLI. From your workspace terminal run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @quasar/cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then create a project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;quasar create quasar-wavesurfer-audio-player
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Insert the following answers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Project name: default&lt;/li&gt;
&lt;li&gt;Project product name: change to "Cool Player"&lt;/li&gt;
&lt;li&gt;Project Description: A soundcloud-like audio player using quasar and wavesurfer&lt;/li&gt;
&lt;li&gt;Author: you&lt;/li&gt;
&lt;li&gt;Features: we don't need any features&lt;/li&gt;
&lt;li&gt;Cordova id: leave as is, unless you want to publish to the store(s)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Quasar CLI will install required packages. After it's done, enter the folder, and run &lt;code&gt;quasar dev&lt;/code&gt; to see the basic layout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;quasar-wavesurfer-audio-player
quasar dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.learningsomethingnew.com%2Fquasar-sc%2Fquasar-dev.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.learningsomethingnew.com%2Fquasar-sc%2Fquasar-dev.png" alt="quasar-dev"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A working version! This is a good time to init a git repo and make the first commit.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Adding the cordova mode
&lt;/h3&gt;

&lt;p&gt;Now we have a Quasar app running in SPA mode. In order to turn it into a mobile app we can then publish to the app store, we need to add Quasar's &lt;code&gt;cordova mode&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;quasar mode add cordova
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This Quasar CLI command adds an &lt;code&gt;src-cordova&lt;/code&gt; folder, containing our Cordova project. Within this folder we can run any Cordova CLI command. Quasar will build our Vue code and put the assets in &lt;code&gt;src-cordova/www&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.learningsomethingnew.com%2Fquasar-sc%2Fcordova-mode.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.learningsomethingnew.com%2Fquasar-sc%2Fcordova-mode.png" alt="quasar-dev"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we'll check if it's running on our device. This should be done as early as possible as a sanity check to see that the setup's working. I'll use android for this, but it works the same for iOS.&lt;/p&gt;

&lt;p&gt;Make sure your device is connected by USB, and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;quasar dev &lt;span class="nt"&gt;-m&lt;/span&gt; android
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Note: Newer chrome versions block cleartext traffic by default, so Cordova's internal WebView will throw an &lt;code&gt;ERR_CLEARTEXT_NOT_PERMITTED&lt;/code&gt; error. To solve this, uncomment &lt;code&gt;https: true,&lt;/code&gt; under &lt;code&gt;devServer&lt;/code&gt; section in &lt;code&gt;quasar.conf.js&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Note2: If you're building for iOS, it's recommended to upgrade from the deprecated UIWebView (that Cordova uses by default) to WKWebView using one of the methods described &lt;a href="https://quasar.dev/quasar-cli/developing-cordova-apps/preparation" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now the CLI asks you which IP to serve on. Choose an IP that's accessible from your mobile device (they have to be on the same network). Quasar will create a Cordova app that's inner WebView loads from this IP, thereby giving us the ability to develop with hot reload on our device. This is a crucial feature for an efficient dev process.&lt;/p&gt;

&lt;p&gt;We'll continue developing with &lt;code&gt;quasar dev&lt;/code&gt;, while periodically testing on an actual device to see things are working. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you're building an app you want to work on android &lt;em&gt;and&lt;/em&gt; iOS, I recommend periodically running it on both platforms. They are never the same.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Quasar Project Structure
&lt;/h2&gt;

&lt;p&gt;Now let's have a look at the project structure the CLI has created for us, for a quick overview of Quasar. Quasar CLI scaffolds a folder structure with the default layout &lt;code&gt;MyLayout.vue&lt;/code&gt; inside the &lt;code&gt;layouts&lt;/code&gt; folder. The layout is a Vue component that has all the Quasar UI elements including a navigation drawer, a toolbar and a main page. You can spot the Quasar components starting with &lt;code&gt;q-&lt;/code&gt; e.g. &lt;code&gt;q-layout&lt;/code&gt;, &lt;code&gt;q-toolbar&lt;/code&gt; etc. These are all Vue components written by the Quasar team (similar to the &lt;code&gt;v-&lt;/code&gt; components of Vuetify).&lt;/p&gt;

&lt;p&gt;In order to use a Quasar UI component, you need to explicitly include them in &lt;code&gt;quasar.conf.js&lt;/code&gt;, as we'll see later.&lt;br&gt;&lt;br&gt;
The main page is contained inside the &lt;code&gt;&amp;lt;q-page-container&amp;gt;&lt;/code&gt; element in &lt;code&gt;MyLayout.vue&lt;/code&gt;, which contains a &lt;code&gt;&amp;lt;router-view /&amp;gt;&lt;/code&gt; - the same &lt;code&gt;vue-router&lt;/code&gt; you know and love. The routes are saved in the &lt;code&gt;pages&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;The main page loaded by default is &lt;code&gt;Index.vue&lt;/code&gt; under the &lt;code&gt;pages&lt;/code&gt; folder, so we'll add our code there for this example.&lt;/p&gt;
&lt;h2&gt;
  
  
  Adding wavesurfer.js
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://wavesurfer-js.org/" rel="noopener noreferrer"&gt;WaveSurfer&lt;/a&gt; is an open source audio player that renders the audio wave onto an html5 canvas. Let's add it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i wavesurfer.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we'll import WaveSurfer into &lt;code&gt;Index.vue&lt;/code&gt;, add a &lt;code&gt;wavesurfer&lt;/code&gt; data member, and a method &lt;code&gt;createWaveSurfer&lt;/code&gt; that will initialize it when the component is mounted. The &lt;code&gt;wavesurfer&lt;/code&gt; object needs a &lt;code&gt;container&lt;/code&gt;, which is the ID of the html element it will be rendered in.&lt;br&gt;
So we'll also add a container div to the component's template. We'll load a demo mp3 file just for testing at this stage. This is how &lt;code&gt;Index.vue&lt;/code&gt; looks like now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"waveform"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;WaveSurfer&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;wavesurfer.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PageIndex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&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="na"&gt;wavesurfer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&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;async&lt;/span&gt; &lt;span class="nf"&gt;mounted&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;wavesurfer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createWaveSurfer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;methods&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;createWaveSurfer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;wavesurfer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;WaveSurfer&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="na"&gt;container&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#waveform&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;barWidth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;wavesurfer&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://ia902606.us.archive.org/35/items/shortpoetry_047_librivox/song_cjrg_teasdale_64kb.mp3&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="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running using &lt;code&gt;quasar dev&lt;/code&gt; you should see the waveform rendered:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.learningsomethingnew.com%2Fquasar-sc%2Fadding-waveform.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.learningsomethingnew.com%2Fquasar-sc%2Fadding-waveform.png" alt="adding-waveform"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Loading a local audio file
&lt;/h2&gt;

&lt;p&gt;We'll use html's &lt;code&gt;&amp;lt;input type="file"&amp;gt;&lt;/code&gt; to load files from the local device. Let's add a file-loading button to our toolbar. In &lt;code&gt;MyLayout.vue&lt;/code&gt; replace &lt;code&gt;&amp;lt;div&amp;gt;Quasar v{{ $q.version }}&amp;lt;/div&amp;gt;&lt;/code&gt; with a &lt;code&gt;q-btn&lt;/code&gt; component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;q-btn&lt;/span&gt; &lt;span class="na"&gt;color=&lt;/span&gt;&lt;span class="s"&gt;"white"&lt;/span&gt; &lt;span class="na"&gt;text-color=&lt;/span&gt;&lt;span class="s"&gt;"primary"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
Load File
&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;
    &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"file"&lt;/span&gt;
    &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"q-uploader__input overflow-hidden absolute-full"&lt;/span&gt;
    &lt;span class="na"&gt;v-on:change=&lt;/span&gt;&lt;span class="s"&gt;"fileChosen"&lt;/span&gt;
    &lt;span class="na"&gt;ref=&lt;/span&gt;&lt;span class="s"&gt;"fileInput"&lt;/span&gt;
    &lt;span class="na"&gt;accept=&lt;/span&gt;&lt;span class="s"&gt;"audio/mpeg"&lt;/span&gt;
&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/q-btn&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What we have here is a &lt;code&gt;&amp;lt;q-btn&amp;gt;&lt;/code&gt; (Quasar button), containing a regular html &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; element, with &lt;code&gt;type="file"&lt;/code&gt; (making it a file chooser) that will only accept audio files (&lt;code&gt;accept="audio/mpeg"&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Notice that browsers try to enforce file inputs to have one of the internet's ugliest designs, something like this: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.learningsomethingnew.com%2Fquasar-sc%2Fdefault-file-input.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.learningsomethingnew.com%2Fquasar-sc%2Fdefault-file-input.png" alt="default-file-input"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While there is no official way to change the appearance of a file input, we'll "borrow" the css classes from Quasar's &lt;code&gt;q-uploader&lt;/code&gt; component in order to make it appear more like a button, and less like a relic from the 90's. Clicking on this in a Cordova app will open the native device's file choosing interface.&lt;/p&gt;

&lt;p&gt;In the methods section add a handler for receiving the file:&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="nf"&gt;fileChosen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Chosen file passed as argument&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Sending the file to the main page
&lt;/h3&gt;

&lt;p&gt;As we have the file object received in the &lt;code&gt;MyLayout&lt;/code&gt; component, but the wavesurfer object in the &lt;code&gt;Index&lt;/code&gt; component, we'll use an event bus to communicate between them and send the file when it's selected. An event bus can easily be created by using another Vue object. Add a &lt;code&gt;services&lt;/code&gt; folder with a new &lt;code&gt;event-bus.js&lt;/code&gt; file, containing:&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="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Vue&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;EventBus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Vue&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We'll import it in &lt;code&gt;MyLayout.vue&lt;/code&gt; and emit an event whenever the file input value changes:&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;EventBus&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../services/event-bus.js&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;methods&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;fileChosen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;EventBus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;$emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fileChosen&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;file&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;Now we'll want to catch the event in &lt;code&gt;Index.vue&lt;/code&gt;'s &lt;code&gt;mounted&lt;/code&gt; 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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;EventBus&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../services/event-bus.js&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="nf"&gt;mounted&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="nx"&gt;EventBus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;$on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fileChosen&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&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;And finally we'll load the file using wavesurfer's &lt;code&gt;loadBlob&lt;/code&gt; method:&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="nf"&gt;loadFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&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="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;wavesurfer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadBlob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&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;Try loading a file from your device, and you should see it rendered:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.learningsomethingnew.com%2Fquasar-sc%2Fload-file.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.learningsomethingnew.com%2Fquasar-sc%2Fload-file.png" alt="load-file"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding controls
&lt;/h2&gt;

&lt;p&gt;Let's add some play/pause/skip buttons. In &lt;code&gt;Index.vue&lt;/code&gt; add the following code to the template section:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;q-page&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"audio-container"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"row q-ma-md"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"col-12"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"waveform"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"controls row flex flex-center fixed-bottom q-pb-md q-pt-md shadow-10"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;q-btn&lt;/span&gt;
          &lt;span class="na"&gt;color=&lt;/span&gt;&lt;span class="s"&gt;"primary"&lt;/span&gt;
          &lt;span class="na"&gt;flat&lt;/span&gt;
          &lt;span class="na"&gt;round&lt;/span&gt;
          &lt;span class="na"&gt;icon=&lt;/span&gt;&lt;span class="s"&gt;"fast_rewind"&lt;/span&gt;
          &lt;span class="na"&gt;size=&lt;/span&gt;&lt;span class="s"&gt;"xl"&lt;/span&gt;
          &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;click=&lt;/span&gt;&lt;span class="s"&gt;"wavesurfer.skipBackward(1)"&lt;/span&gt;
        &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;q-btn&lt;/span&gt;
          &lt;span class="na"&gt;v-if=&lt;/span&gt;&lt;span class="s"&gt;"isPlaying"&lt;/span&gt;
          &lt;span class="na"&gt;color=&lt;/span&gt;&lt;span class="s"&gt;"primary"&lt;/span&gt;
          &lt;span class="na"&gt;round&lt;/span&gt;
          &lt;span class="na"&gt;icon=&lt;/span&gt;&lt;span class="s"&gt;"pause"&lt;/span&gt;
          &lt;span class="na"&gt;size=&lt;/span&gt;&lt;span class="s"&gt;"xl"&lt;/span&gt;
          &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;click=&lt;/span&gt;&lt;span class="s"&gt;"wavesurfer.playPause()"&lt;/span&gt;
        &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;q-btn&lt;/span&gt;
          &lt;span class="na"&gt;v-if=&lt;/span&gt;&lt;span class="s"&gt;"!isPlaying"&lt;/span&gt;
          &lt;span class="na"&gt;color=&lt;/span&gt;&lt;span class="s"&gt;"primary"&lt;/span&gt;
          &lt;span class="na"&gt;round&lt;/span&gt;
          &lt;span class="na"&gt;icon=&lt;/span&gt;&lt;span class="s"&gt;"play_arrow"&lt;/span&gt;
          &lt;span class="na"&gt;size=&lt;/span&gt;&lt;span class="s"&gt;"xl"&lt;/span&gt;
          &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;click=&lt;/span&gt;&lt;span class="s"&gt;"wavesurfer.playPause()"&lt;/span&gt;
        &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;q-btn&lt;/span&gt;
          &lt;span class="na"&gt;color=&lt;/span&gt;&lt;span class="s"&gt;"primary"&lt;/span&gt;
          &lt;span class="na"&gt;flat&lt;/span&gt;
          &lt;span class="na"&gt;round&lt;/span&gt;
          &lt;span class="na"&gt;icon=&lt;/span&gt;&lt;span class="s"&gt;"fast_forward"&lt;/span&gt;
          &lt;span class="na"&gt;size=&lt;/span&gt;&lt;span class="s"&gt;"xl"&lt;/span&gt;
          &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;click=&lt;/span&gt;&lt;span class="s"&gt;"wavesurfer.skipForward(1)"&lt;/span&gt;
        &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/q-page&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And in the script section:&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="nx"&gt;computed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;isPlaying&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;wavesurfer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;wavesurfer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isPlaying&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;We've added some controls using Quasar's &lt;code&gt;&amp;lt;q-btn&amp;gt;&lt;/code&gt; components inside a Quasar &lt;code&gt;"row"&lt;/code&gt; div, which arranges them neatly using the positioning helpers &lt;code&gt;flex&lt;/code&gt;, &lt;code&gt;fixed-center&lt;/code&gt;, and &lt;code&gt;fixed-bottom&lt;/code&gt;. &lt;code&gt;q-pb-md/q-pt-md&lt;/code&gt; gives us some predefined margins. The &lt;code&gt;@click&lt;/code&gt; handlers call wavesurfer's methods directly.&lt;/p&gt;

&lt;p&gt;Notice how we're designing the UI declaratively using the template section, with Quasar's modifiers like &lt;code&gt;round&lt;/code&gt;, &lt;code&gt;flat&lt;/code&gt; and &lt;code&gt;size&lt;/code&gt;. This saves us 99% of the css we'd have to write manually.&lt;/p&gt;

&lt;p&gt;We've also added an &lt;code&gt;isPlaying&lt;/code&gt; computed, which is a wrapper around wavesurfer's method, in order to decide whether to show a play or pause button. It should look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.learningsomethingnew.com%2Fquasar-sc%2Fcontrols.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.learningsomethingnew.com%2Fquasar-sc%2Fcontrols.png" alt="controls"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Using wavesurfer hooks to add a loading spinner
&lt;/h2&gt;

&lt;p&gt;Almost done! Now we'll add a spinner in order to give some feedback to the user when the file is loading. Wavesurfer exposes some hooks so we can handle various events. We'll use the &lt;code&gt;error&lt;/code&gt;, &lt;code&gt;loading&lt;/code&gt; and &lt;code&gt;ready&lt;/code&gt; events.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;quasar.conf.js&lt;/code&gt; components section, add &lt;code&gt;'QCircularProgress'&lt;/code&gt; to the list.&lt;br&gt;
Then in &lt;code&gt;Index.vue&lt;/code&gt;, above the play button, add a &lt;code&gt;q-circular-progress&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;q-circular-progress&lt;/span&gt; &lt;span class="na"&gt;v-if=&lt;/span&gt;&lt;span class="s"&gt;"isLoading"&lt;/span&gt; &lt;span class="na"&gt;size=&lt;/span&gt;&lt;span class="s"&gt;"72px"&lt;/span&gt; &lt;span class="na"&gt;indeterminate&lt;/span&gt; &lt;span class="na"&gt;color=&lt;/span&gt;&lt;span class="s"&gt;"primary"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Change the play button to show only if we're not loading:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;q-btn&lt;/span&gt; &lt;span class="na"&gt;v-if=&lt;/span&gt;&lt;span class="s"&gt;"!isPlaying &amp;amp;&amp;amp; !isLoading"&lt;/span&gt;
&lt;span class="err"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the &lt;code&gt;isLoading&lt;/code&gt; member to the &lt;code&gt;data&lt;/code&gt; section:&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="nx"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And in the &lt;code&gt;createWaveSurfer()&lt;/code&gt; method, we'll hook into wavesurfer events:&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;wavesurfer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&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="o"&gt;=&amp;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="nf"&gt;error&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isLoading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;message&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;wavesurfer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;loading&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isLoading&lt;/span&gt; &lt;span class="o"&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;wavesurfer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ready&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isLoading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&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;We've also added a Quasar notification (snackbar) whenever there's an error. This is made super easy in Quasar using &lt;code&gt;this.$q.notify&lt;/code&gt; from anywhere in your code.&lt;/p&gt;

&lt;h2&gt;
  
  
  A little design
&lt;/h2&gt;

&lt;p&gt;To finalize the design, we'll add a nice background image and some styling on the waveform. &lt;br&gt;
I've used James Owen's &lt;a href="https://unsplash.com/photos/c-NBiJrhwdM" rel="noopener noreferrer"&gt;picture&lt;/a&gt; as the background. Save the picture as &lt;code&gt;audio.png&lt;/code&gt; under &lt;code&gt;assets&lt;/code&gt; folder, and set it as our main div's background using following css:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.controls&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.audio-container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;top&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="nl"&gt;left&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="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;linear-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rgba&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="m"&gt;0&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="m"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;rgba&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="m"&gt;0&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="m"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="sx"&gt;url("../assets/audio.jpg")&lt;/span&gt; &lt;span class="nb"&gt;no-repeat&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cover&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;And some styling for the waveform using WaveSurfer options:&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;wavesurfer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;WaveSurfer&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="na"&gt;container&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#waveform&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;hideScrollbar&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;waveColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;white&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;progressColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hsla(200, 100%, 30%, 0.5)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;cursorColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#fff&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;barWidth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that's it!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.learningsomethingnew.com%2Fquasar-sc%2Fdevice_mockup.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.learningsomethingnew.com%2Fquasar-sc%2Fdevice_mockup.png" alt="final"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;I've found Quasar to be a very rapid way of developing a well designed mobile / desktop / web app using Vue, with very little CSS. The CLI is also pretty awesome, and saves a lot of Cordova configuration headache.&lt;/p&gt;

&lt;p&gt;WaveSurfer can be customized and has a lot of plugins. We've loaded local files for demonstration, but it can also fetch remote urls from your server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Full source code: &lt;a href="https://github.com/syonip/quasar-wavesurfer-audio-player" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Ask a question / drop a line on twitter at: &lt;a href="https://twitter.com/johnnymakestuff" rel="noopener noreferrer"&gt;@johnnymakestuff&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://quasar.dev/" rel="noopener noreferrer"&gt;Quasar&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wavesurfer-js.org/" rel="noopener noreferrer"&gt;WaveSurfer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Device mockup created with &lt;a href="https://threed.io/" rel="noopener noreferrer"&gt;threed.io&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Originally posted on my &lt;a href="https://www.learningsomethingnew.com/how-to-build-a-sound-cloud-like-audio-player-app-with-vue-js-quasar-and-wave-surfer" rel="noopener noreferrer"&gt;blog&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>webdev</category>
      <category>cordova</category>
      <category>showdev</category>
      <category>vue</category>
    </item>
    <item>
      <title>Don't be an Update Junkie</title>
      <dc:creator>Jonathan P</dc:creator>
      <pubDate>Wed, 17 Jul 2019 00:00:00 +0000</pubDate>
      <link>https://forem.com/johnnymakestuff/don-t-be-an-update-junkie-211d</link>
      <guid>https://forem.com/johnnymakestuff/don-t-be-an-update-junkie-211d</guid>
      <description>&lt;h2&gt;
  
  
  "The Update Loop"
&lt;/h2&gt;

&lt;p&gt;Ever found yourself in an update loop? You check your email, refresh your twitter feed, check how many people read your blog post, check your website traffic, your instagram likes, the news updates, and then check your email again?&lt;/p&gt;

&lt;p&gt;Recently I've noticed a negative correlation between the amount of focus and creativity I have, and the amount of updates I check for.&lt;br&gt;
Checking for updates is a seductive form of distraction that is built on a need for &lt;em&gt;new, external information&lt;/em&gt;.&lt;br&gt;
Updates are addictive, and not by chance. Our brains are ill-equipped to handle them.  &lt;/p&gt;

&lt;p&gt;I've noticed that the more I slip into this update-checking loop, my mind is more diffused and restless. It's harder to get into the zone of deep focus.&lt;br&gt;
Being in the zone is required for creative work, but it's also more &lt;em&gt;fun&lt;/em&gt; than having diffused attention.&lt;br&gt;
This is especially true if you work remotely / freelance, because you have more freedom.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why we get hooked on updates - supernormal stimulus
&lt;/h2&gt;

&lt;p&gt;While reading &lt;a href="https://jamesclear.com/atomic-habits" rel="noopener noreferrer"&gt;Atomic Habits&lt;/a&gt; I was introduced to the concept of "supernormal stimulus", which makes it very hard for our primate brains to resist certain aspects of the modern world.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A supernormal stimulus or superstimulus is an exaggerated version of a stimulus to which there is an existing response tendency, or any stimulus that elicits a response more strongly than the stimulus for which it evolved.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Source: &lt;a href="https://en.wikipedia.org/wiki/Supernormal_stimulus" rel="noopener noreferrer"&gt;Wikipedia&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Junk food is a good example of this. Our brains evolved in a world where liking high-sugar foods like bananas (normal stimulus) increased your survival rate, because it was scarce. But now we have donuts with 100x more sugar and fat (supernormal stimulus). They're &lt;em&gt;easy&lt;/em&gt; to access, and hard to resist.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.learningsomethingnew.com%2Fupdate-junkie%2Fdonuts.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.learningsomethingnew.com%2Fupdate-junkie%2Fdonuts.jpg" alt="donuts"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Similarly, in hunter-gatherer tribes, being socially validated by your tribe's members (normal stimulus) was important for your survival.&lt;br&gt;
Now you can post on social media, and get validated (or invalidated) by &lt;em&gt;billions&lt;/em&gt; of people (supernormal stimulus). That's why it's addicting - getting an update on your social status on a huge scale is just too easy.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.learningsomethingnew.com%2Fupdate-junkie%2Fphone.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.learningsomethingnew.com%2Fupdate-junkie%2Fphone.jpg" alt="phone"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The same goes for our curiosity instinct. Instead of getting news from a traveler who came from the next town south, we can get instant updates on anything that happens anywhere in the world.&lt;br&gt;
To add to the mix, we have the best engineers in the world engaged in an arms-race for capturing our attention, and machine learning algorithms that get better at figuring out which content is the most "engaging" (read: addictive). &lt;br&gt;
&lt;a href="https://www.ted.com/talks/tristan_harris_the_manipulative_tricks_tech_companies_use_to_capture_your_attention?language=en" rel="noopener noreferrer"&gt;Tristan Harris&lt;/a&gt; does a good job of explaining how out-gunned our brains are in this battle.&lt;/p&gt;

&lt;h2&gt;
  
  
  The damage
&lt;/h2&gt;

&lt;p&gt;I think that the addiction to &lt;em&gt;external updates&lt;/em&gt; is the real danger when talking about productivity and creativity. You get to a state where if you haven't had some &lt;em&gt;external input&lt;/em&gt; for 5 minutes, you get agitated and restless. You check for updates, and then you're out of the zone. You become a polling algorithm that constantly polls the server (the internet) for updates.&lt;br&gt;
Creativity requires focus and something coming from within yourself, which is blurred by constant input from outside.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to stop
&lt;/h2&gt;

&lt;p&gt;I'm not saying we should never get distracted, or do idle things. That's impossible, and probably unhealthy.&lt;br&gt;
It's just becoming too &lt;em&gt;easy&lt;/em&gt; to get distracted. And with the blessing of smartphones, it makes it 10 time easier still.&lt;/p&gt;

&lt;p&gt;It's not practical be completely disconnected these days, but knowing how vulnerable our mind is, we have to set some limits.&lt;/p&gt;

&lt;h3&gt;
  
  
  Systems, not will-power
&lt;/h3&gt;

&lt;p&gt;Update-checking is a habit like any other, and to fight it we need to use methods from habit theory.&lt;br&gt;
In order to change a habit, don't rely on will-power. Rely on &lt;strong&gt;systems&lt;/strong&gt;.&lt;br&gt;
You need to create systems that make it less &lt;em&gt;easy&lt;/em&gt; to do what you're prone to do.&lt;/p&gt;

&lt;p&gt;Here are some ideas for systems you can put in place, that will protect you from yourself.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Turn off social notifications on your phone
&lt;/h3&gt;

&lt;p&gt;Notifications like "someone liked your photo" or "someone mentioned you in a tweet" are hooks designed to get you into the app. Once in, you're prone to refresh your feed and get into an update loop. &lt;br&gt;
You can still keep notifications for direct messages sent to you, with WhatsApp or Facebook Messenger.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Don't install social apps on your phone at all
&lt;/h3&gt;

&lt;p&gt;This is more extreme, but makes it harder to check your feed 100 times a day. It also protects you from walking head on into traffic while checking your feed.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Don't log into your email on your phone
&lt;/h3&gt;

&lt;p&gt;This way you can only access your email explicitly from your desktop. This limits the frequency you're able to do it. This will depend on your work place of course, sometimes you just have to be available via email.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Start your day pre-input
&lt;/h3&gt;

&lt;p&gt;The idea of not checking your phone when you wake up is becoming almost eccentric. One of the biggest signs of addiction is doing the addictive behavior first thing in the morning. Try putting your phone into flight mode before you go to sleep, and not going out of flight mode until you do some creative thinking / writing / meditation. Starting your day with a mind clear of &lt;em&gt;external input&lt;/em&gt; in the morning can be very powerful.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Use Pomodoro Technique 🍅
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Pomodoro_Technique" rel="noopener noreferrer"&gt;The Pomodoro Technique&lt;/a&gt; - basically cycles of 25 minutes of work, and 5 minutes break. This method is something I revert to every time I notice I'm getting too distracted. Update checking is allowed only during breaks, thereby giving you at least 25 minutes to get into deep focus.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Go cold turkey
&lt;/h3&gt;

&lt;p&gt;Sometimes you just need to go cold turkey, and get completely off any kind of updates for a while. A silent retreat is a perfect setting for this, and I highly recommend trying it at least once. Until you experience it, it's hard to imagine how it's like to spend a week without any news from the outside world.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;These are all way of making it &lt;em&gt;less easy&lt;/em&gt; for us to slip into a behavior that's designed to be addictive: checking for updates. If you make it too easy, it's just impossible to resist.&lt;/p&gt;

&lt;p&gt;Do you notice that checking for updates is becoming a habit and makes you less focused? &lt;br&gt;
What are your methods of dealing with this?&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>sideprojects</category>
      <category>philosophy</category>
      <category>entrepreneurship</category>
    </item>
    <item>
      <title>Do you include hobbies in your CV?</title>
      <dc:creator>Jonathan P</dc:creator>
      <pubDate>Tue, 16 Jul 2019 06:07:11 +0000</pubDate>
      <link>https://forem.com/johnnymakestuff/do-you-include-hobbies-in-your-cv-2fop</link>
      <guid>https://forem.com/johnnymakestuff/do-you-include-hobbies-in-your-cv-2fop</guid>
      <description>&lt;p&gt;A friend asked for feedback on their CV, and I thought adding some hobbies is a good idea, as it gives a little more depth as a person.&lt;br&gt;
He strongly disagreed, saying the proper place to mention hobbies is F2F only.&lt;/p&gt;

&lt;p&gt;What do you think?&lt;br&gt;
Do you include hobbies in your CV?&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>cv</category>
    </item>
    <item>
      <title>How to *finish* your side-project</title>
      <dc:creator>Jonathan P</dc:creator>
      <pubDate>Mon, 15 Jul 2019 00:00:00 +0000</pubDate>
      <link>https://forem.com/johnnymakestuff/how-to-finish-your-side-project-2ip6</link>
      <guid>https://forem.com/johnnymakestuff/how-to-finish-your-side-project-2ip6</guid>
      <description>&lt;p&gt;I've read some good posts about the importance of having side projects, and I agree, they are important. And fun.&lt;br&gt;
I want to talk about &lt;strong&gt;finishing&lt;/strong&gt; your side project.&lt;br&gt;
Part of the reason I chose to work part-time is so I can have more time for my side projects. &lt;br&gt;
I've started many projects. I've actually finished only a small fraction of them. Here's what I think made the difference.&lt;/p&gt;

&lt;h2&gt;
  
  
  Definitions
&lt;/h2&gt;

&lt;p&gt;But first, some definitions.&lt;/p&gt;

&lt;h3&gt;
  
  
  PSP - Personal Side Project
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Personal - you're doing it alone, or with a friend&lt;/li&gt;
&lt;li&gt;Side project - you're not doing it full time, and if full time, not for a long time
(if you're dedicating the next 5 years of your life to work on it, then it's not a side project, it's your startup.)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  "Finishing"
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;For our purposes, &lt;strong&gt;finishing&lt;/strong&gt; your PSP is &lt;strong&gt;shipping a working version&lt;/strong&gt; out there (an MVP if you will) for people to use. 
In reality, a product is never really "finished". Apps constantly change and evolve. Your product might hit off and you'll decide to continue working on it for 10 years. Or you might release it and realize it's worthless. But for our purposes, finishing is getting it out there for people to start using.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The danger
&lt;/h2&gt;

&lt;p&gt;Pop quiz: what's the biggest danger your PSP faces? &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a. Being a bad product&lt;/li&gt;
&lt;li&gt;b. Having bad design&lt;/li&gt;
&lt;li&gt;c. Having 0 traction&lt;/li&gt;
&lt;li&gt;d. Making you so much money you go insane&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.learningsomethingnew.com%2Ffinishing-psp%2Fmoney.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.learningsomethingnew.com%2Ffinishing-psp%2Fmoney.gif" alt="money"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All real dangers no doubt, but I would argue that the &lt;em&gt;real&lt;/em&gt; danger your side project faces is &lt;strong&gt;abandonment&lt;/strong&gt;.&lt;br&gt;
Why? Because you have limited time, energy and &lt;em&gt;motivation&lt;/em&gt;.&lt;br&gt;
No matter how enthusiastic you are about it now, your motivation is bound to wane. The more time it takes to finish, the more likely you are to abandon it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The biggest danger your side project faces is &lt;strong&gt;abandonment&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  &lt;em&gt;Why&lt;/em&gt; you should finish your project
&lt;/h2&gt;

&lt;p&gt;But why should I finish it? Isn't it enough to revel in the joy of creation?&lt;br&gt;
Sure, you'll learn a lot from everything you do, and might have a lot of fun in the process. But I've noticed that no matter how much fun I have developing, an unfinished project impinges on my psyche. Like an unrealized dream. Like that girl/guy you thought liked you, and you never made a move, and you'll never know if it could've worked.&lt;br&gt;
But the main reason to finish a project is that it gives you more motivation to keep working on it. Circular argument? Perhaps... Let's see how that works.&lt;/p&gt;

&lt;h3&gt;
  
  
  Motivation and traction
&lt;/h3&gt;

&lt;p&gt;I've noticed a recurring pattern in my motivation to work on something. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;I get super excited about something, and start working on it.&lt;/li&gt;
&lt;li&gt;Work on it for about a month, maybe two.&lt;/li&gt;
&lt;li&gt;The project grows in complexity, I can't get the design right, progress slows. I get bored.&lt;/li&gt;
&lt;li&gt;I get super excited about something else and I'm ready to switch. 
This is the stage I usually abandon the project, telling myself I'll get back to it later, but it rarely happens.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.learningsomethingnew.com%2Ffinishing-psp%2Fpsp-lifecycle.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.learningsomethingnew.com%2Ffinishing-psp%2Fpsp-lifecycle.png" alt="dark-pattern"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What breaks this pattern, and breathes new air into a project, is &lt;strong&gt;traction&lt;/strong&gt;.&lt;br&gt;
When you see your product out there and people using it, you get motivated. And for good reason. When people are using your product, you know it's viable, and you know it's worth your time to continue with it. &lt;br&gt;
If not, you know you tried and can continue on wholeheartedly to other endeavors. Also, moving on at this stage doesn't kill your project, because it's out there, and can start gaining traction with time, while you work on something else.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.learningsomethingnew.com%2Ffinishing-psp%2Fpsp-lifecycle-2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.learningsomethingnew.com%2Ffinishing-psp%2Fpsp-lifecycle-2.png" alt="good-pattern"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  You never know
&lt;/h3&gt;

&lt;p&gt;Your project could reach more success than you think, or even become a source of income. You'll never know if you don't get it out there.&lt;br&gt;
When making the language learning app &lt;a href="https://play.google.com/store/apps/details?id=com.cognitusapps.flamingo.french" rel="noopener noreferrer"&gt;Flamingo French&lt;/a&gt;, I thought it would be mildly successful. It wasn't.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.learningsomethingnew.com%2Ffinishing-psp%2Ffrench-downloads.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.learningsomethingnew.com%2Ffinishing-psp%2Ffrench-downloads.png" alt="flamingo-french"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But then we had the idea to test out some languages that don't actually exist, and the results were surprising:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.learningsomethingnew.com%2Ffinishing-psp%2Felvish-downloads.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.learningsomethingnew.com%2Ffinishing-psp%2Felvish-downloads.png" alt="flamingo-elvish"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;I would never have guessed this when first starting the project.&lt;br&gt;
The point is, you never know until you get it out there.&lt;/p&gt;

&lt;h3&gt;
  
  
  Better estimations - The 40% rule
&lt;/h3&gt;

&lt;p&gt;Until you finish your project, you're likely to &lt;strong&gt;grossly underestimate&lt;/strong&gt; the work required to actually finish it. &lt;br&gt;
You think to yourself "it's basically done, just need some finishing touches". Those finishing touches can take more time than building it!&lt;/p&gt;

&lt;p&gt;After I "finished" building my morning pages app &lt;a href="https://www.blurrish.com" rel="noopener noreferrer"&gt;Blurrish&lt;/a&gt; (built with electron and vue), I decided to upload it to Apple's app store. It was already working on mac, so I thought it would take a couple of hours to publish to the app store. It took me &lt;em&gt;a week!&lt;/em&gt; (I had to re-write large parts of my code due to sandboxing and app signing problems). I could only have learned this by actually trying to publish the app to the store. &lt;/p&gt;

&lt;p&gt;I will paraphrase &lt;a href="https://thehustle.co/40-percent-rule-navy-seal-secret-mental-toughness" rel="noopener noreferrer"&gt;David Goggins&lt;/a&gt; here:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Whenever you think the project is done, it's 40% done.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You learn this by experiencing it.&lt;/p&gt;



&lt;h2&gt;
  
  
  &lt;em&gt;How&lt;/em&gt; to finish your side project
&lt;/h2&gt;

&lt;p&gt;Now that we agree that we want to finish it, let's see how.&lt;br&gt;
The most important part of finishing you project is choosing what &lt;em&gt;not&lt;/em&gt; to do.&lt;br&gt;
The best way to finish your project is by &lt;em&gt;constraining&lt;/em&gt; it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Eu-constraints
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Constrain yourself so you don't strain yourself&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Making a product is a form of creation. Whenever you're creating, you're bringing something into existence that wasn't there before. And when you do that, you need to choose from an &lt;em&gt;infinite number of options&lt;/em&gt;. This is called the blank page problem in the writing world. And that's the hard part of creating.&lt;/p&gt;

&lt;p&gt;Constraints are crucial.&lt;br&gt;
Having a good set of constraints limits the infinite number of possibilities, and even &lt;a href="https://www.fastcompany.com/3067925/how-constraints-force-your-brain-to-be-more-creative" rel="noopener noreferrer"&gt;makes you more creative&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Because we usually perceive constraints as negative, let's add the greek prefix "eu" (as in euphoria, not the European Union), to remind ourselves that they're good.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.learningsomethingnew.com%2Ffinishing-psp%2Feu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.learningsomethingnew.com%2Ffinishing-psp%2Feu.png" alt="eu"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Source: &lt;a href="https://en.wiktionary.org/wiki/eu-" rel="noopener noreferrer"&gt;wiktionary&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Luckily, we have some constraints built-in to the definition of a PSP, and this is exactly what forces us to be creative. &lt;br&gt;
I like naming things in order to remember their importance. And I also like acronyms. Enter SCRUF.&lt;/p&gt;

&lt;h2&gt;
  
  
  SCRUF - Simple, Cheap, Re-Using and Fast
&lt;/h2&gt;

&lt;p&gt;The SCRUF method (never miss an opportunity to invent a new acronym) shows the most important constraints for a side project &lt;strong&gt;you'll finish&lt;/strong&gt;:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Simple - become a "feature minimalist"
&lt;/h3&gt;

&lt;p&gt;It's hard to build software. The bigger it is, the harder it is.&lt;br&gt;
As developers we have a tendency to under-estimate the time it takes to build something.&lt;br&gt;
NOT developing features you think you &lt;em&gt;can develop&lt;/em&gt; is one of the hardest things for developers.&lt;br&gt;
Take yourself by the scruff of your neck and learn to say &lt;em&gt;no&lt;/em&gt; to features! &lt;br&gt;
For every feature you add, coding it is just the tip of the iceberg. 90% of the time it will take is in the complexity it adds to the rest of the system, the bugs it will create, the UI problems, etc.&lt;br&gt;
Practice &lt;strong&gt;feature minimalism&lt;/strong&gt; and ruthlessly cut away any feature that's not essential.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Cheap - burn rate will kill your project
&lt;/h3&gt;

&lt;p&gt;The greater the burn rate of each product you publish, the more likely you are to &lt;em&gt;pull the plug on it&lt;/em&gt; prematurely.&lt;br&gt;
Use free services, or services that will only start costing when your product is generating income.&lt;br&gt;
I think 10-20$ per year burn rate is a good number to aim for. Basically only pay for the domain name (if you need it), and use free stuff for the rest. The goal is to have multiple projects, that can run long enough to see if they stick. &lt;br&gt;
Maybe you think paying 20$ a month isn't a lot, but if you have 5-10 side projects running, it can add up.&lt;br&gt;
Some suggestions for cost cutting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No dedicated email server / GSuite. You can configure GMail to send and receive emails from &lt;a href="mailto:you@yourdomain.com"&gt;you@yourdomain.com&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Use Netlify over Heroku for hosting your public facing website - SSL is free.&lt;/li&gt;
&lt;li&gt;Domain - get the cheapest version - shouldn't cost more than 10-20$. &lt;/li&gt;
&lt;li&gt;Start paying for upgrades and premiums only when you're making profit.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Re-Using - reinventing the wheel is a DRY violation
&lt;/h3&gt;

&lt;p&gt;DRY - Don't Repeat Yourself, the most fundamental rule of programming. Maybe even the &lt;em&gt;definition&lt;/em&gt; of programming.&lt;br&gt;
Don't repeat your own logic, and don't repeat other people's work unnecessarily.&lt;/p&gt;

&lt;p&gt;A few suggestions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Make it cross-platform. Use cross-platform libraries like cordova, react native, or electron. Building and maintaining two separate code-bases for Android and IOS, or Mac+Windows+Linux is a huge waste of time (and a huge DRY violation).&lt;/li&gt;
&lt;li&gt;Use a UI framework - instead of inventing css for UI components that many people have used before, use a UI framework. Unless a new amazing UI is a core feature of your product, it's better to spend time in other areas than css. Does your app really need that custom CSS you'll spend a week creating? Do your users really care?&lt;/li&gt;
&lt;li&gt;Starter project - Spend time on finding a good starter project e.g. Vue project running in a Cordova app with Quasar UI Framework. This will save a ton of time in scaffolding and creating a good dev process.

&lt;ul&gt;
&lt;li&gt;Test the components and performance of the stack and UI framework on your TARGET OS before choosing. &lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Fast
&lt;/h3&gt;

&lt;p&gt;For me, the time constraint is huge, and it's a constraint you can easily let go of, because you're in charge of your own time.&lt;/p&gt;

&lt;p&gt;I aim for a product I can ship in about &lt;strong&gt;one month&lt;/strong&gt; - calendar time.&lt;br&gt;
This is for first version, and is mainly in order to dictate the size of the project and what features your first version should have. It's OK (and beneficial) to change the idea a bit in order to fit the size of the project to this time frame.&lt;/p&gt;

&lt;p&gt;Why calendar time? Because that's the determining factor in how motivated you'll stay. If you have 3 hours a day to work on it, you can do more. If you have 3 hours a week, you can do less. Choose the size of the project according to the time you have to work on it, and keep the total amount of calendar time constant. I've noticed that the time frame of a month works well, and obviously this will vary from person to person. Part of the benefit of making things on your own is that you learn to know your own process and how your motivation works.&lt;/p&gt;

&lt;p&gt;A point about deadlines: I don't believe in setting arbitrary deadlines for yourself. This has never really worked for me. You can get into a habit of elimination by keeping a time frame in mind, but an arbitrary date seems redundant. It is a form of constraint though and it might work for you. Try it.&lt;/p&gt;

&lt;p&gt;You need to be ruthless to be fast. Cut anything that's not 100% necessary for a useable first version. Good examples are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cut server side. Making your app serverless will not only save about 50% of dev time actually writing and debugging server code, but will also make your &lt;em&gt;client&lt;/em&gt; code much simpler. I don't know if there is any data to support this, but I would estimate that you cut about 70% of the workload if you can make your project serverless (I don't mean &lt;a href="//serverless.com"&gt;serverless.com&lt;/a&gt;, but actually serverless. As in no servers at all). This won't always be possible, but remember that hard constraints make you be creative and might end up giving your product an edge over the competition.&lt;/li&gt;
&lt;li&gt;Cut user management / login systems - Is having a user profile a core feature your app will fail without? If not, cut it. You can always add it later.&lt;/li&gt;
&lt;li&gt;Cut unit tests. Yes, I just said that. Shoot me. Unit testing is not gospel, and in our case the overhead is not worth the benefit. (Unless your side project is a temperature-regulating app for nuclear reactors. Then please write unit tests.)
As your project grows in complexity, and as your team size grows, you might want to add unit testing or another form of automatic testing, but for now, skip it. If SO can do with &lt;a href="https://speakerdeck.com/sklivvz/the-architecture-of-stackoverflow-developer-conference-2013?slide=14" rel="noopener noreferrer"&gt;very few unit tests&lt;/a&gt;, so can your small, simple side project.
Of course, if you have a super critical piece of code you can protect it from regressions or bugs with unit tests. I did that for user's journal encryption logic in &lt;a href="https://www.blurrish.com" rel="noopener noreferrer"&gt;Blurrish&lt;/a&gt;, because corrupting journal entries is unforgivable.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Summary - High level work process
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Choose a project, using SCRUF to ensure it's "finishable". Don't multi-task here, one project at a time in phase 1.&lt;/li&gt;
&lt;li&gt;Publish a first version. Get it out there.&lt;/li&gt;
&lt;li&gt;Marketing, analysis, feedback - This can go on while starting the next project.&lt;/li&gt;
&lt;li&gt;Maintain, expand, or discard - Is this project worth working more on? If so, great. If not, leave it and move on to more interesting things.&lt;/li&gt;
&lt;li&gt;Repeat&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Do you have side projects you've never finished? &lt;br&gt;
How long does it usually take you to finish a side project?&lt;br&gt;
Would love to hear feedback / thoughts :)&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>productivity</category>
      <category>sideprojects</category>
      <category>philosophy</category>
    </item>
  </channel>
</rss>
