<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Tinacious Design Blog]]></title><description><![CDATA[A Software Engineering blog by Tina Holly, a Full Stack Software Developer from Canada 🇨🇦]]></description><link>https://blog.tinaciousdesign.com</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1659395576295/UPo8c2bUg.png</url><title>Tinacious Design Blog</title><link>https://blog.tinaciousdesign.com</link></image><generator>RSS for Node</generator><lastBuildDate>Sat, 18 Apr 2026 03:45:56 GMT</lastBuildDate><atom:link href="https://blog.tinaciousdesign.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Uninstalling a package installed with Go]]></title><description><![CDATA[Have you ever installed a package by using go install <some-package> and wondered how to uninstall it?
Go is really great in that respect. Go packages are a single binary and they’re installed based on how your Go tooling is set up.
Provided we have ...]]></description><link>https://blog.tinaciousdesign.com/uninstalling-a-package-installed-with-go</link><guid isPermaLink="true">https://blog.tinaciousdesign.com/uninstalling-a-package-installed-with-go</guid><category><![CDATA[Go Language]]></category><category><![CDATA[cli]]></category><dc:creator><![CDATA[Tina Holly]]></dc:creator><pubDate>Sat, 15 Mar 2025 17:17:36 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1742058941848/9ed716b4-a39f-49ae-b4c7-fe2a4f62b326.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Have you ever installed a package by using <code>go install &lt;some-package&gt;</code> and wondered how to uninstall it?</p>
<p>Go is really great in that respect. Go packages are a single binary and they’re installed based on how your Go tooling is set up.</p>
<p>Provided we have Go installed, when we run <code>go help install</code> in the command line, we can see where packages are installed:</p>
<blockquote>
<p><code>usage: go install [build flags] [packages]</code></p>
<p><code>Install compiles and installs the packages named by the import paths.</code></p>
<p><code>Executables are installed in the directory named by the GOBIN environment variable, which defaults to $GOPATH/bin or $HOME/go/bin if the GOPATH environment variable is not set. Executables in $GOROOT are installed in $GOROOT/bin or $GOTOOLDIR instead of $GOBIN.</code></p>
</blockquote>
<p>So that’s pretty straight-forward. Let’s see where our packages are installed by trying the first command:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">echo</span> <span class="hljs-variable">$GOPATH</span>
</code></pre>
<p>If that returns blank, it’s not defined, so we can probably assume they’re going to be installed in <code>~/go/bin</code>.</p>
<pre><code class="lang-bash">ls <span class="hljs-variable">$HOME</span>/go/bin
</code></pre>
<p>This should return a list of executable binaries installed using <code>go install</code>.</p>
<h2 id="heading-installing-a-package-with-go">Installing a package with Go</h2>
<p>A while back, I built a really simple CLI tool to assist me with testing deep links and statically generated sites that I built. This CLI tool <a target="_blank" href="https://github.com/tinacious/static-server">static-server</a> allows me to serve the entire contents of a directory as a static website.</p>
<p>As the instructions in the README say, I can install the latest version of the tool using Go with the following command:</p>
<pre><code class="lang-bash">go install github.com/tinacious/static-server@latest
</code></pre>
<p>Then, I can navigate to any static HTML website folder on my computer and serve it. Let’s assume I have the following directory somewhere:</p>
<pre><code class="lang-plaintext">test
├── index.html
└── style.css
</code></pre>
<p>I can run the following:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> path/to/<span class="hljs-built_in">test</span>
static-server
</code></pre>
<p>This will serve the contents of the directory on a random port.</p>
<h2 id="heading-uninstalling-a-package-installed-with-go">Uninstalling a package installed with Go</h2>
<p>Let’s say you’ve tried this tool and you absolutely hate it. You prefer to type out the long Python 3 command, or use an NPM package that depends on you having the correct Node.js runtime on your computer. Here’s how you can uninstall <code>static-server</code>.</p>
<pre><code class="lang-bash"><span class="hljs-built_in">which</span> static-server
</code></pre>
<p>This should output the path to the binary. Simply delete it.</p>
<p>Or, if you’re feeling brave and want to do it in a single line:</p>
<pre><code class="lang-bash">rm $(<span class="hljs-built_in">which</span> static-server)
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Open API (Part 3): Generating an OpenAPI spec file from code]]></title><description><![CDATA[Overview
This is the 3rd article in a series of posts about working with Open API to create generated API clients and documentation websites.

In the 1st post we went over what we can do with Open API spec files, e.g. generate API clients and documen...]]></description><link>https://blog.tinaciousdesign.com/open-api-generate-from-code</link><guid isPermaLink="true">https://blog.tinaciousdesign.com/open-api-generate-from-code</guid><category><![CDATA[OpenApi]]></category><category><![CDATA[swagger]]></category><category><![CDATA[swagger-ui]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[Kotlin]]></category><category><![CDATA[golang]]></category><category><![CDATA[decorators]]></category><category><![CDATA[annotations]]></category><category><![CDATA[documentation]]></category><dc:creator><![CDATA[Tina Holly]]></dc:creator><pubDate>Mon, 14 Oct 2024 10:00:57 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1727488981667/15ed39b8-8c91-4fbb-a8db-580b243a8f88.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-overview">Overview</h2>
<p>This is the 3rd article in a series of posts about working with Open API to create generated API clients and documentation websites.</p>
<ol>
<li><p>In <a target="_blank" href="https://blog.tinaciousdesign.com/open-api-generate-client">the 1st post</a> we went over what we can do with Open API spec files, e.g. generate API clients and documentation websites.</p>
</li>
<li><p>In the <a target="_blank" href="https://blog.tinaciousdesign.com/open-api-contract-first">2nd post</a> we went over how we can create the Open API spec file from an API contract-first approach, using a hand-crafted approach to create the definition of the API, and then having teams build against that.</p>
</li>
<li><p>In this 3rd post, we’ll go over how we can annotate our code to support generating the Open API spec file, which means developers won’t have to touch any YAML or JSON files to write API definitions.</p>
</li>
</ol>
<h2 id="heading-what-does-using-annotations-to-generate-an-open-api-spec-look-like">What does using annotations to generate an Open API spec look like?</h2>
<p>Code can be annotated in a variety of ways to support generating an Open API spec file. This is supported to various degrees in different programming languages.</p>
<h3 id="heading-java-and-kotlin">Java and Kotlin</h3>
<p>Probably one of the best supported languages for doing it this way is Java (and Kotlin). The Open API tools <a target="_blank" href="https://github.com/OpenAPITools/openapi-generator">were originally written in (and for) Java</a>. And while they were probably amongst the first set of tools available for devs to leverage for this purpose, Java and languages interoperable with Java aren’t the only ones to have these tools. Before going onto those, I’ll share the approach that can be used in Kotlin and Java projects.</p>
<p>Using the starter Spring web project generated from <a target="_blank" href="https://start.spring.io/">this website</a>, you can add this dependency:</p>
<pre><code class="lang-kotlin">implementation(<span class="hljs-string">"org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0"</span>)
</code></pre>
<p>Assuming you want to make a simple todo list app, this could be the handler to retrieve all todos, accepting a query to filter if you want only completed or incomplete todos:</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@RestController</span>
<span class="hljs-meta">@RequestMapping(<span class="hljs-meta-string">"/api/todos"</span>)</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TodoController</span> </span>{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> todos = mutableListOf&lt;Todo&gt;(
        Todo(<span class="hljs-string">"Drink coffee"</span>, <span class="hljs-literal">true</span>),
        Todo(<span class="hljs-string">"Write blog post about handcrafting bespoke artisanal Open API specs"</span>, <span class="hljs-literal">true</span>),
        Todo(<span class="hljs-string">"Write blog post about creating Open API specs from code annotations"</span>, <span class="hljs-literal">false</span>),
    )

    <span class="hljs-meta">@Operation(summary = <span class="hljs-meta-string">"Gets all of your todos"</span>)</span>
    <span class="hljs-meta">@ApiResponses(
        value = [
            ApiResponse(responseCode = <span class="hljs-meta-string">"200"</span>, description = <span class="hljs-meta-string">"Success"</span>)</span>
        ]
    )
    <span class="hljs-meta">@GetMapping(<span class="hljs-meta-string">""</span>, produces = [MediaType.APPLICATION_JSON_VALUE])</span>
    <span class="hljs-meta">@ResponseStatus(HttpStatus.OK)</span>
    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getTodos</span><span class="hljs-params">(
        <span class="hljs-meta">@PathVariable</span> <span class="hljs-meta">@RequestParam(name = <span class="hljs-meta-string">"is_completed"</span>, required = false)</span> filterState: <span class="hljs-type">Boolean</span>?
    )</span></span>: List&lt;Todo&gt; {
        <span class="hljs-keyword">val</span> response = todos
            .filter { todo -&gt;
                <span class="hljs-keyword">if</span> (filterState == <span class="hljs-literal">null</span>) <span class="hljs-keyword">return</span><span class="hljs-symbol">@filter</span> <span class="hljs-literal">true</span>
                filterState == todo.isCompleted
            }

        <span class="hljs-keyword">return</span> response
    }
}
</code></pre>
<p>And the <code>Todo</code> class itself would be annotated as well:</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@Schema(description = <span class="hljs-meta-string">"Pending or completed item"</span>)</span>
<span class="hljs-keyword">data</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Todo</span></span>(
    <span class="hljs-meta">@field:Schema</span>(
        description = <span class="hljs-string">"The task that needs to be completed"</span>,
        example = <span class="hljs-string">"Wash car"</span>,
        type = <span class="hljs-string">"string"</span>
    )
    <span class="hljs-keyword">val</span> text: String,

    <span class="hljs-meta">@field:Schema</span>(
        description = <span class="hljs-string">"Completed state of the todo"</span>,
        type = <span class="hljs-string">"boolean"</span>
    )
    <span class="hljs-keyword">val</span> isCompleted: <span class="hljs-built_in">Boolean</span>,
)
</code></pre>
<p>From these code snippets, you can see <code>@Schema</code>, <code>@field:Schema</code>, <code>@Operation</code>, <code>@ApiResponses</code>. The pattern here is generally there are annotations that map to the respective properties in the spec.</p>
<p>We can see that after it’s configured and we annotate the handler, we can get the information to show up in the Swagger UI tool.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727485087568/93d8f797-a125-4ede-b3f9-b964ae98c9b3.png" alt class="image--center mx-auto" /></p>
<p>There is of course other methods to support, e.g. post and put requests that accept a request body, and annotating those correctly.</p>
<h3 id="heading-typescript">TypeScript</h3>
<p>In TypeScript, there’s a framework called <a target="_blank" href="https://nestjs.com/">Nest.js</a> that is quite similar to Spring in that it’s also an object-oriented MVC framework. Nest can do something similar with the <a target="_blank" href="https://docs.nestjs.com/recipes/swagger">@nestjs/swagger library</a>. Here’s an example controller for a stocks-related app:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Body, Controller, Get, Post } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
<span class="hljs-keyword">import</span> { ApiOperation, ApiResponse, ApiTags } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/swagger'</span>;
<span class="hljs-keyword">import</span> { IsNotEmpty } <span class="hljs-keyword">from</span> <span class="hljs-string">'class-validator'</span>;
<span class="hljs-keyword">import</span> { Stock } <span class="hljs-keyword">from</span> <span class="hljs-string">'./stock.entity'</span>;
<span class="hljs-keyword">import</span> { StockService } <span class="hljs-keyword">from</span> <span class="hljs-string">'./stock.service'</span>;
<span class="hljs-keyword">import</span> { StocksByNameRequest } <span class="hljs-keyword">from</span> <span class="hljs-string">'./stocks-by-name.dto'</span>;

<span class="hljs-meta">@ApiTags</span>(<span class="hljs-string">'stocks'</span>)
<span class="hljs-meta">@Controller</span>(<span class="hljs-string">'stock'</span>)
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> StockController {
  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">private</span> stockService: StockService</span>) {}

  <span class="hljs-meta">@ApiOperation</span>({
    summary: <span class="hljs-string">'Get all available stock prices'</span>,
  })
  <span class="hljs-meta">@ApiResponse</span>({
    status: <span class="hljs-number">200</span>,
    description: <span class="hljs-string">'List of stocks'</span>,
    <span class="hljs-keyword">type</span>: [Stock],
  })
  <span class="hljs-meta">@Get</span>()
  <span class="hljs-keyword">async</span> findAll(): <span class="hljs-built_in">Promise</span>&lt;Stock[]&gt; {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.stockService.findAll();
  }

  <span class="hljs-meta">@ApiOperation</span>({
    summary: <span class="hljs-string">'Get stock prices by name'</span>,
  })
  <span class="hljs-meta">@ApiResponse</span>({
    status: <span class="hljs-number">200</span>,
    description: <span class="hljs-string">'List of stocks'</span>,
    <span class="hljs-keyword">type</span>: [Stock],
  })
  <span class="hljs-meta">@Post</span>()
  <span class="hljs-keyword">async</span> findByNames(
    <span class="hljs-meta">@Body</span>() stocksByNameRequest: StocksByNameRequest,
  ): <span class="hljs-built_in">Promise</span>&lt;Stock[]&gt; {
    <span class="hljs-keyword">const</span> names = stocksByNameRequest.names;

    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.stockService.bulkFindByName(names);
  }
}

<span class="hljs-comment">// Requests</span>

<span class="hljs-keyword">class</span> StocksByNameRequest {
  <span class="hljs-meta">@ApiProperty</span>({
    description: <span class="hljs-string">'List of stock tickers'</span>,
    example: [<span class="hljs-string">'AAPL'</span>, <span class="hljs-string">'TSLA'</span>],
  })
  <span class="hljs-meta">@IsNotEmpty</span>()
  names: <span class="hljs-built_in">string</span>[];
}
</code></pre>
<p>You can see similar OpenAPI-related annotations, <code>@ApiResponse</code>, <code>@ApiOperation</code>, <code>@ApiTags</code>, <code>@ApiProperty</code>, and <code>@IsNotEmpty</code>, the last of which comes from the <a target="_blank" href="https://github.com/typestack/class-validator">class-validator</a> library which is used for object validation for request objects.</p>
<p>This also carries through to the <code>Stock</code> class:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { ApiProperty } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/swagger'</span>;
<span class="hljs-keyword">import</span> { Column, Entity, PrimaryGeneratedColumn } <span class="hljs-keyword">from</span> <span class="hljs-string">'typeorm'</span>;

<span class="hljs-meta">@Entity</span>({
  name: <span class="hljs-string">'stocks'</span>,
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> Stock {
  <span class="hljs-meta">@PrimaryGeneratedColumn</span>()
  id: <span class="hljs-built_in">number</span>;

  <span class="hljs-meta">@ApiProperty</span>({
    description: <span class="hljs-string">'Stock ticker'</span>,
    example: <span class="hljs-string">'AAPL'</span>,
  })
  <span class="hljs-meta">@Column</span>()
  name: <span class="hljs-built_in">string</span>;

  <span class="hljs-meta">@ApiProperty</span>({
    description: <span class="hljs-string">'Price in US dollars'</span>,
    example: <span class="hljs-number">100</span>,
  })
  <span class="hljs-meta">@Column</span>()
  price: <span class="hljs-built_in">number</span>;
}
</code></pre>
<p>Again we can see a similar pattern in that it maps properties in the spec.</p>
<p>If we look at the output JavaScript, we can see that Swagger code does end up getting bundled with the dist files.</p>
<h3 id="heading-go-lang">Go lang</h3>
<p>This has also been attempted in Go lang using Go’s structure tags. Struct tags are commonly used in Go for mapping fields of a struct to other objects like database fields or JSON fields. Here’s a simple example of how struct tags are commonly used for JSON marshalling and unmarshalling:</p>
<pre><code class="lang-go"><span class="hljs-keyword">var</span> person *<span class="hljs-keyword">struct</span> {
    Name    <span class="hljs-keyword">string</span> <span class="hljs-string">`json:"name"`</span>
    Address *<span class="hljs-keyword">struct</span> {
        Street <span class="hljs-keyword">string</span> <span class="hljs-string">`json:"street"`</span>
        City   <span class="hljs-keyword">string</span> <span class="hljs-string">`json:"city"`</span>
    } <span class="hljs-string">`json:"address"`</span>
}
</code></pre>
<p>There’s <a target="_blank" href="https://github.com/swaggest/openapi-go">a project</a> that aims to provide the same code generation abilities for Open API in Go that leverages these struct tags. The code looks like this:</p>
<pre><code class="lang-go"><span class="hljs-keyword">type</span> req <span class="hljs-keyword">struct</span> {
    ID     <span class="hljs-keyword">string</span> <span class="hljs-string">`path:"id" example:"XXX-XXXXX"`</span>
    Locale <span class="hljs-keyword">string</span> <span class="hljs-string">`query:"locale" pattern:"^[a-z]{2}-[A-Z]{2}$"`</span>
    Title  <span class="hljs-keyword">string</span> <span class="hljs-string">`json:"title"`</span>
    Amount <span class="hljs-keyword">uint</span>   <span class="hljs-string">`json:"amount"`</span>
    Items  []<span class="hljs-keyword">struct</span> {
        Count <span class="hljs-keyword">uint</span>   <span class="hljs-string">`json:"count"`</span>
        Name  <span class="hljs-keyword">string</span> <span class="hljs-string">`json:"name"`</span>
    } <span class="hljs-string">`json:"items"`</span>
}
</code></pre>
<p>It also requires you to run lines of code in order to register objects into the Open API document. From the <a target="_blank" href="https://github.com/swaggest/openapi-go/tree/cdbf0a73418c416c59000778488f751a58f7cc54?tab=readme-ov-file#example">project’s documentation</a>, we can see we need to execute quite a bit of code in order to generate the OpenAPI spec:</p>
<pre><code class="lang-go"><span class="hljs-comment">// Source: https://github.com/swaggest/openapi-go/tree/cdbf0a73418c416c59000778488f751a58f7cc54?tab=readme-ov-file#example</span>
reflector := openapi3.Reflector{}
reflector.Spec = &amp;openapi3.Spec{Openapi: <span class="hljs-string">"3.0.3"</span>}
reflector.Spec.Info.
    WithTitle(<span class="hljs-string">"Things API"</span>).
    WithVersion(<span class="hljs-string">"1.2.3"</span>).
    WithDescription(<span class="hljs-string">"Put something here"</span>)

<span class="hljs-keyword">type</span> req <span class="hljs-keyword">struct</span> {
    ID     <span class="hljs-keyword">string</span> <span class="hljs-string">`path:"id" example:"XXX-XXXXX"`</span>
    Locale <span class="hljs-keyword">string</span> <span class="hljs-string">`query:"locale" pattern:"^[a-z]{2}-[A-Z]{2}$"`</span>
    Title  <span class="hljs-keyword">string</span> <span class="hljs-string">`json:"string"`</span> <span class="hljs-comment">// This looks like a typo and should be `json:"title"`</span>
    Amount <span class="hljs-keyword">uint</span>   <span class="hljs-string">`json:"amount"`</span>
    Items  []<span class="hljs-keyword">struct</span> {
        Count <span class="hljs-keyword">uint</span>   <span class="hljs-string">`json:"count"`</span>
        Name  <span class="hljs-keyword">string</span> <span class="hljs-string">`json:"name"`</span>
    } <span class="hljs-string">`json:"items"`</span>
}

<span class="hljs-keyword">type</span> resp <span class="hljs-keyword">struct</span> {
    ID     <span class="hljs-keyword">string</span> <span class="hljs-string">`json:"id" example:"XXX-XXXXX"`</span>
    Amount <span class="hljs-keyword">uint</span>   <span class="hljs-string">`json:"amount"`</span>
    Items  []<span class="hljs-keyword">struct</span> {
        Count <span class="hljs-keyword">uint</span>   <span class="hljs-string">`json:"count"`</span>
        Name  <span class="hljs-keyword">string</span> <span class="hljs-string">`json:"name"`</span>
    } <span class="hljs-string">`json:"items"`</span>
    UpdatedAt time.Time <span class="hljs-string">`json:"updated_at"`</span>
}

putOp, err := reflector.NewOperationContext(http.MethodPut, <span class="hljs-string">"/things/{id}"</span>)
handleError(err)

putOp.AddReqStructure(<span class="hljs-built_in">new</span>(req))
putOp.AddRespStructure(<span class="hljs-built_in">new</span>(resp), <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(cu *openapi.ContentUnit)</span></span> { cu.HTTPStatus = http.StatusOK })
putOp.AddRespStructure(<span class="hljs-built_in">new</span>([]resp), <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(cu *openapi.ContentUnit)</span></span> { cu.HTTPStatus = http.StatusConflict })

reflector.AddOperation(putOp)

getOp, err := reflector.NewOperationContext(http.MethodGet, <span class="hljs-string">"/things/{id}"</span>)
handleError(err)

getOp.AddReqStructure(<span class="hljs-built_in">new</span>(req))
getOp.AddRespStructure(<span class="hljs-built_in">new</span>(resp), <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(cu *openapi.ContentUnit)</span></span> { cu.HTTPStatus = http.StatusOK })

reflector.AddOperation(getOp)

schema, err := reflector.Spec.MarshalYAML()
<span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
    log.Fatal(err)
}

fmt.Println(<span class="hljs-keyword">string</span>(schema))
</code></pre>
<p>This is quite a lot of code to write to support an OpenAPI spec but it supports the generation of the Open API spec YAML file.</p>
<h2 id="heading-why-and-why-not-use-annotations-to-generate-the-openapi-spec">Why (and why not) use annotations to generate the OpenAPI spec?</h2>
<p>Let’s go over some of the reasons why you would or wouldn’t want to use this approach.</p>
<p>Why you may want to use this approach:</p>
<ul>
<li>The documentation is right near the code, so if you change the code, you may be more likely to update the documentation since it’s right there</li>
</ul>
<p>Why you may not want to use this approach:</p>
<ul>
<li><p>Same reason: the documentation is right near the code. This could, arguably, muck up the code, in that it adds more you need to skim past when maintaining or debugging this code. It can be argued that maybe verbose documentation doesn’t belong there.</p>
</li>
<li><p>You need to learn the abstraction layer of whatever library you’re using. This can be tedious and arguably not a valuable thing to learn and memorize. The initial learning curve can be higher than learning the spec outright since there are tools to help with code completion and validation of the schema file.</p>
</li>
<li><p>Depending on the language (e.g. Go lang), you may end up writing and shipping a significant amount of extra code related to documenting the Open API spec when using this approach. You may even end up writing more in code than you would have in YAML. Depending on when the code runs and if it’s a compile time operation or a runtime operation, we may create bugs or crashes in the application.</p>
</li>
</ul>
<p>There may be other reasons to prefer or not prefer this approach, but these are the ones I thought of immediately. If you have reasons you like or dislike this approach, feel free to drop a comment.</p>
<h2 id="heading-summary">Summary</h2>
<p>This is the 3rd article in a series of articles about working with Open API. In this post we looked at 3 programming languages where we can generate an Open API spec and sometimes Swagger UI documentation website by annotating code or also by writing lines of code inline. Depending on the language, this can result in a significant amount of code that needs to be written.</p>
<p>In <a target="_blank" href="https://blog.tinaciousdesign.com/open-api-generate-client">part 1</a> we went over how to generate API clients in TypeScript and API Reference documentation websites.</p>
<p>In <a target="_blank" href="https://blog.tinaciousdesign.com/open-api-contract-first">part 2</a> we went over hand-coding Open API spec files ourselves.</p>
<p>If you have anything to add to the discussion, feel free to leave a comment!</p>
]]></content:encoded></item><item><title><![CDATA[Open API (Part 2): Handcrafted, bespoke, artisanal Open API specs: API contract-first]]></title><description><![CDATA[Overview
This article is the 2nd in a series of blog posts that discuss using Open API to help with API documentation and API client code generation.
In the previous article, we went over how we can generate documentation and API clients using existi...]]></description><link>https://blog.tinaciousdesign.com/open-api-contract-first</link><guid isPermaLink="true">https://blog.tinaciousdesign.com/open-api-contract-first</guid><category><![CDATA[OpenApi]]></category><category><![CDATA[swagger]]></category><category><![CDATA[swagger-ui]]></category><category><![CDATA[documentation]]></category><category><![CDATA[json]]></category><dc:creator><![CDATA[Tina Holly]]></dc:creator><pubDate>Mon, 07 Oct 2024 10:00:34 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1727414255773/be149859-3feb-4ac8-95c0-5b4a1b72b308.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-overview">Overview</h2>
<p>This article is the 2nd in a series of blog posts that discuss using Open API to help with API documentation and API client code generation.</p>
<p>In <a target="_blank" href="https://blog.tinaciousdesign.com/open-api-generate-client">the previous article</a>, we went over how we can generate documentation and API clients using existing Open API spec files. In this article, we’ll go over one way we can create this spec file, which will be the foundation for documentation and code generation.</p>
<p>The next 2 articles (this one and the next) will go over the 2 separate approaches I’m covering for creating an Open API spec file:</p>
<ol>
<li><p>API contract-first, bespoke artisanal handcrafted Open API spec file</p>
</li>
<li><p>Generated Open API spec file</p>
</li>
</ol>
<h2 id="heading-why-and-why-not-api-contract-first">Why (and why not) API contract-first?</h2>
<p>One approach to creating Open API spec files is to create the API contract and definition first and then build to the spec.</p>
<p>This is a great approach if you have multiple teams working on an API and you are building towards a common goal.</p>
<p>This approach only works if all teams are on board to doing it this way. If, say, one team is building out the front-end to an API contract, but the backend team is not adhering to the defined contract, then the front-end will not work, and the documentation is essentially useless.</p>
<p>There are some pros with this approach:</p>
<ul>
<li><p>API contract can be formalized up front, allowing back-end and front-end teams to respectively work in parallel on the software to support the back-end and front-end of the product</p>
</li>
<li><p>API design is up front and centre, enabling API designers to optimize for the developer experience of the future API consumers</p>
</li>
</ul>
<p>There are some cons with this approach:</p>
<ul>
<li><p>If you’re working in a setup where back-end and front-end teams do not collaborate, what can happen is that the front-end team builds towards an API contract they thought was finalized, meanwhile the backend team is building out an API that does not adhere to the contract. This would then require the front-end team to catch up on the API changes in order for their software to work, or the back-end team to revisit the API they implemented to respect the contract.</p>
</li>
<li><p>Hand-coding these spec files can be tedious and prone to error if you do not have the right tools at your disposal to assist in validating your API schema and testing the documented endpoints</p>
</li>
<li><p>Spec files are disconnected from the code and risk becoming out of date</p>
</li>
</ul>
<p>In this blog post, covering the scenario where API contract changes are not adequately communicated is out of scope, but we will be going over the tools we can use to help make hand-coding Open API spec files an accurate and enjoyable process.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727414448176/b896dbaa-940b-4091-b0ac-ed2a9ceca5cc.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-optimizing-for-hand-coding-nested-json-files">Optimizing for hand-coding nested JSON files</h2>
<p>I will be blunt with this part and state that I prefer using the JSON format for Open API specs. I have not been a fan of YAML in general as I find it difficult to follow the indentation. I like my code braces.</p>
<p>In the below screenshot, I’ve got JSON and YML side-by-side for comparison.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727409883583/c27e512b-9f4b-4a06-a3df-637897942ca6.png" alt class="image--center mx-auto" /></p>
<p>In the above screenshot you may have noticed the rainbow-coloured JSON properties that change colours as the level of JSON is nested deeper. This is my custom <a target="_blank" href="https://marketplace.visualstudio.com/items?itemName=tinaciousdesign.theme-tinaciousdesign">Visual Studio Code theme called Tinacious Design</a>. Since <a target="_blank" href="https://github.com/tinacious/vscode-tinacious-design-syntax/pull/9">early 2020 I’ve had support for over 30 levels of JSON in my theme</a>. This is probably excessive but I like it when working with Open API specs or other large, deeply nested JSON files.</p>
<p>I’m guessing there’s similar support for YML but I haven’t run into it.</p>
<p>If you’re using the <a target="_blank" href="https://editor.swagger.io/">Swagger Editor</a> online, you may prefer YAML since they have better syntax highlighting support for it.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727410497098/92bcf4e3-be0f-4403-96d7-5b3e652f5bfe.png" alt class="image--center mx-auto" /></p>
<p>I prefer to use my own tools since I have access to the code in the same editor.</p>
<p>If you’re using your own tool, it would be a great idea to ensure you have the ability to preview your API. There are <a target="_blank" href="https://marketplace.visualstudio.com/search?term=swagger%20preview&amp;target=VSCode&amp;category=All%20categories&amp;sortBy=Relevance">many extensions</a> that have support for previews.</p>
<p>Another thing you’d definitely want is language support so that you can ensure you’re your spec is valid. There are also <a target="_blank" href="https://marketplace.visualstudio.com/search?term=swagger%20language&amp;target=VSCode&amp;category=All%20categories&amp;sortBy=Relevance">many extensions</a> that claim to have language for Open API specs.</p>
<p>I ended up choosing <a target="_blank" href="https://marketplace.visualstudio.com/items?itemName=42Crunch.vscode-openapi">one extension that did both</a>. Please note that this appears to be an extension that is a very helpful suite of tools that wraps a commercial product. I did not use the paid features of this extension, only the free ones for editing and previewing my spec file.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727411046520/55e16889-2dde-4172-a67d-eb97a8945ff1.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727411232208/df155d10-c7ce-4810-a664-0505913e62ba.png" alt class="image--center mx-auto" /></p>
<p>The first screenshot above shows the OpenAPI preview with all elements collapsed using the Tinacious Design (dark) theme, while the second shows the OpenAPI preview with one of the elements expanded, using the Tinacious Design (High Contrast, Dark) theme. Both of those themes support colourizing for 30+ levels of nested JSON and are <a target="_blank" href="https://marketplace.visualstudio.com/items?itemName=tinaciousdesign.theme-tinaciousdesign">available in this extension</a>.</p>
<h2 id="heading-generating-api-types-from-the-hand-coded-open-api-spec">Generating API types from the hand-coded Open API spec</h2>
<p>There are many tools available for this task. After a quick Google search and looking over the documentation, I ended up going with <a target="_blank" href="https://openapistack.co/docs/openapicmd/intro/">openapicmd</a>. While this was a good tool to generate all of the types into a single file, the API wasn’t readable enough for me to know at a quick glance how to use the same tool for implementing a generated client as discussed in the Part 1 blog post, so for that part I ended up choosing a different tool, <a target="_blank" href="https://heyapi.vercel.app/openapi-ts/clients.html">heyapi/openapi-ts</a>, which can do both in a straightforward way.</p>
<p>Here’s the command I used to generate the types from the spec file using <a target="_blank" href="https://openapistack.co/docs/openapicmd/intro/">openapicmd</a>:</p>
<pre><code class="lang-bash">npx openapi typegen public/openapi.json &gt; models/api-types/api-schema.d.ts
</code></pre>
<p>This outputs all the types to a single file.</p>
<h2 id="heading-dogfooding-the-api-types">Dogfooding the API types</h2>
<p>One thing we can do to try to connect the API spec to the real code is using the types that end up being generated. For example, if you’re writing a Next.js or Express handler, you can do so in a way where you use the generated response types. Here’s a very naive example of that:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { NextApiRequest, NextApiResponse } <span class="hljs-keyword">from</span> <span class="hljs-string">'next'</span>;
<span class="hljs-keyword">import</span> { BaseResponse } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../utils/api/api-utils'</span>;
<span class="hljs-keyword">import</span> { Environment } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../utils/Environment'</span>;
<span class="hljs-keyword">import</span> { ResponseGetHealth } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../models/api-types/api-schema'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">healthHandler</span>(<span class="hljs-params">
  request: NextApiRequest,
  response: NextApiResponse&lt;BaseResponse&lt;ResponseGetHealth['data']&gt;&gt;
</span>) </span>{
  <span class="hljs-keyword">return</span> response
    .status(<span class="hljs-number">200</span>)
    .json({
      data: {
        version: Environment.getGitCommitHash() || <span class="hljs-string">'unknown'</span>,
        openapi: <span class="hljs-string">'https://tinaciousdesign.com/openapi.json'</span>
      }
    })
}
</code></pre>
<p>It uses the generated type <code>ResponseGetHealth</code>:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> ResponseGetHealth {
  data: {
    <span class="hljs-comment">/**
     * example:
     * 7ca91a975bfb85b940a290cd3116330ef9fbe6a9
     */</span>
    version: <span class="hljs-built_in">string</span>;
    <span class="hljs-comment">/**
     * example:
     * https://tinaciousdesign.com/openapi.json
     */</span>
    openapi: <span class="hljs-built_in">string</span>;
  };
}
</code></pre>
<p>For example, if I delete the <code>openapi</code> property which is required by the spec, it’ll show an error:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727412248072/bd2a1734-3924-4e2a-bce4-b0d1be5b5801.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-summary">Summary</h2>
<p>This set of tools and approaches is one way we can create Open API specs to support generating API clients and documentation. This is a great approach for creating high-quality API documentation provided that all teams involved are willing to work towards creating and building against an agreed-upon API contract.</p>
<p>This is the 2nd post in a series of post. In the next one, we’ll be going over a different approach, which is code-first rather than API contract-first, in which documentation is generated from the code. Stay tuned for the next one!</p>
]]></content:encoded></item><item><title><![CDATA[Open API (Part 1): Generating API clients and documentation from an Open API spec]]></title><description><![CDATA[Overview
This article starts a series of Open API-related blog posts.
OpenAPI, previously known as Swagger (a name still in use today for many of its tools), is a suite of tools that generate documentation and networking clients for various programmi...]]></description><link>https://blog.tinaciousdesign.com/open-api-generate-client</link><guid isPermaLink="true">https://blog.tinaciousdesign.com/open-api-generate-client</guid><category><![CDATA[OpenApi]]></category><category><![CDATA[swagger]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[React]]></category><category><![CDATA[documentation]]></category><category><![CDATA[swagger-ui]]></category><dc:creator><![CDATA[Tina Holly]]></dc:creator><pubDate>Mon, 30 Sep 2024 10:00:40 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1727408511569/20588c5a-2d0e-4efe-8bc7-f4dae083d01e.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-overview">Overview</h2>
<p>This article starts a series of Open API-related blog posts.</p>
<p>OpenAPI, previously known as Swagger (a name still in use today for many of its tools), is a suite of tools that generate documentation and networking clients for various programming languages. It uses what’s called an Open API spec, which is versioned, and can be written in YAML and JSON (my preference being JSON).</p>
<p>The order of operations for these blog posts may be somewhat reversed, but in future posts I’ll get to how we can create these spec files in a couple of ways.</p>
<h2 id="heading-options-for-generating-documentation-from-an-open-api-spec-yaml-or-json-file">Options for generating documentation from an Open API spec YAML or JSON file</h2>
<p>Generating documentation for an OpenAPI spec file is probably the easiest task out of all of these. There are many tools available for performing this operation, each with varying difficulties of time or money spent, here’s a few:</p>
<ul>
<li><p>Self-hosted options:</p>
<ul>
<li><p><a target="_blank" href="https://github.com/PaloAltoNetworks/docusaurus-openapi-docs">Docusaurus plugin</a></p>
</li>
<li><p>maybe <a target="_blank" href="https://github.com/vuejs/vitepress/issues/4133">VitePress soon</a></p>
</li>
<li><p>maybe <a target="_blank" href="https://github.com/HiDeoo/starlight-openapi">Astro Starlight</a></p>
</li>
<li><p>Roll your own</p>
</li>
</ul>
</li>
<li><p>Hosted options:</p>
<ul>
<li><p><a target="_blank" href="https://mintlify.com/">Mintlify</a></p>
</li>
<li><p><a target="_blank" href="https://redocly.com/">Redocly</a></p>
</li>
<li><p><a target="_blank" href="https://hashnode.com/blog/create-a-rich-api-playground-documentation-for-developers-with-one-click-from-openapi-specification">Hashnode Documentation</a> (just launched recently)</p>
</li>
</ul>
</li>
</ul>
<p>As you can see, there’s no shortage of options. There’s probably countless options I’ve missed, so feel free to mention them in the comments.</p>
<p>I got an email recently about the new feature for Hashnode’s documentation generator, and since I already use the platform for my blog and think very highly of it, I thought I’d give it a try.</p>
<p>It was very easy to create a docs site and point it to my OpenAPI spec. The hard part was creating that spec properly so that I liked what I saw on the generated API Reference.</p>
<p>For this, I decided to document the API for my portfolio website <a target="_blank" href="https://tinaciousdesign.com">Tinacious Design</a>. <a target="_blank" href="https://tinaciousdesign.com/openapi.json">The OpenAPI spec can be viewed here</a>.</p>
<p>You can take a look at the generated documentation here: <a target="_blank" href="https://tinacious-design-api.hashnode.space/">https://tinacious-design-api.hashnode.space</a></p>
<p>Here it is in dark mode:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727403775700/915711f1-733b-4d54-9ce7-5ad8d590e40e.png" alt class="image--center mx-auto" /></p>
<p>And in light mode:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727403854904/b88016f8-5997-4096-ba30-2a96fbd9efeb.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-docs-summary">Docs Summary</h3>
<p>While the final product of this isn’t particularly useful for anyone except myself, I wanted to build out another proof-of-concept OpenAPI documentation approach. Another, you say? Yes, I’ve already tried out one method, and so I tried out another. More to come in future blog posts. This is only part 1 of a multiple part series.</p>
<h2 id="heading-generating-openapi-clients-in-typescript">Generating OpenAPI clients in TypeScript</h2>
<p>One of the benefits of using OpenAPI is that since the spec follows a consistent and detailed format for an API, it can be used to generate API clients to avoid time spent writing out the code manually, which can be time-consuming and is prone to human error.</p>
<h3 id="heading-typescript-axios-generator">typescript-axios-generator</h3>
<p>When I was working with GrowthBook I built them a CLI tool for generating type-safe feature flags and interfacing with the REST API. While there are countless options for JavaScript and TypeScript client generation with varying degrees of satisfaction, for <a target="_blank" href="https://github.com/growthbook/growthbook-cli/pull/29">this implementation</a>, I ended up using the <a target="_blank" href="https://openapi-generator.tech/docs/generators/typescript-axios/">typescript-axios-generator</a>. This tool generated API clients which I ended up <a target="_blank" href="https://github.com/growthbook/growthbook-cli/blob/e0faa75cfad5f2585faad311b2eb26f6440d5e03/src/repositories/features.repository.ts">wrapping using the repository pattern</a>, which helped me quickly build out commands to support all of the REST API endpoints following a simple pattern.</p>
<h3 id="heading-hey-apiopenapi-ts">hey-api/openapi-ts</h3>
<p>For my own API that I documented using Hashnode’s new tool as shown above, I decided to try another solution and went with <a target="_blank" href="https://heyapi.vercel.app/openapi-ts/clients.html">hey-api/openapi-ts</a>.</p>
<p>First, I downloaded the OpenAPI script and then used their CLI tool to generate API clients using the spec.</p>
<pre><code class="lang-bash">curl https://tinaciousdesign.com/openapi.json &gt; tmp/openapi.json
npx @hey-api/openapi-ts -i tmp/openapi.json -o src/api/client -c @hey-api/client-fetch
</code></pre>
<p>In the above script, I use <a target="_blank" href="https://curl.se/">curl</a> to make a network request and send the output into a file at <code>tmp/openapi.json</code>, where <code>tmp</code> is a directory configured to be ignored by version control in my project.</p>
<p>After saving the spec to a file, I then run the <code>openapi-ts</code> CLI tool to generate a client and have it output to <code>src/api/client</code> which is the directory I’ve chosen for this task. Passing <code>-c</code> with a value configures which client we want to generate, in this case a networking client that uses the browser native <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API">fetch</a>.</p>
<p>This generates multiple files, but the ones I care about most for my implementation are the <code>services.gen.ts</code> file and the supporting <code>types.gen.ts</code> file, which respectively contain the generated services I can use to call all the endpoints and supporting types.</p>
<p>For my proof-of-concept implementation, I decided to use React. The generator is framework-agnostic, so you can use any framework (or lack thereof) that you prefer. My implementation code looks like this:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// App.tsx</span>

<span class="hljs-keyword">import</span> { useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { portfolioGet } <span class="hljs-keyword">from</span> <span class="hljs-string">"./api/services.gen"</span>;
<span class="hljs-keyword">import</span> { PortfolioItem } <span class="hljs-keyword">from</span> <span class="hljs-string">"./api/types.gen"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"./styles.css"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [isLoading, setIsLoading] = useState(<span class="hljs-literal">false</span>);
  <span class="hljs-keyword">const</span> [items, setItems] = useState&lt;PortfolioItem[]&gt;([]);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    setIsLoading(<span class="hljs-literal">true</span>);

    portfolioGet()
      .then(<span class="hljs-function">(<span class="hljs-params">{ data }</span>) =&gt;</span> {
        setItems(data.data);
      })
      .catch(<span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> {
        <span class="hljs-comment">// todo: handle error</span>
      })
      .finally(<span class="hljs-function">() =&gt;</span> {
        setIsLoading(<span class="hljs-literal">false</span>);
      });
  }, []);

  <span class="hljs-keyword">return</span> (
    &lt;&gt;
      {<span class="hljs-comment">/* View template-related code */</span>}
    &lt;/&gt;
  );
}
</code></pre>
<p>In order for this to work, I also needed to do some very minor bootstrapping in the index file, which requires setting the base URL for the client:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// index.tsx</span>

<span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> ReactDOM <span class="hljs-keyword">from</span> <span class="hljs-string">"react-dom/client"</span>;
<span class="hljs-keyword">import</span> App <span class="hljs-keyword">from</span> <span class="hljs-string">"./App"</span>;
<span class="hljs-keyword">import</span> { client } <span class="hljs-keyword">from</span> <span class="hljs-string">"./api/services.gen"</span>;

<span class="hljs-keyword">const</span> rootElement = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"root"</span>)!;
<span class="hljs-keyword">const</span> root = ReactDOM.createRoot(rootElement);

<span class="hljs-comment">// Initialize OpenAPI client</span>
client.setConfig({
  baseUrl: <span class="hljs-string">"https://tinaciousdesign.com/api"</span>,
});

root.render(
  &lt;React.StrictMode&gt;
    &lt;App /&gt;
  &lt;/React.StrictMode&gt;
);
</code></pre>
<p>Arguably this OpenAPI client bootstrapping code could’ve gone elsewhere but putting it here makes the proof-of-concept quick to read.</p>
<p>The React proof-of-concept fetches all portfolio items and renders them in a 2-up to 4-up grid (depending on the screen size).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727406759931/4889f9dd-98ef-4a79-8ca5-83f1f42deaf1.png" alt class="image--center mx-auto" /></p>
<p>You can view <a target="_blank" href="https://codesandbox.io/p/sandbox/openapi-client-example-nrlzfn?file=%2Fsrc%2FApp.tsx">this CodeSandbox here</a>. The generated API files can be found in the <code>api</code> directory <a target="_blank" href="https://codesandbox.io/p/sandbox/openapi-client-example-nrlzfn?file=%2Fsrc%2Fapi%2Findex.ts%3A4%2C29">here</a>.</p>
<h3 id="heading-api-client-generation-summary">API client-generation summary</h3>
<p>We looked at how we can generate API clients in a couple of ways, and what example implementations could look like for 2 separate code generation approaches.</p>
<h2 id="heading-summary">Summary</h2>
<p>So that covers part 1 of this Open API series of blog tools. We went over how we can use an already-generated spec file to its full potential by generating documentation and API clients. Next, we’ll look at creating this spec file in 2 ways.</p>
<p>Stay tuned for more!</p>
]]></content:encoded></item><item><title><![CDATA[Android apps are reading your clipboard – here's how you can stop them]]></title><description><![CDATA[You've probably already seen the scary alert (that's possibly why you're here), but in case you haven't, it looks something like this:

NewAppIJustDownloaded pasted from your clipboard

Sometimes it h]]></description><link>https://blog.tinaciousdesign.com/android-apps-are-reading-your-clipboard-heres-how-you-can-stop-them</link><guid isPermaLink="true">https://blog.tinaciousdesign.com/android-apps-are-reading-your-clipboard-heres-how-you-can-stop-them</guid><category><![CDATA[Android]]></category><category><![CDATA[privacy]]></category><dc:creator><![CDATA[Tina Holly]]></dc:creator><pubDate>Fri, 20 Sep 2024 21:30:36 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/t8TOMKe6xZU/upload/040413fdb78d56bd943f4c2c13b33672.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>You've probably already seen the scary alert (that's possibly why you're here), but in case you haven't, it looks something like this:</p>
<blockquote>
<p>NewAppIJustDownloaded pasted from your clipboard</p>
</blockquote>
<p>Sometimes it happens during a flow where it's possibly convenient and expected (e.g. you just copied a multi-factor auth code and it would be convenient if the app pasted it for you), and other times, it's when you open the app for the first time and it absolutely is not necessary.</p>
<h2>Why am I seeing this warning?</h2>
<p>You're seeing this warning because you're on <a href="https://developer.android.com/develop/ui/views/touch-and-input/copy-paste#PastingSystemNotifications">Android version 13 or higher</a>, and because "Show clipboard access" is enabled in your privacy settings. This privacy alert is enabled by default.</p>
<p>In the below screenshot, you can see where this setting can be toggled off if you'd rather not know about every time an app accesses your clipboard (not recommended, I like to leave it on):</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1715826892748/5d94846a-b8d5-463b-94cf-5f3dea578da8.png" alt="" style="display:block;margin:0 auto" />

<p>It's important to note that this doesn't mean that prior to Android 13 apps couldn't read our clipboard, it just means they did it without us knowing about it.</p>
<h2>Disabling clipboard read permissions</h2>
<p>Currently, there doesn't appear to be a way to disable clipboard access via the Android Privacy settings as a regular Android user. I would love to be wrong about this, so if it is actually possible, please let me know in the comments.</p>
<p>You can, however, do it by remotely executing scripts via <a href="https://developer.android.com/tools/adb">ADB</a> shell, but this requires downloading the <a href="https://developer.android.com/tools/releases/platform-tools">Android platform tools</a> so you can have access to the <code>adb</code> executable. It also requires you to enable USB debugging on your Android device so you can access your device with these tools. I'm going to go over this here.</p>
<h3>Installing the tools and preparing your device</h3>
<p>Before you can disable apps from accessing your keyboard, you'll need to download the right tools and configure the device:</p>
<ol>
<li><p>Install the <a href="https://developer.android.com/tools/releases/platform-tools">Android platform tools</a></p>
</li>
<li><p><a href="https://developer.android.com/studio/debug/dev-options">Enable USB debugging</a> on your Android device</p>
</li>
<li><p>Execute the command <code>adb devices</code> in your terminal to make sure ADB can connect to your device</p>
</li>
</ol>
<p>If you can run the following script:</p>
<pre><code class="language-bash">adb devices
</code></pre>
<p>And you see something like this:</p>
<pre><code class="language-bash">$ adb devices
List of devices attached
ABC123XYZ456 device
</code></pre>
<p>You're ready to proceed.</p>
<p>If there is an error executing the <code>adb</code> command, it's likely you don't have the tool installed, or it's not on your path (i.e. your command prompt doesn't know where to find it). If you see an error like this, this is the case:</p>
<pre><code class="language-plaintext">command not found: adb
</code></pre>
<p>You'll need to install the <a href="https://developer.android.com/tools/releases/platform-tools">Android platform tools</a> somewhere accessible in your path.</p>
<p>If, however, you don't see that error and the list is empty, it's possible you haven't enabled USB debugging on your device. Here's some example output for that:</p>
<pre><code class="language-plaintext">$ adb devices
List of devices attached
</code></pre>
<p>You'll need to <a href="https://developer.android.com/studio/debug/dev-options">enable USB debugging</a> on your Android device.</p>
<h3>Seeing which apps have clipboard read access</h3>
<p>Once you've got the Android platform tools installed and you can run <code>adb</code> and see your device, you should be able to proceed.</p>
<p>Run the following script to see which apps have access to read your clipboard data:</p>
<pre><code class="language-bash">#!/bin/bash

echo "";
echo "  👁️‍🗨️  The following apps have access to read your clipboard data";
echo "";
adb shell cmd appops query-op --user 0 READ_CLIPBOARD allow
</code></pre>
<p>You should see a list of package names that looks something like this:</p>
<pre><code class="language-plaintext">
  👁️‍🗨️  The following apps have access to read your clipboard data

com.google.android.as
com.google.android.apps.messaging
com.google.android.dialer
com.android.chrome
com.google.android.calendar
com.google.android.inputmethod.latin
com.google.android.gm
com.android.systemui
</code></pre>
<p>In the above snippet I've only listed some packages, but your list should be much more comprehensive and include apps you've downloaded. In that list, you should see the offending app.</p>
<p>If you don't know an app package name, just Google "<code>app com.whatever.package.name</code>" and see what comes up—one of the first hits should be the Google Play Store listing. Alternatively you could install this <a href="https://github.com/tinacious/about-apps-android">app I built called AboutApps</a> that shows you all your installed apps, allowing you to search by package name (you’ll need to build it yourself). Here’s a screenshot of the app’s UI:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1726867718695/77e21a58-741d-4620-bd22-3037fb17d68a.png" alt="" style="display:block;margin:0 auto" />

<h3>Are all these apps reading my clipboard?</h3>
<p>Short answer: probably not. If you haven't seen the alert that says that they are, then we should be able to trust that they aren't since Android would tell us when they do. So why are they showing up in this list? It's likely that they are <strong>writing</strong> to the clipboard.</p>
<p>On my device, there are a lot of apps listed here, including apps I've developed that I know first hand that don't read the clipboard. Apps appear to be listed even if they just write to your clipboard.</p>
<p>For example, one app I developed called <a href="https://cannedreplies.com/">Canned Replies</a> (package name <code>com.tinaciousdesign.cannedreplies</code>) is a productivity tool that allows you to quickly copy messages so you can paste them wherever you need them. You enter your frequently-sent messages (canned replies) in the app, and you tap one when you're ready to send it. It gets copied to your clipboard when you tap it. In order to allow you to quickly paste it somewhere else, I need to copy it first. The app only writes to your clipboard but is still listed in this list as an app that reads your clipboard. You can safely revoke the app's access to reading your clipboard data (explained below) and it will still function since it does not read your clipboard data.</p>
<p>I suspect that accessing the <a href="https://developer.android.com/reference/android/content/ClipboardManager">ClipboardManager</a> class or querying for <code>context.getSystemService("clipboard")</code> is enough to have it show up in this list. This is the relevant code that is making <a href="https://cannedreplies.com/">Canned Replies</a> show up in the list:</p>
<pre><code class="language-kotlin">fun copyText(text: String?, clipboardKey: String, context: Context) {
    val clipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
    val clipData = ClipData.newPlainText(clipboardKey, text)
    clipboardManager.setPrimaryClip(clipData)
}
</code></pre>
<p>Many apps need to query this service in order to copy data to your clipboard, which can happen when apps enable common functionality like the ability to copy content to your clipboard so you can share it more easily.</p>
<p>If an app is listed and they haven't been doing anything creepy, it's probably safe to ignore it. Alternatively, you can revoke its permissions (see below) and it should continue to behave as expected, which is the case with <a href="https://cannedreplies.com/">Canned Replies</a>.</p>
<p>With <a href="https://developer.android.com/develop/ui/views/touch-and-input/copy-paste#PastingSystemNotifications">Android 13+'s new clipboard read alerts as described here</a>, you should be aware of which apps are accessing the clipboard anyway. If you're on Android 10+ you should be able to trust that apps will only be able to try to read your clipboard while they're foregrounded according to the <a href="https://developer.android.com/about/versions/10/privacy/changes#clipboard-data">privacy changes in Android 10 -&gt; Limited access to clipboard data</a>.</p>
<h3>Revoking clipboard read access from an app manually</h3>
<p>You can prevent one of the apps listed from accessing the clipboard by running the following command against the package. Let's say we want to prevent the Shopify Shop app from accessing our clipboard data, which is an app, from my experience, that reads your clipboard on startup, we can do so by running the following command:</p>
<pre><code class="language-bash">adb shell cmd appops set com.shopify.arrive READ_CLIPBOARD ignore
</code></pre>
<p>We can also do that for LinkedIn (another app I've experienced this from):</p>
<pre><code class="language-bash">adb shell cmd appops set com.linkedin.android READ_CLIPBOARD ignore
</code></pre>
<p>And TikTok (allegedly, according to people on the internet that are not myself as I have not installed this app to test it):</p>
<pre><code class="language-bash">adb shell cmd appops set com.zhiliaoapp.musically READ_CLIPBOARD ignore
</code></pre>
<h3>Revoking clipboard read access from an app with a script</h3>
<p>If manually revoking packages one by one sounds tedious, you can do so with the following script, which prompts you to paste a list of app packages as input. When you’re done, press Ctrl+D to let it proceed.</p>
<pre><code class="language-shell">#!/bin/bash

echo "";
echo '  🤷🏻‍♀️ Clipboard permissions before script:'
echo "";

adb shell cmd appops query-op --user 0 READ_CLIPBOARD allow

echo "";
echo '  💥 Paste a list of package names you would like to remove clipboard read permissions for and press Ctrl+D when done:'
echo "";

TARGET_APPS=()
while IFS= read -r line
do
  # Add each non-empty line to TARGET_APPS array
  [[ -n "\(line" ]] &amp;&amp; TARGET_APPS+=("\)line")
done

for target_app in "${TARGET_APPS[@]}"
do
  echo "";
  echo "⏳ Attempting to remove READ_CLIPBOARD permissions from app: $target_app"
  adb shell cmd appops set $target_app READ_CLIPBOARD ignore
done

echo "";
echo '  ✅ Clipboard permissions after script:'
echo "";
adb shell cmd appops query-op --user 0 READ_CLIPBOARD allow
</code></pre>
<p>This produces the following output:</p>
<pre><code class="language-plaintext">./clipboard_permissions_deny.sh

  🤷🏻‍♀️ Clipboard permissions before script:

# ...

  💥 Paste a list of package names you would like to remove clipboard read permissions for and press Ctrl+D when done:

com.shopify.arrive
com.linkedin.android
^D
⏳ Attempting to remove READ_CLIPBOARD permissions from app: com.shopify.arrive

⏳ Attempting to remove READ_CLIPBOARD permissions from app: com.linkedin.android

  ✅ Clipboard permissions after script:

# ...
</code></pre>
<h2>What you can do as an Android developer</h2>
<p>Are you developing for Android, and is your Android app currently warning users that it's reading their clipboard data? Are you using any libraries? If so, do you know if any of these libraries need to read the clipboard for their functionality? If so, one way you can avoid scaring your users is by warning them ahead of time before you attempt to access the clipboard data with <a href="https://developer.android.com/develop/ui/views/touch-and-input/copy-paste#PastingSystemNotifications">getPrimaryClip()</a>. If you don't know of any libraries you're using that require this functionality, it would be a good idea to look into why it's happening so that you don't lose the trust of your privacy-conscious users.</p>
<h2>Get these scripts</h2>
<p>You can get these scripts and any other Android-related privacy scripts I decide to add from this Github repository.</p>
<p>The Github repository will have up-to-date scripts while these ones inlined in the article might become out of date.</p>
<p><a class="embed-card" href="https://github.com/tinacious/android-privacy-scripts">https://github.com/tinacious/android-privacy-scripts</a></p>
You can clone the project and run the scripts from the project’s directory.

<pre><code class="language-bash">git clone https://github.com/tinacious/android-privacy-scripts.git
</code></pre>
]]></content:encoded></item><item><title><![CDATA[PDF to PNG to PDF on macOS using ImageMagick]]></title><description><![CDATA[ImageMagick is an awesome command line tool for working with images.
Convert PDF to PNG files with magick
Convert a multi-page PDF file to PDFs with ImageMagick using the following command:
magick density -300 input.pdf -resize 30% outdir/output.png
...]]></description><link>https://blog.tinaciousdesign.com/pdf-to-png-to-pdf-macos-imagemagick-finder</link><guid isPermaLink="true">https://blog.tinaciousdesign.com/pdf-to-png-to-pdf-macos-imagemagick-finder</guid><dc:creator><![CDATA[Tina Holly]]></dc:creator><pubDate>Thu, 01 Aug 2024 04:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1728850332776/68464679-72ca-4134-815a-971a4aabd968.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a target="_blank" href="https://imagemagick.org/index.php">ImageMagick</a> is an awesome command line tool for working with images.</p>
<h2 id="heading-convert-pdf-to-png-files-with-magick">Convert PDF to PNG files with magick</h2>
<p>Convert a multi-page PDF file to PDFs with ImageMagick using the following command:</p>
<pre><code class="lang-bash">magick density -300 input.pdf -resize 30% outdir/output.png
</code></pre>
<p>You can adjust the density and resize values for your preferred file size and quality.</p>
<h2 id="heading-combine-png-files-to-pdf-in-finder">Combine PNG files to PDF in Finder</h2>
<p>Select all output PNG files, right click, go to Quick Actions → Create PDF.</p>
<h2 id="heading-improving-this">Improving this</h2>
<h3 id="heading-can-this-be-done-with-just-imagemagick">Can this be done with just ImageMagick?</h3>
<p>I did attempt the following command but the images were smaller than ideal.</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> outdir
magick *.png -scale 1311x1819 \
    -units PixelsPerInch \
    -density 300x300 output.pdf
</code></pre>
<p>Doing it all with ImageMagick would make this easier to script.</p>
<h2 id="heading-references">References</h2>
<ul>
<li><p><a target="_blank" href="https://stackoverflow.com/a/18863052">https://stackoverflow.com/a/18863052</a></p>
</li>
<li><p><a target="_blank" href="https://imagemagick.org/discourse-server/viewtopic.php?t=26548">https://imagemagick.org/discourse-server/viewtopic.php?t=26548</a></p>
</li>
<li><p><a target="_blank" href="https://www.imagemagick.org/discourse-server/viewtopic.php?t=35159">https://www.imagemagick.org/discourse-server/viewtopic.php?t=35159</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Simple Event Bus in Android using coroutines and flows]]></title><description><![CDATA[In this post I will describe a simple event bus pattern in Android that is implemented using coroutines and flows. Thanks to Kotlin, coroutines, and flows, this is a simple approach that requires very little code.
What is an Event Bus?
Event bus is a...]]></description><link>https://blog.tinaciousdesign.com/simple-event-bus-android-coroutines-flows</link><guid isPermaLink="true">https://blog.tinaciousdesign.com/simple-event-bus-android-coroutines-flows</guid><category><![CDATA[Kotlin]]></category><category><![CDATA[kotlin beginner]]></category><category><![CDATA[coroutines]]></category><category><![CDATA[Android]]></category><category><![CDATA[android development]]></category><dc:creator><![CDATA[Tina Holly]]></dc:creator><pubDate>Tue, 24 Aug 2021 22:51:52 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1629845318429/VPkBokOVJ.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this post I will describe a simple event bus pattern in Android that is implemented using coroutines and flows. Thanks to Kotlin, coroutines, and flows, this is a simple approach that requires very little code.</p>
<h2 id="what-is-an-event-bus">What is an Event Bus?</h2>
<p>Event bus is a pattern that can be implemented in any programming language. An Event Bus is simply a class that implements this pattern. </p>
<p>In Android, there are libraries that have named themselves "EventBus," but it is not a library by default, it is a pattern that can be implemented quite easily without the use of any libraries.</p>
<p>The event bus approach and pattern is a tool that can be leveraged in an event-driven application. Event-driven applications are reactive and react to events as they occur. </p>
<p>One of the benefits of using the event bus pattern is to simplify your application architecture and decouple components that may need to know about each others' data. So, rather than injecting components into each other—which is not very scalable as your app grows—components can emit events and other components can subscribe to these events. This means that your components can instead depend on a shared EventBus class, to which they can publish and subscribe events, rather than on the components themselves that would be required to get this information.</p>
<h2 id="when-should-i-use-an-event-bus">When should I use an Event Bus?</h2>
<p>You can use an Event Bus when you need to notify the rest of the app that something has happened, without worrying about all of the dependencies, the hierarchy of your app, or the lifecycles.</p>
<p>Some examples of events that may be appropriate to use the event bus pattern for:</p>
<ul>
<li>internet connection is lost or regained</li>
<li>the user logs out of the app</li>
<li>a long-running task has completed in the background and other components may want to be notified of this task completion</li>
<li>data is updated in the background and other components may want to know when this data is updated so that they may re-fetch it</li>
</ul>
<p>You can use events for pretty much anything but they may not make sense for everything. </p>
<h2 id="when-should-i-not-use-an-event-bus">When should I NOT use an Event Bus?</h2>
<p>An Event Bus is not a one-stop solution—it is a tool that is useful in some cases but may not make sense in other cases. </p>
<p>If you have a view that, on mount, fetches data, and you want the view (fragment or activity) to be aware of this data once it's fetched, an event bus in Android is not the best approach for this. For this, I would instead recommend using MVVM (Model - View - View Model) architecture, and implementing a <a target="_blank" href="https://developer.android.com/topic/libraries/architecture/livedata">LiveData</a> observable. With LiveData, you can observe <strong>changes</strong> to this data, so that this data (once changed) can be updated in the view.</p>
<p>The event bus pattern is not meant to replace Live Data and observables, but rather complement it. You can use both at the same time, if necessary. It is very valid to have both an event bus and live data observables in an application as they have different use cases and value.</p>
<h2 id="a-simple-example-eventbus-implementation">A simple example EventBus implementation</h2>
<h3 id="eventbus-class">EventBus class</h3>
<p>The following example <code>EventBus</code> class uses Kotlin coroutine flows to provide events to subscribers. You can learn more about coroutines in Android <a target="_blank" href="https://developer.android.com/kotlin/flow">here</a>. Here is the example <code>EventBus</code> class:</p>
<pre><code class="lang-kt"><span class="hljs-keyword">import</span> android.util.Log
<span class="hljs-keyword">import</span> kotlinx.coroutines.flow.MutableSharedFlow
<span class="hljs-keyword">import</span> kotlinx.coroutines.flow.asSharedFlow

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">EventBus</span> </span>{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> _events = MutableSharedFlow&lt;AppEvent&gt;()
    <span class="hljs-keyword">val</span> events = _events.asSharedFlow()

    <span class="hljs-keyword">suspend</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">emitEvent</span><span class="hljs-params">(event: <span class="hljs-type">AppEvent</span>)</span></span> {
        Log.d(TAG, <span class="hljs-string">"Emitting event = <span class="hljs-variable">$event</span>"</span>)
        _events.emit(event)
    }

    <span class="hljs-keyword">companion</span> <span class="hljs-keyword">object</span> {
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">val</span> TAG = <span class="hljs-string">"EventBus"</span>
    }
}
</code></pre>
<p>In the above code sample, <code>AppEvent</code> is a simple enum. This approach would keep your event bus simple. You may want to instead use a sealed class so you can pass data alongside this event, though I would probably limit the complexity of the event bus by just emitting simple events and having subscribers do what they want with that data. This may or may not be the simplest or most practical approach in your application, so consider your app's needs.</p>
<p>It is important to have a <strong>single instance</strong> of the event bus so that consumers are publishing and subscribing to the same event bus. In most cases, this <code>EventBus</code> would be provided as a singleton using your dependency injection framework of choice, like <a target="_blank" href="https://insert-koin.io/">Koin</a> or <a target="_blank" href="https://developer.android.com/training/dependency-injection/hilt-android">Dagger Hilt</a>, both of which are out of scope for this article. </p>
<h3 id="publishing-events">Publishing events</h3>
<p>Publishers who want to publish events would call the <code>emitEvent</code> method with the desired event on the <code>EventBus</code> instance:</p>
<pre><code class="lang-kt">eventBus.emitEvent(AppEvent.CONNECTION_RECONNECTED)
</code></pre>
<h3 id="subscribing-to-events">Subscribing to events</h3>
<p>Subscribers who want to listen to events would collect the emitted events on the flow. This implementation could look something like this:</p>
<pre><code class="lang-kt">eventBus.events
    .filter { it == AppEvent.CONNECTION_RECONNECTED }
    .collectLatest { handleReconnection() }
</code></pre>
<h2 id="caveats-with-the-event-bus-approach">Caveats with the Event Bus approach</h2>
<p>While an event bus is a useful tool, like anything, it can be abused. An application's state can quickly get out of hand if every component is using the event bus for everything. It could get to a point where events are being fired unexpectedly, causing unexpected side effects in your app.</p>
<p>Due to this, it may make sense to only use such an approach for events that need to be global in nature (e.g. network connection is dropped or connected, the user logs out, etc.), and stick to conventional MVVM architecture for most of your app's data.</p>
<h2 id="summary">Summary</h2>
<p>In summary, the event bus pattern is a useful approach to reactive programming but is not a solution for everything. In most cases, LiveData observables may solve your problem, but in some cases you may want to leverage an event bus to react to events that are more global in nature as they occur.</p>
]]></content:encoded></item><item><title><![CDATA[Covid Today Android app]]></title><description><![CDATA[Close to the beginning of the pandemic, I made a React app that displayed COVID-19 data per country. 
This is the Android version. Built in native Android with Kotlin, I developed it as an opportunity to build something with some newer Android API's ...]]></description><link>https://blog.tinaciousdesign.com/covid-today-android-app</link><guid isPermaLink="true">https://blog.tinaciousdesign.com/covid-today-android-app</guid><category><![CDATA[Android]]></category><category><![CDATA[Kotlin]]></category><category><![CDATA[Java]]></category><dc:creator><![CDATA[Tina Holly]]></dc:creator><pubDate>Sun, 03 Jan 2021 06:35:23 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1609655683693/3CJs8ulF1.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Close to the beginning of the pandemic, I made a <a target="_blank" href="https://blog.tinaciousdesign.com/covid-19-data-by-country">React app that displayed COVID-19 data per country</a>. </p>
<p>This is the Android version. Built in native Android with Kotlin, I developed it as an opportunity to build something with some newer Android API's including LiveData, ViewPager2, and coroutines.</p>
<p>You can see the <a target="_blank" href="https://github.com/tinacious/CovidToday-Android">source code on Github</a> or <a target="_blank" href="https://install.appcenter.ms/users/info-145/apps/covid-today/distribution_groups/everybody">download the app on AppCenter</a>.</p>
<h2 id="demo-time">Demo time</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1609654665977/RbwvI2u66.gif" alt="Demo screencast of the Covid Today Android app" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1609653735601/78u1xDc-j.png" alt="Covid Today Android app screenshot" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1609653742290/kJzcNY-_0.png" alt="Covid Today Android app screenshot" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1609653748948/mrs0OH6Hp.png" alt="Covid Today Android app screenshot" /></p>
]]></content:encoded></item><item><title><![CDATA[How I made my keyboard flash red when the build breaks on CI 🚨]]></title><description><![CDATA[I recently got a Steelseries keyboard. Other than that this keyboard was one of the only RGB keyboards I could find that was supported on Mac, it had an extra draw, which was that it was programmable. 
Steelseries offers their GameSense SDK which has...]]></description><link>https://blog.tinaciousdesign.com/how-i-made-my-keyboard-flash-red-when-the-build-breaks-on-ci</link><guid isPermaLink="true">https://blog.tinaciousdesign.com/how-i-made-my-keyboard-flash-red-when-the-build-breaks-on-ci</guid><category><![CDATA[ci]]></category><category><![CDATA[Continuous Integration]]></category><category><![CDATA[Productivity]]></category><dc:creator><![CDATA[Tina Holly]]></dc:creator><pubDate>Tue, 29 Dec 2020 08:31:21 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1609229338415/uR6aVa8mC.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I recently got a <a target="_blank" href="https://steelseries.com/gaming-keyboards/apex-7?language=english&amp;switch=blue">Steelseries keyboard</a>. Other than that this keyboard was one of the only RGB keyboards I could find that was supported on Mac, it had an extra draw, which was that it was programmable. </p>
<p>Steelseries offers their <a target="_blank" href="https://github.com/SteelSeries/gamesense-sdk">GameSense SDK</a> which has a REST API you can use. This sounded simple enough, so I decided to create a dream for myself:</p>
<blockquote>
<p>As a developer, when I push up code that breaks the build on CI, I want my keyboard to flash red</p>
</blockquote>
<p>So now I had a new side project to waste my time on!</p>
<p>Here's a picture of the keyboard display showing a message for the build status. This one is for successful builds.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1609228974758/JYA-cPzAm.jpeg" alt="Build success on the Steelseries keyboard display" /></p>
<p>Here's a picture of the display when builds fail.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1609228996686/mz4VE50YE.jpeg" alt="Build fail on the Steelseries keyboard display" /></p>
<p>The function keys also flash either red or green, which you can sort of see in the demo video below.</p>
<p>This app leverages <a target="_blank" href="https://app.codeship.com/">Codeship</a> as the continuous integration service. I like Codeship for this purpose because it allows you to configure a web hook for the project and doesn't require you to modify any Yaml files in your source code to get this to work, you can just set it up in the dashboard without making a scene in your team's code. I initially looked at Github Actions and then Circle CI but neither of those had a setup as quick and easy as Codeship's.</p>
<p>I also used <a target="_blank" href="https://ngrok.com/">ngrok</a> to create a local tunnel so that Codeship had a public URL to hit.</p>
<h2 id="how-the-steelseries-gamesense-sdk-works">How the Steelseries GameSense SDK works</h2>
<p>When you install the Engine software, a server is created on your machine. Steelseries keyboards listen for events on this port, e.g. <code>localhost:&lt;port&gt;</code>.</p>
<p>Steelseries also offers official SDK's for game engines like Unity. There was an unofficial Node.js client, which I tried first, but unfortunately it didn't run and threw a bunch of errors when implementing the example code, so I needed to build my own solution.</p>
<h2 id="demo-time">Demo time</h2>
<p>If this sounds interesting, watch this demo to see it in action.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=SdvTkHwpJSU">https://www.youtube.com/watch?v=SdvTkHwpJSU</a></div>
<h2 id="get-the-code">Get the code</h2>
<p>If you like what you see, get the <a target="_blank" href="https://github.com/tinacious/steelseries-build-watch">code on Github</a>. The readme includes a tutorial on how you can set this up for yourself. Feel free to fork it. If you add any cool features, I'd love to see them!</p>
]]></content:encoded></item><item><title><![CDATA[Saving to photos in Android Q+ (Android 10+)]]></title><description><![CDATA[As of Android 10, file access permissions have changed. In a previous blog post Migrating from getExternalStoragePublicDirectory in Android 10 & 11, I moved from the legacy storage to a simpler intent-based approach that opened the native share sheet...]]></description><link>https://blog.tinaciousdesign.com/save-to-photos-gallery-android-10-android-q</link><guid isPermaLink="true">https://blog.tinaciousdesign.com/save-to-photos-gallery-android-10-android-q</guid><category><![CDATA[Android]]></category><category><![CDATA[Kotlin]]></category><category><![CDATA[React Native]]></category><category><![CDATA[iOS]]></category><dc:creator><![CDATA[Tina Holly]]></dc:creator><pubDate>Mon, 28 Dec 2020 00:42:55 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1609116157292/GoVsI0c2p.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>As of Android 10, file access permissions have changed. In a previous blog post <a target="_blank" href="https://blog.tinaciousdesign.com/migrating-from-get-external-storage-public-directory-in-android-10-and-11">Migrating from getExternalStoragePublicDirectory in Android 10 &amp; 11</a>, I moved from the legacy storage to a simpler intent-based approach that opened the native share sheet. While this solved the problem in a different way than the original implementation, it changed the functionality—instead of writing directly to disk, I created an intent with the file data and the user chose where to put it. While this was a better solution for that app that dealt with JSON files, it's not a great solution for apps that deal with images.</p>
<p>I came across this problem again in a different app, and it was time to solve it the right way. </p>
<h2 id="saving-to-photos-before-android-10">Saving to photos before Android 10</h2>
<p>Before Android 10, you could save to photos by writing to the following directory:</p>
<pre><code class="lang-java">Environment
  .getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
</code></pre>
<p>As of Android 10, this approach has been deprecated. You no longer have access to folders directly in this way. You could still use it for a little while though with the <code>requestLegacyExternalStorage</code> flag in your manifest, but this is a bandaid solution, and as soon as you upgrade your app to target Android 11, <a target="_blank" href="https://developer.android.com/about/versions/11/privacy/storage">this will no longer work</a>, which means you'll need to eventually overhaul your storage implementation. Targeting Android 11 will become mandatory in August 2021, giving developers about 1 year to migrate from the legacy storage implementation.</p>
<h2 id="share-sheet-for-images">Share sheet for images</h2>
<p>Sharing via the share sheet on Android is the easiest approach, and it's the one I outline in <a target="_blank" href="https://blog.tinaciousdesign.com/migrating-from-get-external-storage-public-directory-in-android-10-and-11">this blog post</a>, but it's a cumbersome experience for some users.</p>
<p>On Pixel devices, Google Photos is the default photos program. When you want to see the photos on your device, you open Google Photos. There is no difference between Google Photos and your device photos anymore. </p>
<p>On other (non-Google) Android devices, this isn't the case, and many other device manufacturers have their own version of a gallery app. Unfortunately, some of these apps are not available as an option in the native share sheet when creating an image share intent. The <a target="_blank" href="https://play.google.com/store/apps/details?id=com.oneplus.gallery&amp;hl=en_CA&amp;gl=US">OnePlus Gallery app</a>, for example—which ships with One Plus devices—doesn't show up as an option to share to. This creates a cumbersome experience for these Android users who just want to save an image to their phone.</p>
<h2 id="problem-the-go-to-react-native-plugin-doesnt-work-on-android-10">Problem: the go-to React Native plugin doesn't work on Android 10+</h2>
<p>The <a target="_blank" href="https://github.com/react-native-cameraroll/react-native-cameraroll">React Native CameraRoll plugin</a>, which works well enough for iOS, unfortunately doesn't work for recent Android versions. There are a few issues and/or pull requests open for this topic: <a target="_blank" href="https://github.com/react-native-cameraroll/react-native-cameraroll/issues/244">#244</a>, <a target="_blank" href="https://github.com/react-native-cameraroll/react-native-cameraroll/issues/219">#219</a>, <a target="_blank" href="https://github.com/react-native-cameraroll/react-native-cameraroll/pull/268">#268</a>, <a target="_blank" href="https://github.com/react-native-cameraroll/react-native-cameraroll/issues/248">#248</a>, <a target="_blank" href="https://github.com/react-native-cameraroll/react-native-cameraroll/pull/271">#271</a> to name a few.</p>
<p>The problem I was solving was quite unique and had multiple layers: React Native app with an embedded web view, and the image is an export of an HTML5 canvas, which provides the image data as a base64 encoded string. To solve this problem on Android, I needed 2 separate plugins: the above-mentioned <code>react-native-cameraroll</code> plugin for iOS, as well as the <a target="_blank" href="https://github.com/react-native-share/react-native-share">React Native Share</a> plugin to share with the native share sheet on Android since saving to photos didn't work. While this allowed me to get the image to the user to some degree, it wasn't the ideal solution for my needs.</p>
<h2 id="solution-implement-scoped-storage-for-android-q-with-legacy-storage-fallback">Solution: Implement scoped storage for Android Q+ with legacy storage fallback</h2>
<p>Because the React Native community didn't have a solution for my needs, I decided to develop one. While the <code>react-native-cameraroll</code> plugin sort of worked on iOS, it wasn't perfect—it would crash the app if the user had declined to share access to their photos.</p>
<p>I ended up developing the plugin I called <a target="_blank" href="https://github.com/tinacious/react-native-save-base-sixty-four">React Native Save Base Sixty Four</a>. The naming is unfortunately quite verbose but numbers in package names get marked as spam by NPM and prevent publishing.</p>
<p>The plugin I developed solves the following problems, which are limited in scope to an image that is in the format of an encoded base64 string.</p>
<p>On iOS:</p>
<ul>
<li>Enable iOS users to save a base64 image to their photos</li>
<li>Allow the React Native app to handle the case that the iOS users decline to grant permissions to photos</li>
</ul>
<p>On Android:</p>
<ul>
<li>Enable users on Android 10 and higher to save a base64 image to their photos on their device</li>
<li>Enable users on Android 9 and older to save a base64 image to their photos on their device</li>
<li>Allow the React Native app to handle the case that the user declines to grant permissions to photos</li>
</ul>
<p>The plugin also allows user to share a base64 image via the native share sheet on both platforms, but if that's all you need to do, I would recommend you use the <a target="_blank" href="https://github.com/react-native-share/react-native-share">React Native Share</a> plugin as it's more mature and is supported by the community and handles base64 strings perfectly. I added this functionality to my plugin because it was simple enough, and I could use the same plugin for both platforms and uninstall the other two from my project.</p>
<h3 id="how-to-use-this-plugin">How to use this plugin</h3>
<p>The full instructions can be found in the <a target="_blank" href="https://github.com/tinacious/react-native-save-base-sixty-four/blob/main/README.md">project readme</a>, including instructions for how to modify your Android manifest and iOS <code>Info.plist</code> file, but here's a snippet. It uses the promises API, which can be implemented with a set of <code>.then().catch()</code> blocks or async await.</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> SaveBase64Image <span class="hljs-keyword">from</span> <span class="hljs-string">'react-native-save-base-sixty-four'</span>;

<span class="hljs-comment">// Save to device</span>
<span class="hljs-comment">//    with async await</span>
<span class="hljs-keyword">try</span> {
  <span class="hljs-keyword">const</span> success = <span class="hljs-keyword">await</span> SaveBase64Image.save(base64ImageString, options);
  <span class="hljs-keyword">if</span> (!success) {
    <span class="hljs-comment">// 😭 user did not grant permission</span>
  }
} <span class="hljs-keyword">catch</span> (error) {
  <span class="hljs-comment">// 💥 there was a crash</span>
}

<span class="hljs-comment">//    with promises</span>
SaveBase64Image
  .save(base64ImageString, options)
  .then(<span class="hljs-function">(<span class="hljs-params">success</span>) =&gt;</span> {
    <span class="hljs-keyword">if</span> (!success) {
      <span class="hljs-comment">// 😭 user did not grant permission</span>
    }
  })
  .catch(<span class="hljs-function">(<span class="hljs-params">error</span>) =&gt;</span> {
    <span class="hljs-comment">// 💥 there was a crash</span>
  });
</code></pre>
<p>The linked readme also includes further instructions on how to implement permission checking on Android. Legacy versions of Android require you to prompt the user when you run the app, while Android 10+ does not because access is scoped to files you create. Older versions of Android will crash if you try to access something you don't have permission to access. <a target="_blank" href="https://gist.github.com/tinacious/5e4d752f951d452360aa58c52145b380">This snippet</a> is a handy utility function for React Native apps to check Android permissions. It depends on the <a target="_blank" href="https://github.com/react-native-device-info/react-native-device-info"><code>react-native-device-info</code> plugin</a>. The example app in the plugin I developed has an <a target="_blank" href="https://github.com/tinacious/react-native-save-base-sixty-four/blob/main/example/src/App.tsx#L55-L79">example of how to implement the plugin with permission checking</a>.</p>
<h3 id="implementing-scoped-storage-for-android-10">Implementing scoped storage for Android 10+</h3>
<p>I have provided some links below in the <strong>Further reading</strong> section to specifics on implementing scoped storage. The TL;DR is as follows:</p>
<ul>
<li>Convert your base64 encoded string into a <code>Bitmap</code>. For this, you can use Android SDK classes <code>Base64</code> and <code>BitmapFactory</code> to accomplish this task. <a target="_blank" href="https://github.com/tinacious/react-native-save-base-sixty-four/blob/a03adaa7ba0966ac06f6a8a563b7054abf47214c/android/src/main/java/com/reactnativesavebase64image/Utils.kt#L20-L26">Here is the source</a>.</li>
<li><a target="_blank" href="https://github.com/tinacious/react-native-save-base-sixty-four/blob/a03adaa7ba0966ac06f6a8a563b7054abf47214c/android/src/main/java/com/reactnativesavebase64image/Utils.kt#L43-L51">Implement the <code>ContentResolver</code> API</a> to create an output stream and pass the bitmap to that stream.</li>
</ul>
<p>The full source code can be viewed on the Github page but most of it is <a target="_blank" href="https://github.com/tinacious/react-native-save-base-sixty-four/blob/main/android/src/main/java/com/reactnativesavebase64image/Utils.kt">in this file</a>. The proof of concept app is available for download on <a target="_blank" href="https://install.appcenter.ms/users/info-145/apps/rnsavebase64imageexample/distribution_groups/everybody/releases/6">AppCenter</a>.</p>
<h2 id="gratitude">Gratitude 🙏</h2>
<p>I must give a huge thank you to the team that built the bob tool for scaffolding React Native modules as it was a pleasure to work with and saved me a huge amount of time.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/tinaciousdesign/status/1343310880331579393">https://twitter.com/tinaciousdesign/status/1343310880331579393</a></div>
<h2 id="further-reading">Further reading</h2>
<ul>
<li><a target="_blank" href="https://developer.android.com/reference/android/content/ContentResolver">Content Resolver</a></li>
<li><a target="_blank" href="https://developer.android.com/training/data-storage/shared/media?hl=en">MediaStore guide</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Migrating from get External Storage Public Directory in Android 10 & 11]]></title><description><![CDATA[Android 11 introduces breaking changes to working with files on Android. One of these breaking changes includes the deprecation of the following method:
Environment
  .getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)

The Canned Rep...]]></description><link>https://blog.tinaciousdesign.com/migrating-from-get-external-storage-public-directory-in-android-10-and-11</link><guid isPermaLink="true">https://blog.tinaciousdesign.com/migrating-from-get-external-storage-public-directory-in-android-10-and-11</guid><category><![CDATA[Android]]></category><category><![CDATA[Kotlin]]></category><dc:creator><![CDATA[Tina Holly]]></dc:creator><pubDate>Sun, 04 Oct 2020 02:02:18 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1601776107675/35OFGejg2.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Android 11 introduces breaking changes to working with files on Android. One of these breaking changes includes the deprecation of the following method:</p>
<pre><code class="lang-kotlin">Environment
  .getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
</code></pre>
<p>The <a target="_blank" href="https://cannedreplies.com">Canned Replies</a> Android app used to use this deprecated API. As of Android 11, both importing and exporting features broke. 😬 This article will go through the solution I used to fix these bugs.</p>
<h2 id="why-did-google-deprecate-get-external-storage-public-directory">Why did Google deprecate get External Storage Public Directory?</h2>
<p>In the <a target="_blank" href="https://developer.android.com/reference/android/os/Environment#public-methods_1">Android documentation</a>, Google mentions that access to the user's shared and external storage directories is deprecated as of API level 29 for privacy reasons.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1606866140908/ECrdrI8cc.png" alt="Screenshot of deprecation notice" /></p>
<h2 id="what-should-i-do-instead">What should I do instead?</h2>
<p>The documentation makes a few suggestions as to which alternative API's to use instead. Some suggestions include <a target="_blank" href="https://developer.android.com/reference/android/provider/MediaStore">MediaStore</a> and <code>getExternalFilesDir</code>, which may not always be available.</p>
<p>You may, however, be able to get away with solving this problem with a much simpler approach by using the intent system.</p>
<h3 id="do-users-access-these-files">Do users access these files?</h3>
<p>Does your app read and write private files to disk, e.g. for caching reasons? Or will the user specifically be involved in the process?</p>
<p>If you are creating features like allowing the user to import or export their data using files, then you can do this with using the intent system. You don't actually need permissions to write to any storage directories and can do so via intents.</p>
<h2 id="using-intents-to-read-and-write-files">Using Intents to read and write files</h2>
<p>You can uses the <code>Intent</code> API to read and write files in Android. This API implementation has 2 steps:</p>
<ol>
<li>Creating your intent to perform an action, e.g. get a file</li>
<li>Handling the system's response to your intent, i.e. success and failure cases</li>
</ol>
<p>This blog post will go through the example of reading and writing JSON files. This blog post code is written in Kotlin.</p>
<h2 id="reading-files-using-intents">Reading files using Intents</h2>
<p>To read files using Intents, we can prompt the user to pick files and then we can perform actions with those files.</p>
<p>This example will read a file of any type.</p>
<h3 id="creating-our-intent-to-read-a-file">Creating our intent to read a file</h3>
<p>First, we need to create an intent to read a file. We can attach this to a user action, for example, the tapping of a button or menu item. Once the user interacts with our view, we can execute <code>requestImportIntent()</code> which is implemented as follows:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">requestImportIntent</span><span class="hljs-params">()</span></span> {
  <span class="hljs-keyword">val</span> pickIntent = Intent(Intent.ACTION_GET_CONTENT)
  pickIntent.type = <span class="hljs-string">"*/*"</span>
  startActivityForResult(pickIntent, INTENT_IMPORT_REQUEST)
}
</code></pre>
<p>The above code is creating an intent for <code>ACTION_GET_CONTENT</code> which allows the user to pick a file of any type.</p>
<p>Then, we start an activity for request with the request code stored in the <code>INTENT_IMPORT_REQUEST</code>, which is an <code>Int</code> value.</p>
<p>Once this intent activity is created, the user will be prompted to provide a file. This will allow the user to browse their file system and choose the file they want to provide.</p>
<h3 id="responding-to-our-intent-to-read-the-file">Responding to our intent to read the file</h3>
<p>To respond to this intent, we need to implement the override method <code>onActivityResult</code>. We can use the following code, which only cares about successful results, i.e. <code>Activity.RESULT_OK</code>:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onActivityResult</span><span class="hljs-params">(requestCode: <span class="hljs-type">Int</span>, resultCode: <span class="hljs-type">Int</span>, <span class="hljs-keyword">data</span>: <span class="hljs-type">Intent</span>?)</span></span> {
  <span class="hljs-keyword">super</span>.onActivityResult(requestCode, resultCode, <span class="hljs-keyword">data</span>)

  <span class="hljs-keyword">if</span> (resultCode != Activity.RESULT_OK) {
      <span class="hljs-keyword">return</span>
  }

  <span class="hljs-keyword">when</span> (requestCode) {
    INTENT_IMPORT_REQUEST -&gt; handleIntentImport(<span class="hljs-keyword">data</span>)
  }
}
</code></pre>
<p>When the <code>requestCode</code> is for the intent we assigned to <code>INTENT_IMPORT_REQUEST</code>, then we should handle it. I've created a method <code>handleIntentImport(data)</code> which takes the nullable Intent data:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">handleIntentImport</span><span class="hljs-params">(intent: <span class="hljs-type">Intent</span>?)</span></span> {
  <span class="hljs-keyword">val</span> importedData: Uri = intent?.<span class="hljs-keyword">data</span> ?: <span class="hljs-keyword">return</span>

  <span class="hljs-keyword">try</span> {
    <span class="hljs-comment">// Do things with imported data</span>
  } <span class="hljs-keyword">catch</span> (e: IOException) {
    e.printStackTrace()
  }
}
</code></pre>
<p>The above code allows us to read the data from the intent, which may be null. If it's null, we make an early return out of the function, otherwise we try to perform our actions on the file data.</p>
<h2 id="writing-files-using-intents">Writing files using Intents</h2>
<p>Similar to reading files with Intents, we can also write files with Intents. The following example will write a JSON file to the user's desired destination. It will allow the user to name the file.</p>
<h3 id="creating-our-intent-to-write-a-json-file">Creating our intent to write a JSON file</h3>
<p>We need to create an intent to write to a file. The following code will allow us to prompt the user to choose where they'd like the document we create to be written:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">requestExportIntent</span><span class="hljs-params">()</span></span> {
  <span class="hljs-keyword">val</span> intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
  intent.addCategory(Intent.CATEGORY_OPENABLE)
  intent.type = <span class="hljs-string">"application/json"</span>
  intent.putExtra(Intent.EXTRA_TITLE, generatedFileNameWithTimestamp)

  startActivityForResult(intent, INTENT_EXPORT_REQUEST)
}
</code></pre>
<p>In the above code, we first create an intent for <code>ACTION_CREATE_DOCUMENT</code> and specify that the category is openable. We need to add the category <code>CATEGORY_OPENABLE</code> in order to be able to have full control over the file via the <code>ContentResolver</code> API's.</p>
<p>Specifying the right type is required to ensure that the file has the right file extension. For example, to ensure we have the <code>.json</code> file extension, we need to set the type to <code>application/json</code>.</p>
<p>We can also provide a default file name. In this case, I'm using a generated file name with a timestamp. The user will be able to change this.</p>
<p>Next, the user will choose the location and the file name. Once they successfully create the file, we will be able to respond to the intent.</p>
<h3 id="responding-to-our-intent-to-write-the-file">Responding to our intent to write the file</h3>
<p>Similar to our read intent above, we will need to implement the override method <code>onActivityResult</code>. The following code has both the existing read intent code and the new code added:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onActivityResult</span><span class="hljs-params">(requestCode: <span class="hljs-type">Int</span>, resultCode: <span class="hljs-type">Int</span>, <span class="hljs-keyword">data</span>: <span class="hljs-type">Intent</span>?)</span></span> {
  <span class="hljs-keyword">super</span>.onActivityResult(requestCode, resultCode, <span class="hljs-keyword">data</span>)

  <span class="hljs-keyword">if</span> (resultCode != Activity.RESULT_OK) {
      <span class="hljs-keyword">return</span>
  }

  <span class="hljs-keyword">when</span> (requestCode) {
    INTENT_IMPORT_REQUEST -&gt; handleIntentImport(<span class="hljs-keyword">data</span>) <span class="hljs-comment">// old</span>

    INTENT_EXPORT_REQUEST -&gt; handleExportIntent(<span class="hljs-keyword">data</span>) <span class="hljs-comment">// new ✨</span>
  }
}
</code></pre>
<p>Given that we get a successful export intent request (i.e. the user has chosen a directory and filename), we can handle that in our <code>handleExportIntent</code> method:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">handleExportIntent</span><span class="hljs-params">(intent: <span class="hljs-type">Intent</span>?)</span></span> {
  <span class="hljs-keyword">val</span> uri = intent?.<span class="hljs-keyword">data</span> ?: <span class="hljs-keyword">return</span>

  <span class="hljs-keyword">val</span> outputStream = contentResolver.openOutputStream(uri)
  <span class="hljs-keyword">val</span> exportText = myStringifiedJsonData

  <span class="hljs-keyword">if</span> (exportText == <span class="hljs-literal">null</span> || outputStream == <span class="hljs-literal">null</span>) {
    showExportFailureMessage()
    <span class="hljs-keyword">return</span>
  }

  <span class="hljs-keyword">try</span> {
    outputStream.write(exportText.toByteArray())
    outputStream.close()
    showExportSuccessMessage()
  } <span class="hljs-keyword">catch</span> (e: Exception) {
    showExportFailureMessage()
    e.printStackTrace()
  }
}
</code></pre>
<p>We can now use the Content Resolver API's to write to this data URI using an output stream.</p>
<p>The example above of <code>myStringifiedJsonData</code> is just a serialized list of POJO's (Plain Old Java Objects). So I serialize the data as JSON, write it to the file, and close the output stream.</p>
<h2 id="benefits-of-using-intents">Benefits of using intents</h2>
<ul>
<li>No permissions required</li>
<li>User has full control over the files</li>
</ul>
<h3 id="no-permissions-required">No permissions required</h3>
<p>What's great about using the intent system is that no extra permissions are required. You do not need access to read and write to storage if you are using intents.</p>
<p>If Google starts cracking down on permissions for Android like they did with the Chrome Web Store, we may find ourselves with more Play Store rejections for requesting permissions we may not need.</p>
<h3 id="user-has-full-control-over-the-files">User has full control over the files</h3>
<p>Unlike when working with <code>getExternalStoragePublicDirectory</code>, your users can choose where the file goes instead of you choosing a default directory. This is more user-friendly as it would allow the user to put it somewhere specific so they can retrieve it later.</p>
<p>Your users will also be able to name the file something sensible, while still allowing your app to provide a default file name. </p>
<h2 id="be-intentional">Be intentional</h2>
<p>Be intentional, spiritually and in an Android way. 🙏 In my opinion, the Intent experience is one of the best features of Android. Nowadays, Apple has caught up and offers something similar with iOS.</p>
<p>When using intents, you can use patterns that Android users are already familiar with instead of coming up with creative solutions to read and write to disk.</p>
<p>That said, intents will not work for all use cases, but if users are interacting with these files, it may be a better user experience to use intents rather than to write to specific directories that you choose.</p>
]]></content:encoded></item><item><title><![CDATA[Generate a Vue component using Plop]]></title><description><![CDATA[Plop is a handy tool for code generation. It uses Handlebars under the hood alongside some provided Handlebars helper functions. 
By using a code generation tool like plop, you can avoid writing tedious boilerplate code and be productive right away. ...]]></description><link>https://blog.tinaciousdesign.com/generate-a-vue-component-using-plop</link><guid isPermaLink="true">https://blog.tinaciousdesign.com/generate-a-vue-component-using-plop</guid><category><![CDATA[Vue.js]]></category><category><![CDATA[Nuxt]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[Productivity]]></category><dc:creator><![CDATA[Tina Holly]]></dc:creator><pubDate>Sun, 20 Sep 2020 22:33:50 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1600640696764/StDI9cxZA.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a target="_blank" href="https://plopjs.com/">Plop</a> is a handy tool for code generation. It uses <a target="_blank" href="https://handlebarsjs.com/">Handlebars</a> under the hood alongside some provided Handlebars helper functions. </p>
<p>By using a code generation tool like plop, you can avoid writing tedious boilerplate code and be productive right away. It's also great for maintaining consistency across the project and company.</p>
<p><strong>Updated 4 Nov 2020</strong>: Added template for Vue 3 syntax.</p>
<h2 id="setup">Setup</h2>
<h3 id="create-a-vue-project">Create a Vue project</h3>
<p>To make sure you're set up to generate components using Plop, you'll need to have a Vue.js project. You can generate one with <a target="_blank" href="https://cli.vuejs.org/guide/creating-a-project.html">Vue CLI</a> or with <a target="_blank" href="https://nuxtjs.org/guide/installation/">Nuxt</a>.</p>
<h3 id="add-plop-as-a-dependency">Add Plop as a dependency</h3>
<p>Run the following to add Plop as a dependency:</p>
<pre><code class="lang-sh">npm install --save-dev plop
</code></pre>
<h3 id="add-an-npm-script">Add an NPM script</h3>
<p>Add a command to call <code>plop</code> from an NPM script, e.g.</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"name"</span>: <span class="hljs-string">"example-project"</span>,
  <span class="hljs-attr">"version"</span>: <span class="hljs-string">"1.0.0"</span>,
  <span class="hljs-attr">"private"</span>: <span class="hljs-literal">true</span>,
  <span class="hljs-attr">"scripts"</span>: {
    <span class="hljs-attr">"start"</span>: <span class="hljs-string">"nuxt-ts start"</span>,
    <span class="hljs-attr">"plop"</span>: <span class="hljs-string">"plop"</span>
  }
}
</code></pre>
<h3 id="create-a-plopfile">Create a Plopfile</h3>
<p>Create a file named <code>plopfile.js</code> in the root of your project. Put the following code in it:</p>
<pre><code class="lang-js"><span class="hljs-comment">// plopfile.js</span>
<span class="hljs-built_in">module</span>.exports = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">plop</span>) </span>{
  plop.setGenerator(<span class="hljs-string">'component'</span>, {
    <span class="hljs-attr">description</span>: <span class="hljs-string">'Generates a Vue component'</span>,
    <span class="hljs-attr">prompts</span>: [
      {
        <span class="hljs-attr">type</span>: <span class="hljs-string">'input'</span>,
        <span class="hljs-attr">name</span>: <span class="hljs-string">'component'</span>,
        <span class="hljs-attr">message</span>: <span class="hljs-string">'Component name, e.g. NatureBanner'</span>
      }
    ],
    <span class="hljs-attr">actions</span>: [
      {
        <span class="hljs-attr">type</span>: <span class="hljs-string">'add'</span>,
        <span class="hljs-attr">path</span>: <span class="hljs-string">'components/{{pascalCase component}}/{{pascalCase component}}.vue'</span>,
        <span class="hljs-attr">templateFile</span>: <span class="hljs-string">'plop-templates/component.hbs'</span>
      }
    ]
  })
}
</code></pre>
<h3 id="create-a-plop-template">Create a plop template</h3>
<p>You'll notice the above plopfile references a template file <code>plop-templates/component.hbs</code>. You will need to create this file.</p>
<h4 id="vue-2">Vue 2</h4>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">template</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"{{kebabCase component}}"</span>&gt;</span>{{pascalCase component}}<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"ts"</span>&gt;</span><span class="javascript">
<span class="hljs-keyword">import</span> Vue <span class="hljs-keyword">from</span> <span class="hljs-string">'vue'</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Vue.extend({
  <span class="hljs-attr">name</span>: <span class="hljs-string">'{{pascalCase component}}'</span>,
  <span class="hljs-attr">props</span>: {}
})
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">style</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"scss"</span>&gt;</span>
.{{kebabCase component}} {}
<span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span>
</code></pre>
<h4 id="vue-3">Vue 3</h4>
<p>For Vue 3, we don't need to import or extend Vue:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">template</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"{{kebabCase component}}"</span>&gt;</span>{{pascalCase component}}<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"ts"</span>&gt;</span><span class="javascript">
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {
  <span class="hljs-attr">name</span>: <span class="hljs-string">'{{pascalCase component}}'</span>,
  <span class="hljs-attr">props</span>: {}
};
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">style</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"scss"</span>&gt;</span>
.{{kebabCase component}} {}
<span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span>
</code></pre>
<p>These are the differences:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1606866245006/w05JvPrhA.png" alt="Diff patch of changes between Vue 2 and Vue 3" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1606866278598/Cfv8QornV.png" alt="Diff word changes between Vue 2 and Vue 3" /></p>
<p>Above diffs generated with <a target="_blank" href="https://textdiffer.app">TextDiffer</a>.</p>
<p><strong>Note</strong>: If you're using Nuxt (or any project set up to use Prettier), you will likely need to disable the "helpful" Prettier JS formatting to avoid destroying your Handlebars file.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1606866315336/UPI3nVdZl.png" alt="prettier-borked.png" /></p>
<p>Once you've added all your files, your project should look something like this:</p>
<pre><code class="lang-sh">plop-vue-example/
├── package.json
├── plop-templates
│   └── component.hbs
└── plopfile.js

1 directory, 3 files
</code></pre>
<h3 id="run-your-script">Run your script</h3>
<p>Now all you need to do is run your script to generate a component:</p>
<pre><code class="lang-sh">npm run plop
</code></pre>
<p>You will be prompted for the component name. Enter it.</p>
<p>You should now have generated a component in the project's component directory:</p>
<pre><code class="lang-sh">components/
├── Logo.vue
├── NatureBanner
│   └── NatureBanner.vue
└── README.md

1 directory, 3 files
</code></pre>
<p>If you open the file, you should see a Vue component that uses Sass and TypeScript was generated.</p>
<pre><code class="lang-js">&lt;template&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"nature-banner"</span>&gt;</span>NatureBanner<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
&lt;/template&gt;

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"ts"</span>&gt;</span><span class="javascript">
<span class="hljs-keyword">import</span> Vue <span class="hljs-keyword">from</span> <span class="hljs-string">'vue'</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Vue.extend({
  <span class="hljs-attr">name</span>: <span class="hljs-string">'NatureBanner'</span>,
  <span class="hljs-attr">props</span>: {}
})
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></span>

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">style</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"scss"</span>&gt;</span><span class="css">
<span class="hljs-selector-class">.nature-banner</span> {}
</span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span></span>
</code></pre>
<p>You can see an <a target="_blank" href="https://codesandbox.io/s/generate-vue-components-with-plop-example-nuxtjs-ivcxb">example without Sass or TypeScript here</a>.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https%3A%2F%2Fcodesandbox.io%2Fs%2Fgenerate-vue-components-with-plop-example-nuxtjs-ivcxb%3Ffontsize%3D14%26hidenavigation%3D1%26module%3D%252Fplopfile.js%26theme%3Ddark%26view%3Deditor">https%3A%2F%2Fcodesandbox.io%2Fs%2Fgenerate-vue-components-with-plop-example-nuxtjs-ivcxb%3Ffontsize%3D14%26hidenavigation%3D1%26module%3D%252Fplopfile.js%26theme%3Ddark%26view%3Deditor</a></div>
]]></content:encoded></item><item><title><![CDATA[Use BEM for scalable and maintainable CSS]]></title><description><![CDATA[BEM CSS stands for Block Element Modifier, which is a CSS methodology to help you write maintainable CSS.
Here's BEM's official website but keep reading for a brief overview.
BEM Basics
BEM takes a component-based approach to CSS development. It has ...]]></description><link>https://blog.tinaciousdesign.com/bem-css-scalable-maintainable</link><guid isPermaLink="true">https://blog.tinaciousdesign.com/bem-css-scalable-maintainable</guid><category><![CDATA[CSS]]></category><category><![CDATA[CSS3]]></category><category><![CDATA[CSS Frameworks]]></category><dc:creator><![CDATA[Tina Holly]]></dc:creator><pubDate>Sun, 13 Sep 2020 03:58:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1599969398451/q7boFAr4C.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>BEM CSS stands for Block Element Modifier, which is a CSS methodology to help you write maintainable CSS.</p>
<p>Here's BEM's <a target="_blank" href="http://getbem.com/">official website</a> but keep reading for a brief overview.</p>
<h2 id="bem-basics">BEM Basics</h2>
<p>BEM takes a <strong>component-based</strong> approach to CSS development. It has some strict rules about inheritance and how to write your CSS selectors.</p>
<h3 id="rules-for-inheritance">Rules for Inheritance</h3>
<p>To follow BEM properly, you <strong>should not use inherited CSS</strong> styles in order to style your components. </p>
<p>Some CSS properties are inheritable like font- and table-related properties, but most are not (<a target="_blank" href="https://www.w3.org/TR/CSS21/propidx.html">more info</a>).</p>
<p>In order to follow this rule, you should <strong>set inherited properties explicitly</strong>, where applicable. So, for example, if you are building a card component but the whole card has a smaller font size, all text elements should have their font sizes specified explicitly rather than inheriting the smaller font size from the parent.</p>
<p>This rule exists in order to prevent bugs that can occur due to the cascading nature of CSS, i.e. when inheritable styles are added or removed.</p>
<h3 id="rules-for-selectors">Rules for Selectors</h3>
<p>The most important rule is that you <strong>should not nest your selectors</strong>. The reason for not nesting your selectors is so that you do not have specificity wars. </p>
<p>What's a specificity war, you ask? Remember when you tried to apply a CSS style and it didn't work? And then you had to go digging in the dev console to find out why? Sometimes it was because you specified your style before the style that was applied, but more often than not it was because the style was declared with a combination of classes and HTML element selectors, which resulted in a higher level of specificity. </p>
<p>Some of the most popular CSS frameworks (like Twitter Bootstrap, Material UI, Semantic UI) have this problem, which is why it can be cumbersome to use them when working with an actual UI designer.</p>
<p>The following selector does not obey BEM for multiple reasons:</p>
<ul>
<li>because it nests selectors</li>
<li>because it uses an element selector <code>h2</code></li>
</ul>
<p>Here's the bad stuff you <strong>should not</strong> copy:</p>
<pre><code class="lang-css"><span class="hljs-comment">/* bad */</span>
<span class="hljs-selector-class">.page</span> <span class="hljs-selector-tag">h1</span> {
  <span class="hljs-attribute">font-size</span>: <span class="hljs-number">2.5rem</span>;
}

<span class="hljs-selector-class">.page</span> <span class="hljs-selector-tag">h2</span> {
  <span class="hljs-attribute">font-size</span>: <span class="hljs-number">2rem</span>;
}
</code></pre>
<p>The following is how you can do the same thing using BEM.</p>
<pre><code class="lang-css"><span class="hljs-comment">/* good */</span>
<span class="hljs-selector-class">.page__title</span> {
  <span class="hljs-attribute">font-size</span>: <span class="hljs-number">2.5rem</span>;
}

<span class="hljs-selector-class">.page__subtitle</span> {
  <span class="hljs-attribute">font-size</span>: <span class="hljs-number">2rem</span>;
}
</code></pre>
<p>You'll notice some underscores there, more on that later. For this, see the <strong>Element</strong> section.</p>
<p>Following the selector rule in BEM means you can end up with more verbose HTML because it would require you to add a corresponding CSS class to everything you want to style. This can make tables more verbose, for example, but it means that in the event you need to have a table that's styled differently, you won't have to have any CSS specificity wars.</p>
<h3 id="anatomy-of-bem">Anatomy of BEM</h3>
<p>Essentially, the BEM syntax looks like this:</p>
<pre><code>.block__element<span class="hljs-comment">--modifier</span>
</code></pre><h4 id="b-block">B – Block</h4>
<p>Blocks are the main component. If you've worked with React or Vue, you can break up your components the same way in your CSS.</p>
<p>You can also nest blocks inside of other blocks, if needed, but this is generally avoided.</p>
<p>A block can be as simple as a single HTML element, like a button, or it can be as complex as a set of them, like a card with an image, subtitle, excerpt, and a button.</p>
<p>Here is a simple block:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"btn"</span>&gt;</span>
  Click me
<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
</code></pre>
<p>Here is a more complex block, which could be a card view for a blog where a single card describes a single blog post:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"card"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">h3</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"card__title"</span>&gt;</span>
    How do I use BEM CSS?
  <span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"card__excerpt"</span>&gt;</span>
    BEM CSS stands for Block Element Modifier, which is a 
    CSS methodology to help you write maintainable CSS.
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"btn btn--secondary"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/path/to/blog/post"</span>&gt;</span>
    Read More
  <span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
</code></pre>
<p>In the above example, the following are blocks:</p>
<ul>
<li><code>card</code></li>
<li><code>btn</code></li>
</ul>
<h4 id="e-element">E – Element</h4>
<p>A block can contain elements, but doesn't have to.</p>
<p>Though you can nest blocks inside of other blocks, you can only nest elements inside of blocks and you cannot nest elements inside of other elements. This is to maintain a flat CSS structure.</p>
<p>In the above card HTML, the following are elements:</p>
<ul>
<li><code>card__excerpt</code></li>
<li><code>card__title</code></li>
</ul>
<h4 id="m-modifiers">M – Modifiers</h4>
<p>Modifiers are used to modify either blocks or elements. They are used to demonstrate state or variants. </p>
<p>They are used <strong>in addition to</strong> block and element classes, <strong>not in place of</strong> them.</p>
<p>Possible states:</p>
<ul>
<li>active</li>
<li>disabled</li>
</ul>
<p>Possible variants, e.g. for button variants:</p>
<ul>
<li>primary </li>
<li>secondary</li>
<li>small</li>
</ul>
<p>In the above card HTML, the following are modifiers:</p>
<ul>
<li><code>btn--secondary</code></li>
</ul>
<p>Here is how you would style buttons using BEM:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"btn btn--primary"</span>&gt;</span>
  Click me!
<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"btn btn--primary btn--disabled"</span>&gt;</span>
  Nope, can't touch this!
<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
</code></pre>
<p>The modifier modifies only the relevant part that needs to be modified. If you had a button that was blue by default and grey when it was disabled, then you would only change the background colour:</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.btn</span> {
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">10px</span> <span class="hljs-number">20px</span>;
  <span class="hljs-attribute">font-size</span>: <span class="hljs-number">1rem</span>;
}

<span class="hljs-selector-class">.btn--primary</span> {
  <span class="hljs-attribute">background</span>: blue;
  <span class="hljs-attribute">color</span>: white;
}

<span class="hljs-selector-class">.btn--secondary</span> {
  <span class="hljs-attribute">background</span>: green;
  <span class="hljs-attribute">color</span>: white;
}

<span class="hljs-selector-class">.btn--disabled</span> {
  <span class="hljs-attribute">background</span>: grey;
  <span class="hljs-attribute">color</span>: white;
}
</code></pre>
<p>Generally, I list my state modifiers after my theme modifiers. To avoid ambiguity or any specificity or inheritance issues, if a modifier needs to override another modifier, I will explicitly set all properties, e.g. <code>background</code> and <code>color</code> in this case.</p>
<h5 id="combining-modifiers"><strong>Combining modifiers</strong></h5>
<p>Combining modifiers can be a bit tricky, and it seems like everyone on the internet has their own opinion for how to do this. Many of the posts I've found have produced barely-legible CSS. </p>
<p>Given that our primary button is blue, and our secondary button is green, let's assume that the designer said that the background for a disabled primary button should be dark blue, and the disabled secondary button should be dark green. Not only that, but disabled buttons should be at 50% opacity.</p>
<p>Here's an example mockup:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1606866471989/o2IhRkNTC.png" alt="Mockup of primary and secondary buttons. The first row is the default style, the second row is the disabled state where the background is darker and the buttons are transparent" /></p>
<h6 id="option-1-break-the-specificity-rule"><strong>Option 1: Break the specificity rule</strong></h6>
<p>We can still keep the same HTML:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"btn btn--primary btn--disabled"</span>&gt;</span>
  Nope!
<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
</code></pre>
<p>As for the CSS, this is where it can get complicated. It may make sense to skip strict BEM rules and do something like this:</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.btn--primary</span><span class="hljs-selector-class">.btn--disabled</span> {
  <span class="hljs-attribute">background</span>: darkGreen;
  <span class="hljs-attribute">color</span>: white;
  <span class="hljs-attribute">opacity</span>: <span class="hljs-number">0.5</span>;
}

<span class="hljs-selector-class">.btn--secondary</span><span class="hljs-selector-class">.btn--disabled</span> {
  <span class="hljs-attribute">background</span>: darkGreen;
  <span class="hljs-attribute">color</span>: white;
  <span class="hljs-attribute">opacity</span>: <span class="hljs-number">0.5</span>;
}
</code></pre>
<p>This technically breaks the specificity rule, which means that the disabled- and variant-related classes are combined as a single selector, making them more specific than other selectors.</p>
<h6 id="option-2-write-a-joined-modifier"><strong>Option 2: Write a joined modifier</strong></h6>
<p>Instead of having a single <code>--disabled</code> modifier, you can have one for each variant you need to override. </p>
<p>The HTML could look like this:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"btn btn--primary--disabled"</span>&gt;</span>
  Nope!
<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
</code></pre>
<p>And the CSS like this:</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.btn--primary</span> {
  <span class="hljs-attribute">background</span>: green;
  <span class="hljs-attribute">color</span>: white;
}

<span class="hljs-selector-class">.btn--primary--disabled</span> {
  <span class="hljs-attribute">background</span>: darkGreen;
  <span class="hljs-attribute">color</span>: white;
  <span class="hljs-attribute">opacity</span>: <span class="hljs-number">0.5</span>;
}
</code></pre>
<p>Doing this won't require you to break any rules, but it may add some complexity when writing your modifiers.</p>
<p>When you use only one modifier class for each style, you don't need to worry about the order that you declare your classes in the HTML. Unfortunately, with this approach, you'll need to remember which order your modifiers are written in, or duplicate the order.</p>
<p>That means you need to remember that you put <code>--disabled</code> after <code>--primary</code>, and not vice-versa, or you'll have to duplicate the CSS with the flipped version:</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.btn--primary</span> {
  <span class="hljs-attribute">background</span>: green;
  <span class="hljs-attribute">color</span>: white;
}

<span class="hljs-selector-class">.btn--primary--disabled</span>,
<span class="hljs-selector-class">.btn--disabled--primary</span> {
  <span class="hljs-attribute">background</span>: darkGreen;
  <span class="hljs-attribute">color</span>: white;
  <span class="hljs-attribute">opacity</span>: <span class="hljs-number">0.5</span>;
}
</code></pre>
<p>That's not so bad with just 2 but if we added a third, you can see how this could exponentially get out of hand. This is why I, personally, would probably break the selector rule.</p>
<h3 id="example-coffee-menu">Example: Coffee Menu</h3>
<p>Let's assume you have a coffee menu design that the designer gave you.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1606866504259/8-tcDPDnH.png" alt="Coffee menu mock-up of a table with a brown border, coffees for sale, and their prices" /></p>
<p>Here is an example of non-BEM HTML:</p>
<pre><code class="lang-html"><span class="hljs-comment">&lt;!-- bad: does not follow BEM --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">table</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">thead</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">th</span>&gt;</span>Coffee<span class="hljs-tag">&lt;/<span class="hljs-name">th</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">th</span>&gt;</span>Price<span class="hljs-tag">&lt;/<span class="hljs-name">th</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">thead</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">tbody</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Americano<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>$3.50<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Long Black<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>$3.50<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Latte<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>$4.50<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Pour-over<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>$6.00<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">tbody</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">table</span>&gt;</span>
</code></pre>
<p>With the non-BEM version, we could've styled the table like this, but it would've made assumptions about all tables. This is a common problem when using a CSS framework that provides a lot of opinionated styles for you:</p>
<pre><code class="lang-css"><span class="hljs-comment">/* bad */</span>
<span class="hljs-selector-tag">table</span> {
  <span class="hljs-attribute">border</span>: <span class="hljs-number">4px</span> solid brown;
}

<span class="hljs-selector-tag">table</span> <span class="hljs-selector-tag">th</span> {
  <span class="hljs-attribute">font-weight</span>: <span class="hljs-number">700</span>;
}

<span class="hljs-selector-tag">table</span> <span class="hljs-selector-tag">td</span> {
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">8px</span>;
  <span class="hljs-attribute">font-style</span>: italic;
}

<span class="hljs-selector-tag">table</span> <span class="hljs-selector-tag">td</span><span class="hljs-selector-pseudo">:last-child</span> {
  <span class="hljs-attribute">font-weight</span>: <span class="hljs-number">700</span>;
  <span class="hljs-attribute">font-style</span>: normal; <span class="hljs-comment">/* we need to override the default here 😞 */</span>
}
</code></pre>
<p>The CSS is nice and short and this works perfectly fine if we have only one table style, but as soon as we have different table styles, things start to get crazy.</p>
<p>Also, it's not very legible why the styles are like that:</p>
<ul>
<li>why is the last child bold?</li>
<li>why does the last child have their font style modified to normal, which is the default? </li>
</ul>
<p>Here's the same example using BEM:</p>
<pre><code class="lang-html"><span class="hljs-comment">&lt;!-- good --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">table</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"coffee-menu"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">thead</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">th</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"coffee-menu__heading"</span>&gt;</span>Coffee<span class="hljs-tag">&lt;/<span class="hljs-name">th</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">th</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"coffee-menu__heading"</span>&gt;</span>Price<span class="hljs-tag">&lt;/<span class="hljs-name">th</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">thead</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">tbody</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">td</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"coffee-menu__cell coffee-menu__cell--item"</span>&gt;</span>
        Americano
      <span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">td</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"coffee-menu__cell coffee-menu__cell--price"</span>&gt;</span>
        $3.50
      <span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">td</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"coffee-menu__cell coffee-menu__cell--item"</span>&gt;</span>
        Long Black
      <span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">td</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"coffee-menu__cell coffee-menu__cell--price"</span>&gt;</span>
        $3.50
      <span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">td</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"coffee-menu__cell coffee-menu__cell--item"</span>&gt;</span>
        Latte
      <span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">td</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"coffee-menu__cell coffee-menu__cell--price"</span>&gt;</span>
        $4.50
      <span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">td</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"coffee-menu__cell coffee-menu__cell--item"</span>&gt;</span>
        Pour-over
      <span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">td</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"coffee-menu__cell coffee-menu__cell--price"</span>&gt;</span>
        $6.00
      <span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">tbody</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">table</span>&gt;</span>
</code></pre>
<p>You'll notice that there's a lot more HTML due to all of the CSS classes that are applied. Each <code>td</code> has 2 classes, one as the base class and one as the modifier.</p>
<p>Here's how we would write the CSS of the BEM-friendly version:</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.coffee-menu</span> {
  <span class="hljs-attribute">border</span>: <span class="hljs-number">4px</span> solid brown;
}

<span class="hljs-selector-class">.coffee-menu__heading</span> {
  <span class="hljs-attribute">font-weight</span>: <span class="hljs-number">700</span>;
}

<span class="hljs-selector-class">.coffee-menu__cell</span> {
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">8px</span>;
}

<span class="hljs-selector-class">.coffee-menu__cell--item</span> {
  <span class="hljs-attribute">font-style</span>: italic;
}

<span class="hljs-selector-class">.coffee-menu__cell--price</span> {
  <span class="hljs-attribute">font-weight</span>: <span class="hljs-number">700</span>;
}
</code></pre>
<p>With the BEM-friendly example, we can read the CSS and understand it better. We can see that the price is bold, and the coffee menu item is italic. We don't have to refer to the HTML to see which element is the last child of the table. </p>
<p>So, while the HTML and CSS are more verbose, they provide more information, ultimately making both more legible.</p>
<p>We write code for people, not for computers. You'll thank yourself later as your projects grow.</p>
]]></content:encoded></item><item><title><![CDATA[Building a macOS Touch Bar app using interprocess communication in Electron]]></title><description><![CDATA[In this post, I will go through the process of building a feature in a macOS desktop application that uses Electron. This feature will implement the native macOS Touch Bar API's.
Feature we need to build
I have a desktop application called TextDiffer...]]></description><link>https://blog.tinaciousdesign.com/building-a-macos-touch-bar-app-using-interprocess-communication-in-electron</link><guid isPermaLink="true">https://blog.tinaciousdesign.com/building-a-macos-touch-bar-app-using-interprocess-communication-in-electron</guid><category><![CDATA[Electron]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[macOS]]></category><dc:creator><![CDATA[Tina Holly]]></dc:creator><pubDate>Sun, 30 Aug 2020 05:54:09 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1598765705878/7NP8Q5-S8.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this post, I will go through the process of building a feature in a macOS desktop application that uses Electron. This feature will implement the native macOS Touch Bar API's.</p>
<h2 id="heading-feature-we-need-to-build">Feature we need to build</h2>
<p>I have a desktop application called <a target="_blank" href="https://textdiffer.tinaciousdesign.com">TextDiffer</a> which allows you to see the difference between two snippets of text. The app will hopefully be published soon and is currently awaiting for approval from the Mac AppStore. 🤞 <strong>Update</strong> it's <a target="_blank" href="https://apps.apple.com/us/app/id1528117546">available now</a>.</p>
<p>The <a target="_blank" href="https://textdiffer.tinaciousdesign.com">TextDiffer</a> app has a button in the UI that says "Perform Diff" which will allow you to run the diff checker. As a user, I would like to access this action using a button in my macOS Touch Bar that says "Perform Diff."</p>
<p>In <a target="_blank" href="https://textdiffer.tinaciousdesign.com">TextDiffer</a>, I am also able to configure my UI theme between light and dark mode. Depending on the theme I've chosen, the "Perform Diff" button will be a different colour: orange for light mode, and purple for dark mode. As a user, I would like the colour of the "Perform Diff" button in my macOS Touch Bar to be the right colour depending on my theme.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1606866383556/P6zj2dLFg.png" alt="Light and dark variants of the Perform Diff button" /></p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before getting started, you will need to understand JavaScript and how to develop basic web apps using any (or no) framework or library.</p>
<p>You should also be familiar with Node.js.</p>
<p>Having a basic understanding of how Electron works helps too.</p>
<h2 id="heading-apis">API's</h2>
<p>For this exercise, we will only be using the standard Electron API's including <a target="_blank" href="https://www.electronjs.org/docs/api/touch-bar">TouchBar</a>, <a target="_blank" href="https://www.electronjs.org/docs/api/ipc-main">IPC Main</a>, and <a target="_blank" href="https://www.electronjs.org/docs/api/ipc-renderer">IPC Renderer</a>.</p>
<h2 id="heading-ipc-basics">IPC Basics</h2>
<p>Let's go over some basics for interprocess communication in Electron.</p>
<h3 id="heading-processes">Processes</h3>
<p>There are two processes that we need to concern ourselves with. If you're familiar with Electron app development, or with developing native mobile apps, you are likely already familiar with these concepts:</p>
<ul>
<li><p><strong>Main</strong> – the main process is the one that runs in Node.js. This is the layer that has access to the native desktop API's via corresponding Electron API's.</p>
</li>
<li><p><strong>Render</strong> – the web process. This is the one that runs in the client-side web app of your Electron application. This can be an app written in Vue, React, Angular, or simply vanilla JavaScript.</p>
</li>
</ul>
<h3 id="heading-about-message-passing">About message passing</h3>
<p>In order for the main process to communicate with the render process, and vice-versa, these processes will need to send messages to each other.</p>
<p>Messages in Electron have two parts:</p>
<ul>
<li><p>the channel</p>
</li>
<li><p>the message data</p>
</li>
</ul>
<p><strong>The channel</strong> is a string value and represents the name or topic of messages. Messages are sent on a channel. In this application, I have set up two separate channels:</p>
<ul>
<li><p><strong>main-actions</strong> – the channel I use to send messages to the main process</p>
</li>
<li><p><strong>render-actions</strong> – the channel I use to send messages to the render process</p>
</li>
</ul>
<p>Message passing uses an event-based/observables approach, allowing you to send messages with data to a channel, and to listen for messages and subscribe to a channel, and subsequently perform actions in response to messages you receive.</p>
<p>You can set up your channels as you like—one single channel, one channel per action, one channel per process where actions are shared, etc.</p>
<p>Though I am using two separate channels—one for each process—you may be able to use a single channel to communicate both ways. I have not tested this, but if you have, please let me know in the comments how it turned out!</p>
<h3 id="heading-listening-for-messages">Listening for messages</h3>
<p>Electron allows you to listen for events on the main process via the <code>ipcMain</code> module.</p>
<pre><code class="lang-js"><span class="hljs-comment">// main.js</span>
<span class="hljs-keyword">const</span> { ipcMain } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'electron'</span>);

ipcMain.on(<span class="hljs-string">'main-actions'</span>, <span class="hljs-function">(<span class="hljs-params">event, data = {}</span>) =&gt;</span> {
  <span class="hljs-comment">// Do things on the main process</span>
})
</code></pre>
<p>Similarly, Electron allows you to listen for events on the render process via the <code>ipcRenderer</code> module.</p>
<pre><code class="lang-js"><span class="hljs-comment">// render.js</span>
<span class="hljs-keyword">const</span> { ipcRenderer } = <span class="hljs-built_in">window</span>.require(<span class="hljs-string">'electron'</span>);

ipcRenderer.on(<span class="hljs-string">'render-actions'</span>, <span class="hljs-function">(<span class="hljs-params">event, data = {}</span>) =&gt;</span> {
  <span class="hljs-comment">// Do things on the render process</span>
})
</code></pre>
<p><strong>Important</strong>: You may have noticed that I am using <code>require</code> differently for each of these processes. While the main process uses the standard Node.js <code>require</code>, the render process does things differently. This is because Electron <em>requires</em> that you use <code>require</code> on the window object in the render process. It's a good idea to check the existence of it ahead of time so that no exceptions are thrown—this is useful if you build your JavaScript app as a standalone web app initially and integrate it with Electron afterwards, in which case <code>window.require</code> would not be present in your standalone web app.</p>
<h3 id="heading-structuring-the-message-data">Structuring the message data</h3>
<p>Messages you send can have any shape. The message data is the second argument in the callback. In my example code above, I've called that argument <code>data</code> and I've defaulted it to the empty object <code>{}</code>. Your <code>data</code> could just be a string if you wanted, or a number, or any value. I use an object so that I can pass multiple values if needed. I'm doing this because I will be sending multiple types of messages on each of these channels.</p>
<p>I like to structure my message data similar to how <a target="_blank" href="https://redux.js.org/basics/actions">Redux actions</a> are structured:</p>
<ul>
<li><p>each action has a <code>type</code> property which is a string that I can use to <code>switch/case</code> on</p>
</li>
<li><p>additional properties are custom, based on the action</p>
</li>
</ul>
<p>While a simpler action may just have a <code>type</code> property and no additional data, a more complex action would have a <code>type</code> property and whatever data is required to perform that action.</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> performDiffAction = {
  <span class="hljs-attr">type</span>: <span class="hljs-string">'performDiff'</span>
}

<span class="hljs-keyword">const</span> initTouchBarAction = {
  <span class="hljs-attr">type</span>: <span class="hljs-string">'init_touch_bar'</span>,
  <span class="hljs-attr">theme</span>: <span class="hljs-string">'dark'</span>
}
</code></pre>
<p>There is no requirement as to what format your <code>type</code> property should follow. In Redux, the community as a whole is not unanimous between <code>camelCase</code>, <code>snake_case</code>, and <code>SCREAMING_CASE</code>, so feel free to use whichever you prefer.</p>
<p>So, given that my actions are shaped as above, my listeners could look something like this in the render process:</p>
<pre><code class="lang-js"><span class="hljs-comment">// render.js</span>

ipcRenderer.on(<span class="hljs-string">'render-actions'</span>, <span class="hljs-function">(<span class="hljs-params">event, data = {}</span>) =&gt;</span> {
  <span class="hljs-keyword">switch</span> (data.type) {
    <span class="hljs-keyword">case</span> <span class="hljs-string">'performDiff'</span>:
      <span class="hljs-keyword">return</span> <span class="hljs-built_in">window</span>.globalPerformDiff();

    <span class="hljs-keyword">default</span>:
      <span class="hljs-comment">// no-op</span>
  }
})
</code></pre>
<p>And something like this on the main process:</p>
<pre><code class="lang-js"><span class="hljs-comment">// main.js</span>

ipcMain.on(<span class="hljs-string">'main-actions'</span>, <span class="hljs-function">(<span class="hljs-params">event, data = {}</span>) =&gt;</span> {
  <span class="hljs-keyword">switch</span> (data.type) {
    <span class="hljs-keyword">case</span> <span class="hljs-string">'init_touch_bar'</span>:
      <span class="hljs-keyword">return</span> initTouchBarWithConfig({ <span class="hljs-attr">theme</span>: data.theme });

    <span class="hljs-keyword">default</span>:
      <span class="hljs-comment">// no-op</span>
  }
})
</code></pre>
<h3 id="heading-sending-messages">Sending messages</h3>
<p>While listening for messages on the main and render processes is quite similar, sending messages is slightly different.</p>
<p>I should also mention that <strong>there are limitations with the data</strong> that can be sent in a message.</p>
<p>Your message data must be serializable. Primitives work well for this case, e.g. strings, numbers, and JSON objects that consist only of primitives. Put simply, if you are passing an object, you should be able to run <code>JSON.parse</code> and <code>JSON.stringify</code> on your data and have it work reliably.</p>
<p>If your data is not serializable, you will encounter some errors. One error that you may see is <code>Error: An object could not be cloned</code>. This can occur if you try to pass a reference to a function as part of your message data. The reference cannot be saved and you will likely encounter an error. So, put simply, you cannot just send a message to the main process with a click handler and attach that to the Touch Bar button—this will not work, i.e. this would likely fail: <code>{ type: 'doSomething', onClick: doSomething }</code></p>
<p>We need to use message passing <strong>both ways</strong> in order to implement Touch Bar functionality end-to-end.</p>
<h4 id="heading-render-gt-main">Render -&gt; Main</h4>
<p>This part will go over sending a message from the render process (the client-side web app) to the main process (the Node.js app with access to the native layer via Electron).</p>
<p>Let's say when my app initializes, I would like to initialize the Touch Bar with the dark theme. I am choosing to do this from the render process. Doing it from the render process adds an extra step of message passing, but it also gives me more flexibility.</p>
<p>I could have simply done it in the main process without going through the render process, but since I want to configure the style based on the user's UI theme, I need the user to tell me this (from the browser).</p>
<p>So, given that I'd like to send a message from the render process to the main process, the message could look something like this:</p>
<pre><code class="lang-js"><span class="hljs-comment">// render.js</span>

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">initTouchBarWithTheme</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> theme = <span class="hljs-built_in">localStorage</span>.getItem(<span class="hljs-string">'theme'</span>) || <span class="hljs-string">'light'</span>;

  ipcRenderer.send(<span class="hljs-string">'main-actions'</span>, {
    <span class="hljs-attr">type</span>: <span class="hljs-string">'init_touch_bar'</span>,
    <span class="hljs-attr">theme</span>: theme
  })
}
</code></pre>
<p>The above code will result in the <code>init_touch_bar</code> message being sent from the browser to the Node.js process. This would be a good spot to implement the Touch Bar API, creating a button, and configuring the colour based on the theme sent from the render process.</p>
<h4 id="heading-main-gt-render">Main -&gt; Render</h4>
<p>This part will go over sending a message from the main process (the Node.js app) to the render process (the web app running in the browser).</p>
<p>Given that the main process is where I need to be to interact with native API's, I would set up my Touch Bar and all of its buttons on the main process. But, considering my application is an Electron app and the bulk of my logic is happening in the UI, I would likely need any user interaction with that button to be propagated to the render process.</p>
<p>So far we've sent a message from the render process and included the theme, which tells us on the main process that we'll need to set the button to be a specific colour. Let's implement that instruction now.</p>
<p>Here is some basic code for creating a Touch Bar with a single button where the colour will change depending on the provided theme:</p>
<pre><code class="lang-js"><span class="hljs-comment">// main.js</span>

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">initTouchBar</span>(<span class="hljs-params">{ theme }</span>) </span>{
  <span class="hljs-keyword">const</span> <span class="hljs-built_in">window</span> = BrowserWindow.getFocusedWindow();

  <span class="hljs-keyword">const</span> touchBar = <span class="hljs-keyword">new</span> TouchBar({
    <span class="hljs-attr">items</span>: [
      <span class="hljs-keyword">new</span> TouchBarButton({
        <span class="hljs-attr">label</span>: <span class="hljs-string">'Perform Diff'</span>,
        <span class="hljs-attr">backgroundColor</span>: theme === <span class="hljs-string">'light'</span> ? <span class="hljs-string">'#FF9A49'</span> : <span class="hljs-string">'#302AE6'</span>,
        <span class="hljs-attr">click</span>: <span class="hljs-function">() =&gt;</span> {
          <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'TODO!'</span>);
        },
      })
    ]
  });

  <span class="hljs-built_in">window</span>.setTouchBar(touchBar);
}
</code></pre>
<p>The code above is enough code to get a Touch Bar with a single button that changes colour depending on the theme.</p>
<p>That's great and all, but the button won't do anything until we implement the click handler. In this case, clicking on this button should trigger a form submit in our render process. We won't go into detail about what that form submission does or how to implement it, we just need to trigger it, which we can do by clicking on the submit button. This will propagate the event and submit the form.</p>
<p>There is more than one way that we can tell the render process to execute JavaScript from the main process. The two ways I'll touch on both use the <a target="_blank" href="https://www.electronjs.org/docs/api/web-contents#webcontents">webContents</a> API:</p>
<ul>
<li><p><strong>Sending a message</strong> – That one is no surprise, given that's what this article is about. This is the approach I prefer.</p>
</li>
<li><p><strong>executeJavaScript API</strong> – An alternative approach that I do not prefer</p>
</li>
</ul>
<p>So, before we get into how I would do it, let's briefly talk about the alternative (i.e. how I would <strong>not</strong> do it). The alternative way this can be done is without using message passing, and instead using the <a target="_blank" href="https://www.electronjs.org/docs/api/web-contents#contentsexecutejavascriptcode-usergesture">executeJavaScript</a> API, which takes a string of JavaScript, and a callback with the result. If we used this approach, it means I could look up my DOM element and trigger a gesture (like a button click) and put that code right in the argument string.</p>
<p>While this API would be adequate for very simple tasks, it may not be ideal in the following cases:</p>
<ul>
<li><p>my application is using a framework like Angular, Vue, or React, as this would be outside of the framework code</p>
</li>
<li><p>my code is relatively complex or comprehensive</p>
</li>
</ul>
<p>I would not personally use this approach. The full range of support via this Electron wrapper is not predictable, may be unreliable, and will likely be quite difficult to debug if you have any issues. Users have experienced some <a target="_blank" href="https://github.com/electron/electron/issues/9288">limitations</a> with this approach, at least in its earlier days.</p>
<p>I would prefer to leverage message passing here as well, and send a message from the main process to the render process, and then once I receive that message, execute my code. This is because I can send a very simple message with primitive data I am confident will arrive as intended, and just make sure I'm listening to this message in my web app. The code I execute is not contained in a string, which means I can write tests or execute it in other scenarios without duplicating it into the string argument of a function call.</p>
<p>Message passing from the main process to the render process is also done via the webContents API. I'm going to create the button click handler, which will send a message to the render process. This message will not have any additional data other than the <code>type</code> property.</p>
<pre><code class="lang-js"><span class="hljs-comment">// main.js</span>

<span class="hljs-keyword">const</span> onPerformDiff = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> <span class="hljs-built_in">window</span> = BrowserWindow.getFocusedWindow();
  <span class="hljs-built_in">window</span>.webContents.send(<span class="hljs-string">'render-actions'</span>, { 
    <span class="hljs-attr">type</span>: <span class="hljs-string">'performDiff'</span> 
  });
};
</code></pre>
<p>Then, I'll attach it to the button I just made:</p>
<pre><code class="lang-js"><span class="hljs-comment">// main.js</span>

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">initTouchBar</span>(<span class="hljs-params">{ theme }</span>) </span>{
  <span class="hljs-keyword">const</span> <span class="hljs-built_in">window</span> = BrowserWindow.getFocusedWindow();

  <span class="hljs-keyword">const</span> touchBar = <span class="hljs-keyword">new</span> TouchBar({
    <span class="hljs-attr">items</span>: [
      <span class="hljs-keyword">new</span> TouchBarButton({
        <span class="hljs-attr">label</span>: <span class="hljs-string">'Perform Diff'</span>,
        <span class="hljs-attr">backgroundColor</span>: theme === <span class="hljs-string">'light'</span> ? <span class="hljs-string">'#FF9A49'</span> : <span class="hljs-string">'#302AE6'</span>,
        <span class="hljs-attr">click</span>: onPerformDiff,
      })
    ]
  });

  <span class="hljs-built_in">window</span>.setTouchBar(touchBar);
}
</code></pre>
<p>This means that once my orange (or purple) button is tapped on the Touch Bar, it will send the message <code>{ type: 'performDiff' }</code> to the render process.</p>
<p>I can listen for that message on the render process and execute some JavaScript. A reminder of the listener we've already set up:</p>
<pre><code class="lang-js"><span class="hljs-comment">// render.js</span>

ipcRenderer.on(<span class="hljs-string">'render-actions'</span>, <span class="hljs-function">(<span class="hljs-params">event, data = {}</span>) =&gt;</span> {
  <span class="hljs-keyword">switch</span> (data.type) {
    <span class="hljs-keyword">case</span> <span class="hljs-string">'performDiff'</span>:
      <span class="hljs-keyword">return</span> <span class="hljs-built_in">window</span>.globalPerformDiff();

    <span class="hljs-keyword">default</span>:
      <span class="hljs-comment">// no-op</span>
  }
})
</code></pre>
<p>In this case, my app is a vanilla JavaScript app and I've saved a method <code>globalPerformDiff</code> to the window so I can easily access it:</p>
<pre><code class="lang-js"><span class="hljs-comment">// render.js</span>

<span class="hljs-built_in">window</span>.globalPerformDiff = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> button = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'perform-diff-button'</span>);
  button.click();
};
</code></pre>
<p>I just need to trigger a click on the "Perform diff" button, which will propagate a form submission event, and my app will take care of the rest.</p>
<p>This is it! With everything we've gone over, you should be able to implement a Touch Bar button end-to-end.</p>
<p>I have put my Touch Bar initialization code in a separate method, <code>initTouchBar</code>, which in reality does more than this article covers. I am also calling it multiple times. Here are some examples of when I'm calling it:</p>
<ul>
<li><p>when my app loads</p>
</li>
<li><p>when the user toggles between the light and dark UI themes</p>
</li>
<li><p>when the user performs a diff, where I conditionally show and hide buttons depending on some settings</p>
</li>
</ul>
<h2 id="heading-the-ipc-lifecycle-in-electron-visualized">The IPC lifecycle in Electron, visualized</h2>
<p>Now that you've seen the code, let's go over the lifecycle of the interprocess communication events for this feature.</p>
<ol>
<li><p><strong>from render</strong>: Send a message from the render process to the main process to create the Touch Bar for the specified UI theme</p>
</li>
<li><p><strong>from main</strong>: Configure the button with the theme colour using the Electron TouchBar API</p>
</li>
<li><p><strong>from main</strong>: Configure the button click handler of the above button to send a message to the render process using the Electron webContents API.</p>
</li>
<li><p><strong>on main</strong>: User interacts with the Touch Bar button</p>
</li>
<li><p><strong>from main</strong>: Send a message from the main process to the render process for the clicked button</p>
</li>
<li><p><strong>from render</strong>: Perform an action based on the message and its data</p>
</li>
</ol>
<h2 id="heading-conclusion">Conclusion</h2>
<p>And that's how I would implement a macOS Touch Bar feature in an Electron app.</p>
<p>Interprocess communication in Electron is useful for implementing any and all native desktop functionality end-to-end, like custom desktop notifications, interacting with the camera, or more. Check out the <a target="_blank" href="https://www.electronjs.org/docs/api">Electron documentation</a> for a list of available API's.</p>
<p>I hope you've learned something here today. Let me know about your experiences with IPC in Electron, and your experience with Electron in general.</p>
<p>And feel free to check out <a target="_blank" href="https://textdiffer.tinaciousdesign.com">TextDiffer</a> once it's launched! 🚀🚢</p>
]]></content:encoded></item><item><title><![CDATA[Migrating my blog from WordPress to Hashnode]]></title><description><![CDATA[A step-by-step guide for migrating a blog from WordPress to Hashnode.
Why did I switch from WordPress to Hashnode?
Currently, my portfolio website and blog are on WordPress. It's been really great for search engine optimization, and the experience wi...]]></description><link>https://blog.tinaciousdesign.com/migrating-my-blog-from-wordpress-to-hashnode</link><guid isPermaLink="true">https://blog.tinaciousdesign.com/migrating-my-blog-from-wordpress-to-hashnode</guid><category><![CDATA[WordPress]]></category><category><![CDATA[wordpress plugins]]></category><category><![CDATA[Hashnode]]></category><category><![CDATA[Blogger]]></category><dc:creator><![CDATA[Tina Holly]]></dc:creator><pubDate>Wed, 05 Aug 2020 06:17:04 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1596515954464/B4y6Ud6g3.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>A step-by-step guide for migrating a blog from WordPress to Hashnode.</p>
<h2 id="why-did-i-switch-from-wordpress-to-hashnode">Why did I switch from WordPress to Hashnode?</h2>
<p>Currently, my portfolio website and blog are on WordPress. It's been really great for search engine optimization, and the experience with <a target="_blank" href="https://www.shareasale.com/r.cfm?B=943418&amp;U=779380&amp;M=41388&amp;urllink=">WP Engine</a> has been almost flawless, but it's been a long time since I built it so everything I use to run it locally is broken: Vagrant, Scotchbox (LAMP stack), WP Distillery (Wordpress-flavoured LAMP), etc. It's no longer as quick as it used to be to make small changes.</p>
<p>I have been evaluating different blogging platforms. I considered Medium, but I am not a fan of the experience of landing on a Medium link and hitting a paywall, so I didn't want users to experience that if they find one of my posts. It also didn't have any ads injected, so that's great. </p>
<p>I also thought about using a headless CMS and a static site generator like Nuxt as I've had a great experience with it, but that would've taken a lot of dev time to build. I think that's something I would do for the rest of my site once I have more time.</p>
<p>Hashnode has a variety of features that I appreciate:</p>
<ul>
<li>Markdown editor</li>
<li>Canonical link support so I can link to my original post if I wanted to</li>
<li>Automated backups to a Github repository</li>
<li>Custom domain linking</li>
<li>Nice code syntax highlighting, with the bonus of embedding external services for code snippets</li>
</ul>
<p>What's great is that these are all free, too. There's also a helpful community on Discord to get some help if needed. </p>
<h2 id="walkthrough-for-migrating-from-wordpress-to-hashnode">Walkthrough for migrating from WordPress to Hashnode</h2>
<h3 id="download-post-content">Download post content</h3>
<p>To begin the migration, you'll need to download your existing posts. By default these are in XML format. Since I don't really like working with XML, I used the plugin <a target="_blank" href="https://wordpress.org/plugins/wp-import-export-lite/">WP Import Export Lite</a> to export my posts as JSON. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1596515059753/j37zcB9uY.png" alt="wp-import-export-lite.png" /></p>
<p>The resulting output was an array of objects that had the following shape.</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"ID"</span>: <span class="hljs-string">"463"</span>,
  <span class="hljs-attr">"Title"</span>: <span class="hljs-string">"iTerm colour schemes"</span>,
  <span class="hljs-attr">"Content"</span>: <span class="hljs-string">"Introducing 2 iTerm colour schemes [...]"</span>,
  <span class="hljs-attr">"Excerpt"</span>: <span class="hljs-string">"iTerm colour schemes based off of my Visual Studio Code theme"</span>,
  <span class="hljs-attr">"Date"</span>: <span class="hljs-string">"June 22, 2020"</span>,
  <span class="hljs-attr">"Post Type"</span>: <span class="hljs-string">"post"</span>,
  <span class="hljs-attr">"Permalink"</span>: <span class="hljs-string">"https:\/\/tinaciousdesign.com\/blog\/iterm-colour-schemes\/"</span>
}
</code></pre>
<h3 id="download-images">Download images</h3>
<p>Next, you'll need to download all your images. There's more than one way you can approach it:</p>
<ol>
<li>Download all of the images uploaded to your WordPress install</li>
<li>Programmatically process a JSON or XML export of the Media Library, which has links to the media</li>
</ol>
<p>The first option is by far the easiest, so I went with that.</p>
<p>Images are stored in the <code>./wp-content/uploads</code> directory by year and month.</p>
<pre><code><span class="hljs-string">wp-content/</span>
<span class="hljs-string">└──</span> <span class="hljs-string">uploads</span>
    <span class="hljs-string">├──</span> <span class="hljs-number">2019</span>
    <span class="hljs-string">│</span>   <span class="hljs-string">├──</span> <span class="hljs-number">01</span>
    <span class="hljs-string">│</span>   <span class="hljs-string">├──</span> <span class="hljs-number">02</span>
    <span class="hljs-string">│</span>   <span class="hljs-string">└──</span> <span class="hljs-number">03</span>
    <span class="hljs-string">└──</span> <span class="hljs-number">2020</span>
        <span class="hljs-string">├──</span> <span class="hljs-number">01</span>
        <span class="hljs-string">├──</span> <span class="hljs-number">02</span>
        <span class="hljs-string">└──</span> <span class="hljs-number">03</span>
</code></pre><p>Your posts reference images like this:</p>
<pre><code class="lang-text">https://example.com/wp-content/uploads/2020/08/filename.png
</code></pre>
<p>You can use an SFTP tool like Cyberduck or FileZilla to download this folder.</p>
<h3 id="upload-images-elsewhere">Upload images elsewhere</h3>
<p><em><strong>Update</strong>: This section is out of date. The service Fast no longer offers a free plan and Hashnode has its own CDN so I have been manually updating them in an effort to save on server costs.</em></p>
<p>That "elsewhere" I chose is <a target="_blank" href="https://fast.io">Fast.io</a> because it had a generous free plan that leverages a variety of storage options like Dropbox, Google Drive, OneDrive, etc. I can take advantage of one of those amazing storage services and link it to Fast.io. I chose Dropbox. After manually deleting some images that weren't used in the blog posts, I uploaded the entire contents of the downloaded <code>./wp-content/uploads</code> directory to Dropbox.</p>
<h3 id="programmatically-publish-posts-to-hashnode-using-their-api">Programmatically publish posts to Hashnode using their API</h3>
<p>Hashnode has a <a target="_blank" href="https://api.hashnode.com/">GraphQL API</a>. It's somewhat limited in functionality but has most of the basic features. There's a <a target="_blank" href="https://engineering.hashnode.com/introducing-hashnode-graphql-api-public-beta-cjydzvp59001q2gs1b5zxaeaf">helpful blog post</a> on how to use it.</p>
<p>Back-dating posts is not supported in the API so I needed to do this manually for each of my posts, which was tedious.</p>
<p>I've created a <a target="_blank" href="https://github.com/tinacious/migrate-wordpress-blog-hashnode">repository of the scripts</a> I used to facilitate cleaning up and bulk uploading posts.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Overall, I was happy with my migration to Hashnode. It didn't take very long. Hashnode has a lot of features I look forward to using and I was happy to invest the time in migrating.</p>
<p>Setting up my custom domain was a bit painful compared to other services I've set up domains with, e.g. Heroku, Netlify, Github Pages, Amazon S3. I use Cloudflare, and eventually what worked was contacting support on Discord, disabling Cloudflare's default proxying behaviour, and waiting until the SSL certificate was re-generated. Within a day it was working, so the trouble was only a minor setback.</p>
<p>The authoring experience is nice, typographically. There's also support for a lot of different types of embeds. Showing code is pretty without any effort on my part, and their embeds support the services I use most (Codepen, Codesandbox).</p>
<p>Overall, migrating to Hashnode is a relatively low investment, low risk migration.</p>
<p>If you like the sound of this and want to migrate your WordPress blog to Hashnode, you can use some handy <a target="_blank" href="https://github.com/tinacious/migrate-wordpress-blog-hashnode">scripts I created</a>.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/tinacious/migrate-wordpress-blog-hashnode?v=1">https://github.com/tinacious/migrate-wordpress-blog-hashnode?v=1</a></div>
]]></content:encoded></item><item><title><![CDATA[iTerm colour schemes]]></title><description><![CDATA[Introducing 2 iTerm colour schemes. These are based off of my Visual Studio Code theme. See the Download and installation instructions on Github.]]></description><link>https://blog.tinaciousdesign.com/iterm-colour-schemes</link><guid isPermaLink="true">https://blog.tinaciousdesign.com/iterm-colour-schemes</guid><category><![CDATA[terminal]]></category><category><![CDATA[theme]]></category><dc:creator><![CDATA[Tina Holly]]></dc:creator><pubDate>Tue, 23 Jun 2020 01:27:04 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1596504738761/_ZzwaDtAe.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Introducing 2 iTerm colour schemes. These are based off of my Visual Studio Code theme. See the <a target="_blank" href="https://github.com/tinacious/iterm-tinacious-design-theme">Download and installation instructions</a> on Github. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1606866869940/dv8HgKduq.png" alt="Tinacious Design (Dark) iTerm colour scheme" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1606866889723/luuCfowwv.png" alt="Tinacious Design (Light) iTerm colour scheme" /></p>
]]></content:encoded></item><item><title><![CDATA[ImageMagick Cheatsheet]]></title><description><![CDATA[An ImageMagick cheatsheet with some helpful commands for working with ImageMagick on the command line. This is a work in progress that will be periodically updated and is by no means a complete list.
Resizing images
Resize a single image:
convert inp...]]></description><link>https://blog.tinaciousdesign.com/imagemagick-cheatsheet</link><guid isPermaLink="true">https://blog.tinaciousdesign.com/imagemagick-cheatsheet</guid><dc:creator><![CDATA[Tina Holly]]></dc:creator><pubDate>Sun, 24 May 2020 02:23:02 GMT</pubDate><content:encoded><![CDATA[<p>An ImageMagick cheatsheet with some helpful commands for working with ImageMagick on the command line. This is a work in progress that will be periodically updated and is by no means a complete list.</p>
<h2 id="resizing-images">Resizing images</h2>
<p>Resize a single image:</p>
<pre><code><span class="hljs-selector-tag">convert</span> <span class="hljs-selector-tag">input</span><span class="hljs-selector-class">.png</span> <span class="hljs-selector-tag">-resize</span> 20<span class="hljs-selector-tag">x20</span> <span class="hljs-selector-tag">-quality</span> 100 <span class="hljs-selector-tag">output</span><span class="hljs-selector-class">.png</span>
</code></pre><p>Batch resize some images:</p>
<pre><code><span class="hljs-attribute">mogrify</span> -resize <span class="hljs-number">600</span> <span class="hljs-regexp">*.png</span>
</code></pre><h2 id="replacing-the-background-colour-of-a-transparent-png">Replacing the background colour of a transparent PNG</h2>
<p>For transparent PNG, you can add a background colour.</p>
<pre><code><span class="hljs-attribute">convert</span> source.png -background <span class="hljs-string">'#ff3399'</span> -flatten destination.png
</code></pre><p>Or overwrite all images in a directory:</p>
<pre><code><span class="hljs-attribute">mogrify</span> -background <span class="hljs-string">'dodgerblue'</span> -flatten <span class="hljs-regexp">*.png</span>
</code></pre><h2 id="rounded-corners">Rounded corners</h2>
<p>You can round the corners of an image with the following script:</p>
<pre><code>convert input.png -alpha <span class="hljs-keyword">set</span> -<span class="hljs-keyword">virtual</span>-pixel transparent -channel A -blur <span class="hljs-number">0x8</span>  -threshold <span class="hljs-number">50</span>% +channel <span class="hljs-keyword">output</span>.png
</code></pre><p>And you can batch round the corners of images using the following:</p>
<pre><code>mogrify -alpha <span class="hljs-built_in">set</span> -<span class="hljs-keyword">virtual</span>-pixel transparent -channel A -blur <span class="hljs-number">0x8</span>  -threshold <span class="hljs-number">50</span>% +channel *.png
</code></pre>]]></content:encoded></item><item><title><![CDATA[BuildConfig.DEBUG always returns false]]></title><description><![CDATA[If you see the error that BuildConfig.DEBUG always returns false in Android Studio, this is likely because you've imported another package's BuildConfig instead of your own. Follow the instructions below to fix it.
About BuildConfig
BuildConfig is a ...]]></description><link>https://blog.tinaciousdesign.com/buildconfigdebug-always-returns-false</link><guid isPermaLink="true">https://blog.tinaciousdesign.com/buildconfigdebug-always-returns-false</guid><dc:creator><![CDATA[Tina Holly]]></dc:creator><pubDate>Tue, 19 May 2020 02:23:02 GMT</pubDate><content:encoded><![CDATA[<p>If you see the error that <code>BuildConfig.DEBUG</code> always returns false in Android Studio, this is likely because you&#39;ve imported another package&#39;s BuildConfig instead of your own. Follow the instructions below to fix it.</p>
<h2 id="about-buildconfig">About BuildConfig</h2>
<p><strong>BuildConfig</strong> is a generated class. It gets output to your <strong>build</strong> folder, which is ignored by source control, so you likely are not realizing the file being generated at all. Many projects appear to expose their <strong>BuildConfig</strong> class so the intellisense in Android Studio will pick up on these, especially if yours hasn&#39;t been generated yet.</p>
<h3 id="why-wouldn-t-be-buildconfig-be-generated-">Why wouldn&#39;t be BuildConfig be generated?</h3>
<p>Your <strong>BuildConfig</strong> may not be generated for a number of reasons:</p>
<ul>
<li>The <strong>build</strong> directory has been deleted</li>
<li>You did a Project clean via Gradle, which deletes the build directory</li>
<li>You updated Android Studio and Gradle, which may have a side effect of doing a Gradle clean</li>
</ul>
<h2 id="how-to-fix-buildconfig-debug-always-returns-false">How to fix BuildConfig.DEBUG always returns false</h2>
<h3 id="delete-any-buildconfig-import-from-the-file">Delete any BuildConfig import from the file</h3>
<p>Delete any imports that import BuildConfig. You don&#39;t need them.</p>
<h3 id="ignore-import-prompts">Ignore import prompts</h3>
<p>Just ignore the import prompts. <strong>Do not import another project&#39;s BuildConfig</strong>. Of course this will always be false because projects don&#39;t ship debug builds. If you haven&#39;t generated you&#39;re likely to see this error. <img src="https://i.stack.imgur.com/UQ5nC.png" alt="Android Studio prompting to import BuildConfig"> If you import another package&#39;s build config, you&#39;re going to get the warning: <code>BuildConfig.DEBUG: condition is always false</code>. <img src="https://i.stack.imgur.com/DX4g2.png" alt="Android Studio presenting import options"> <img src="https://i.stack.imgur.com/2VXw4.png" alt="BuildConfig.DEBUG always returns false warning"></p>
<h3 id="run-gradle-build">Run Gradle build</h3>
<p>Run the project as usual. You should see this error disappear. <img src="https://i.stack.imgur.com/LDD6J.png" alt="BiuldConfig.DEBUG no longer has warning"></p>
<h2 id="summary">Summary</h2>
<p>TLDR: BuildConfig is a generated class in your project. If Android Studio can&#39;t find it, it&#39;ll prompt you to import it. Don&#39;t import it or you&#39;ll get the lint warning. Run the project as usual, ignoring the red. Your problem should be solved.</p>
]]></content:encoded></item><item><title><![CDATA[Writing a simple Gulp plugin tutorial]]></title><description><![CDATA[In this simple Gulp plugin tutorial, I will be going through a plugin I wrote. The plugin is simple and transforms an input JSON file. Though Gulp was a great tool I fully leveraged for bundling front-end apps at its inception, I do not currently rec...]]></description><link>https://blog.tinaciousdesign.com/writing-a-simple-gulp-plugin-tutorial</link><guid isPermaLink="true">https://blog.tinaciousdesign.com/writing-a-simple-gulp-plugin-tutorial</guid><dc:creator><![CDATA[Tina Holly]]></dc:creator><pubDate>Tue, 14 Apr 2020 02:23:02 GMT</pubDate><content:encoded><![CDATA[<p>In this simple Gulp plugin tutorial, I will be going through a plugin I wrote. The plugin is simple and transforms an input JSON file. Though Gulp was a great tool I fully leveraged for bundling front-end apps at its inception, <strong>I do not currently recommend using Gulp for new projects</strong>. You may, however, find yourself in an existing project that already uses Gulp. Keeping an existing Gulp build system is perfectly reasonable. This tutorial would be useful in that case. In order to gain value from this blog post, you should have some basic experience setting up Gulp to bundle your projects.</p>
<h2 id="our-problem">Our problem</h2>
<p>Writing a Gulp plugin that transforms JSON would be useful when bundling a Chrome extension for distribution. In the final bundle, developers need to provide a <strong>manifest.json</strong> file. This manifest, among other things, declares lists of domains that you are authorizing your Chrome extension to use. Such domains can include your Chrome extension&#39;s back-end API server, for example, a value that changes based on the environment. A plugin that manipulates the <strong>manifest.json</strong>, adding/removing/replacing values depending on the environment, can be useful to publish different Chrome extension builds for each of the supported environments (development, staging, production). We currently have the following <strong>manifest.json</strong> file:</p>
<pre><code>{
  "<span class="hljs-attr">manifest_version</span>": <span class="hljs-number">2</span>,
  "<span class="hljs-attr">name</span>": <span class="hljs-string">"My Cool Extension"</span>,
  "<span class="hljs-attr">version</span>": <span class="hljs-string">"1.0.0"</span>,
  "<span class="hljs-attr">content_security_policy</span>": <span class="hljs-string">"default-src 'self'; connect-src 'self' http://localhost:1337 https://staging.example.com https://app.example.com"</span>,
  "<span class="hljs-attr">permissions</span>": [
    <span class="hljs-string">"tabs"</span>,
    <span class="hljs-string">"storage"</span>,
    <span class="hljs-string">"cookies"</span>,
    <span class="hljs-string">"http://localhost:1337"</span>,
    <span class="hljs-string">"https://staging.example.com"</span>,
    <span class="hljs-string">"https://app.example.com"</span>
  ]
}
</code></pre><p>For production, we would like the <strong>manifest.json</strong> file to look like this:</p>
<pre><code>{
  "<span class="hljs-attr">manifest_version</span>": <span class="hljs-number">2</span>,
  "<span class="hljs-attr">name</span>": <span class="hljs-string">"My Cool Extension"</span>,
  "<span class="hljs-attr">version</span>": <span class="hljs-string">"1.0.0"</span>,
  "<span class="hljs-attr">content_security_policy</span>": <span class="hljs-string">"default-src 'self'; connect-src 'self' https://app.example.com"</span>,
  "<span class="hljs-attr">permissions</span>": [
    <span class="hljs-string">"tabs"</span>,
    <span class="hljs-string">"storage"</span>,
    <span class="hljs-string">"cookies"</span>,
    <span class="hljs-string">"https://app.example.com"</span>
  ]
}
</code></pre><p>Our Gulp plugin needs to remove the localhost and staging entries (<code>http://localhost:1337</code> and <code>https://staging.example.com</code>) from both the content security policy and the permissions list.</p>
<h2 id="structuring-our-gulp-plugin">Structuring our Gulp plugin</h2>
<p>Ideally we should structure our Gulp plugin in 2 files:</p>
<ol>
<li>Primary business logic</li>
<li>The glue that ties the business logic to the Gulp context</li>
</ol>
<p>When we break our plugin into 2 files instead of one, we can isolate our business logic into a simpler method that is a simple function that takes simple arguments. We can then write tests for our business logic alone. This makes our plugin&#39;s logic much easier to test, compared to if we had only one file and we needed to test it within the context of a Gulp stream.</p>
<h3 id="the-business-logic">The business logic</h3>
<p>This file is all of the business logic. Given a file buffer as an argument, as well as any other arguments passed through from the Gulpfile, we can perform actions on the provided input.</p>
<pre><code><span class="hljs-comment">// transform-manifest.js</span>
<span class="hljs-keyword">const</span> transformManifest = <span class="hljs-function">(<span class="hljs-params">opts = {}, manifestJsonBuffer</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> manifest = <span class="hljs-built_in">JSON</span>.parse(manifestJsonBuffer.contents.toString());

  <span class="hljs-comment">// Modify CSP</span>
  <span class="hljs-keyword">const</span> csp = <span class="hljs-string">'[my new CSP that is possibly created by removoing things]'</span>
  manifest.content_security_policy = csp;

  <span class="hljs-comment">// Modify permissions</span>
  <span class="hljs-keyword">const</span> permissions = manifest.permissions.filter(myFilteringCriteria);
  manifest.permissions = permissions;

  <span class="hljs-comment">/**
   * Rewrite file buffer
   */</span>
  <span class="hljs-keyword">const</span> stringContents = <span class="hljs-built_in">JSON</span>.stringify(manifest, <span class="hljs-literal">null</span>, <span class="hljs-number">2</span>);
  <span class="hljs-keyword">const</span> newBuffer = Buffer.alloc(stringContents.length, stringContents);
  manifestJsonBuffer.contents = newBuffer;

  <span class="hljs-keyword">return</span> manifestJsonBuffer;
};


<span class="hljs-built_in">module</span>.exports = transformManifest;
</code></pre><p>Our resulting file exports a function that takes a JSON file Buffer and returns a JSON file Buffer to continue down the Gulp stream.</p>
<h3 id="the-gulp-glue">The Gulp glue</h3>
<p>This is the part that ties our primary business logic to the Gulp stream. The Gulp documentation provides multiple options for how to create plugins, but in this example I&#39;ll use the module through2 to help tie it together.</p>
<pre><code><span class="hljs-comment">// gulp-transform-manifest.js</span>
<span class="hljs-keyword">const</span> through = <span class="hljs-built_in">require</span>(<span class="hljs-string">'through2'</span>);
<span class="hljs-keyword">const</span> transformManifest = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./transform-manifest'</span>);

<span class="hljs-built_in">module</span>.exports = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">opts</span>) </span>{
  <span class="hljs-keyword">return</span> through.obj(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">file, encoding, callback</span>) </span>{
    callback(<span class="hljs-literal">null</span>, transformManifest(opts, file));
  });
};
</code></pre><p>You can essentially just copy and paste this Gulp glue verbatim. Keep in mind that <strong>opts</strong> are options that you can pass in from the Gulpfile. In my Gulpfile, the implementation is as follows:</p>
<pre><code><span class="hljs-comment">// Gulpfile.js</span>
<span class="hljs-keyword">const</span> gulp = <span class="hljs-built_in">require</span>(<span class="hljs-string">'gulp'</span>);
<span class="hljs-keyword">const</span> transformManifest = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./lib/gulp-transform-manifest'</span>);

gulp.task(<span class="hljs-string">'process-manifest'</span>, <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> {
  <span class="hljs-keyword">return</span> gulp.src([
    <span class="hljs-string">'./src/manifest.json'</span>
  ])
  .pipe(transformManifest({ env: process.env.NODE_ENV }))
  .pipe(gulp.dest(<span class="hljs-string">'./dist'</span>));
});
</code></pre><p>In the <strong>package.json</strong> I call my my task like so:</p>
<pre><code><span class="hljs-attr">NODE_ENV</span>=production gulp dist
</code></pre><p>The Gulp task <strong>dist</strong> uses the <strong>process-manifest</strong> task as a dependency alongside other tasks. The environment variable is passed through from the executed NPM script to Gulp, which passes it through to our Gulp plugin.</p>
<h2 id="conclusion">Conclusion</h2>
<p>That wraps up this simple Gulp plugin tutorial. Let&#39;s take a look at what we&#39;ve learned:</p>
<ul>
<li>Our plugin transforms JSON and passes it through the Gulp stream on to any following plugins to process</li>
<li>We can pass arguments from the invocation of the call in the NPM scripts, to the Gulpfile (where we can add additional options), all the way down to the business logic of our function</li>
<li>We&#39;ve structured our plugin in such a way that we can write unit tests for the business logic of our plugin</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[COVID-19 data by country]]></title><description><![CDATA[When the world gives you COVID-19, use the community-driven APIs it provides for COVID-19 data by country to make a tool to show data better? COVID-19 Today shows daily stats for the coronavirus. It's a web app built in React that displays COVID-19 d...]]></description><link>https://blog.tinaciousdesign.com/covid-19-data-by-country</link><guid isPermaLink="true">https://blog.tinaciousdesign.com/covid-19-data-by-country</guid><category><![CDATA[covid]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[React]]></category><category><![CDATA[CSS]]></category><dc:creator><![CDATA[Tina Holly]]></dc:creator><pubDate>Sun, 29 Mar 2020 02:23:02 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1596513558998/yIyuu_PUt.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When the world gives you COVID-19, use the community-driven APIs it provides for COVID-19 data by country to make a tool to show data better? <a target="_blank" href="https://codepen.io/tinacious/full/eYNbEoE">COVID-19 Today</a> shows daily stats for the coronavirus. It's a web app built in React that displays COVID-19 data by country. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1606866981769/ArXsjlHSU.png" alt="COVID-19 data by country, sorted by default criteria for most confirmed cases today" /></p>
<h2 id="sorting-criteria">Sorting criteria</h2>
<p>The tool allows you to sort countries using the following criteria:</p>
<ul>
<li>total confirmed cases</li>
<li>total new cases today</li>
<li>total deaths</li>
<li>total deaths today</li>
<li>total recovered</li>
<li>current active cases</li>
<li>current cases in critical condition</li>
<li>highest cases per 1 million population</li>
<li>highest deaths per 1 million population</li>
</ul>
<p>You can click or tap on the different buttons to sort the countries. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1606867031806/CSWc2w5ha.png" alt="COVID-19 data by country (mobile view) for the countries with the most recoveries" /></p>
<h2 id="search-by-country">Search by country</h2>
<p>The tool also makes it easy to search for COVID-19 stats for a specific country. You can type a country name in the search box to find that country. It filters the countries list by the provided search criteria. This uses the country name provided by the API. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1606867063336/3x94o-sS3.png" alt="COVID-19 data by country search filtering" /></p>
<h2 id="data">Data</h2>
<p>This tool couldn't have been made possible without the dated collected and shared by the open-source community:</p>
<ul>
<li><a target="_blank" href="https://covid-19-apis.postman.com/">Postman COVID-19 API Resource Centre</a></li>
<li>NovelCOVID API<ul>
<li><a target="_blank" href="https://github.com/NOVELCOVID/API">Source code</a></li>
<li><a target="_blank" href="https://documenter.getpostman.com/view/8854915/SzS7R6uu?version=latest">API documentation</a></li>
</ul>
</li>
</ul>
<p>There's also lots of other data available, but that's the one I used.</p>
<h2 id="source-code">Source code</h2>
<p>The source code for this app that displays COVID-19 data by country is available <a target="_blank" href="https://codepen.io/tinacious/pen/eYNbEoE">on Codepen here</a>. The web application is built using the following technologies:</p>
<ul>
<li>HTML</li>
<li>SCSS</li>
<li>React</li>
<li>jQuery (Ajax)</li>
<li>the above-mentioned API for COVID-19 data by country</li>
</ul>
]]></content:encoded></item></channel></rss>