<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Flutter on mm-dev</title>
    <link>https://mm-dev.rocks/tags/flutter/</link>
    <description>Recent content in Flutter on mm-dev</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-gb</language>
    <lastBuildDate>Thu, 08 Aug 2024 17:47:33 +0100</lastBuildDate><atom:link href="https://mm-dev.rocks/tags/flutter/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Set up Vim for Flutter</title>
      <link>https://mm-dev.rocks/posts/flutter-in-the-terminal/set-up-vim-for-flutter/</link>
      <pubDate>Thu, 08 Aug 2024 17:47:33 +0100</pubDate>
      <guid>https://mm-dev.rocks/posts/flutter-in-the-terminal/set-up-vim-for-flutter/</guid>
      <description>&lt;p&gt;There are various ways of installing Vim plugins. I use &lt;code&gt;vim-plug&lt;/code&gt; in my examples here but I&amp;rsquo;ll add links to instructions for each plugin so you can do it your own way.&lt;/p&gt;
&lt;h2 id=&#34;dart&#34;&gt;Dart&lt;/h2&gt;
&lt;p&gt;As Flutter uses the Dart language, install &lt;a href = &#34;https://github.com/dart-lang/dart-vim-plugin&#34; target = &#34;_blank&#34; rel = &#34;nofollow noopener noreferrer&#34;&gt;Dart support for Vim&lt;/a&gt;. I add the following to my &lt;code&gt;~/.vimrc&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-vim&#34; data-lang=&#34;vim&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c&#34;&gt;&amp;#34; Between the &amp;#39;plug#begin()&amp;#39; and &amp;#39;plug#end()&amp;#39; lines&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;Plug&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;dart-lang/dart-vim-plugin&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then I restart Vim and run &lt;code&gt;:PlugInstall&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This adds syntax highlighting and auto-indentation of Dart code.&lt;/p&gt;
&lt;h2 id=&#34;language-servers&#34;&gt;Language Servers&lt;/h2&gt;
&lt;p&gt;Language Servers/LSPs (LSP = Language Server Protocol but both terms are often used interchangeably) give your editor information about the structure of a language and enable it to do syntax highlighting and code completion, along with deeper functionality such as allowing you to jump to the definition of a function, do refactoring etc.&lt;/p&gt;
&lt;p&gt;LSPs are used in Visual Studio for a lot of its advanced functionality but they can also be used in Vim. There&amp;rsquo;s more than one way of doing this but I use &lt;a href = &#34;https://github.com/neoclide/coc.nvim&#34; target = &#34;_blank&#34; rel = &#34;nofollow noopener noreferrer&#34;&gt;coc-nvim&lt;/a&gt; (despite the name it works in modern versions of Vim, not just Neovim).&lt;/p&gt;
&lt;h3 id=&#34;install-the-cocnvim-plugin-in-vim&#34;&gt;Install the &lt;code&gt;coc.nvim&lt;/code&gt; plugin in Vim&lt;/h3&gt;
&lt;p&gt;Using &lt;code&gt;vim-plug&lt;/code&gt; I add the following to my &lt;code&gt;~/.vimrc&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-vim&#34; data-lang=&#34;vim&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c&#34;&gt;&amp;#34; Between the &amp;#39;plug#begin()&amp;#39; and &amp;#39;plug#end()&amp;#39; lines&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;Plug&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;neoclide/coc.nvim&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; {&lt;span class=&#34;s1&#34;&gt;&amp;#39;branch&amp;#39;&lt;/span&gt;: &lt;span class=&#34;s1&#34;&gt;&amp;#39;release&amp;#39;&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Again, I restart Vim and run &lt;code&gt;:PlugInstall&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For other ways of installing just follow the &lt;a href = &#34;https://github.com/neoclide/coc.nvim/wiki/Install-coc.nvim&#34; target = &#34;_blank&#34; rel = &#34;nofollow noopener noreferrer&#34;&gt;instructions &lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;install-a-flutter-lsp&#34;&gt;Install a Flutter LSP&lt;/h3&gt;
&lt;p&gt;Install the Flutter language server in CoC, &lt;a href = &#34;https://github.com/iamcco/coc-flutter&#34; target = &#34;_blank&#34; rel = &#34;nofollow noopener noreferrer&#34;&gt;coc-flutter&lt;/a&gt;. This provides lots of helpful functionality for working with Flutter in Vim as described in the link.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-vim&#34; data-lang=&#34;vim&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;CocInstall&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;coc&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;flutter&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This LSP needs to know where the Flutter SDK is installed. In Vim, run &lt;code&gt;:CocList FlutterSDKs&lt;/code&gt; and make sure it finds at least 1 SDK. If not, add a line to CoC settings (&lt;code&gt;:CocConfig&lt;/code&gt;):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;flutter.sdk.path&amp;#34;&lt;/span&gt;: &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$HOME&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;/.local/flutter/bin&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can find the correct path with &lt;code&gt;which flutter&lt;/code&gt; in the terminal.&lt;br&gt;
&lt;em&gt;Originally, I had installed my Flutter SDK at &lt;code&gt;/usr/bin&lt;/code&gt;. That caused invisible errors with &lt;code&gt;coc-flutter&lt;/code&gt; as it couldn&amp;rsquo;t access the path properly to find the Flutter SDK. I was having trouble getting this step working. I then moved the Flutter SDK to &lt;code&gt;$HOME/.local/flutter&lt;/code&gt; and everything started to work properly&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&#34;cocnvim-settings&#34;&gt;&lt;code&gt;coc.nvim&lt;/code&gt; settings&lt;/h2&gt;
&lt;p&gt;To change CoC settings the command &lt;code&gt;:CocConfig&lt;/code&gt; can be used in Vim. On my system it opens up the file &lt;code&gt;~/.vim/coc-settings.json&lt;/code&gt;. You can open/edit this file directly but depending on your Vim configuration it may be in a different location.&lt;/p&gt;
&lt;p&gt;Here are some of the settings I prefer:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;diagnostic.checkCurrentLine&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;diagnostic.enableMessage&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;jump&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;colors.enable&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;suggest.noselect&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;inlayHint&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;diagnosticcheckcurrentline-true&#34;&gt;&lt;code&gt;&amp;quot;diagnostic.checkCurrentLine&amp;quot;: true,&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;By default, Coc will show you a diagnostic error or warning when your cursor is over the word causing the problem. Setting this to &lt;code&gt;true&lt;/code&gt; means you only have to be on the same line as the error to see the dialog, not the exact word.&lt;/p&gt;
&lt;h3 id=&#34;diagnosticenablemessage-jump&#34;&gt;&lt;code&gt;&amp;quot;diagnostic.enableMessage&amp;quot;: &amp;quot;jump&amp;quot;,&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;This diagnostic popups can be really helpful, but it&amp;rsquo;s all a bit frenetic for me, I&amp;rsquo;m a &lt;code&gt;prefers-reduced-motion&lt;/code&gt; kind of person. Setting this to &lt;code&gt;jump&lt;/code&gt; means they don&amp;rsquo;t pop up automatically, but can still be shown when I want to see them. I also add this line to &lt;code&gt;~/.vimrc&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-vim&#34; data-lang=&#34;vim&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;nmap&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;silent&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;gh&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Plug&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;coc&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;float&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;hide&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;nmap&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;silent&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;gl&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Plug&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;coc&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;diagnostic&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;info&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Which allows me to show/hide the diagnostics with &lt;code&gt;gh&lt;/code&gt;/&lt;code&gt;gl&lt;/code&gt; respectively.&lt;/p&gt;
&lt;h3 id=&#34;colorsenable-true&#34;&gt;&lt;code&gt;&amp;quot;colors.enable&amp;quot;: true,&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;This setting is for another CoC addition called &lt;code&gt;coc-highlight&lt;/code&gt;. I installed this in Vim with:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-vim&#34; data-lang=&#34;vim&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;CocInstall&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;coc&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;highlight&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This adds various types of colour highlighting, but the one I was most interested in is how it will detect any colours (eg RGB or hex) in your code and change the colour of their text to give you a preview of them. This feature is enabled with the &lt;code&gt;colors.enable&lt;/code&gt; line as above.&lt;/p&gt;
&lt;h3 id=&#34;suggestnoselect-true&#34;&gt;&lt;code&gt;&amp;quot;suggest.noselect&amp;quot;: true,&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;When you start typing a word, CoC will default to inserting its first suggested autocompletion word. This is a bit more &amp;lsquo;help&amp;rsquo; than I like, so I add this setting to make the process more intentional.&lt;/p&gt;
&lt;h3 id=&#34;inlayhint-false&#34;&gt;&lt;code&gt;&amp;quot;inlayHint&amp;quot;: false,&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;By default CoC inserts &lt;em&gt;inlay hints&lt;/em&gt;, for example if you haven&amp;rsquo;t added a type hint to indicate what kind of widget is being referred to somewhere, it will insert the &lt;code&gt;&amp;lt;Widget&amp;gt;&lt;/code&gt; hint for you as a piece of magical ghost text.&lt;/p&gt;
&lt;p&gt;These bizarre bits of text appear in the middle of lines of code, except they aren&amp;rsquo;t really there, you can&amp;rsquo;t select them or delete them and if you&amp;rsquo;re moving your cursor around it acts as if they don&amp;rsquo;t exist. I&amp;rsquo;m sure some people enjoy them, and for those people I&amp;rsquo;m glad that they exist. For me, they are disgusting, obnoxious devil-warts.&lt;/p&gt;
&lt;p&gt;Thankfully, they can be vanquished by adding this line to the CoC settings.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Install Flutter</title>
      <link>https://mm-dev.rocks/posts/flutter-in-the-terminal/install-flutter/</link>
      <pubDate>Thu, 08 Aug 2024 17:47:32 +0100</pubDate>
      <guid>https://mm-dev.rocks/posts/flutter-in-the-terminal/install-flutter/</guid>
      <description>&lt;h3 id=&#34;follow-instructions-from&#34;&gt;Follow instructions from &lt;a href = &#34;https://docs.flutter.dev/get-started/install/linux/android&#34; target = &#34;_blank&#34; rel = &#34;nofollow noopener noreferrer&#34;&gt;https://docs.flutter.dev/get-started/install/linux/android&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Download the &lt;code&gt;flutter_*.tar.xz&lt;/code&gt; archive&lt;/li&gt;
&lt;li&gt;Unpack the archive into &lt;code&gt;$HOME/.local/bin&lt;/code&gt; (creates &lt;code&gt;$HOME/.local/bin/flutter/bin&lt;/code&gt;)&lt;br&gt;
&lt;em&gt;At first I tried putting this at &lt;code&gt;/usr/bin&lt;/code&gt;, but that caused invisible errors in coc-flutter as it couldn&amp;rsquo;t access the path properly to find the Flutter SDK&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Add the new path as above to $PATH eg in &lt;code&gt;~/.profile&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# ~/.profile&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;PATH&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$HOME&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;/.local/bin/flutter/bin:&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$PATH&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;For ARM64 architectures the Flutter SDK cannot be found at the above link, nor in the archives. In this case the SDK can be obtained by cloning the main flutter repo, then running a &lt;code&gt;flutter&lt;/code&gt; command to trigger a download of dependencies&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;git clone -b main https://github.com/flutter/flutter.git
./flutter/bin/flutter --version
&lt;/code&gt;&lt;/pre&gt;&lt;/blockquote&gt;
&lt;h3 id=&#34;agree-to-more-licenses-and-get-flutter-doctor-to-pass&#34;&gt;Agree to more licenses and get Flutter doctor to pass&lt;/h3&gt;
&lt;p&gt;First, agree to several laughably long licence agreements which Google knows full well that nobody reads.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;flutter doctor --android-licenses
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then type Y and Enter for each prompt&lt;/p&gt;
&lt;p&gt;Now run &lt;code&gt;flutter doctor&lt;/code&gt;. It should pass every test except &amp;lsquo;Android Studio (not installed)&amp;rsquo;. If so, we&amp;rsquo;re ready to start building Flutter apps (with Android Studio not installed).&lt;/p&gt;
&lt;h3 id=&#34;sample-project&#34;&gt;Sample project&lt;/h3&gt;
&lt;p&gt;You can make a really simple sample project to check that everything&amp;rsquo;s installed correctly and see how it works.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;flutter create sample-project
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This will create a &lt;code&gt;sample-project&lt;/code&gt; directory with a Flutter app in it, a simple built-in demo of a button which counts up as you tap it.&lt;/p&gt;
&lt;p&gt;To run the app:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;cd&lt;/span&gt; sample_project
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;flutter run
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description>
    </item>
    
    <item>
      <title>Install the Android SDK</title>
      <link>https://mm-dev.rocks/posts/flutter-in-the-terminal/install-the-android-sdk/</link>
      <pubDate>Thu, 08 Aug 2024 17:47:31 +0100</pubDate>
      <guid>https://mm-dev.rocks/posts/flutter-in-the-terminal/install-the-android-sdk/</guid>
      <description>&lt;p&gt;Getting the Android SDK set up manually can be tricky, I think the expectation is that most people will use Android Studio so not a lot of work has been put in to making manual installation user-friendly.&lt;/p&gt;
&lt;h3 id=&#34;pick-a-directory-where-youll-keep-the-sdk&#34;&gt;Pick a directory where you&amp;rsquo;ll keep the SDK&lt;/h3&gt;
&lt;p&gt;This is an important directory that will always store all the Android-SDK specific tools, platforms, build tools etc. Various bits and pieces need to know where these things are, so we&amp;rsquo;ll set some environment variables for them later.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m using &lt;code&gt;$HOME/android_sdk/&lt;/code&gt; in these examples.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;mkdir &lt;span class=&#34;nv&#34;&gt;$HOME&lt;/span&gt;/android_sdk
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;install-the-sdk-manager&#34;&gt;Install the SDK Manager&lt;/h3&gt;
&lt;p&gt;This is a set of command line tools used to install different versions of the SDK (for when you want to target different Android devices).&lt;/p&gt;
&lt;p&gt;Based on Google&amp;rsquo;s instructions at &lt;a href = &#34;https://developer.android.com/tools/sdkmanager&#34; target = &#34;_blank&#34; rel = &#34;nofollow noopener noreferrer&#34;&gt;https://developer.android.com/tools/sdkmanager&lt;/a&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;From the &lt;a href = &#34;https://developer.android.com/studio&#34; target = &#34;_blank&#34; rel = &#34;nofollow noopener noreferrer&#34;&gt;downloads page&lt;/a&gt; (I had to scroll down a long way), download the &lt;code&gt;cmdline-tools&lt;/code&gt; zip and unzip it to the directory you created above eg &lt;code&gt;$HOME/android_sdk/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Inside the unzipped directory &lt;code&gt;cmdline-tools&lt;/code&gt; make a &amp;rsquo;latest&amp;rsquo; directory and move all of the other contents of &amp;lsquo;cmdline-tools&amp;rsquo; inside it (so you end up with &lt;code&gt;cmdline-tools/latest/bin&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You should end up with something like the following (I&amp;rsquo;m using &lt;code&gt;tree&lt;/code&gt; to list the directory contents but don&amp;rsquo;t worry about that, just make sure your directories are in the right places):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ tree -L &lt;span class=&#34;m&#34;&gt;2&lt;/span&gt; cmdline-tools/
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;cmdline-tools/
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;└── latest
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    ├── NOTICE.txt
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    ├── bin
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    ├── lib
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    └── source.properties
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;add-some-environment-variables-to-profile&#34;&gt;Add some environment variables to &lt;code&gt;~/.profile&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Make sure these make sense based on what we&amp;rsquo;ve done so far if you&amp;rsquo;ve used different directories to me.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# ~/.profile&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;ANDROID_SDK_ROOT&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$HOME&lt;/span&gt;/android_sdk
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;PATH&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$PATH&lt;/span&gt;:&lt;span class=&#34;nv&#34;&gt;$ANDROID_SDK_ROOT&lt;/span&gt;/cmdline-tools/latest/bin
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;PATH&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$PATH&lt;/span&gt;:&lt;span class=&#34;nv&#34;&gt;$ANDROID_SDK_ROOT&lt;/span&gt;/platform-tools
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Source the profile with &lt;code&gt;source ~/.profile&lt;/code&gt;. Then run &lt;code&gt;sdkmanager&lt;/code&gt; and see if it does something. If you get &lt;code&gt;command not found&lt;/code&gt; double-check everything and try rebooting.&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;&lt;span&gt;My sdkmanager binary name clash (this won&amp;rsquo;t affect you but click to expand if you&amp;rsquo;re interested)&lt;/span&gt;&lt;/summary&gt;

&lt;div&gt;
&lt;p&gt;In my case I already had a binary installed with the name &lt;code&gt;sdkmanager&lt;/code&gt;, as that&amp;rsquo;s the name Garmin use for their ConnectIQ SDK Manager. Apparently neither Garmin nor Google could foresee that anyone other than themselves might call their SDK manager software &amp;lsquo;sdkmanager&amp;rsquo;.&lt;/p&gt;
&lt;p&gt;So I had to do some hacky stuff with bash aliases in &lt;code&gt;~/.profile&lt;/code&gt; to let the two live alongside each other, and remind me of the situation:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;alias&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;sdkmanager&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;echo &amp;#34;Use either *sdkmanager-garmin* or *sdkmanager-android*&amp;#34;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;alias&lt;/span&gt; sdkmanager-android&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$ANDROID_SDK_ROOT&lt;/span&gt;/cmdline-tools/latest/bin/sdkmanager
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;alias&lt;/span&gt; sdkmanager-garmin&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$HOME&lt;/span&gt;/.local/bin/sdkmanager
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;/details&gt;
&lt;h3 id=&#34;accept-sdk-licences&#34;&gt;Accept SDK licences&lt;/h3&gt;
&lt;p&gt;The SDK licences must be accepted for everything to work.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sdkmanager --licenses
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then type Y and press Enter for each prompt&lt;/p&gt;
&lt;h3 id=&#34;install-the-android-toolchain&#34;&gt;Install the Android toolchain&lt;/h3&gt;
&lt;p&gt;To pass the &amp;lsquo;Android toolchain&amp;rsquo; step of &lt;code&gt;flutter doctor&lt;/code&gt; (in a bit) we need 3 things:&lt;/p&gt;
&lt;ol start=&#34;0&#34;&gt;
&lt;li&gt;Platform tools (generic)&lt;/li&gt;
&lt;li&gt;A valid platform&lt;/li&gt;
&lt;li&gt;Build tools to match the platform&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Various instructions online will tell you to do things like &lt;code&gt;sdkmanager &amp;quot;platform-tools&amp;quot; &amp;quot;platforms;android-33&amp;quot;&lt;/code&gt; (ie pass multiple arguments to &lt;code&gt;sdkmanager&lt;/code&gt;), but as observed in &lt;a href = &#34;https://stackoverflow.com/questions/76650927/failed-to-find-platform-sdk-with-path-platformsandroid-33&#34; target = &#34;_blank&#34; rel = &#34;nofollow noopener noreferrer&#34;&gt;this StackOverflow answer&lt;/a&gt; &lt;em&gt;if using the command line tools, the platform tools need to be installed via separate commands ie:&lt;/em&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sdkmanager &lt;span class=&#34;s1&#34;&gt;&amp;#39;platform-tools&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sdkmanager &lt;span class=&#34;s1&#34;&gt;&amp;#39;platforms;android-33&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sdkmanager &lt;span class=&#34;s1&#34;&gt;&amp;#39;build-tools;33.0.2&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If the installation has worked correctly you should end up with something like:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ tree -L &lt;span class=&#34;m&#34;&gt;1&lt;/span&gt; android_sdk/
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;android_sdk/
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── build-tools
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── cmdline-tools
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── licenses
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── platform-tools
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;└── platforms
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;details&gt;
&lt;summary&gt;&lt;span&gt;Another weird, me-specific problem I encountered&amp;hellip;&lt;/span&gt;&lt;/summary&gt;

&lt;div&gt;
&lt;p&gt;At first, my installations weren&amp;rsquo;t appearing in my &lt;code&gt;android_sdk&lt;/code&gt; directory and Flutter tools were complaining about not being able to find the Android SDK.&lt;/p&gt;
&lt;p&gt;Forcing the SDK path like this worked:
&lt;code&gt;sdkmanager --verbose &amp;quot;platform-tools&amp;quot; --sdk_root=$HOME/android_sdk/&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;But something smelt wrong, as the original &lt;code&gt;sdkmanager&lt;/code&gt; install commands seemed to be completing without error. I investigated this by running the following:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sdkmanager &lt;span class=&#34;s1&#34;&gt;&amp;#39;platform-tools&amp;#39;&lt;/span&gt; --verbose
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Which revealed that they were going into:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;/data/data/com.termux/files/usr/share/android-sdk/
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is because I was on Android using Termux, in a proot container, and I had previously installed the SDK in the main (non proot) Termux environment. This was not needed any more so I deleted the other installation.&lt;/p&gt;
&lt;p&gt;This is an exotic situation and you&amp;rsquo;re probably not as stupid as I am to get into this situation, but maybe this note will help in some other situation.&lt;/p&gt;
&lt;/div&gt;
&lt;/details&gt;
</description>
    </item>
    
    <item>
      <title>Dependencies and Environment</title>
      <link>https://mm-dev.rocks/posts/flutter-in-the-terminal/dependencies-and-environment/</link>
      <pubDate>Thu, 08 Aug 2024 17:47:30 +0100</pubDate>
      <guid>https://mm-dev.rocks/posts/flutter-in-the-terminal/dependencies-and-environment/</guid>
      <description>&lt;p&gt;To get started I&amp;rsquo;m presuming you have a Linux installation of something like Debian/Ubuntu.&lt;/p&gt;
&lt;h2 id=&#34;general-dependencies&#34;&gt;General Dependencies&lt;/h2&gt;
&lt;h3 id=&#34;git&#34;&gt;Git&lt;/h3&gt;
&lt;p&gt;You probably already have this but just in case:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo apt install git
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;ensure-java-jdk-is-installed-and-recent-enough&#34;&gt;Ensure Java JDK is installed and recent enough&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo apt install openjdk-17-jdk
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;stuff-flutter-needs&#34;&gt;Stuff Flutter Needs&lt;/h3&gt;
&lt;p&gt;The latest info about this should be &lt;a href = &#34;https://docs.flutter.dev/get-started/install/linux/android&#34; target = &#34;_blank&#34; rel = &#34;nofollow noopener noreferrer&#34;&gt;here&lt;/a&gt; but at time of writing:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo apt-get install -y curl git unzip xz-utils zip libglu1-mesa
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And some other tools Flutter uses:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo apt install clang cmake ninja-build
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;if-using-chromium-browser-instead-of-chrome&#34;&gt;If using Chromium browser instead of Chrome&lt;/h3&gt;
&lt;p&gt;The Android SDK likes to know where Chrome is on your system. I use Chromium and the SDK can&amp;rsquo;t find it.&lt;/p&gt;
&lt;p&gt;So I export an environment variable to let Flutter know where to find Chromium eg in &lt;code&gt;~/.profile&lt;/code&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;CHROME_EXECUTABLE&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;/usr/bin/chromium
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description>
    </item>
    
    <item>
      <title>Parts of the SDK</title>
      <link>https://mm-dev.rocks/posts/flutter-in-the-terminal/parts-of-the-sdk/</link>
      <pubDate>Thu, 08 Aug 2024 17:47:30 +0100</pubDate>
      <guid>https://mm-dev.rocks/posts/flutter-in-the-terminal/parts-of-the-sdk/</guid>
      <description>&lt;p&gt;There are several components making up the SDK. I&amp;rsquo;ll list them here along with examples of how they can be installed later on when the SDK manager is set up and working.&lt;/p&gt;
&lt;p&gt;This is just an overview to help your mental model of what&amp;rsquo;s what, we&amp;rsquo;re not installing anything just yet.&lt;/p&gt;
&lt;p&gt;We download them as a zip and place them in a specific directory, as detailed later.&lt;/p&gt;
&lt;h3 id=&#34;command-line-tools&#34;&gt;Command Line Tools&lt;/h3&gt;
&lt;p&gt;These commands can do several things like signing and optimising APKs, but the main one we are interested in is &lt;code&gt;sdkmanager&lt;/code&gt;. This is the command we use to install the other tools.&lt;/p&gt;
&lt;h3 id=&#34;platform-tools&#34;&gt;Platform Tools&lt;/h3&gt;
&lt;p&gt;These are tools for interfacing with Android which you may already be familiar with, such as &lt;code&gt;adb&lt;/code&gt; and &lt;code&gt;fastboot&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sdkmanager &lt;span class=&#34;s1&#34;&gt;&amp;#39;platform-tools&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;platforms&#34;&gt;Platforms&lt;/h3&gt;
&lt;p&gt;A platform represents a specific version of Android. It includes multiple system images, which the device emulator uses to emulate various devices on that platform.&lt;/p&gt;
&lt;p&gt;Information used to compile your app for this platform version, and source code to be used for debugging are also included.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Multiple platforms can be installed alongside each other on the same system.&lt;/em&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sdkmanager &lt;span class=&#34;s1&#34;&gt;&amp;#39;platforms;android-33&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;build-tools&#34;&gt;Build Tools&lt;/h3&gt;
&lt;p&gt;These are tools used for building Android apps. I&amp;rsquo;m not 100% clear on what these are and why they are different from Platform Tools, it seems that they were part of Platform Tools in the past but have been sectioned off into their own thing.&lt;/p&gt;
&lt;p&gt;Anyway, we need them. Usually use the latest version, but sometimes if you&amp;rsquo;re targeting specific platforms eg older ones, errors may indicate that you need an older version.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sdkmanager &lt;span class=&#34;s1&#34;&gt;&amp;#39;build-tools;33.0.2&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description>
    </item>
    
    <item>
      <title>Intro</title>
      <link>https://mm-dev.rocks/posts/flutter-in-the-terminal/intro/</link>
      <pubDate>Thu, 08 Aug 2024 17:47:29 +0100</pubDate>
      <guid>https://mm-dev.rocks/posts/flutter-in-the-terminal/intro/</guid>
      <description>&lt;p&gt;Given the choice, I&amp;rsquo;d rather avoid huge complex apps like Android Studio. They use a lot of system resources and are very GUI/point-and-clicky. Those things definitely have their place but I prefer keyboard control where it makes sense, and for me programming in Flutter is one of those situations.&lt;/p&gt;
&lt;p&gt;A lot of what Android Studio does behind the scenes is automatically editing text files and running shell commands such as &lt;code&gt;flutter run&lt;/code&gt;, &lt;code&gt;flutter build&lt;/code&gt; and so on. If you&amp;rsquo;re comfortable with the terminal these commands are fairly simple to run manually.&lt;/p&gt;
&lt;p&gt;As an IDE Android Studio also helps with stuff like syntax highlighting, code completion, refactoring etc. If you&amp;rsquo;re into Vim it can be set up to give you these pleasantries. I&amp;rsquo;m sure there is stuff Android Studio can do that Vim can&amp;rsquo;t, but whatever it is I&amp;rsquo;m not missing it and I feel good when I use Vim.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve set up a couple of systems with this Flutter dev environment recently and hit some stumbling blocks. They weren&amp;rsquo;t too hard to resolve but I wanted to write up the process for future reference and to help anybody else in a similar situation.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>BendyStraw (Android App)</title>
      <link>https://mm-dev.rocks/posts/bendystraw-android-app/</link>
      <pubDate>Tue, 10 Oct 2023 13:17:20 +0100</pubDate>
      <guid>https://mm-dev.rocks/posts/bendystraw-android-app/</guid>
      <description>&lt;p&gt;If you don&amp;rsquo;t already know, &lt;a href = &#34;https://newpipe.net/&#34; target = &#34;_blank&#34; rel = &#34;nofollow noopener noreferrer&#34;&gt;NewPipe&lt;/a&gt; is a privacy-friendly, ad-free Android app for accessing YouTube. It&amp;rsquo;s a great piece of software and I use it all the time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NewPipe&lt;/strong&gt; works without an account. You can subscribe to channels, save playlists and do other useful things, but everything is saved locally on your device (and I love that it works like this).&lt;/p&gt;
&lt;p&gt;I use &lt;strong&gt;NewPipe&lt;/strong&gt; on several Android devices. Over time I&amp;rsquo;ve ended up with different playlists and subscriptions on each device, and sometimes I can&amp;rsquo;t be sure which device I need to use to find a certain song or video. To help with this I made &lt;strong&gt;BendyStraw&lt;/strong&gt;.&lt;/p&gt;
&lt;ul class=&#39;gallery&#39;&gt;&lt;li&gt;
      &lt;input type=&#39;checkbox&#39; id=&#39;item_OIkr_1&#39; /&gt;
      &lt;label for=&#39;item_OIkr_1&#39; aria-label=&#39;Expand image&#39; tabindex=&#39;0&#39;&gt;&lt;img src=&#39;https://mm-dev.rocks/images/bendy-straw/bendystraw-grab-3.webp&#39; alt=&#39;Multiple databases in colour-coded tabs&#39; /&gt;
        &lt;span&gt;Multiple databases in colour-coded tabs&lt;/span&gt;&lt;/label&gt;
    &lt;/li&gt;&lt;li&gt;
      &lt;input type=&#39;checkbox&#39; id=&#39;item_OIkr_2&#39; /&gt;
      &lt;label for=&#39;item_OIkr_2&#39; aria-label=&#39;Expand image&#39; tabindex=&#39;0&#39;&gt;&lt;img src=&#39;https://mm-dev.rocks/images/bendy-straw/bendystraw-grab-1.webp&#39; alt=&#39;Copy streams from one playlist to another&#39; /&gt;
        &lt;span&gt;Copy streams from one playlist to another&lt;/span&gt;&lt;/label&gt;
    &lt;/li&gt;&lt;li&gt;
      &lt;input type=&#39;checkbox&#39; id=&#39;item_OIkr_3&#39; /&gt;
      &lt;label for=&#39;item_OIkr_3&#39; aria-label=&#39;Expand image&#39; tabindex=&#39;0&#39;&gt;&lt;img src=&#39;https://mm-dev.rocks/images/bendy-straw/bendystraw-grab-2.webp&#39; alt=&#39;Selecting streams to delete, copy or move&#39; /&gt;
        &lt;span&gt;Selecting streams to delete, copy or move&lt;/span&gt;&lt;/label&gt;
    &lt;/li&gt;&lt;li&gt;
      &lt;input type=&#39;checkbox&#39; id=&#39;item_OIkr_4&#39; /&gt;
      &lt;label for=&#39;item_OIkr_4&#39; aria-label=&#39;Expand image&#39; tabindex=&#39;0&#39;&gt;&lt;img src=&#39;https://mm-dev.rocks/images/bendy-straw/bendystraw-grab-5.webp&#39; alt=&#39;Create new playlists&#39; /&gt;
        &lt;span&gt;Create new playlists&lt;/span&gt;&lt;/label&gt;
    &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;BendyStraw&lt;/strong&gt; imports &lt;code&gt;NewPipeData-*.zip&lt;/code&gt; files and lets you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Open multiple &lt;code&gt;zip&lt;/code&gt;s at the same time, so you can combine data from several devices&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Delete&lt;/code&gt; &lt;code&gt;Copy&lt;/code&gt; &lt;code&gt;Move&lt;/code&gt; &lt;code&gt;Rename&lt;/code&gt; your custom playlists&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Delete&lt;/code&gt; &lt;code&gt;Copy&lt;/code&gt; &lt;code&gt;Move&lt;/code&gt; streams from one playlist to another&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Delete&lt;/code&gt; &lt;code&gt;Copy&lt;/code&gt; &lt;code&gt;Move&lt;/code&gt; channel subscriptions between databases&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Delete&lt;/code&gt; &lt;code&gt;Copy&lt;/code&gt; &lt;code&gt;Move&lt;/code&gt; remote (bookmarked) playlists&lt;/li&gt;
&lt;li&gt;Re-order playlists, sorting the streams by &lt;code&gt;Title&lt;/code&gt;, &lt;code&gt;Channel&lt;/code&gt; or &lt;code&gt;Length&lt;/code&gt; (just tap the column headings in the tables)&lt;/li&gt;
&lt;li&gt;Streams (audio/video) can be opened directly from &lt;strong&gt;BendyStraw&lt;/strong&gt;, as the URLs are clickable
&lt;ul&gt;
&lt;li&gt;If you set video links (in your Android settings) to open in &lt;strong&gt;NewPipe&lt;/strong&gt; you can make a split-screen view and jump around your playlists&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Export playlist as raw text, for example to be used with &lt;a href = &#34;https://github.com/yt-dlp/yt-dlp&#34; target = &#34;_blank&#34; rel = &#34;nofollow noopener noreferrer&#34;&gt;yt-dlp&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Dark/light themes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After editing simply export a new &lt;code&gt;zip&lt;/code&gt; file, then import it back into &lt;strong&gt;NewPipe&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id=&#34;installation&#34;&gt;Installation&lt;/h2&gt;
&lt;h3 id=&#34;f-droid&#34;&gt;F-Droid&lt;/h3&gt;
&lt;p&gt;Search for &amp;lsquo;bendystraw&amp;rsquo; (all one word) in the F-Droid app, or find it at &lt;a href = &#34;https://f-droid.org/en/packages/rocks.mm_dev.BendyStraw/&#34; target = &#34;_blank&#34; rel = &#34;nofollow noopener noreferrer&#34;&gt;https://f-droid.org/en/packages/rocks.mm_dev.BendyStraw/&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;direct-download&#34;&gt;Direct download&lt;/h3&gt;
&lt;p&gt;The apks are also directly available on the Codeberg &lt;a href = &#34;https://codeberg.org/mm-dev/bendy-straw/releases&#34; target = &#34;_blank&#34; rel = &#34;nofollow noopener noreferrer&#34;&gt;releases page&lt;/a&gt;.&lt;/p&gt;






&lt;div class=&#34;expand-all-toggle&#34;&gt;
    &lt;label for=&#34;expandAllCheckbox_1ZU7&#34;&gt;&lt;h2 id=&#34;how-to-use&#34;&gt;How to Use&lt;/h2&gt;
&lt;span&gt;Expand all&lt;/span&gt;
      &lt;input id=&#34;expandAllCheckbox_1ZU7&#34; type=&#34;checkbox&#34; onchange=&#34;setChildExpandedStates_1ZU7();&#34;/&gt;
    &lt;/label&gt;
  
  &lt;script&gt;
    function setChildExpandedStates_1ZU7() {
      var checkbox_el = document.getElementById(&#39;expandAllCheckbox_1ZU7&#39;);
      var all = checkbox_el.parentNode.parentNode.getElementsByTagName(&#39;details&#39;);
      for (i = 0; i &lt; all.length; i++) { 
        all[i].open = checkbox_el.checked;
      }
    }
    window.addEventListener(&#39;load&#39;, () =&gt; {
      document.getElementById(&#39;expandAllCheckbox_1ZU7&#39;).parentNode.parentNode.classList.add(&#39;js-visible&#39;);
      setChildExpandedStates_1ZU7();
    });
  &lt;/script&gt;

&lt;details&gt;
&lt;summary&gt;&lt;span&gt;&lt;h4 id=&#34;1-export-your-database-from-newpipe&#34;&gt;1. Export your database from &lt;strong&gt;NewPipe&lt;/strong&gt;&lt;/h4&gt;
&lt;/span&gt;&lt;/summary&gt;

&lt;div&gt;
&lt;ul&gt;
&lt;li&gt;In &lt;strong&gt;NewPipe&lt;/strong&gt; tap &lt;code&gt;Settings&lt;/code&gt; &lt;code&gt;Backup and restore&lt;/code&gt; &lt;code&gt;Export database&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;This will create a file on your device with a name like &lt;code&gt;NewPipeData-2023-09-01.zip&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/details&gt;

&lt;details&gt;
&lt;summary&gt;&lt;span&gt;&lt;h4 id=&#34;2-import-it-into-bendystraw&#34;&gt;2. Import it into &lt;strong&gt;BendyStraw&lt;/strong&gt;&lt;/h4&gt;
&lt;/span&gt;&lt;/summary&gt;

&lt;div&gt;
&lt;ul&gt;
&lt;li&gt;In &lt;strong&gt;BendyStraw&lt;/strong&gt;, tap the button with the add/plus icon, and select the file you just exported&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/details&gt;


&lt;details&gt;
&lt;summary&gt;&lt;span&gt;&lt;h4 id=&#34;3-edit-with-bendystraw&#34;&gt;3. Edit with &lt;strong&gt;BendyStraw&lt;/strong&gt;&lt;/h4&gt;
&lt;/span&gt;&lt;/summary&gt;

&lt;div&gt;
&lt;p&gt;Custom playlists have icons at the top which you can click to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Delete the playlist&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Copy the playlist&lt;/strong&gt; to another &lt;code&gt;zip&lt;/code&gt; (you need more than one &lt;code&gt;zip&lt;/code&gt; added to do this)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Export the playlist&lt;/strong&gt; as a plain text file, which for example can be used with &lt;a href = &#34;https://github.com/yt-dlp/yt-dlp&#34; target = &#34;_blank&#34; rel = &#34;nofollow noopener noreferrer&#34;&gt;yt-dlp&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Tap on a playlist name to edit&lt;/strong&gt; it, and tap outside (or press return on the keyboard) to finish.&lt;/p&gt;
&lt;/div&gt;
&lt;/details&gt;

&lt;details&gt;
&lt;summary&gt;&lt;span&gt;&lt;h4 id=&#34;4-export-from-bendystraw&#34;&gt;4. Export from &lt;strong&gt;BendyStraw&lt;/strong&gt;&lt;/h4&gt;
&lt;/span&gt;&lt;/summary&gt;

&lt;div&gt;
&lt;ul&gt;
&lt;li&gt;In &lt;strong&gt;BendyStraw&lt;/strong&gt; tap the export button (the arrow icon on the bottom-right of the screen)&lt;/li&gt;
&lt;li&gt;This will create a new file named like the original but with &lt;code&gt;-bendy-straw&lt;/code&gt; at the end, so &lt;code&gt;NewPipeData.zip&lt;/code&gt; is saved as &lt;code&gt;NewPipeData-bendy-straw.zip&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/details&gt;

&lt;details&gt;
&lt;summary&gt;&lt;span&gt;&lt;h4 id=&#34;5-import-it-back-into-newpipe&#34;&gt;5. Import it back into &lt;strong&gt;NewPipe&lt;/strong&gt;&lt;/h4&gt;
&lt;/span&gt;&lt;/summary&gt;

&lt;div&gt;
&lt;ul&gt;
&lt;li&gt;In &lt;strong&gt;NewPipe&lt;/strong&gt; tap &lt;code&gt;Settings&lt;/code&gt; &lt;code&gt;Backup and restore&lt;/code&gt; &lt;code&gt;Import database&lt;/code&gt; and select the file you just saved&lt;/li&gt;
&lt;li&gt;After importing the database, &lt;strong&gt;NewPipe&lt;/strong&gt; will ask if you also want to import settings, if you&amp;rsquo;re not sure what this means just tap &amp;lsquo;cancel&amp;rsquo;, your current &lt;strong&gt;NewPipe&lt;/strong&gt; settings will remain unchanged&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/details&gt;


&lt;/div&gt;

&lt;h2 id=&#34;json-playlist-import&#34;&gt;JSON Playlist Import&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;BendyStraw&lt;/strong&gt; can import playlists from a JSON file. This is a new and basic feature which has some requirements and limitations.&lt;/p&gt;
&lt;h3 id=&#34;show-the-json-import-button&#34;&gt;Show the JSON Import Button&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;The button is hidden by default&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Go to the preferences screen (hamburger menu in top-right corner)&lt;/li&gt;
&lt;li&gt;Tick the &amp;lsquo;Show JSON Import Button&amp;rsquo; box&lt;/li&gt;
&lt;li&gt;Open at least 1 database&lt;br&gt;
&lt;em&gt;The button only shows when there is a database open, as it imports the JSON into the currently-opened database&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;limitations&#34;&gt;Limitations&lt;/h3&gt;
&lt;p&gt;We assume that all playlist entries are &lt;strong&gt;YouTube&lt;/strong&gt; streams.&lt;br&gt;
&lt;em&gt;Although &lt;strong&gt;NewPipe&lt;/strong&gt; is able to handle streams from other services too, for now this import feature is just for &lt;strong&gt;YouTube&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The JSON import format has been designed to prefer simplicity over richness of data. This comes with some pros and cons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Several stream attributes (eg &amp;lsquo;uploader URL&amp;rsquo;, &amp;lsquo;duration&amp;rsquo;, &amp;lsquo;upload date&amp;rsquo;, &amp;lsquo;view count&amp;rsquo;, &amp;rsquo;thumbnail&amp;rsquo;) are omitted initially&lt;/li&gt;
&lt;li&gt;After import into &lt;strong&gt;NewPipe&lt;/strong&gt;, before the playlist has been played, the above details (attributes) will be missing (so eg the thumbnail will just be a grey blank)&lt;/li&gt;
&lt;li&gt;The first time each stream is started, &lt;strong&gt;NewPipe&lt;/strong&gt; will grab the missing details for the stream and fill in the database&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;requirements&#34;&gt;Requirements&lt;/h3&gt;
&lt;p&gt;JSON playlists will be imported into whichever database is currently open in &lt;strong&gt;BendyStraw&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The general (pseudocode) schema is:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;PLAYLIST_NAME&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;ARRAY_OF_STREAMS&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;PLAYLIST_NAME&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;ARRAY_OF_STREAMS&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;err&#34;&gt;etc...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;playlist_name&#34;&gt;&lt;code&gt;PLAYLIST_NAME&lt;/code&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Is expected to be unique (if it isn&amp;rsquo;t, identically-named lists will be combined into 1 playlist)&lt;/li&gt;
&lt;li&gt;If a playlist with exactly the same name already exists in the target database, entries from the JSON will be appended to that existing database&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;array_of_streams&#34;&gt;&lt;code&gt;ARRAY_OF_STREAMS&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;Each stream must be an object with named properties as follows:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I use comments in this explanation but remember comments are not valid JSON &amp;mdash; do not include comments in your JSON&lt;/p&gt;&lt;/blockquote&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;c1&#34;&gt;// These are required
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;stream_type&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;VIDEO_STREAM&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;// or &amp;#39;AUDIO_STREAM&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;title&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Seasonal Affective Disorder&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;url&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;https://www.youtube.com/watch?v=3RJ5GftYT2c&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;uploader&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Hidden Valley Bushcraft&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;// Uploader/channel name,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;c1&#34;&gt;// These below are optional. NewPipe will update them when the stream is loaded, so they only have meaning on first import before the playlist has been played.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;  &lt;span class=&#34;c1&#34;&gt;// It&amp;#39;s probably better to omit them but they are allowed if you want to use them.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;uploader_url&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;https://www.youtube.com/channel/UCB7BPYlL4f5j5R1Y7HUgeVg&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;// Uploader/channel URL 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;duration&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;120&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;// Length of stream in seconds 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;view_count&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;1273621&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;// Number of times the stream has been viewed on YouTube --- simple number ie no commas, abbreviations etc 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;complete-example-json&#34;&gt;Complete Example JSON&lt;/h4&gt;
&lt;p&gt;Below is some complete sample JSON. If this was imported into a database in &lt;strong&gt;BendyStraw&lt;/strong&gt; it would do one of the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create 2 new playlists (&amp;ldquo;Tech Things&amp;rdquo; and &amp;ldquo;Good Songs&amp;rdquo;)&lt;/li&gt;
&lt;li&gt;Or (if playlists with those names already existed) then the streams would be appended to those existing playlists&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;Tech Things&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nt&#34;&gt;&amp;#34;stream_type&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;VIDEO_STREAM&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nt&#34;&gt;&amp;#34;title&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Free software, free society: Richard Stallman at TEDxGeneva 2014&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nt&#34;&gt;&amp;#34;url&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;https://www.youtube.com/watch?v=Ag1AKIl_2GM&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nt&#34;&gt;&amp;#34;uploader&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;TEDx Talks&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nt&#34;&gt;&amp;#34;uploader_url&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;https://www.youtube.com/channel/UCsT0YIqwnpJCM-mx7-gSA4Q&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nt&#34;&gt;&amp;#34;stream_type&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;VIDEO_STREAM&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nt&#34;&gt;&amp;#34;title&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Shenzhen: The Silicon Valley of Hardware (Full Documentary) | Future Cities&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nt&#34;&gt;&amp;#34;url&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;https://www.youtube.com/watch?v=SGJ5cZnoodY&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nt&#34;&gt;&amp;#34;uploader&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Wired UK&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nt&#34;&gt;&amp;#34;uploader_url&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;https://www.youtube.com/@wireduk&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;Good Songs&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nt&#34;&gt;&amp;#34;stream_type&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;VIDEO_STREAM&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nt&#34;&gt;&amp;#34;title&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Aztec Camera - Somewhere In My Heart (Official Music Video)&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nt&#34;&gt;&amp;#34;url&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;https://www.youtube.com/watch?v=2w1Q8ZkXZ1Q&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nt&#34;&gt;&amp;#34;uploader&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;RHINO&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nt&#34;&gt;&amp;#34;uploader_url&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;https://www.youtube.com/channel/UCWEtnEiVwUy7mwFeshyAWLA&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nt&#34;&gt;&amp;#34;stream_type&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;VIDEO_STREAM&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nt&#34;&gt;&amp;#34;title&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Imagine Dragons - Thunder&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nt&#34;&gt;&amp;#34;url&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;https://www.youtube.com/watch?v=fKopy74weus&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nt&#34;&gt;&amp;#34;uploader&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;ImagineDragonsVEVO&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nt&#34;&gt;&amp;#34;uploader_url&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;https://www.youtube.com/channel/UCpx_k19S2vUutWUUM9qmXEg&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nt&#34;&gt;&amp;#34;stream_type&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;VIDEO_STREAM&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nt&#34;&gt;&amp;#34;title&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;𝑰𝒕&amp;#39;𝒔 𝑨 𝑯𝒆𝒂𝒓𝒕𝒂𝒄𝒉𝒆 -Bonnie Tyler (Cover by Yhuan) #gutomversion #hitback #goodvibestambayan #yhuan&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nt&#34;&gt;&amp;#34;url&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;https://www.youtube.com/watch?v=AQCm7VWyg-U&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nt&#34;&gt;&amp;#34;uploader&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Yhuan Official&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nt&#34;&gt;&amp;#34;uploader_url&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;https://www.youtube.com/channel/UCDxVjSFhfiPGispcK9o0qzw&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;





&lt;div class=&#34;expand-all-toggle&#34;&gt;
    &lt;label for=&#34;expandAllCheckbox_fJI7&#34;&gt;&lt;h2 id=&#34;faq&#34;&gt;FAQ&lt;/h2&gt;
&lt;span&gt;Expand all&lt;/span&gt;
      &lt;input id=&#34;expandAllCheckbox_fJI7&#34; type=&#34;checkbox&#34; onchange=&#34;setChildExpandedStates_fJI7();&#34;/&gt;
    &lt;/label&gt;
  
  &lt;script&gt;
    function setChildExpandedStates_fJI7() {
      var checkbox_el = document.getElementById(&#39;expandAllCheckbox_fJI7&#39;);
      var all = checkbox_el.parentNode.parentNode.getElementsByTagName(&#39;details&#39;);
      for (i = 0; i &lt; all.length; i++) { 
        all[i].open = checkbox_el.checked;
      }
    }
    window.addEventListener(&#39;load&#39;, () =&gt; {
      document.getElementById(&#39;expandAllCheckbox_fJI7&#39;).parentNode.parentNode.classList.add(&#39;js-visible&#39;);
      setChildExpandedStates_fJI7();
    });
  &lt;/script&gt;


&lt;details&gt;
&lt;summary&gt;&lt;span&gt;&lt;h4 id=&#34;whats-in-the-zip-files&#34;&gt;What&amp;rsquo;s in the &lt;code&gt;zip&lt;/code&gt; files?&lt;/h4&gt;
&lt;/span&gt;&lt;/summary&gt;

&lt;div&gt;
&lt;p&gt;&lt;strong&gt;NewPipe&lt;/strong&gt; exports a &lt;code&gt;zip&lt;/code&gt; file, containing 2 files:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;newpipe.db&lt;/code&gt;: A database which has all the info &lt;strong&gt;BendyStraw&lt;/strong&gt; uses, such as your playlists and channel subscriptions, plus other things like your watch history&lt;/li&gt;
&lt;li&gt;&lt;code&gt;newpipe.settings&lt;/code&gt;: Your &lt;strong&gt;NewPipe&lt;/strong&gt; settings and preferences&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For simplicity &lt;strong&gt;BendyStraw&lt;/strong&gt; sometimes refers to the &lt;code&gt;zip&lt;/code&gt; file as the database, because that&amp;rsquo;s the file &lt;strong&gt;NewPipe&lt;/strong&gt; works with for imports/exports.&lt;/p&gt;
&lt;/div&gt;
&lt;/details&gt;

&lt;details&gt;
&lt;summary&gt;&lt;span&gt;&lt;h4 id=&#34;is-my-data-private&#34;&gt;Is my data private?&lt;/h4&gt;
&lt;/span&gt;&lt;/summary&gt;

&lt;div&gt;
Yes! This is important to me as a developer. &lt;strong&gt;BendyStraw&lt;/strong&gt; only works with the tables in the database which it requires to let you do your editing. It doesn&amp;rsquo;t look into other things like your settings or watch history and it doesn&amp;rsquo;t send your data to any servers or share it with anybody. There&amp;rsquo;s no tracking and no adverts or anything like that.&lt;/div&gt;
&lt;/details&gt;

&lt;details&gt;
&lt;summary&gt;&lt;span&gt;&lt;h4 id=&#34;what-happens-to-my-newpipe-settings&#34;&gt;What happens to my &lt;strong&gt;NewPipe&lt;/strong&gt; settings?&lt;/h4&gt;
&lt;/span&gt;&lt;/summary&gt;

&lt;div&gt;
&lt;p&gt;&lt;strong&gt;BendyStraw&lt;/strong&gt; doesn&amp;rsquo;t look inside them or change them.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If you edit &lt;code&gt;zip&lt;/code&gt;s from multiple devices, or old/archived &lt;code&gt;zip&lt;/code&gt;s, be aware that your new &lt;code&gt;zip&lt;/code&gt; will have the settings from whichever original file you opened. &lt;em&gt;So be careful not to overwrite settings with an old version.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;When you import your new &lt;code&gt;zip&lt;/code&gt; into &lt;strong&gt;NewPipe&lt;/strong&gt; the simplest thing to do is just tap &amp;lsquo;cancel&amp;rsquo; when it asks if you want to also import settings.&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;/details&gt;

&lt;details&gt;
&lt;summary&gt;&lt;span&gt;&lt;h4 id=&#34;ive-opened-multiple-databases-but-dont-see-enough-tabs&#34;&gt;I&amp;rsquo;ve opened multiple databases but don&amp;rsquo;t see enough tabs&lt;/h4&gt;
&lt;/span&gt;&lt;/summary&gt;

&lt;div&gt;
&lt;p&gt;The tabs can scroll off the edge of the screen, swipe left/right on the tab bar to see any tabs which have overflowed.&lt;/p&gt;
&lt;p&gt;You can also swipe left or right on any blank area to switch tabs.&lt;/p&gt;
&lt;/div&gt;
&lt;/details&gt;

&lt;details&gt;
&lt;summary&gt;&lt;span&gt;&lt;h4 id=&#34;how-do-i-use-an-exported-text-playlist&#34;&gt;How do I use an exported text playlist?&lt;/h4&gt;
&lt;/span&gt;&lt;/summary&gt;

&lt;div&gt;
&lt;p&gt;If you have &lt;a href = &#34;https://github.com/yt-dlp/yt-dlp&#34; target = &#34;_blank&#34; rel = &#34;nofollow noopener noreferrer&#34;&gt;yt-dlp&lt;/a&gt; installed, you can batch download all of the files from the playlist like so:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;In &lt;strong&gt;BendyStraw&lt;/strong&gt; locate the playlist and tap the &amp;rsquo;export&amp;rsquo; button&lt;/li&gt;
&lt;li&gt;Check the dialog to see where the text file will be saved&lt;/li&gt;
&lt;li&gt;In your terminal run &lt;code&gt;yt-dlp --batch-file [path_to_text_file.txt]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For more info check out &lt;a href = &#34;https://github.com/yt-dlp/yt-dlp&#34; target = &#34;_blank&#34; rel = &#34;nofollow noopener noreferrer&#34;&gt;the yt-dlp github&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/details&gt;

&lt;details&gt;
&lt;summary&gt;&lt;span&gt;&lt;h4 id=&#34;can-i-export-an-m3u-playlist&#34;&gt;Can I export an M3U playlist?&lt;/h4&gt;
&lt;/span&gt;&lt;/summary&gt;

&lt;div&gt;
&lt;p&gt;I originally did build this functionality, only to find that links to eg YouTube streams no longer work in &lt;code&gt;m3u&lt;/code&gt; playlists. This is true for the players I tested and to the best of my knowledge.&lt;/p&gt;
&lt;p&gt;If I&amp;rsquo;m wrong about this, you have some other relevant info, or would like to see some other export format, please let me know.&lt;em class=&#34;star&#34;&gt;*&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;d be happy to revisit this if it&amp;rsquo;s useful.&lt;/p&gt;
&lt;p&gt;&lt;em class=&#34;star&#34;&gt;*&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;a class=&#34;big-button&#34; href=&#34;mailto:hello@mm-dev.rocks&#34;&gt;&lt;a &gt;hello@mm-dev.rocks&lt;/a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/details&gt;


&lt;details&gt;
&lt;summary&gt;&lt;span&gt;&lt;h4 id=&#34;why-do-i-need-to-grant-all-files-access-permission-in-android-13&#34;&gt;Why do I need to grant &amp;lsquo;all files access&amp;rsquo; permission in Android 13?&lt;/h4&gt;
&lt;/span&gt;&lt;/summary&gt;

&lt;div&gt;
&lt;p&gt;&lt;strong&gt;BendyStraw&lt;/strong&gt; needs to work with files from a different app (&lt;strong&gt;NewPipe&lt;/strong&gt;). The way permissions have changed in Android 13 makes this complicated, as document files (ie non-media files such as &lt;code&gt;.zip&lt;/code&gt;) can only be read from and written to the directory belonging to the app that created them. The only simple way to keep the app easy to use is to ask for these permissions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;BendyStraw&lt;/strong&gt; has no interest in doing anything unexpected to your filesystem. The code is open source, so any developer can check to make sure that it only does what it says it does.&lt;/p&gt;
&lt;/div&gt;
&lt;/details&gt;


&lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>auDav Audiobook Player</title>
      <link>https://mm-dev.rocks/posts/audav-audiobook-player/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://mm-dev.rocks/posts/audav-audiobook-player/</guid>
      <description>&lt;p&gt;&lt;em&gt;auDav&lt;/em&gt; is an audiobook player app. It&amp;rsquo;s for use with libraries of audiobooks stored on NextCloud (or other WebDAV storage), and runs on Android, iOS/iPadOS, Windows, Linux and macOS.&lt;/p&gt;
&lt;p&gt;Key features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Visual listening progress&lt;/strong&gt; each chapter of a book is represented as a block, painted in different colours to indicate played/unplayed segments&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Full-featured custom bookmarks&lt;/strong&gt; you can add notes to a bookmark and notes can be searched&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No server install&lt;/strong&gt; the app is installed on whichever device/devices you want to listen on, you enter details for your NextCloud server (login info and path to the folder where your books are stored) and the app does the rest&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Clean footprint&lt;/strong&gt; &lt;em&gt;auDav&lt;/em&gt; only does what it needs to do, data such as listening progress is tied to your NextCloud user, to create different accounts you do that on NextCloud&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Loads of themes&lt;/strong&gt; multiple colours in light, dark and OLED black varieties&lt;/li&gt;
&lt;/ul&gt;
&lt;ul class=&#39;gallery&#39;&gt;&lt;li&gt;
      &lt;input type=&#39;checkbox&#39; id=&#39;item_Myxs_1&#39; /&gt;
      &lt;label for=&#39;item_Myxs_1&#39; aria-label=&#39;Expand image&#39; tabindex=&#39;0&#39;&gt;&lt;img src=&#39;https://mm-dev.rocks/images/audav/audav-1.webp&#39; alt=&#39;Entering a bookmark and note&#39; /&gt;
        &lt;span&gt;Entering a bookmark and note&lt;/span&gt;&lt;/label&gt;
    &lt;/li&gt;&lt;li&gt;
      &lt;input type=&#39;checkbox&#39; id=&#39;item_Myxs_2&#39; /&gt;
      &lt;label for=&#39;item_Myxs_2&#39; aria-label=&#39;Expand image&#39; tabindex=&#39;0&#39;&gt;&lt;img src=&#39;https://mm-dev.rocks/images/audav/audav-2.webp&#39; alt=&#39;Library page with theme chooser open&#39; /&gt;
        &lt;span&gt;Library page with theme chooser open&lt;/span&gt;&lt;/label&gt;
    &lt;/li&gt;&lt;li&gt;
      &lt;input type=&#39;checkbox&#39; id=&#39;item_Myxs_3&#39; /&gt;
      &lt;label for=&#39;item_Myxs_3&#39; aria-label=&#39;Expand image&#39; tabindex=&#39;0&#39;&gt;&lt;img src=&#39;https://mm-dev.rocks/images/audav/audav-4.webp&#39; alt=&#39;Playing book with visual chapter progress&#39; /&gt;
        &lt;span&gt;Playing book with visual chapter progress&lt;/span&gt;&lt;/label&gt;
    &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;this-app-is-95-finished-but-temporarily-on-hold-while-i-attend-to-some-other-priorities-coming-soon&#34;&gt;This app is 95% finished but temporarily on hold while I attend to some other priorities&amp;hellip; COMING SOON!&lt;/h3&gt;
</description>
    </item>
    
  </channel>
</rss>
