<?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: Alexis Reigel</title>
    <description>The latest articles on DEV Community by Alexis Reigel (@koffeinfrei).</description>
    <link>https://dev.to/koffeinfrei</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%2F103731%2Fce97e608-ec35-449e-ad1a-863e0de35e67.jpeg</url>
      <title>DEV Community: Alexis Reigel</title>
      <link>https://dev.to/koffeinfrei</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/koffeinfrei"/>
    <language>en</language>
    <item>
      <title>Share to PWA from mobile</title>
      <dc:creator>Alexis Reigel</dc:creator>
      <pubDate>Wed, 10 Jul 2024 18:35:37 +0000</pubDate>
      <link>https://dev.to/koffeinfrei/share-to-pwa-from-mobile-302i</link>
      <guid>https://dev.to/koffeinfrei/share-to-pwa-from-mobile-302i</guid>
      <description>&lt;p&gt;If you have a website that can be installed as a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps" rel="noopener noreferrer"&gt;PWA&lt;/a&gt; on your mobile device it has also the ability to receive data from the "share to" functionality on mobile phones. Depending on what you're trying to do you don't need to create a native app.&lt;/p&gt;

&lt;p&gt;This article shows how I made use of this for &lt;a href="https://www.unnote.io" rel="noopener noreferrer"&gt;unnote&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F138lxix2cqs4z802eiye.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F138lxix2cqs4z802eiye.jpg" alt="" width="369" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are 3 things that are needed as part of your application to make this work:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Update your manifest to support "share to"&lt;/li&gt;
&lt;li&gt;Handle the "share to" request in your service worker&lt;/li&gt;
&lt;li&gt;Handle the shared data in your app&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's go through each part in some detail.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Manifest
&lt;/h2&gt;

&lt;p&gt;You need to add a new entry &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Manifest/share_target" rel="noopener noreferrer"&gt;share_target&lt;/a&gt; to your &lt;code&gt;manfest.json&lt;/code&gt;. The following manifest allows any text data and all types of images to be sent to your application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"share_target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/share-target/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"POST"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"enctype"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"multipart/form-data"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"params"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"files"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"image"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"accept"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"image/*"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;action&lt;/code&gt; is the URL that will receive the data. This will be explained in the next chapter about the service worker.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;params&lt;/code&gt; represent the different ways the mobile phone sends the data

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;title&lt;/code&gt; is provided when a document is shared&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;text&lt;/code&gt; contains the contents of the shared document&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;url&lt;/code&gt; is provided when a resource with a URL (as e.g. a website) is shared&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;files&lt;/code&gt; is provded when a binary file is shared. The definition accepts an array of mimetypes that your aplication should accept.&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;/ol&gt;

&lt;p&gt;Depending on what data your application should be able to receive you don't need to configure all the &lt;code&gt;params&lt;/code&gt;. &lt;code&gt;files&lt;/code&gt; is only needed when you want to support binary data.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Service Worker
&lt;/h2&gt;

&lt;p&gt;The following is a minimal service worker that intercepts the URL &lt;code&gt;/share-target/&lt;/code&gt; which was previously configured in the manifest. It then puts the data in the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/caches" rel="noopener noreferrer"&gt;caches&lt;/a&gt;, which is a global &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/CacheStorage" rel="noopener noreferrer"&gt;CacheStorage&lt;/a&gt; in the browser.&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="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fetch&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;event&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;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&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="nf"&gt;endsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/share-target/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respondWith&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="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;formData&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;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;formData&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;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;image&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;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;title&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;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text&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;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;url&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;cache&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;caches&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;media&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;shared-image&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;shared-data&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&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="nx"&gt;text&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;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/#/handle-share-target&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;303&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;})(),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The service worker handles images and text data differently, and uses two separate cache keys. The text data is stored as JSON, while the image is stored as a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Response" rel="noopener noreferrer"&gt;Response&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Note that the intercepted request won't make it to your server. The last statement makes a client side redirect to where your application will actually handle the received data.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Application
&lt;/h2&gt;

&lt;p&gt;The last part is the actual application fetching the data from the cache and doing whatever you want to do with it. The following two functions are helpers to extract the json data and the binary image data from the cache.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getBlob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&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;cache&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;caches&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;media&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;image&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;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&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;image&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;blob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;blob&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&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;cache&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;caches&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;media&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;data&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;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&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;data&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;json&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;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;json&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 next part is the application specific part. The following shows how to use the previous functions to retrieve the data.&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;blob&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;getBlob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;shared-image&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You could e.g. &lt;a href="https://github.com/koffeinfrei/unnote/blob/ac46bb59a9f8b291ac645e0a6cd6f84f45de25d5/client/src/image.js#L1" rel="noopener noreferrer"&gt;convert the blob to an image&lt;/a&gt; or &lt;a href="https://github.com/koffeinfrei/unnote/blob/ac46bb59a9f8b291ac645e0a6cd6f84f45de25d5/client/src/image.js#L20" rel="noopener noreferrer"&gt;resize the image&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The last piece of code shows how to extract the properties from the cached JSON.&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="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="nx"&gt;text&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="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;shared-data&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/koffeinfrei/unnote/blob/ac46bb59a9f8b291ac645e0a6cd6f84f45de25d5/client/public/manifest.json#L52-L67" rel="noopener noreferrer"&gt;unnote's manifest.json&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/koffeinfrei/unnote/blob/ac46bb59a9f8b291ac645e0a6cd6f84f45de25d5/client/public/service-worker.js#L2-L22" rel="noopener noreferrer"&gt;unnote's service worker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/koffeinfrei/unnote/blob/ac46bb59a9f8b291ac645e0a6cd6f84f45de25d5/client/src/NoteEdit.svelte#L82-L94" rel="noopener noreferrer"&gt;unnote's data handling&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/How_to/Share_data_between_apps#handling_shared_data_from_other_apps" rel="noopener noreferrer"&gt;Handling shared data from other apps (MDN)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/GoogleChrome/samples/blob/gh-pages/web-share/README.md#web-share-demo" rel="noopener noreferrer"&gt;Web Share Demo&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>pwa</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Git cleanup-branches</title>
      <dc:creator>Alexis Reigel</dc:creator>
      <pubDate>Mon, 11 Jul 2022 17:21:00 +0000</pubDate>
      <link>https://dev.to/koffeinfrei/git-cleanup-branches-1hio</link>
      <guid>https://dev.to/koffeinfrei/git-cleanup-branches-1hio</guid>
      <description>&lt;p&gt;Additionally to the existing &lt;a href="https://www.koffeinfrei.org/2018/09/22/git-fixup-and-git-squash/" rel="noopener noreferrer"&gt;&lt;code&gt;git squash&lt;/code&gt; and &lt;code&gt;git fixup&lt;/code&gt;&lt;/a&gt; I added another piece in my custom git toolbox fitting in with the &lt;a href="https://www.koffeinfrei.org/2018/09/18/the-git-fixup-workflow/" rel="noopener noreferrer"&gt;git fixup workflow&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/koffeinfrei/dotfiles/blob/master/bin/git-cleanup-branches" rel="noopener noreferrer"&gt;git cleanup-branches&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This one is especially useful if you work in a team and work with pull / merge requests, and need to once in a while clean up the branches in your local repository.&lt;/p&gt;

&lt;p&gt;The script consists of 3 steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Prune remote branches&lt;/strong&gt;
This means that all branches which don't exist in the remote repository anymore are also deleted locally&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remove merged branches&lt;/strong&gt;
Branches that have been merged to the default branch (i.e. &lt;code&gt;main&lt;/code&gt; or &lt;code&gt;master&lt;/code&gt;) are deleted. This is useful if you previously merged a PR / MR.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remove branches from other authors&lt;/strong&gt;
This step removes branches that don't have any commits by you. This usually happens when you checked out a branch by someone else, possibly to review their work.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A sample session could look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt; ~/src/example ⑂ main ? &lt;span class="o"&gt;=&lt;/span&gt; ➜ git cleanup-branches
No &lt;span class="nb"&gt;local &lt;/span&gt;changes to save
Already on &lt;span class="s1"&gt;'main'&lt;/span&gt;
Your branch is up to &lt;span class="nb"&gt;date &lt;/span&gt;with &lt;span class="s1"&gt;'origin/main'&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
🥁 Pruning remote branches...
Fetching origin
From github.com:koffeinfrei/example
 - &lt;span class="o"&gt;[&lt;/span&gt;deleted]            &lt;span class="o"&gt;(&lt;/span&gt;none&lt;span class="o"&gt;)&lt;/span&gt;    -&amp;gt; origin/fix/weird-stuff
 - &lt;span class="o"&gt;[&lt;/span&gt;deleted]            &lt;span class="o"&gt;(&lt;/span&gt;none&lt;span class="o"&gt;)&lt;/span&gt;    -&amp;gt; origin/feat/nice-stuff

🥁 Removing merged branches...
Deleted branch feature/feat/nice-stuff &lt;span class="o"&gt;(&lt;/span&gt;was fee237764&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;

🥁 Removing branches from other authors...
You don&lt;span class="s1"&gt;'t seem to have any commits in the branch ⑂ feature/docs/add-deployment-section

 * 91e8d233a 2022-07-05 Document deployment (feature/docs/prompt-for-sw-updates) [Gob Bluth]

Delete the branch '&lt;/span&gt;feature/docs/prompt-for-sw-updates&lt;span class="s1"&gt;'? (y/n)? y
Deleted branch feature/docs/prompt-for-sw-updates (was 91e8d233a).

Already on '&lt;/span&gt;main&lt;span class="s1"&gt;'
Your branch is up to date with '&lt;/span&gt;origin/main&lt;span class="s1"&gt;'.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The source code is available &lt;a href="https://github.com/koffeinfrei/dotfiles/blob/master/bin/git-cleanup-branches" rel="noopener noreferrer"&gt;on GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>git</category>
      <category>github</category>
      <category>gitlab</category>
    </item>
    <item>
      <title>Easy tmux session setup</title>
      <dc:creator>Alexis Reigel</dc:creator>
      <pubDate>Tue, 17 Aug 2021 16:25:58 +0000</pubDate>
      <link>https://dev.to/koffeinfrei/easy-tmux-session-setup-2m75</link>
      <guid>https://dev.to/koffeinfrei/easy-tmux-session-setup-2m75</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Original post:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.koffeinfrei.org/2021/08/14/easy-tmux-session-setup/" rel="noopener noreferrer"&gt;www.koffeinfrei.org/2021/08/14/easy-tmux-session-setup&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For development I need a certain tmux setup, which I don't want to kick off every time manually. I used to use &lt;a href="https://github.com/tmuxinator/tmuxinator" rel="noopener noreferrer"&gt;Tmuxinator&lt;/a&gt; to manage my tmux session setup. For mainly two reasons I switched away from it though. To be clear, none of them are the tools' fault.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;I don't need a per project setup&lt;/li&gt;
&lt;li&gt;My setup is fairly simple, which doesn't really justify the dependency&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;What I now have is a bash script that sets up my tmux session with all the windows and split panes bootstrapped and tools running that I usually need. Since my setup isn't very complicated using tmux commands is enough and I don't need the declarative yaml structure that Tmuxinator provides.&lt;/p&gt;

&lt;p&gt;This is my annotated &lt;a href="https://github.com/koffeinfrei/dotfiles/blob/master/bin/tmux-dev" rel="noopener noreferrer"&gt;tmux-dev&lt;/a&gt; script:&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;#!/usr/bin/env bash&lt;/span&gt;

&lt;span class="c"&gt;# Create a tmux session&lt;/span&gt;
tmux new-session &lt;span class="nt"&gt;-d&lt;/span&gt;

&lt;span class="c"&gt;# Start vim in the first window&lt;/span&gt;
tmux send-keys &lt;span class="s1"&gt;'vim'&lt;/span&gt; C-m

&lt;span class="c"&gt;# Create a 2nd window&lt;/span&gt;
tmux new-window

&lt;span class="c"&gt;# Create a horizontal split in the 2nd window, with 75% / 25% width distribution&lt;/span&gt;
tmux split-window &lt;span class="nt"&gt;-h&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 25

&lt;span class="c"&gt;# Start `git watch-status` in the right split&lt;/span&gt;
tmux send-keys &lt;span class="s1"&gt;'git watch-status'&lt;/span&gt; C-m

&lt;span class="c"&gt;# Split the right split vertically&lt;/span&gt;
tmux split-window &lt;span class="nt"&gt;-v&lt;/span&gt;

&lt;span class="c"&gt;# Start `git watch-log` in the right bottom split&lt;/span&gt;
tmux send-keys &lt;span class="s1"&gt;'git watch-log'&lt;/span&gt; C-m

&lt;span class="c"&gt;# Focus the left split pane&lt;/span&gt;
tmux &lt;span class="k"&gt;select&lt;/span&gt;&lt;span class="nt"&gt;-pane&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; 0

&lt;span class="c"&gt;# Create a 3rd window, without running a command&lt;/span&gt;
tmux new-window

&lt;span class="c"&gt;# Go to the 1st window (vim) and put the focus on it&lt;/span&gt;
tmux &lt;span class="k"&gt;select&lt;/span&gt;&lt;span class="nt"&gt;-window&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; 0
tmux &lt;span class="k"&gt;select&lt;/span&gt;&lt;span class="nt"&gt;-pane&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; 0

&lt;span class="c"&gt;# Attach the session. Ready to start hacking&lt;/span&gt;
tmux &lt;span class="nt"&gt;-2&lt;/span&gt; attach-session &lt;span class="nt"&gt;-d&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;This is what the tmux session from the above script looks like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsprny1fmcpqpronqfr5u.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsprny1fmcpqpronqfr5u.jpg" alt="screen"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The most important commands to define the setup are the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;new-window&lt;/code&gt;: Create a new window&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;split-window -h&lt;/code&gt;: Create a horizontal split pane&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;split-window -v&lt;/code&gt;: Create a vertical split pane&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;send-keys&lt;/code&gt;: Execute a command, i.e. start a program&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>tmux</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Why you need to see your test fail</title>
      <dc:creator>Alexis Reigel</dc:creator>
      <pubDate>Tue, 09 Apr 2019 16:18:05 +0000</pubDate>
      <link>https://dev.to/koffeinfrei/why-you-need-to-see-your-test-fail-2gfm</link>
      <guid>https://dev.to/koffeinfrei/why-you-need-to-see-your-test-fail-2gfm</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Original post:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.koffeinfrei.org/2019/04/07/why-you-need-to-see-your-test-fail/" rel="noopener noreferrer"&gt;www.koffeinfrei.org/2019/04/07/why-you-need-to-see-your-test-fail&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Red / green (/ refactor)
&lt;/h2&gt;

&lt;p&gt;When developing using TDD or test-first the first step is to write a test. This test will initially fail, because there isn’t an implementation yet.&lt;/p&gt;

&lt;p&gt;A test-first workflow implicitly ensures that a test is failing before turning green. Read up on the &lt;a href="https://blog.cleancoder.com/uncle-bob/2014/12/17/TheCyclesOfTDD.html" rel="noopener noreferrer"&gt;The Cycles of TDD&lt;/a&gt; if you’re not familiar with it.&lt;/p&gt;

&lt;p&gt;When writing a test after writing the production code you’ll never see the test fail, because the correct implementation is already there.&lt;/p&gt;

&lt;h2&gt;
  
  
  So? What’s the problem with this?
&lt;/h2&gt;

&lt;p&gt;The problem is that if you never see your test fail &lt;strong&gt;you don’t know if your test is actually testing the right thing&lt;/strong&gt;, or testing anything at all.&lt;/p&gt;

&lt;p&gt;The main purpose of a test is to make sure that your code is working properly. (If you’re doing TDD, driving the design of your production code actually is the main purpose of writing a test.)&lt;/p&gt;

&lt;p&gt;If you don’t know if your code is actually testing the right thing, you can’t have confidence in your test, and you can’t have confidence in your production code.&lt;/p&gt;

&lt;p&gt;If you ever write a test after, you need to temporarily change the implementation to see your test fail. Don’t change the test, &lt;strong&gt;change the production code&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real life example
&lt;/h2&gt;

&lt;p&gt;Say we want to fix a bug. Before fixing it, we create a test that asserts that the bug exists in the first place. This means that the test needs to fail. We are at an equivalent as the first step in the TDD cycle.&lt;/p&gt;

&lt;p&gt;Only after writing the test we fix the bug. After fixing the bug, the test goes green.&lt;/p&gt;

&lt;p&gt;Imagine we write the test after fixing the bug. How do we know that the test actually tests that the bug does not exist anymore? Worst case is that we haven’t actually fixed the bug.&lt;/p&gt;

&lt;p&gt;To make sure that we actually have, we need to undo the fix, run the test and make sure that the test fails. By doing that we verify that the bug has an effect on our test that represents the expected behaviour of our production code.&lt;/p&gt;

&lt;p&gt;When re-applying the fix and the test goes green we can be pretty confident that we are testing the bug fix, and thus that the bug does not exist anymore (at least not in the form that our test case covers).&lt;/p&gt;

&lt;h2&gt;
  
  
  The bottom line
&lt;/h2&gt;

&lt;p&gt;If you want to trust your code you need to be able to trust your tests. You can’t do that unless you’ve seen your tests fail.&lt;/p&gt;

&lt;p&gt;So don’t get lazy. Don’t just assume that your test is fine. &lt;strong&gt;See&lt;/strong&gt;. &lt;strong&gt;it&lt;/strong&gt;. &lt;strong&gt;fail&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>coding</category>
      <category>learning</category>
      <category>testing</category>
    </item>
    <item>
      <title>Private methods are a code smell</title>
      <dc:creator>Alexis Reigel</dc:creator>
      <pubDate>Tue, 12 Feb 2019 13:51:11 +0000</pubDate>
      <link>https://dev.to/koffeinfrei/private-methods-are-a-code-smell-5d14</link>
      <guid>https://dev.to/koffeinfrei/private-methods-are-a-code-smell-5d14</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Original post:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.koffeinfrei.org/2019/02/09/private-methods-are-a-code-smell/" rel="noopener noreferrer"&gt;www.koffeinfrei.org/2019/02/09/private-methods-are-a-code-smell&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Just to get it out of the way up front: I'm not saying you should never use private methods.&lt;/p&gt;

&lt;p&gt;I'm trying to convey something a bit more nuanced. If you aren't a person that can appreciate the grey zones of life this blog post may not be for you. Or maybe even so. You should decide for yourself. If you can't do that, then this blog post may not be for you. Or maybe it is nevertheless… Let's do this.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your private is someone else's public
&lt;/h2&gt;

&lt;p&gt;The bottom line is that a private method of one class could be a public method of another class. Every functionality is a public interface of something.&lt;/p&gt;

&lt;p&gt;If it has to be made private this usually means that it is in the wrong place, i.e. in the wrong class.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't violate the SRP
&lt;/h2&gt;

&lt;p&gt;Making a method private often means this: "This method doesn't really belong in this class, so I'll just make it private so nobody notices." It's just sweeping the dirt under the carpet. Just clean your floor properly and move this method out of that class.&lt;/p&gt;

&lt;h2&gt;
  
  
  Private methods should not be tested
&lt;/h2&gt;

&lt;p&gt;I believe that private methods should not be tested, even if your language supports calling them. Tests should only verify the public interface of a class. A private method is an internal implementation detail which may change and thus will break the test.&lt;/p&gt;

&lt;p&gt;If the functionality of a private method is trivial it may be fine not to explicitly test it. If it is non trivial though, testing the behavior of a class’s private methods indirectly through the class’s public methods is pretty much the same as testing it directly. If the private method has a complexity that should be under test, it should be public. If it doesn't make sense to be public in that class, it should be moved to another class.&lt;/p&gt;

&lt;p&gt;If your goal is to achieve high test coverage, everything should be public and therefore testable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Public interfaces should only expose what's relevant
&lt;/h2&gt;

&lt;p&gt;The consumer of a public interface should not be bothered with unnecessary internals. A method that does not seem to belong to the public interface usually does not have any business of being part of that class and should usually be moved somewhere else.&lt;/p&gt;

&lt;h2&gt;
  
  
  Did you say reusability?
&lt;/h2&gt;

&lt;p&gt;Having private methods excludes that functionality from being reused. If that functionality is moved to a public method though it can be consumed by someone else as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Internal helper methods are (probably) fine
&lt;/h2&gt;

&lt;p&gt;There are cases where a private method is fine. If a method is really just a helper for a public method and doesn't make sense to live on its own in another context then it's fine to keep it as a private method. If you start to touch any of the guidelines mentioned before, this private method may evolve though from being private.&lt;/p&gt;

&lt;h2&gt;
  
  
  An example
&lt;/h2&gt;

&lt;p&gt;This is just a really trivial academic example, but it should be enough to illustrate my point:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Car&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;start&lt;/span&gt;
    &lt;span class="n"&gt;ignite&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;

  &lt;span class="c1"&gt;# Is ignition the responsibility of the `Car`?&lt;/span&gt;
  &lt;span class="c1"&gt;# Or rather of the motor?&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;ignite&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's move the private method to the &lt;code&gt;Motor&lt;/code&gt; class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Car&lt;/span&gt;
  &lt;span class="nb"&gt;attr_accessor&lt;/span&gt; &lt;span class="ss"&gt;:motor&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;start&lt;/span&gt;
    &lt;span class="n"&gt;motor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ignite&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Motor&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;ignite&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The "private" story here is this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The car starts by igniting itself&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;or&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The car starts by igniting the motor itself&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The refactored story on the other hand is this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The car starts by telling the motor to ignite&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  4 questions to get private methods out of your face
&lt;/h2&gt;

&lt;p&gt;If the answer to any of the following questions is "yes" you should consider extracting the private method:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does this method violate the SRP?&lt;/li&gt;
&lt;li&gt;Should I test this method?&lt;/li&gt;
&lt;li&gt;Is this method relevant as part of the public interface?&lt;/li&gt;
&lt;li&gt;Could this method be reused and be consumed by another class as well?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Perfection is never achieved
&lt;/h2&gt;

&lt;p&gt;Having no private methods at all is the result of a perfect software design.  This doesn't imply that we cannot use private methods at all. It means that we should try and strive towards a perfect design and reduce the use of private to a minimum.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>coding</category>
      <category>learning</category>
      <category>devtips</category>
    </item>
    <item>
      <title>The Git fixup workflow</title>
      <dc:creator>Alexis Reigel</dc:creator>
      <pubDate>Sat, 29 Sep 2018 19:04:59 +0000</pubDate>
      <link>https://dev.to/koffeinfrei/the-git-fixup-workflow-386d</link>
      <guid>https://dev.to/koffeinfrei/the-git-fixup-workflow-386d</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Original post:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.koffeinfrei.org/2018/09/18/the-git-fixup-workflow/" rel="noopener noreferrer"&gt;www.koffeinfrei.org/2018/09/18/the-git-fixup-workflow/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://guides.github.com/introduction/flow/" rel="noopener noreferrer"&gt;GitHub flow&lt;/a&gt;, or a similar Git strategy where the main work is done on feature branches which result in pull or merge requests, has become the standard way to work with Git and GitHub or GitLab (or Bitbucket even) for many developer teams. Besides the main advantages of this way of working it also enables developers to keep a clean Git history (see also &lt;a href="https://www.koffeinfrei.org/2016/11/10/5-rules-to-git-better/" rel="noopener noreferrer"&gt;5 rules to Git better&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Let's assume the following as a starting point for the suggested workflow in this post:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You want to maintain a clean Git history, meaning that fix commits should be avoided&lt;/li&gt;
&lt;li&gt;You don't want to interrupt your development workflow by having to rewrite your Git history all the time&lt;/li&gt;
&lt;li&gt;You can use &lt;code&gt;git commit --amend&lt;/code&gt; only for the latest commit&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Git has some powerful commands that allow us to both keep a clean history and not having to interrupt our development workflow. We'll call it "the Git fixup workflow".&lt;/p&gt;

&lt;h2&gt;
  
  
  The linear workflow
&lt;/h2&gt;

&lt;p&gt;To illustrate the Git fixup workflow, we'll use the following example hack session:&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;# coding the user component's index view&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"add user component's index view"&lt;/span&gt;

&lt;span class="c"&gt;# coding the user component's edit view&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"add user component's edit view"&lt;/span&gt;

&lt;span class="c"&gt;# updating the component's index view so it actually works&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"add user component's index view, it works now"&lt;/span&gt;

&lt;span class="c"&gt;# a typo is noticed in the index view&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"fix typo in index view"&lt;/span&gt;

&lt;span class="c"&gt;# fix a bug in the view rendering&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"fix view rendering"&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;rubocop
&lt;span class="nv"&gt;$ &lt;/span&gt;yarn lint

&lt;span class="c"&gt;# fix linting errors&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"fix linting errors"&lt;/span&gt;

&lt;span class="c"&gt;# team members do a code review on the PR / MR&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"fix import in index"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above session leads to the following Git history:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fix import in index
fix linting errors
fix view rendering
fix typo in index view
add user component's index view, it works now
add user component's edit view
add user component's index view
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The following commits reflect the actual work that's relevant from another developer's standpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fix view rendering
add user component's edit view
add user component's index view
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All commits that amend a previous commit just clutter the history and should be squashed. The commit "fix typo in index view" for example should be squashed together with "add user component's index view".&lt;/p&gt;

&lt;p&gt;To clean up the history, we could manually interactively rebase the commits before merging the branch. Imagine though that you may have many commits and later in the process, you won't remember which ones should be squashed together. Also, it's a lot of manual work to mark the relevant commits in the interactive rebase mode.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fixup workflow
&lt;/h2&gt;

&lt;p&gt;Git includes everything required to automate this. So let's redo the previous hack session with our new toolset:&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;# coding the user component's index view&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"add user component's index view"&lt;/span&gt;

&lt;span class="c"&gt;# coding the user component's edit view&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"add user component's edit view"&lt;/span&gt;

&lt;span class="c"&gt;# updating the component's index view so it actually works&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;git commit &lt;span class="nt"&gt;--fixup&lt;/span&gt; &amp;lt;COMMIT HASH OF THE COMMIT &lt;span class="s2"&gt;"add user component's index view"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;# a typo is noticed in the index view&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;git commit &lt;span class="nt"&gt;--fixup&lt;/span&gt; &amp;lt;COMMIT HASH OF THE COMMIT &lt;span class="s2"&gt;"add user component's index view"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;# fix a bug in the view rendering&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"fix view rendering"&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;rubocop
&lt;span class="nv"&gt;$ &lt;/span&gt;yarn lint

&lt;span class="c"&gt;# fix linting errors, assuming the linting error was introduced in the commit&lt;/span&gt;
&lt;span class="c"&gt;# "add user component's edit view"&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;git commit &lt;span class="nt"&gt;--fixup&lt;/span&gt; &amp;lt;COMMIT HASH OF THE COMMIT &lt;span class="s2"&gt;"add user component's edit view"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;# team members do a code review on the PR / MR&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;git commit &lt;span class="nt"&gt;--fixup&lt;/span&gt; &amp;lt;COMMIT HASH OF THE COMMIT &lt;span class="s2"&gt;"add user component's index view"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The resulting Git log looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fixup! add user component's index view
fixup! add user component's edit view
fix view rendering
fixup! add user component's index view
fixup! add user component's index view
add user component's edit view
add user component's index view
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of regularly committing (and also writing a commit message) we use the &lt;code&gt;--fixup&lt;/code&gt; flag to create fixup commits.&lt;/p&gt;

&lt;p&gt;Now, before merging the merge request, we issue an interactive rebase with the &lt;code&gt;--autosquash&lt;/code&gt; flag.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;br&gt;
GitLab marks MRs that include fixup commits as &lt;code&gt;WIP&lt;/code&gt;, so merging is prevented if you haven't squashed.&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;&lt;span class="nv"&gt;$ &lt;/span&gt;git rebase &lt;span class="nt"&gt;--interactive&lt;/span&gt; &lt;span class="nt"&gt;--autosquash&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &amp;lt;COMMIT HASH OF THE COMMIT &lt;span class="s2"&gt;"add user component's index view"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;~1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We'll enter interactive rebase mode with the relevant commits already marked for fixup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pick 364003b add user component's index view
fixup afa6970 fixup! add user component's index view
fixup 1da5d7c fixup! add user component's index view
fixup c998b08 fixup! add user component's index view
pick d9731b5 add user component's edit view
fixup 33d6080 fixup! add user component's edit view
pick 73e8a6a fix view rendering
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The resulting Git history looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fix view rendering
add user component's edit view
add user component's index view
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The final result is a very consistent and readable Git history, where every other developer knows what happens, without the uninteresting sidesteps you took while developing.&lt;/p&gt;

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

&lt;p&gt;In summary, the following two essential Git commands are necessary to make use of the Git fixup workflow:&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="nv"&gt;$ &lt;/span&gt;git commit &lt;span class="nt"&gt;--fixup&lt;/span&gt; &amp;lt;COMMIT THAT NEEDS FIXING&amp;gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;git rebase &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;--autosquash&lt;/span&gt; &amp;lt;FIRST COMMIT THAT NEEDS FIXING&amp;gt;~1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Bonus: Create Git aliases
&lt;/h2&gt;

&lt;p&gt;If you start adopting this workflow, you'll have to type a lot in your terminal.  At this point it makes sense to create some Git aliases to ease your brain and hands.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[alias]
    cif = commit --fixup
    ri = rebase -i --autosquash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  More bonus: Global autosquash config
&lt;/h2&gt;

&lt;p&gt;Instead of having to provide the &lt;code&gt;--autosquash&lt;/code&gt; flag the configuration can be applied globally. The rebase command still requires &lt;code&gt;-i&lt;/code&gt; though.&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="nv"&gt;$ &lt;/span&gt;git config &lt;span class="nt"&gt;--global&lt;/span&gt; rebase.autoSquash &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Edit (2018-09-18)
&lt;/h2&gt;

&lt;p&gt;I wrote two git scripts &lt;code&gt;git fixup&lt;/code&gt; and &lt;code&gt;git squash&lt;/code&gt; to support this workflow and added a &lt;a href="https://www.koffeinfrei.org/2018/09/22/git-fixup-and-git-squash/" rel="noopener noreferrer"&gt;blog post describing them&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>git</category>
      <category>devtips</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
