<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Sam (NBTX)</title>
    <description>The latest articles on DEV Community by Sam (NBTX) (@samjakob).</description>
    <link>https://dev.to/samjakob</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%2F81560%2Fc1e5dfa1-324d-4741-9615-a408ceb26fc9.png</url>
      <title>DEV Community: Sam (NBTX)</title>
      <link>https://dev.to/samjakob</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/samjakob"/>
    <language>en</language>
    <item>
      <title>Install Windows 11 (UEFI) via USB 3.0 Drive</title>
      <dc:creator>Sam (NBTX)</dc:creator>
      <pubDate>Wed, 08 Jun 2022 18:45:03 +0000</pubDate>
      <link>https://dev.to/samjakob/install-windows-11-uefi-via-usb-30-drive-mdd</link>
      <guid>https://dev.to/samjakob/install-windows-11-uefi-via-usb-30-drive-mdd</guid>
      <description>&lt;p&gt;This article is &lt;a href="https://blog.samjakob.com/install-windows-11-uefi-via-usb-30-drive"&gt;imported from my blog&lt;/a&gt;. Please let me know if there are any issues (broken links or grammatical errors due to missing characters) by commenting on this article.&lt;/p&gt;




&lt;p&gt;Installing Windows 11 in UEFI mode from a USB 3.0 drive proves to be rather painful but luckily sticking together a few different workarounds for various issues sourced from all over the internet makes it possible - though rather convoluted...&lt;/p&gt;




&lt;h2&gt;
  
  
  Problem 1: Creating the bootable USB drive: FAT32 File System limit
&lt;/h2&gt;

&lt;p&gt;Right off the bat, creating the drive manually &lt;em&gt;instantly&lt;/em&gt; presents a problem; the Windows Imaging file for installation, &lt;code&gt;install.wim&lt;/code&gt; is too large (&amp;gt; 4GiB) for the FAT32 partition that the installation media boots from so a direct image cannot be made.&lt;/p&gt;

&lt;p&gt;Therefore, the image file needs to be split up into multiple split .wim files (SWMs). On macOS this can be done with &lt;code&gt;wimlib&lt;/code&gt; as follows (for other systems there are undoubtably guides available and on Windows the Media Creation tool can presumably be used):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;See below for an explanation.&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;open windows.iso &lt;span class="c"&gt;# Replace windows.iso with the name of the installation ISO.&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; /Volumes/CCCOMA_X64FRE_EN-US_DV9 &lt;span class="c"&gt;# Replace with the name of the mounted volume from the ISO.&lt;/span&gt;
rsync &lt;span class="nt"&gt;-avh&lt;/span&gt; &lt;span class="nt"&gt;--progress&lt;/span&gt; &lt;span class="nt"&gt;--exclude&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sources/install.wim /Volumes/CCCOMA_X64FRE_EN-US_DV9/ /Volumes/WINDOWS

&lt;span class="c"&gt;# If you don't already have it installed, you will need to install wimlib with `brew install wimlib`&lt;/span&gt;
wimlib-imagex &lt;span class="nb"&gt;split&lt;/span&gt; /Volumes/CCCOMA_X64FRE_EN-US_DV9/sources/install.wim /Volumes/WINDOWS/sources/install.swm 3800

&lt;span class="nb"&gt;sudo &lt;/span&gt;diskutil unmountDisk /Volumes/WINDOWS
&lt;span class="nb"&gt;sudo &lt;/span&gt;diskutil unmountDisk /Volumes/CCCOMA_X64FRE_EN-US_DV9
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Format your USB drive to have a FAT32 partition with GPT (GUID Partition Table) in Disk Utility. This guide assumes you named the drive &lt;code&gt;WINDOWS&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Mount the Windows Installation ISO with &lt;code&gt;open windows.iso&lt;/code&gt; in a Terminal window, where &lt;code&gt;windows.iso&lt;/code&gt; is the Windows Installation ISO downloaded from Microsoft.&lt;/li&gt;
&lt;li&gt;Using Terminal, switch to the mounted ISO, which will be something like &lt;code&gt;/Volumes/CCCOMA_X64FRE_EN-US_DV9&lt;/code&gt; (&lt;code&gt;ls /Volumes&lt;/code&gt; will allow you to check, but this guide will assume the aforementioned name).&lt;/li&gt;
&lt;li&gt;Copy the contents of the Windows Installation ISO (excluding the &lt;code&gt;install.wim&lt;/code&gt; file to the USB drive): &lt;code&gt;rsync -avh --progress --exclude=sources/install.wim /Volumes/CCCOMA_X64FRE_EN-US_DV9/ /Volumes/WINDOWS&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Install &lt;code&gt;wimlib&lt;/code&gt; with &lt;a href="https://brew.sh/"&gt;Homebrew&lt;/a&gt; - a tool used to split the WIM file: &lt;code&gt;brew install wimlib&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Split the image with &lt;code&gt;wimlib&lt;/code&gt; (this should yield two files, named similarly to &lt;code&gt;install.wim&lt;/code&gt; and &lt;code&gt;install1.wim&lt;/code&gt; on the USB drive): &lt;code&gt;wimlib-imagex split /Volumes/CCCOMA_X64FRE_EN-US_DV9/sources/install.wim /Volumes/WINDOWS/sources/install.swm 3800&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Finally unmount both the USB drive and the ISO file: &lt;code&gt;sudo diskutil unmountDisk /Volumes/WINDOWS&lt;/code&gt; and &lt;code&gt;sudo diskutil unmountDisk /Volumes/CCCOMA_X64FRE_EN-US_DV9&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;...and with that you're ready to begin attempting to install Windows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem 2: Installing Windows from the USB drive
&lt;/h2&gt;

&lt;p&gt;With this done, I encountered another issue - USB 3.0 flash drives seem to cause problems with the Windows Setup wizard's checks as to whether Windows can be installed despite selection of a valid partition. (Presumably because Windows is unable to tell if a partition/volume belongs to a removable drive or not without the necessary drivers.)&lt;/p&gt;

&lt;p&gt;I eventually managed to workaround this issue by performing installation manually with the &lt;code&gt;DISM&lt;/code&gt; (Deployment Image Servicing and Management) tool included in the Windows Installation media.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Boot your Windows Installation Media in UEFI mode, enter your language and region settings and click 'Install Windows'.&lt;/li&gt;
&lt;li&gt;Remove all the partitions on the target drive and click 'New' to create a new partition to install Windows. (Or, alternatively, tweak as desired ensuring that there is a FAT32 EFI system partition of at least 128MiB and an NTFS Windows installation partition of at least 52 GiB.)&lt;/li&gt;
&lt;li&gt;If you are able to select the Windows installation partition and click 'Next', do so and complete setup normally. Otherwise, continue following this section.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  2.1: Mounting the target installation disk
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Press Shift + F10 to open a Command Prompt window and enter &lt;code&gt;diskpart&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Identify the target disk to install Windows onto with &lt;code&gt;list disk&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Select the target disk with &lt;code&gt;sel disk 0&lt;/code&gt; (where &lt;code&gt;0&lt;/code&gt; is the disk number from &lt;code&gt;list disk&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Identify the partition numbers on the target disk with &lt;code&gt;list part&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Select the EFI boot partition (FAT32-formatted and 128MiB in size) with &lt;code&gt;sel part 1&lt;/code&gt; (where &lt;code&gt;1&lt;/code&gt; is the partition number from &lt;code&gt;list part&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Assign the letter &lt;code&gt;S:&lt;/code&gt; with &lt;code&gt;assign letter=s&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Select the Windows installation partition (NTFS formatted and at least 52GiB in size) with &lt;code&gt;sel part 3&lt;/code&gt; (where &lt;code&gt;3&lt;/code&gt; is the partition number from &lt;code&gt;list part&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Assign the letter &lt;code&gt;W:&lt;/code&gt; with &lt;code&gt;assign letter=w&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Exit &lt;code&gt;diskpart&lt;/code&gt; with &lt;code&gt;exit&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  2.2: Installing Windows with &lt;a href="https://docs.microsoft.com/en-us/windows-hardware/manufacture/desktop/what-is-dism?view=windows-11"&gt;DISM (Deployment Image Servicing and Management)&lt;/a&gt;
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;List the editions contained on the Windows Installation USB with &lt;code&gt;dism&lt;/code&gt; and identify the index of the edition you wish to install:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight batchfile"&gt;&lt;code&gt;&lt;span class="nb"&gt;dism&lt;/span&gt; &lt;span class="na"&gt;/Get-WimInfo /WimFile&lt;/span&gt;&lt;span class="nl"&gt;:X&lt;/span&gt;:\sources\install.swm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Perform the Windows installation by applying the &lt;code&gt;install*.swm&lt;/code&gt; image(s) with &lt;code&gt;dism&lt;/code&gt; (where &lt;code&gt;X:\&lt;/code&gt; is the Windows Installation USB and where the &lt;code&gt;/Index&lt;/code&gt; - in this case &lt;code&gt;4&lt;/code&gt; is the edition index identified from before):
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight batchfile"&gt;&lt;code&gt;&lt;span class="nb"&gt;dism&lt;/span&gt; &lt;span class="na"&gt;/Apply-Image /ImageFile&lt;/span&gt;&lt;span class="nl"&gt;:X&lt;/span&gt;:\sources\install.swm &lt;span class="na"&gt;/SWMFile&lt;/span&gt;&lt;span class="nl"&gt;:X&lt;/span&gt;:\sources\install&lt;span class="o"&gt;*&lt;/span&gt;.swm &lt;span class="na"&gt;/ApplyDir&lt;/span&gt;&lt;span class="nl"&gt;:W&lt;/span&gt;: &lt;span class="na"&gt;/Index&lt;/span&gt;:4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Wait for the operation to finish. If this was successful you should see "The operation completed successfully." printed once the progress bar reaches 100%.&lt;/li&gt;
&lt;li&gt;Add the boot files to the EFI boot partition and add a boot entry using &lt;code&gt;bcdboot&lt;/code&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight batchfile"&gt;&lt;code&gt;&lt;span class="nb"&gt;bcdboot&lt;/span&gt; &lt;span class="kd"&gt;W&lt;/span&gt;:\Windows &lt;span class="na"&gt;/S &lt;/span&gt;&lt;span class="kd"&gt;S&lt;/span&gt;: &lt;span class="na"&gt;/F &lt;/span&gt;&lt;span class="kd"&gt;ALL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Wait for the operation to finish. If this was successful you should see "Boot files successfully created."&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;...once you've completed the above you can close the Command Prompt window by typing &lt;code&gt;exit&lt;/code&gt;, then click the close button on the installer and, again, click close (now on the Install/Repair screen) which should prompt you to say that continuing may cause the machine to reboot. Confirm this (and if the machine does not reboot, hold down the power button to shut down your machine and manually power it back on). As soon as the machine shuts down, remove the USB installation drive.&lt;/p&gt;

&lt;p&gt;When the machine boots, you should be booted into your new Windows installation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem 3: Activation
&lt;/h2&gt;

&lt;p&gt;Even after all of the above, in my case I ended up with an 'S version' of Windows and the Activation section of Settings was both failing to load and missing the option to exit S mode.&lt;/p&gt;

&lt;p&gt;Pressing Win + R and entering &lt;code&gt;cmd&lt;/code&gt; launched a dialog that then allowed me to exit S mode.&lt;/p&gt;

&lt;p&gt;After which, launching &lt;code&gt;cmd&lt;/code&gt; and using the &lt;code&gt;slmgr /ipk &amp;lt;product key&amp;gt;&lt;/code&gt; command, where &lt;code&gt;&amp;lt;product key&amp;gt;&lt;/code&gt; is your Windows product activation key to manually install the product key, followed by restarting the machine allowed me to activate the machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap up
&lt;/h2&gt;

&lt;p&gt;It's unclear to me why exactly these complications have not been ironed out (perhaps with the exception of Problem 3) and hopefully they'll be fixed in future but I believe it's useful to have the steps noted down somewhere in case they're needed.&lt;/p&gt;

&lt;p&gt;With the above, you should now have Windows 11 successfully installed and activated. If you notice any mistakes or oversights please be sure to leave a comment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://alexlubbock.com/bootable-windows-usb-on-mac"&gt;https://alexlubbock.com/bootable-windows-usb-on-mac&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gist.github.com/bmatcuk/fda5ab0fb127e9fd62eaf43e845a51c3"&gt;https://gist.github.com/bmatcuk/fda5ab0fb127e9fd62eaf43e845a51c3&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.trishtech.com/2021/10/how-to-clean-install-windows-11-using-dism-on-any-hdd-ssd/amp/"&gt;https://www.trishtech.com/2021/10/how-to-clean-install-windows-11-using-dism-on-any-hdd-ssd/amp/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>install</category>
      <category>windows11</category>
      <category>uefi</category>
      <category>usb</category>
    </item>
    <item>
      <title>Automatically build your Flutter app APKs with Travis-CI</title>
      <dc:creator>Sam (NBTX)</dc:creator>
      <pubDate>Thu, 20 Jun 2019 19:22:40 +0000</pubDate>
      <link>https://dev.to/samjakob/automatically-build-your-flutter-app-apks-with-travis-ci-4ha0</link>
      <guid>https://dev.to/samjakob/automatically-build-your-flutter-app-apks-with-travis-ci-4ha0</guid>
      <description>&lt;p&gt;This article is &lt;a href="https://blog.samjakob.com/automatically-build-your-flutter-apps-with-travis-ci-4c1e47a5ae69"&gt;imported from my blog&lt;/a&gt;. Please let me know if there are any issues (broken links or grammatical errors due to missing characters) by commenting on this article.&lt;/p&gt;




&lt;p&gt;When attempting to setup &lt;a href="https://travis-ci.com/"&gt;Travis-CI&lt;/a&gt;, I saw more than a few articles on running Flutter tests with Travis, but none for actually building and &lt;strong&gt;obtaining&lt;/strong&gt; a compiled APK as we wanted.&lt;/p&gt;

&lt;p&gt;Whenever changes are pushed to the GitHub repository a new build is automatically generated by Travis and a link to the APK on &lt;a href="https://wetransfer.com/"&gt;WeTransfer&lt;/a&gt; is posted to a Discord channel for supporters and beta testers allowing them to comment on the new build.&lt;/p&gt;

&lt;p&gt;In this article I'm going to comment on how we achieved this workflow, as well as giving some general advice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Create Travis Configuration
&lt;/h2&gt;

&lt;p&gt;Start by creating the following directory structure. You only need to include the &lt;code&gt;.travis/&lt;/code&gt; directory (also in the root) if you plan to add additional build scripts to your project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;your_project_root/&lt;/span&gt;
    &lt;span class="s"&gt;- .travis.yml&lt;/span&gt;
    &lt;span class="s"&gt;- .travis/&lt;/span&gt;
        &lt;span class="s"&gt;- utils/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In your &lt;code&gt;.travis.yml&lt;/code&gt; you need to add some basic boilerplate configuration:&lt;em&gt;I didnt set the language to Android because it didnt seem to setup the SDK correctly, plus using node as the language allowed us to easily set up additional build scripts.&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;os&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;linux&lt;/span&gt;

&lt;span class="c1"&gt;# The Ubuntu Trusty release on Travis is known to&lt;/span&gt;
&lt;span class="c1"&gt;# have oraclejdk8 available. For some reason, we couldn't&lt;/span&gt;
&lt;span class="c1"&gt;# get this to work with other distributions/releases.&lt;/span&gt;
&lt;span class="na"&gt;dist&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;trusty&lt;/span&gt;
&lt;span class="na"&gt;jdk&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;oraclejdk8&lt;/span&gt;

&lt;span class="c1"&gt;# We set language to Node.js (JavaScript) for the sake&lt;/span&gt;
&lt;span class="c1"&gt;# of making creating utility scripts later on easier.&lt;/span&gt;
&lt;span class="na"&gt;language&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node_js&lt;/span&gt;
&lt;span class="na"&gt;node_js&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;12"&lt;/span&gt;

&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;global&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ANDROID_SDK_ROOT=/opt/android&lt;/span&gt;

&lt;span class="na"&gt;sudo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;required&lt;/span&gt;

&lt;span class="na"&gt;addons&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;apt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Flutter depends on /usr/lib/x86_64-linux-gnu/libstdc++.so.6 version GLIBCXX_3.4.18&lt;/span&gt;
    &lt;span class="na"&gt;sources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ubuntu-toolchain-r-test&lt;/span&gt; &lt;span class="c1"&gt;# if we don't specify this, the libstdc++6 we get is the wrong version&lt;/span&gt;
    &lt;span class="na"&gt;packages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;lib32stdc++6&lt;/span&gt; &lt;span class="c1"&gt;# https://github.com/flutter/flutter/issues/6207&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;libstdc++6&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;curl&lt;/span&gt;

&lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;directories&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;$HOME/.pub-cache&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;node_modules&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we need to setup our initial &lt;code&gt;before_script&lt;/code&gt; stage. This will execute commands to setup Gradle, the Android SDK and Flutter.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;before_script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Setup gradle.&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;wget https://services.gradle.org/distributions/gradle-4.10.3-bin.zip&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;unzip -qq gradle-4.10.3-bin.zip&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;export GRADLE_HOME=`pwd`/gradle-4.10.3&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;export PATH=$GRADLE_HOME/bin:$PATH&lt;/span&gt;

  &lt;span class="c1"&gt;# (Quick fix: Silence sdkmanager warning)&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mkdir -p /home/travis/.android&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;echo 'count=0' &amp;gt; /home/travis/.android/repositories.cfg&lt;/span&gt;

  &lt;span class="c1"&gt;# Download and setup Android SDK tools.&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;wget https://dl.google.com/android/repository/sdk-tools-linux-4333796.zip&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mkdir android-sdk-tools&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;unzip -qq sdk-tools-linux-4333796.zip -d android-sdk-tools&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;export PATH=`pwd`/android-sdk-tools/tools/bin:$PATH&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mkdir -p $ANDROID_SDK_ROOT&lt;/span&gt;

  &lt;span class="c1"&gt;# This will install the Android SDK 28 using the previously installed SDK tools.&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;yes | sdkmanager --sdk_root=$ANDROID_SDK_ROOT "tools" "build-tools;28.0.3" "extras;android;m2repository" &amp;gt; /dev/null&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;export PATH=${ANDROID_SDK_ROOT}/tools/bin:$PATH&lt;/span&gt;

  &lt;span class="c1"&gt;# List sdkmanager packages&lt;/span&gt;
  &lt;span class="c1"&gt;# (useful when checking the logs)&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;sdkmanager --list&lt;/span&gt;

  &lt;span class="c1"&gt;# Clone Flutter&lt;/span&gt;
  &lt;span class="c1"&gt;# We clone the Flutter beta branch. You should clone whatever branch&lt;/span&gt;
  &lt;span class="c1"&gt;# you know works for building production apps.&lt;/span&gt;
  &lt;span class="c1"&gt;# If in doubt, you are advised to use the stable branch of Flutter&lt;/span&gt;
  &lt;span class="c1"&gt;# for production apps and you would do this by changing -b beta to -b stable&lt;/span&gt;
  &lt;span class="c1"&gt;# but we started the project before stable existed and whilst beta has always&lt;/span&gt;
  &lt;span class="c1"&gt;# worked reasonably well for us and we find stable is usually too outdated&lt;/span&gt;
  &lt;span class="c1"&gt;# and has too many missing framework features.&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;git clone https://github.com/flutter/flutter.git -b beta --depth &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;

  &lt;span class="c1"&gt;# Add Flutter to the PATH environment variable.&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;export PATH=`pwd`/flutter/bin:`pwd`/flutter/bin/cache/dart-sdk/bin:$PATH&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, to get our fundamental configuration working, we of course have to actually execute &lt;code&gt;flutter build&lt;/code&gt;. Which we do in the &lt;code&gt;script&lt;/code&gt; stage.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Prints the flutter version&lt;/span&gt;
  &lt;span class="c1"&gt;# (allows you to ensure, for each build, that Flutter is set up correctly.)&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;flutter doctor -v&lt;/span&gt;

  &lt;span class="c1"&gt;# Run Flutter build&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./flutter/bin/flutter build apk&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Upload the finished build to WeTransfer
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--otGvnriZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1628740457513/iVceJnS1f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--otGvnriZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1628740457513/iVceJnS1f.png" alt="WeTransfer Upload Screen" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Obviously, a key part of using Travis to build the compiled APK is being able to obtain the compiled APK. For this we use &lt;a href="https://wetransfer.com/"&gt;WeTransfer&lt;/a&gt;, a well-designed free (temporary) file sharing service with an API; exactly what we need.&lt;/p&gt;

&lt;p&gt;Register an account for the &lt;a href="https://developers.wetransfer.com/"&gt;WeTransfer Public API&lt;/a&gt; (developers.wetransfer.com) and under &lt;strong&gt;My apps&lt;/strong&gt; , click Create new application. Enter your applications details and copy the API key.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create an environment variable for your key.&lt;/strong&gt; Go to the Travis page for your repository and click More options; in the dropdown choose Settings and under environment variables add a new environment variable called &lt;code&gt;WT_API_KEY&lt;/code&gt; and paste in the key you copied previously. &lt;em&gt;Do &lt;strong&gt;not&lt;/strong&gt; enable the option to Display value in log file, this will expose your WeTransfer API key to anyone viewing your build logs.&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;after_success&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Export commit info&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;export AUTHOR_NAME=`git log -1 "$TRAVIS_COMMIT" --pretty="%aN"`&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;export COMMITTER_NAME=`git log -1 "$TRAVIS_COMMIT" --pretty="%cN"`&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;export COMMIT_SUBJECT=`git log -1 "$TRAVIS_COMMIT" --pretty="%s"`&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;export COMMIT_MESSAGE=`git log -1 "$TRAVIS_COMMIT" --pretty="%b"`&lt;/span&gt;
  &lt;span class="c1"&gt;# Upload to WeTransfer&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;npm install --save @wetransfer/js-sdk&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;export BUILD_OUTPUT_URL=`node ./.travis/utils/runUpload.js`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In order to give the file a clear name, we extract useful commit information from git (note; using the Travis commit reference.)&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;*$TRAVIS_COMMIT&lt;/code&gt;* is an automatic environment variable, exported by Travis at the start of a build. It refers to the git commit hash that is currently being built by Travis.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then, we install the WeTransfer SDK using &lt;code&gt;npm&lt;/code&gt; and call our own script:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;APPLICATION_NAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ApolloTV&lt;/span&gt;&lt;span class="dl"&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;fs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fs&lt;/span&gt;&lt;span class="dl"&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;createWTClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@wetransfer/js-sdk&lt;/span&gt;&lt;span class="dl"&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;commit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TRAVIS_COMMIT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;substring&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;6&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;jobName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TRAVIS_JOB_NUMBER&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;buildName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TRAVIS_BUILD_NUMBER&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;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;COMMIT_SUBJECT&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;author&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AUTHOR_NAME&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;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`Job: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;jobName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, Build: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;buildName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\n\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; (&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;author&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="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="p"&gt;(){&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wtClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createWTClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;WT_API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;appBinaryContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&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="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./build/app/outputs/apk/release/app-release.apk&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;error&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="o"&gt;=&amp;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;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&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="nf"&gt;resolve&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="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;transfer&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;wtClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transfer&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;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="na"&gt;files&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;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;APPLICATION_NAME&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; Build &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;buildName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; - &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.apk`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;appBinaryContent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;appBinaryContent&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="c1"&gt;// This is required; this is so you can obtain the URL in your Travis/bash scripts.&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;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;transfer&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="p"&gt;})();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, it just uploads the file in the Flutter build output directory and prints the URL, from there youre ready to go with your compiled APK.&lt;/p&gt;

&lt;p&gt;If you want to stop here, you can just &lt;code&gt;echo&lt;/code&gt; the URL for the APK and then youre able to download the builds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Execute pre and post build web-hooks
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--eFES_10P--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1628740459500/K-Rm5aCiN.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--eFES_10P--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1628740459500/K-Rm5aCiN.png" alt="We have web-hooks that send a message to a channel in our Discord server when a build has started or ended." width="800" height="382"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;We have web-hooks that send a message to a channel in our Discord server when a build has started or ended.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We find its pretty helpful to be alerted when a new build starts and finishes, however we also wanted to give our supporters access to cutting-edge builds as soon as theyre pushed to GitHub and we can use our web-hooks to notify supporters when a new build is starting as well as when it is finished and available to download.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;before_install&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Execute Travis prebuild webhook.&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./.travis/10_prebuild.sh $WEBHOOK_URL&lt;/span&gt;

&lt;span class="na"&gt;after_success&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Execute success procedure of postbuild webhook script.&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./.travis/40_postbuild.sh success $WEBHOOK_URL $BUILD_OUTPUT_URL&lt;/span&gt;

&lt;span class="na"&gt;after_failure&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Execute failure procedure of postbuild webhook script.&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./.travis/40_postbuild.sh failure $WEBHOOK_URL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;You may have noticed above that we have given numeric prefixes to each of our build scripts. This is because in our full configuration, we have further scripts to prepare our app build. This includes injecting a configuration into the app source code and checking translations.&lt;/em&gt;&lt;em&gt;You can check out our actual build configuration on the &lt;a href="https://github.com/ApolloTVofficial/kamino/blob/master/.travis.yml"&gt;Kamino GitHub repository.&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In Discord, click the cog to edit a channel and then click Webhooks. Choose Create Webhook, give it a name such as Travis Build and copy the URL, then click Save.&lt;/p&gt;

&lt;p&gt;In Travis, create an environment variable (see Step 2 for instructions on how to do this) for your web-hook URL. Again, &lt;em&gt;do &lt;strong&gt;not&lt;/strong&gt; enable display value in log file, this will allow anyone to send messages to your Discord channel with the web-hook.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Finally, you need to create the &lt;code&gt;prebuild&lt;/code&gt; and &lt;code&gt;postbuild&lt;/code&gt; scripts; these should go in the &lt;code&gt;.travis&lt;/code&gt; folder in the root of your project (that you created in Step 1):&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="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="nv"&gt;TIMESTAMP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; +%FT%TZ&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="nv"&gt;AUTHOR_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git log &lt;span class="nt"&gt;-1&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TRAVIS_COMMIT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--pretty&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"%aN"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;COMMITTER_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git log &lt;span class="nt"&gt;-1&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TRAVIS_COMMIT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--pretty&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"%cN"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;COMMIT_SUBJECT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git log &lt;span class="nt"&gt;-1&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TRAVIS_COMMIT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--pretty&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"%s"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;COMMIT_MESSAGE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git log &lt;span class="nt"&gt;-1&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TRAVIS_COMMIT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--pretty&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"%b"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nv"&gt;WEBHOOK_DATA&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{
    "username": "ApolloTV (Travis)",
    "content": "A build has started.\n\n[Job #'&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TRAVIS_JOB_NUMBER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s1"&gt;' (Build #'&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TRAVIS_BUILD_NUMBER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s1"&gt;') '&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$STATUS_MESSAGE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s1"&gt;' ('&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$AUTHOR_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s1"&gt;') - '&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TRAVIS_REPO_SLUG&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s1"&gt;'\n\n'&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$COMMIT_SUBJECT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s1"&gt;']('&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TRAVIS_BUILD_WEB_URL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s1"&gt;')"
}'&lt;/span&gt;

&lt;span class="o"&gt;(&lt;/span&gt;curl &lt;span class="nt"&gt;--fail&lt;/span&gt; &lt;span class="nt"&gt;--progress-bar&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; Content-Type:application/json &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$WEBHOOK_DATA&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;n[Webhook]: Successfully sent the webhook."&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;n[Webhook]: Unable to send webhook."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="nv"&gt;STATUS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nv"&gt;TIMESTAMP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; +%FT%TZ&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$STATUS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"success"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nv"&gt;EMBED_COLOR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3066993
    &lt;span class="nv"&gt;STATUS_MESSAGE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Passed"&lt;/span&gt;
&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$STATUS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"failure"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nv"&gt;EMBED_COLOR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;15158332
    &lt;span class="nv"&gt;STATUS_MESSAGE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Failed"&lt;/span&gt;
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nv"&gt;WEBHOOK_DATA&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{
  "username": "ApolloTV (Travis)",
  "content": "@BuildNotify A new build has completed",
  "embeds": [ {
    "color": '&lt;/span&gt;&lt;span class="nv"&gt;$EMBED_COLOR&lt;/span&gt;&lt;span class="s1"&gt;',
    "author": {
      "name": "Job #'&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TRAVIS_JOB_NUMBER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s1"&gt;' (Build #'&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TRAVIS_BUILD_NUMBER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s1"&gt;') '&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$STATUS_MESSAGE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s1"&gt;' - '&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TRAVIS_REPO_SLUG&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s1"&gt;'",
      "url": "'&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TRAVIS_BUILD_WEB_URL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s1"&gt;'"
    },
    "title": "'&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$COMMIT_SUBJECT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s1"&gt;'",
    "url": "'&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$URL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s1"&gt;'",
    "description": " **Build Information** : '&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;COMMIT_MESSAGE&lt;/span&gt;&lt;span class="p"&gt;//&lt;/span&gt;&lt;span class="s1"&gt;$'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="p"&gt;/ &lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;n&lt;span class="se"&gt;\\&lt;/span&gt;n&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CREDITS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s1"&gt;'",
    "fields": [
      {
        "name": "Commit",
        "value": "'&lt;/span&gt;&lt;span class="s2"&gt;"[&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TRAVIS_COMMIT&lt;/span&gt;:0:7&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;](https://github.com/&lt;/span&gt;&lt;span class="nv"&gt;$TRAVIS_REPO_SLUG&lt;/span&gt;&lt;span class="s2"&gt;/commit/&lt;/span&gt;&lt;span class="nv"&gt;$TRAVIS_COMMIT&lt;/span&gt;&lt;span class="s2"&gt;)"&lt;/span&gt;&lt;span class="s1"&gt;'",
        "inline": true
      },
      {
        "name": "Branch",
        "value": "'&lt;/span&gt;&lt;span class="s2"&gt;"[&lt;/span&gt;&lt;span class="nv"&gt;$TRAVIS_BRANCH&lt;/span&gt;&lt;span class="s2"&gt;](https://github.com/&lt;/span&gt;&lt;span class="nv"&gt;$TRAVIS_REPO_SLUG&lt;/span&gt;&lt;span class="s2"&gt;/tree/&lt;/span&gt;&lt;span class="nv"&gt;$TRAVIS_BRANCH&lt;/span&gt;&lt;span class="s2"&gt;)"&lt;/span&gt;&lt;span class="s1"&gt;'",
        "inline": true
      },
      {
        "name": "Files",
        "value": "'&lt;/span&gt;&lt;span class="s2"&gt;"[Download APK](&lt;/span&gt;&lt;span class="nv"&gt;$BUILD_OUTPUT_URL&lt;/span&gt;&lt;span class="s2"&gt;)"&lt;/span&gt;&lt;span class="s1"&gt;'"
      }
    ],
    "timestamp": "'&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TIMESTAMP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s1"&gt;'"
  } ]
}'&lt;/span&gt;

&lt;span class="o"&gt;(&lt;/span&gt;curl &lt;span class="nt"&gt;--fail&lt;/span&gt; &lt;span class="nt"&gt;--progress-bar&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; Content-Type:application/json &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$WEBHOOK_DATA&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;n[Webhook]: Successfully sent the webhook."&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;n[Webhook]: Unable to send webhook."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Feel free to use and customise these scripts as you wish.&lt;/p&gt;

</description>
      <category>ci</category>
      <category>apps</category>
      <category>flutter</category>
      <category>travis</category>
    </item>
    <item>
      <title>Building an in-app updater (OTA) in Flutter</title>
      <dc:creator>Sam (NBTX)</dc:creator>
      <pubDate>Mon, 25 Feb 2019 22:10:28 +0000</pubDate>
      <link>https://dev.to/samjakob/building-an-in-app-updater-ota-in-flutter-11p9</link>
      <guid>https://dev.to/samjakob/building-an-in-app-updater-ota-in-flutter-11p9</guid>
      <description>&lt;p&gt;This article is &lt;a href="https://blog.samjakob.com/building-an-in-app-updater-ota-in-flutter-b0becddee8ca"&gt;imported from my blog&lt;/a&gt;. Please let me know if there are any issues (broken links or grammatical errors due to missing characters) by commenting on this article.&lt;/p&gt;




&lt;p&gt;In many ways, Flutter is a fantastic framework for building cross-platform mobile applications however when it comes to developing features that arent platform-agnostic many people seem to resort to platform channel code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HbQa0r3G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1628740465239/5xb-dn8K4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HbQa0r3G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1628740465239/5xb-dn8K4.png" alt="Example Screenshot of use in application" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I try to keep as much code as possible in Dart for three reasons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;It maintains the portability of my code base; should I need to implement a feature in another platform, I will have little to no code to rewrite.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It reduces the learning curve for our projects; developers only have to know Dart and they dont have to locate and interpret platform channel code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Keep-it-simple-stupid (KISS) methodology; when you start messing around with platform channels you then have to worry about communicating between the Dart code and the platform code. This can get out of hand really quickly when you throw asynchronous operations into the mix.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So, as were focused on keeping our code in Dart, theoretically our main obstacles are that we need to work with files, system permissions and then we need to launch an intent. File support in Dart is actually not a problem and system permissions can be overcome with a handy plugin, however we did have to resort to platform channels for the intent, but thats about 10 lines of simple synchronous code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: System Permissions
&lt;/h3&gt;

&lt;p&gt;Thanks to a Flutter plugin called &lt;a href="https://pub.dartlang.org/packages/simple_permissions"&gt;&lt;code&gt;simple_permissions&lt;/code&gt;&lt;/a&gt; this wasn't much of an issue.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-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:simple_permissions/simple_permissions.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

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

&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;permissionStatus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;SimplePermissions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;checkPermission&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Permission&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;WriteExternalStorage&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="n"&gt;permissionStatus&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;permissionStatus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;SimplePermissions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;requestPermission&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Permission&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;WriteExternalStorage&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;PermissionStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;authorized&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Remember to add the &lt;code&gt;uses-permission&lt;/code&gt; tag to your &lt;code&gt;AndroidManifest.xml&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;uses-permission&lt;/span&gt; &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"android.permission.WRITE_EXTERNAL_STORAGE"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Filesystem
&lt;/h3&gt;

&lt;p&gt;Whilst theoretically a challenge because of the platform-agnostic nature of Flutter, between the built in &lt;code&gt;dart:io&lt;/code&gt; library and the &lt;code&gt;path-provider&lt;/code&gt; plugin, Flutter actually provides an excellent API for manipulating files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-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;'dart:io'&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:path_provider/path_provider.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:http/http.dart'&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Don't forget to check that you have Filesystem permissions or this will fail!&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FileIO&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;downloadDirReference&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/.apollo"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;///&lt;/span&gt;
  &lt;span class="c1"&gt;/// Download and install the app at [url]. &lt;/span&gt;
  &lt;span class="c1"&gt;/// You should call this method *after* querying your server for any available updates&lt;/span&gt;
  &lt;span class="c1"&gt;/// and getting the download link for the update.&lt;/span&gt;
  &lt;span class="c1"&gt;///&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;runInstallProcedure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;async&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="o"&gt;/&lt;/span&gt;
    &lt;span class="cm"&gt;/* Setup and clean directories */&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="c1"&gt;// Instantiate a directory object&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;downloadDir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Directory&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="n"&gt;getExternalStorageDirectory&lt;/span&gt;&lt;span class="p"&gt;())&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;downloadDirReference&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Create the directory if it doesn't already exist.&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;await&lt;/span&gt; &lt;span class="n"&gt;downloadDir&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;downloadDir&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Instantiate a file object (in this case update.apk within our download folder)&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;downloadFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="si"&gt;${downloadDir.path}&lt;/span&gt;&lt;span class="s"&gt;/update.apk"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Delete the file if it already exists.&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;await&lt;/span&gt; &lt;span class="n"&gt;downloadFile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;downloadFile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;delete&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="o"&gt;/&lt;/span&gt;
    &lt;span class="cm"&gt;/* Download the APK */&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="c1"&gt;// Instantiate an HTTP client&lt;/span&gt;
    &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Client&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Make a request and get the response bytes.&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// (The link to your APK goes here)&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bodyBytes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Write the response bytes to our download file.&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;downloadFile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;writeAsBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// TODO: Trigger intent.&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;The key thing you probably noticed is that in Dart, you use the&lt;code&gt;Directory&lt;/code&gt; class to refer to a directory, and the &lt;code&gt;File&lt;/code&gt; class to refer to a file; in my opinion, this is much more logical and aptly-named than it is in Java.&lt;/p&gt;

&lt;p&gt;Everything is pretty self-explanatory and downloading files is an absolute breeze with Darts built in libraries.&lt;/p&gt;

&lt;h3&gt;
  
  
  NOTE: Android N support
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Whilst nothing to do with Flutter, I've included this as it did take a bit of digging for me to get set up.&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;manifest&lt;/span&gt; &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;application&amp;gt;&lt;/span&gt;

    &lt;span class="c"&gt;&amp;lt;!-- ... --&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;provider&lt;/span&gt;
      &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"xyz.apollotv.kamino.OTAFileProvider"&lt;/span&gt;
      &lt;span class="na"&gt;android:authorities=&lt;/span&gt;&lt;span class="s"&gt;"xyz.apollotv.kamino.provider"&lt;/span&gt;
      &lt;span class="na"&gt;android:exported=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt;
      &lt;span class="na"&gt;android:grantUriPermissions=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

      &lt;span class="c"&gt;&amp;lt;!-- The @xml/filepaths file (see below) is located at /android/app/src/main/res/xml/filepaths.xml
            relative to the Flutter project root. --&amp;gt;&lt;/span&gt;

      &lt;span class="nt"&gt;&amp;lt;meta-data&lt;/span&gt;
          &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"android.support.FILE_PROVIDER_PATHS"&lt;/span&gt;
          &lt;span class="na"&gt;android:resource=&lt;/span&gt;&lt;span class="s"&gt;"@xml/filepaths"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/provider&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;/application&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/manifest&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;xyz.apollotv.kamino&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;android.support.v4.content.FileProvider&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// You need to reference this FileProvider in your AndroidManifest.xml&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OTAFileProvider&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;FileProvider&lt;/span&gt; &lt;span class="o"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;paths&lt;/span&gt; &lt;span class="na"&gt;xmlns:android=&lt;/span&gt;&lt;span class="s"&gt;"http://schemas.android.com/apk/res/android"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

    &lt;span class="c"&gt;&amp;lt;!-- In our example, the APK is downloaded to the /storage/emulated/0/.apollo/ folder. --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;external-path&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;".apollo"&lt;/span&gt; &lt;span class="na"&gt;path=&lt;/span&gt;&lt;span class="s"&gt;".apollo/"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

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

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

&lt;/div&gt;



&lt;p&gt;Inside your &lt;code&gt;application&lt;/code&gt; tag in your Android manifest file, you should include a &lt;code&gt;provider&lt;/code&gt; tag that references your File Provider class. Inside this tag, you should have a &lt;code&gt;meta-data&lt;/code&gt; tag that lists all of the file paths the provider is allowed to access. (See &lt;code&gt;filepaths.xml&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;For more information about the FileProvider, see &lt;a href="https://developer.android.com/reference/android/support/v4/content/FileProvider"&gt;https://developer.android.com/reference/android/support/v4/content/FileProvider&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Platform Channel
&lt;/h3&gt;

&lt;p&gt;The final step, is to launch an &lt;code&gt;ACTION_INSTALL_PACKAGE&lt;/code&gt; intent. You should begin by setting up a basic platform channel.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;OTAHelper&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;installOTA&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;downloadFile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;


&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OTAHelper&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="c1"&gt;// Replace xyz.apollotv.kamino with your package.&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;platform&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;MethodChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'xyz.apollotv.kamino/ota'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;installOTA&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;async&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="n"&gt;platform&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;invokeMethod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'install'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;dynamic&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"path"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kd"&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="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Error installing update: &lt;/span&gt;&lt;span class="si"&gt;$e&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="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, edit your &lt;code&gt;MainActivity.java&lt;/code&gt; file to declare the &lt;code&gt;MethodChannel&lt;/code&gt; and execute the code to call our intent.&lt;/p&gt;

&lt;p&gt;There arent any particularly advanced concepts here, as weve downloaded the file to external memory, so all we need to do is access it and trigger an installation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MainActivity&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;FlutterActivity&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

  &lt;span class="nd"&gt;@Override&lt;/span&gt;
  &lt;span class="kd"&gt;protected&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Bundle&lt;/span&gt; &lt;span class="n"&gt;savedInstanceState&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

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

    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;MethodChannel&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;getFlutterView&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="s"&gt;"xyz.apollotv.kamino/ota"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;setMethodCallHandler&lt;/span&gt;&lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="n"&gt;methodCall&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;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;methodCall&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;equals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"install"&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;installOTA&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;methodCall&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;argument&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"path"&lt;/span&gt;&lt;span class="o"&gt;))){&lt;/span&gt;
                &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;success&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;else&lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ERROR"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"An error occurred whilst installing OTA updates."&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="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="o"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;notImplemented&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;private&lt;/span&gt; &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="nf"&gt;installOTA&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;path&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="nc"&gt;Uri&lt;/span&gt; &lt;span class="n"&gt;fileUri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Uri&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;parse&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"file://"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

          &lt;span class="nc"&gt;Intent&lt;/span&gt; &lt;span class="n"&gt;intent&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="nc"&gt;Build&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;VERSION&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;SDK_INT&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nc"&gt;Build&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;VERSION_CODES&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;N&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

              &lt;span class="c1"&gt;// This line is important: after Android N, an authority must be provided to access files for an app.&lt;/span&gt;
              &lt;span class="nc"&gt;Uri&lt;/span&gt; &lt;span class="n"&gt;apkUri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OTAFileProvider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getUriForFile&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;getApplicationContext&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="s"&gt;"xyz.apollotv.kamino.provider"&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;File&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

              &lt;span class="n"&gt;intent&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;Intent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Intent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ACTION_INSTALL_PACKAGE&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
              &lt;span class="n"&gt;intent&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="n"&gt;apkUri&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
              &lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setFlags&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Intent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;FLAG_GRANT_READ_URI_PERMISSION&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
          &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
              &lt;span class="n"&gt;intent&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;Intent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Intent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ACTION_VIEW&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
              &lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setDataAndType&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fileUri&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"application/vnd.android.package-archive"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
              &lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setFlags&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Intent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;FLAG_ACTIVITY_NEW_TASK&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
          &lt;span class="o"&gt;}&lt;/span&gt;

          &lt;span class="n"&gt;getApplicationContext&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;startActivity&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
          &lt;span class="k"&gt;return&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;catch&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;){&lt;/span&gt;
          &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"[Platform] Error during OTA installation."&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
          &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getMessage&lt;/span&gt;&lt;span class="o"&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="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;And with that, the OTA installation is started!&lt;/p&gt;

</description>
      <category>updater</category>
      <category>flutter</category>
      <category>android</category>
      <category>apps</category>
    </item>
  </channel>
</rss>
