<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Scanbot SDK</title>
    <description>The latest articles on Forem by Scanbot SDK (@scanbot-sdk).</description>
    <link>https://forem.com/scanbot-sdk</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F8989%2F6ff36e9e-5f50-41dc-b6f4-2728391c1a32.png</url>
      <title>Forem: Scanbot SDK</title>
      <link>https://forem.com/scanbot-sdk</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/scanbot-sdk"/>
    <language>en</language>
    <item>
      <title>Storage Wars: Web Edition – or, how we learned to store binary data effectively</title>
      <dc:creator>Aare Undo</dc:creator>
      <pubDate>Mon, 23 Sep 2024 13:00:00 +0000</pubDate>
      <link>https://forem.com/scanbot-sdk/storage-wars-web-edition-or-how-we-learned-to-store-binary-data-effectively-1f7c</link>
      <guid>https://forem.com/scanbot-sdk/storage-wars-web-edition-or-how-we-learned-to-store-binary-data-effectively-1f7c</guid>
      <description>&lt;h2&gt;
  
  
  Prologue
&lt;/h2&gt;

&lt;p&gt;All modern browsers offer ways to store data locally. Our world would look rather different if they didn’t. Since you’re reading this, I’m sure you already have some idea of what local storage in the browser is. Still, let’s quickly go over the basics.&lt;/p&gt;

&lt;p&gt;I’m writing this in Google Docs. I figure it stores my data locally before syncing it to Google’s servers. This is a good thing, because it makes sure that any changes I make are immediately stored on my device, and that Docs doesn’t fully depend on an external server being available.&lt;/p&gt;

&lt;p&gt;Cookies are the oldest way to store data locally – however, they’re ripe for abuse by third parties. They’re also intended for communication with servers anyway, so they are a story for another time.&lt;/p&gt;

&lt;p&gt;Then we have session storage and local storage proper. They’re almost exactly the same, except for their scope and lifetime.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Local storage&lt;/strong&gt; stores data per origin, meaning whenever and from whichever tab you visit the same URL, you have the same data stored locally.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Session storage&lt;/strong&gt;, on the other hand, works per instance, meaning its lifecycle is bound to a single browser tab. It’s intended to allow separate instances of the same web app to run in different windows without interfering with each other, which is a nifty feature. However, it does not exactly fit our requirements.&lt;/p&gt;

&lt;p&gt;Wait, which requirements? What exactly are we looking for?&lt;/p&gt;

&lt;p&gt;We’re here to research storage options for the Scanbot Web SDK, you see.&lt;/p&gt;

&lt;p&gt;This SDK has a complex API that deals with various different types of data. When you scan a document with it, you don’t just get a bunch of strings and numbers. For one, the result data also includes an array with the coordinates of the document’s location on the scanned image. For another, you also get the image itself as binary data (more on this later). All of this needs to be properly stored and managed as conveniently for the user as possible.&lt;/p&gt;

&lt;p&gt;Here’s an example of the data object we need to store (I’ve removed annotations and irrelevant types for the sake of brevity):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CroppedDetectionResult&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;croppedImage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RawImage&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;originalImage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DetectionStatus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NOT_ACQUIRED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;detectionScores&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DetectionScores&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;points&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Point&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="nl"&gt;horizontalLines&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LineSegment&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="nl"&gt;verticalLines&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LineSegment&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="nl"&gt;aspectRatio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;averageBrightness&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, it’s not overly complicated. Some numbers and strings, some classes as types.&lt;/p&gt;

&lt;p&gt;There are only two parts that are actually problematic: &lt;code&gt;RawImage&lt;/code&gt;, which contains &lt;code&gt;Uint8Array&lt;/code&gt; data, and &lt;code&gt;Image&lt;/code&gt;, which is a type that can refer to any of the interfaces &lt;code&gt;ImageData&lt;/code&gt;, &lt;code&gt;ArrayBuffer&lt;/code&gt;, &lt;code&gt;Uint8Array&lt;/code&gt; and &lt;code&gt;RawImage&lt;/code&gt; itself.&lt;/p&gt;

&lt;p&gt;How can we store all this, then?&lt;/p&gt;

&lt;h2&gt;
  
  
  Local storage
&lt;/h2&gt;

&lt;p&gt;Let’s start with the basics, the OG of storage: &lt;code&gt;localStorage&lt;/code&gt;. It has been available since before I could call myself a software developer, all the way back in 2009. Ah, simpler times. &lt;code&gt;localStorage&lt;/code&gt; is still used in all kinds of applications and the API has remained relatively stable throughout the ages, so I will jump right into the implementation.&lt;/p&gt;

&lt;p&gt;Let’s create a separate storage class for our data object. Just the bare bones for storing an item and retrieving it without worrying about unique keys or anything.&lt;/p&gt;

&lt;p&gt;JSON parsing is necessary because &lt;code&gt;localStorage&lt;/code&gt; only accepts strings as values, another indication that this is not meant for larger data sets. And here it is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SBStorage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nf"&gt;storeDocument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CroppedDetectionResult&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;document&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nf"&gt;getDocument&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;CroppedDetectionResult&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;document&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Limitations
&lt;/h3&gt;

&lt;p&gt;At this point, I just wanted to show you what a retrieved document looked like, but my React starter project immediately threw an exception:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;sb&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="nc"&gt;Uncaught &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;promise&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;DOMException&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Failed&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;execute&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;setItem&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="nx"&gt;on&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Storage&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Setting&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;document&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="nx"&gt;exceeded&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;quota&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
    &lt;span class="nx"&gt;at&lt;/span&gt; &lt;span class="nx"&gt;SBStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storeDocument&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is this nifty online tool that shows you how many characters can be held in &lt;code&gt;localStorage&lt;/code&gt;: &lt;a href="https://arty.name/localstorage.html" rel="noopener noreferrer"&gt;https://arty.name/localstorage.html&lt;/a&gt;. As of writing this, my limit seems to be 5,200,000. This number will vary based on the browser and version you’re using.&lt;/p&gt;

&lt;p&gt;The site’s author even explains:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Chrome 6.0.472.36 beta let me save 2600-2700 thousands of characters, Firefox 3.6.8 – 5200-5300k, Explorer 8 – 4900-5000k, and Opera 10.70 build 9013 popped a dialog, that allowed me to give the script unlimited storage. Spec arbitrarily recommends 5 megabytes of storage, but doesn’t say a thing about actual characters, so in UTF-16 you get twice less.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So, let’s check how many characters’ worth of data we’re trying to store. A quick modification to the existing snippet …&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Characters:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;document&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;… and the result we get is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Characters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;68046772&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As it turns out, &lt;code&gt;localStorage&lt;/code&gt; can’t even store a tenth of that.&lt;/p&gt;

&lt;p&gt;Even though we’ve already proven that it’s not a viable option here, I briefly want to touch on the fact that &lt;code&gt;localStorage&lt;/code&gt; does not support binary data. That’s what I was going to get at in the first place, but instead we immediately ran into the quota issue.&lt;/p&gt;

&lt;p&gt;I just want to quickly show you what serialization does to a binary data format. To begin with, the format of our &lt;code&gt;croppedImage&lt;/code&gt; data is an &lt;code&gt;Uint8Array&lt;/code&gt;. It looks more or less like what you would expect (this array is actually 1,504,272 characters long, but here’s the start of it):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;169&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;195&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;195&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;169&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;195&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;195&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;168&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;194&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;194&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;168&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After serialization, the same array looks like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;169&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;195&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;195&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;169&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;195&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;5&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;195&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;6&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;168&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;7&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;194&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;194&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;9&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;168&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We’ll get into why this is in a later section, but what we need to know right now is that &lt;code&gt;localStorage&lt;/code&gt; only accepts strings and therefore doesn’t support binary formats out of the box.&lt;/p&gt;

&lt;p&gt;There are ways to get around this – you can probably already imagine an algorithm to turn this map of keys and values back into an array of integers. It’s always going to be a hack, though. You just shouldn’t have to manipulate binary data manually.&lt;/p&gt;

&lt;h3&gt;
  
  
  Base64
&lt;/h3&gt;

&lt;p&gt;The correct way to do this would be to convert the image data to a Base64 image first, which we can then store properly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Base64&lt;/strong&gt; is a family of binary-to-text encoding schemes that represent binary data in an ASCII string format by transforming it into a radix-64 representation.&lt;/p&gt;

&lt;p&gt;That may sound complicated, but it’s really a straightforward process. You simply create a reader object, pass your array as a blob, and read it in as a data URL using the native JavaScript function. If necessary, also remove the descriptor part of the string. Here’s the convenience function we use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kr"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;toBase64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// See https://stackoverflow.com/a/39452119&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;base64url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FileReader&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readAsDataURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Blob&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="c1"&gt;// remove the `data:...;base64,` part from the start&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;base64url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;base64url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then the following, if not too long, can be effectively held in local storage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;base64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ImageUtils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toBase64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;croppedImage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Base64: wsLQw8LQw8LQw8TRxMXSxMXTxcfUxcjVxMjVxcnXx8vaztHh1dbm19np2dvr2Nv...&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Base64: &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, any kind of operation of this magnitude is computationally heavy, be it the direct manipulation of a key-value map or the Base64 version, and we’re trying to be optimal here. So all that just won’t cut it. We need to be able to work with binary data directly.&lt;/p&gt;

&lt;p&gt;And this brings us to …&lt;/p&gt;

&lt;h2&gt;
  
  
  Binary data
&lt;/h2&gt;

&lt;p&gt;In web development, we meet binary data mostly when dealing with files (creating, uploading, downloading). Another typical use case is image processing – which is what we’re doing here.&lt;/p&gt;

&lt;p&gt;There are many classes for various implementations of binary data in JavaScript, such as &lt;code&gt;ArrayBuffer&lt;/code&gt;, &lt;code&gt;Uint8Array&lt;/code&gt;, &lt;code&gt;DataView&lt;/code&gt;, &lt;code&gt;Blob&lt;/code&gt; and &lt;code&gt;File&lt;/code&gt;, but these different nuances of implementation aren’t important right now.&lt;/p&gt;

&lt;p&gt;Anyway, the core object is &lt;code&gt;ArrayBuffer&lt;/code&gt; – the root of everything, the raw binary data. And that is what we will be working with. Whichever of the aforementioned wrapper classes you use, it will contain a variation of an &lt;code&gt;ArrayBuffer&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Everything is an object (almost)
&lt;/h3&gt;

&lt;p&gt;Now, the explanation as to why stringification of &lt;code&gt;ArrayBuffer&lt;/code&gt; or &lt;code&gt;Uint8Array&lt;/code&gt; turns it into a bit of a nightmare is actually pretty interesting. You see, everything in JavaScript is actually an object, except strings. Strings are still strings. But with arrays, it means you can do the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;foo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bar&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// [ 1, 2, 3, foo: 'bar']&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s because natively, an array is still an object prototype, but it does stringify into &lt;em&gt;looking&lt;/em&gt; like a simple array simply because its &lt;code&gt;toString()&lt;/code&gt; function has been overwritten for the convenience of developers. Because it’s often needed.&lt;/p&gt;

&lt;p&gt;And that is also exactly why stringifying an &lt;code&gt;Uint8Array&lt;/code&gt; turns it into an object of key-value pairs (remember the &lt;code&gt;{"0":169,"1":195,"2":195,"3":169 …}&lt;/code&gt; example from earlier) and why we need additional steps to properly serialize it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Serialization
&lt;/h3&gt;

&lt;p&gt;At this point, it’s also worth it to elaborate on a point I made previously: that binary data is not serializable. While that is still fundamentally true, JavaScript does have some native support for serialization these days, with the latest and greatest tool being &lt;code&gt;TextDecoder&lt;/code&gt;. Take this example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TextEncoder&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;banana&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;testObj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;encodedText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;serializedTestObj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;testObj&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;serializedTestObj&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// {"encodedText":"98,97,110,97,110,97"}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;deserializedTestObj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;serializedTestObj&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;deserializedBuffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;deserializedTestObj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;encodedText&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newBuffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;deserializedBuffer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TextDecoder&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newBuffer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;str&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// banana&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Thank you to &lt;a href="https://dev.to/subterrane/how-to-serialize-that-ing-buffer-12m3"&gt;Will Munslow from dev.to&lt;/a&gt; for this snippet!&lt;/p&gt;

&lt;p&gt;While it is &lt;em&gt;relatively&lt;/em&gt; straightforward, you’ll notice right away that this encoding method requires:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  For the data to initially be a string that’s serialized with the coder class, which is a relatively uncommon use case (in most cases you start off with binary data).&lt;/li&gt;
&lt;li&gt;  String splitting, which is an expensive operation.&lt;/li&gt;
&lt;li&gt;  At the end, both buffer deserialization and text decoding again.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s now move on to the next web storage method.&lt;/p&gt;

&lt;h2&gt;
  
  
  IndexedDB
&lt;/h2&gt;

&lt;p&gt;So, what is IndexedDB? I would’ve gladly given my own short overview of the concept, but &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API" rel="noopener noreferrer"&gt;MDN&lt;/a&gt; says it best:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;IndexedDB is a low-level API for client-side storage of significant amounts of structured data, including files/blobs. This API uses indexes to enable high-performance searches of this data.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What that actually means for us is a convenient way to store the aforementioned binary data of &lt;code&gt;ArrayBuffer&lt;/code&gt; format. Yay, no more of this complex, nonsensical and resource-hungry encoding and decoding logic!&lt;/p&gt;

&lt;p&gt;To quickly reiterate, the main problems with &lt;code&gt;localStorage&lt;/code&gt; are that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  It can only store strings.&lt;/li&gt;
&lt;li&gt;  It has a very small and unstable data cap.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;IndexedDB easily solves both of these issues. On top of that, it has some additional benefits, such as the fact that it can be used inside web workers (more on this later). It’s also asynchronous, meaning the render cycle is not blocked while saving/loading.&lt;/p&gt;

&lt;p&gt;The only thing &lt;code&gt;localStorage&lt;/code&gt; has going for it is the simplicity of its API. It’s just &lt;code&gt;localStorage.setItem&lt;/code&gt; and &lt;code&gt;localStorage.getItem&lt;/code&gt;. There are a couple of additional convenience functions, but that’s roughly it. Done!&lt;/p&gt;

&lt;h3&gt;
  
  
  Messy API &amp;amp; solutions
&lt;/h3&gt;

&lt;p&gt;IndexedDB isn’t perfect, either: The API is famously a fabulous mess. It’s such a mess, in fact, that a whole ecosystem of libraries has been developed around it just so that your average developer doesn’t have to deal with its nonsense.&lt;/p&gt;

&lt;p&gt;So, before we finally move on to our actual implementation, let’s take a look at what’s already out there to help you with this.&lt;/p&gt;

&lt;p&gt;There’s &lt;a href="https://www.npmjs.com/package/use-indexeddb" rel="noopener noreferrer"&gt;use-indexeddb&lt;/a&gt;, which promises to be the most lightweight one, but that’s because it only wraps a few of the basics. It’s not all that convenient to use, and it also features some intrusive logging that cannot be disabled.&lt;/p&gt;

&lt;p&gt;Next up is &lt;a href="https://dexie.org/" rel="noopener noreferrer"&gt;Dexie&lt;/a&gt;, a more fully-fledged storage framework that looks pretty promising. Its API is very straightforward. It’s super intuitive to create a base, write queries and add entries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Dexie&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MyDatabase&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Declare tables, IDs and indexes&lt;/span&gt;
&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;version&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;stores&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;friends&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;++id, name, age&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Find some old friends&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;oldFriends&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;friends&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;age&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;above&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;75&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toArray&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// or make a new one&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;friends&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Camilla&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;street&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;East 13:th Street&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;picture&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getBlob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;camilla.png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At first glance, it seems to be inspired by C#’s LINQ statements, and it also supports live queries. That means you can combine it with, for example, Redux and completely handle state management through that. While that’s a nice feature, Dexie is actually a bit too elaborate for our use case.&lt;/p&gt;

&lt;p&gt;Another one is &lt;a href="https://www.npmjs.com/package/idb" rel="noopener noreferrer"&gt;idb&lt;/a&gt;, which claims to mostly mirror the IndexedDB API, but with some notable usability improvements. But really, looking at the implementation, it just seems to wrap the API in the &lt;code&gt;Promise&lt;/code&gt; class and not much else. It’s definitely not worth the hassle of adding a third-party dependency for such a minor increase in convenience.&lt;/p&gt;

&lt;p&gt;The final popular wrapper framework is called &lt;a href="https://jsstore.net/docs/get-started" rel="noopener noreferrer"&gt;JsStore&lt;/a&gt;, which wraps the IndexedDB API into an SQL-like API.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;insertCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;into&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Product&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Product&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which, in my humble opinion, is not much of an improvement. SQL is still pretty complex and it’s easy to create sub-optimal transactions. It’s not raw SQL, so maybe it’s not too bad, but I’ve been burned by SQL too many times to trust anything like that again.&lt;/p&gt;

&lt;p&gt;Plus, as with Dexie, JsStore seems to be overkill for our use case as well.&lt;/p&gt;

&lt;p&gt;To be honest, I knew in advance that I wasn’t going to be impressed by any of these libraries. The plan was always to get our hands dirty, the Spartan way, and not to tie ourselves down by any more dependencies. I just wanted to give a quick overview of the options available. If our solution were more complex, I’d go with Dexie.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Spartan approach
&lt;/h3&gt;

&lt;p&gt;However, we’re building an SDK for clients, so we do not want to rely on any external libraries. It is possible, and we do use them, but we do not add them lightly, because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  They always come with a license, which we must adhere to and disclose.&lt;/li&gt;
&lt;li&gt;  They add overhead to future development and maintenance (I’m sure you know the pain of upgrading dependencies all too well).&lt;/li&gt;
&lt;li&gt;  They add additional points of failure to our project.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If possible and within reason, we want to be in control of what happens inside our SDK.&lt;/p&gt;

&lt;p&gt;So, for our storage logic, let’s roll our own!&lt;/p&gt;

&lt;p&gt;In terms of use cases, ours is relatively straightforward. The data model itself is complex, but the database transactions are not, so I was not expecting too much of a hassle. The basic setup is simple enough:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;indexedDB&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SBSDKBase&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We create the request, think of a name for the database and add a version number of the database we’re requesting from. Having to specify the version number right away may seem unnecessary, but the documentation makes it pretty clear why: database models change all the time and having an easy option to keep track of version history seems intuitive enough.&lt;/p&gt;

&lt;p&gt;The request object has different callbacks: &lt;code&gt;onerror&lt;/code&gt;, &lt;code&gt;onupgradeneeded&lt;/code&gt;, and &lt;code&gt;onsuccess&lt;/code&gt;. Right now, we can ignore errors, as we’re just trying to add some basic documents to the base and retrieve them. There’s no model changes, no indexing, we only use readwrite mode and increment automatically (let’s hope this doesn’t come back to bite us).&lt;/p&gt;

&lt;p&gt;All we’re interested in at the moment is the success callback.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onsuccess&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;IDBOpenDBRequest&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createObjectStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;documents&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;keyPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;autoIncrement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;transaction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;documents&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;readwrite&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;objectStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;documents&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Added item to store&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getRequest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;getRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onsuccess&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;IDBRequest&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Got item from store&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the most bare-bones code snippet I thought I’d need to add a document to storage. Open the database, create a document table, put an object in it. I’ve been doing this for a long time, I’ve worked on various databases and thought I had a handle on it. I thought this would work.&lt;/p&gt;

&lt;p&gt;Nope. I got bitten right away. The error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Uncaught&lt;/span&gt; &lt;span class="nx"&gt;DOMException&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Failed&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;execute&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;createObjectStore&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="nx"&gt;on&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;IDBDatabase&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;The&lt;/span&gt; &lt;span class="nx"&gt;database&lt;/span&gt; &lt;span class="nx"&gt;is&lt;/span&gt; &lt;span class="nx"&gt;not&lt;/span&gt; &lt;span class="nx"&gt;running&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="nx"&gt;change&lt;/span&gt; &lt;span class="nx"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why would I be unable to create a table when opening the database? I’m barely getting started and I already get the feeling that writing raw SQL is more intuitive than this. There, you can at least execute queries once you open the database.&lt;/p&gt;

&lt;p&gt;Let’s hope it gets better from here on! At least the error message itself is not cryptic. We’ll try again by implementing &lt;code&gt;onupgradeneeded&lt;/code&gt; (don’t forget to upgrade your db version):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onupgradeneeded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;IDBOpenDBRequest&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createObjectStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;documents&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;keyPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;autoIncrement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;IndexedDB upgraded&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can also remove the &lt;code&gt;create&lt;/code&gt; block from our success callback and voilá, we’ve created a database, added our first item to it, and also retrieved it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onsuccess&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;IDBOpenDBRequest&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;transaction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;documents&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;readwrite&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;objectStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;documents&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Added item to store&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getRequest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;getRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onsuccess&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;IDBRequest&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Got item from store&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the retrieved object still contains our image data in the correct &lt;code&gt;Uint8Array&lt;/code&gt; format. Juicy!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Got&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;OK&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="nx"&gt;detectionScores&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{...},&lt;/span&gt; 
    &lt;span class="nx"&gt;points&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
    &lt;span class="nx"&gt;horizontalLines&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
    &lt;span class="nx"&gt;verticalLines&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nl"&gt;aspectRatio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.698217078300424&lt;/span&gt;&lt;span class="nx"&gt;averageBrightness&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;183&lt;/span&gt;
    &lt;span class="nx"&gt;croppedImage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{...,&lt;/span&gt; &lt;span class="nx"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;BGR&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1859760&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Asynchronous improvements
&lt;/h3&gt;

&lt;p&gt;Time to make the entire process a bit more convenient. Let’s wrap the database opening into its own asynchronous block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kr"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nf"&gt;openDB&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;IDBDatabase&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;DB_NAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SBSDKBase&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;DB_VERSION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;indexedDB&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;DB_NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;DB_VERSION&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onsuccess&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;IDBOpenDBRequest&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;

        &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onerror&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onupgradeneeded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Update object stores when necessary&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And another block to get the store itself, so we don’t have to duplicate that any further:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kr"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;getDocumentStore&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;IDBObjectStore&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;SBStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;openDB&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;transaction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;documents&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;readwrite&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;objectStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;documents&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And now we have two super intuitive public functions for our storage service to store a document and retrieve all of them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;storeDocument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CroppedDetectionResult&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;SBStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getDocumentStore&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;getDocuments&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CroppedDetectionResult&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;SBStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getDocumentStore&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAll&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;onsuccess&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;IDBRequest&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Eventually we’ll have to add proper error handling, but as you can see, with a couple of wrapper functions, it’s relatively easy to implement IndexedDB on your own, no third-party libraries required.&lt;/p&gt;

&lt;p&gt;This asynchronous approach with &lt;code&gt;Promise&lt;/code&gt; already utilizes callbacks and does not block the main thread. JavaScript is pretty nice, you see. We just implemented some basic syntactic sugar to make the code look nicer, and that ended up being the optimal approach anyway.&lt;/p&gt;

&lt;h3&gt;
  
  
  Timing transactions
&lt;/h3&gt;

&lt;p&gt;It’s possible to optimize this even further, and we’ll get to that in a second. But first, let’s check out how much time all these transactions take. You should always know exactly what you’re optimizing and why. Performance metrics are key.&lt;/p&gt;

&lt;p&gt;The most robust way to do this is to just go back to our initial implementation and measure how many milliseconds have passed after each meaningful operation. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;currentMS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;getTime&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;indexedDB&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SBSDKBase&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Opening indexedDB&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;getTime&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;currentMS&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onsuccess&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;IDBOpenDBRequest&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;transaction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;documents&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;readwrite&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;objectStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;documents&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Added item to store&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;getTime&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;currentMS&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getRequest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;getRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onsuccess&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;IDBRequest&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Got item from store&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;getTime&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;currentMS&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Opening&lt;/span&gt; &lt;span class="nx"&gt;indexedDB&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="nx"&gt;Added&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt; &lt;span class="mi"&gt;17&lt;/span&gt;
&lt;span class="nx"&gt;Got&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s not too bad, but it’s also not nothing. Considering that I’m currently working on a higher-end MacBook Pro with an M2 chip and an SSD, this operation could easily take 20–50 times as long on a lower-end Android or iOS device.&lt;/p&gt;

&lt;p&gt;We have already implemented basic asynchronous IndexedDB usage with Promises, and in some use cases this would be good enough. But since we’re dealing with large chunks of data, though, it’s worth investing in true parallelism and moving everything off the main thread.&lt;/p&gt;

&lt;p&gt;And this brings us to …&lt;/p&gt;

&lt;h2&gt;
  
  
  Web workers
&lt;/h2&gt;

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

&lt;p&gt;How web workers differ from promises/callbacks is rather fundamental.&lt;/p&gt;

&lt;p&gt;I won’t dive too deep into threading, but to put it briefly, a JavaScript environment is fundamentally single-threaded. In its simplest form, asynchronous is just syntactic sugar for promise chains that allow for the user interface to be rendered before other long-running operations.&lt;/p&gt;

&lt;p&gt;It’s also important to note that two jobs can never run in parallel in this environment. One async operation might run in between two jobs of another one, so in that sense they can run “concurrently.” For true parallelism, e.g. simultaneously rendering the user interface and storing data, we need threads.&lt;/p&gt;

&lt;p&gt;Luckily, all modern browsers give us worker threads, allowing for this kind of operation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Basic implementation &amp;amp; problems
&lt;/h3&gt;

&lt;p&gt;Let’s see how we can set up worker threads for our specific use case, following the same example code used previously.&lt;/p&gt;

&lt;p&gt;Spawning a worker is a relatively trivial task. From &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers" rel="noopener noreferrer"&gt;MDN’s examples&lt;/a&gt;, you just need to create a worker JavaScript file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Worker: Message received from main script&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isNaN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Please write two numbers&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;workerResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Result: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Worker: Posting message back to main script&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;workerResult&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then you’re able to import it in your main JavaScript file and pass data to it as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Worker&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;myWorker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Worker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;worker.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;first&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;second&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onchange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;myWorker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;second&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, running a piece of JavaScript code on another thread is not as trivial as running it asynchronously. You need to spawn a dedicated worker from a separate file. While that seems relatively simple, once your build system becomes more complex, so does this.&lt;/p&gt;

&lt;p&gt;Where do you put your worker file in a Vite environment? Where does it go when you’re using Webpack? Does your environment use bare-bones Webpack that is configurable, or is it wrapped behind another build system? What if you also have automated tests running – are they easily able to pick up your worker file?&lt;/p&gt;

&lt;p&gt;Or, to give a more specific example: We use Webpack to transpile the SDK, and it has a separate worker-loader plugin that can be used to ensure workers are bundled into the end product. That’s pretty straightforward. However, our development environment uses the SDK’s source code directly, so it’s necessary to have another option to make sure this worker file is properly compiled into that environment as well.&lt;/p&gt;

&lt;h3&gt;
  
  
  Loading a file as data
&lt;/h3&gt;

&lt;p&gt;So, the easiest solution turns out to be to simply create the file as you normally would, in your source code, and then simply use JavaScript itself to package it into a blob. That blob is then loaded into the worker, exactly as another external JavaScript file would be (and thank you to &lt;a href="https://dev.to/martinsolumide8/how-to-use-web-worker-in-react-with-typescript-4o79"&gt;Martins Olumide on dev.to&lt;/a&gt; for this hint).&lt;/p&gt;

&lt;p&gt;It may sound convoluted, but it’s a pretty solid solution to start off with. I bet it’s good enough for production, but before shipping this, we will definitely delve into additional performance metrics to see whether using Webpack to pre-compile it into the bundle is better or not.&lt;/p&gt;

&lt;p&gt;First, the basic worker file will look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;workerFunction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="c1"&gt;// Perform every operation we want in this function&lt;/span&gt;
   &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MessageEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Message received!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// This stringifies the whole function&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;codeToString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;workerFunction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// This brings out the code in the bracket in string&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;mainCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;codeToString&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;codeToString&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;codeToString&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LastIndexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="c1"&gt;// Convert the code into a raw data&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;blob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Blob&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;mainCode&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/javascript&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// A url is made out of the blob object and we're good to go&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;worker_script&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createObjectURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;worker_script&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The comments in the block above should make pretty clear what we intend to do: We’re basically mock loading a separate worker file by simply stringifying the entire worker and returning it as a blob.&lt;/p&gt;

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

&lt;p&gt;After that, we just need to refactor our previous code a bit to make it fit into that JavaScript function and we have our worker up and running. We move the previously defined wrapper functions inside that function (because, thankfully, in JavaScript you can have inner functions, else this would be a mess) and do exactly what we did previously, only now we need to post the message instead of returning the data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;workerFunction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;openDB&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;IDBDatabase&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getDocumentStore&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;IDBObjectStore&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MessageEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Received in worker thread:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;storeDocument&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Storing document in worker thread&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nf"&gt;getDocumentStore&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;getDocuments&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Getting documents in worker thread&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nf"&gt;getDocumentStore&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAll&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onsuccess&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;IDBRequest&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="p"&gt;};&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that we’ve moved all of the IndexedDB API implementation to the worker, our main storage service file ends up looking pretty bare:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CroppedDetectionResult&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../../core/worker/ScanbotSDK.Core&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;worker_script&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./worker/sb-storage.worker&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SBStorage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kr"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Worker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Worker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;worker_script&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;storeDocument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CroppedDetectionResult&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;storeDocument&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;getDocuments&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CroppedDetectionResult&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;getDocuments&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CroppedDetectionResult&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All that’s left in this file is the code to import the blob, initialize the worker itself, post messages to the worker and resolve the promise once a message has been received.&lt;/p&gt;

&lt;p&gt;And that’s it! Some additional error handling needs to be implemented, but that’s busywork at this point. The takeaway: In just a few steps, we’ve built ourselves an optimal storage solution for complex objects – using only IndexedDB and web workers, no third-party libraries needed.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>indexeddb</category>
      <category>typescript</category>
      <category>database</category>
    </item>
    <item>
      <title>Try the Integration Challenge – run our Android SDK in 10 minutes or less</title>
      <dc:creator>Kevin</dc:creator>
      <pubDate>Wed, 17 Jul 2024 14:21:11 +0000</pubDate>
      <link>https://forem.com/scanbot-sdk/try-the-integration-challenge-run-our-android-sdk-in-10-minutes-or-less-nob</link>
      <guid>https://forem.com/scanbot-sdk/try-the-integration-challenge-run-our-android-sdk-in-10-minutes-or-less-nob</guid>
      <description>&lt;p&gt;Did you visit Droidcon 2024 in Berlin? If so, you may have noticed our Integration Challenge, for which we asked developers to integrate our Barcode Scanner SDK for Android in less than 10 minutes.&lt;/p&gt;

&lt;p&gt;40 people tried to do so. All of them managed it in less than 6 minutes, with the best time clocking in at incredible 1:02! 🤯&lt;/p&gt;

&lt;p&gt;Can you top that? Let’s find out – we’ve added the instructions to our &lt;a href="https://docs.scanbot.io/barcode-scanner-sdk/android/quick-start/" rel="noopener noreferrer"&gt;Quick Start Guide&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;⏱️ If you want to see how fast you can do it, start your clock when you scroll down.&lt;/p&gt;

&lt;p&gt;Let’s go through the steps one by one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Clone the template app (or create a project from scratch)
&lt;/h2&gt;

&lt;p&gt;We’ve prepared a template project so you won’t waste any time on building a UI. You can clone or download it from our &lt;a href="https://github.com/doo/scanbot-barcode-sdk-integration-challenge" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Open the project in Android Studio and you’re good to go.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Add the Scanbot SDK dependencies
&lt;/h2&gt;

&lt;p&gt;The Scanbot SDK for Android is distributed through our private Maven repository server (&lt;code&gt;nexus.scanbot.io&lt;/code&gt;), which needs to be specified in the &lt;code&gt;settings.gradle.kts&lt;/code&gt; file in the root folder of your project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// settings.gradle.kts in the root of the project:
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()

        // Add Scanbot SDK maven repositories here:
        maven(url = "https://nexus.scanbot.io/nexus/content/repositories/releases/")
        maven(url = "https://nexus.scanbot.io/nexus/content/repositories/snapshots/")
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Afterward, the dependencies can be added in the &lt;code&gt;dependencies&lt;/code&gt; section of your Android application project configuration, usually in the &lt;code&gt;app/build.gradle.kts&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// app/build.gradle.kts (dependencies section):
implementation("io.scanbot:scanbot-barcode-scanner-sdk:5.2.0")
implementation("io.scanbot:rtu-ui-v2-barcode:5.2.0")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To check out the latest version of the Scanbot SDK, please always refer to the SDK’s &lt;a href="https://docs.scanbot.io/barcode-scanner-sdk/android/changelog/" rel="noopener noreferrer"&gt;changelog&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For more details on the dependencies, please refer to our &lt;a href="https://docs.scanbot.io/barcode-scanner-sdk/android/detailed-setup-guide/installation/" rel="noopener noreferrer"&gt;detailed installation&lt;/a&gt; guide.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Add the camera permission​
&lt;/h2&gt;

&lt;p&gt;The Scanbot Barcode Scanner SDK needs access to the device camera so it can scan from a live camera stream. Therefore, you have to define the camera permission in the &lt;code&gt;AndroidManifest.xml&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!-- AndroidManifest.xml: --&amp;gt;
&amp;lt;manifest xmlns:android="http://schemas.android.com/apk/res/android" ...&amp;gt;

    &amp;lt;!-- Add Camera permission here: --&amp;gt;
    &amp;lt;uses-permission android:name="android.permission.CAMERA" /&amp;gt;
    &amp;lt;uses-feature android:name="android.hardware.camera" /&amp;gt;

    &amp;lt;application ...&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note how we also added the &lt;code&gt;uses-feature&lt;/code&gt; tag for better recognition of your app on the Google Play Store (&lt;a href="https://developer.android.com/guide/topics/manifest/uses-feature-element" rel="noopener noreferrer"&gt;see the Android documentation&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Our Ready-to-Use UI Components handle the &lt;a href="https://developer.android.com/training/permissions/requesting" rel="noopener noreferrer"&gt;runtime permissions&lt;/a&gt; automatically, so there is no need to add anything else in the code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Initialize the SDK
&lt;/h2&gt;

&lt;p&gt;Usually, we recommend initializing the SDK in the Application class of your app (see our &lt;a href="https://docs.scanbot.io/barcode-scanner-sdk/android/detailed-setup-guide/sdk-initialization/" rel="noopener noreferrer"&gt;detailed setup&lt;/a&gt; page). However, in this Quick Start guide, we’ll do it in an &lt;code&gt;Activity&lt;/code&gt; class.&lt;/p&gt;

&lt;p&gt;First, make sure to add the following imports to the top of the file (e.g., &lt;code&gt;MainActivity.kt&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Add the following imports in your Activity (for example, MainActivity.kt):
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import io.scanbot.sdk.barcode_scanner.ScanbotBarcodeScannerSDKInitializer
import io.scanbot.sdk.ui_v2.barcode.BarcodeScannerActivity
import io.scanbot.sdk.ui_v2.barcode.configuration.BarcodeScannerConfiguration
import io.scanbot.sdk.ui_v2.common.activity.registerForActivityResultOk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To initialize the SDK, simply use the &lt;code&gt;ScanbotBarcodeScannerSDKInitializer&lt;/code&gt; class in the &lt;code&gt;onCreate&lt;/code&gt; method of your &lt;code&gt;Activity&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Adapt your Activity's onCreate method as follows:
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    ...
    // Initialize the SDK here:
    ScanbotBarcodeScannerSDKInitializer()
        // optional: uncomment the next line if you have a license key
        // .license(this.application, LICENSE_KEY)
        .initialize(this.application)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;💡 You can use the Scanbot Barcode SDK for quick testing or evaluation purposes even without a license key. However, the SDK will only work for 60 seconds per app session and may not be used for production purposes. Want to scan longer than 60 seconds? Get your free trial license key &lt;a href="https://scanbot.io/trial/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Step 5: Start the Barcode Scanner and process the result​
&lt;/h2&gt;

&lt;p&gt;You only need a few lines of code to integrate the Scanbot Barcode Scanner UI (&lt;code&gt;BarcodeScannerActivity&lt;/code&gt;) into your application’s workflow. It’s as simple as starting a regular Android &lt;code&gt;Activity&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;First, you need to register the &lt;code&gt;ActivityResultLauncher&lt;/code&gt; in your &lt;code&gt;Activity&lt;/code&gt; class and save it as a variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Add the following variable in your Activity:
private val barcodeScreenLauncher: ActivityResultLauncher&amp;lt;BarcodeScannerConfiguration&amp;gt; =
    registerForActivityResultOk(BarcodeScannerActivity.ResultContract()) { resultEntity -&amp;gt;
        // Barcode Scanner result callback:
        // Get the first scanned barcode from the result object...
        val barcodeItem = resultEntity.result?.items?.first()
        // ... and process the result as needed, for example, display as a Toast:
        Toast.makeText(
            this,
            "Scanned: ${barcodeItem?.text} (${barcodeItem?.type})",
            Toast.LENGTH_LONG
        ).show()
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result of the Barcode Scanner UI will be delivered to the callback you just defined.&lt;/p&gt;

&lt;p&gt;Now, to launch the Barcode Scanner UI, you just call the &lt;code&gt;barcodeScreenLauncher&lt;/code&gt; where needed. For example, in the &lt;code&gt;setOnClickListener&lt;/code&gt; of your button:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Launch the barcode scanner in your Activity:
barcodeScreenLauncher.launch(BarcodeScannerConfiguration())
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🚀 That’s it! 🚀 You have successfully integrated a full-featured barcode scanner as an RTU UI Activity into your app. If you’d like, let us know what your time was!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 Customization: In this Quick Start guide, we have used the default configuration for the scanner UI. Feel free to explore the configs and customize the UI and the behavior according to your needs via the &lt;code&gt;BarcodeScannerConfiguration&lt;/code&gt; class. For more details, please refer to the &lt;a href="https://docs.scanbot.io/barcode-scanner-sdk/android/barcode-scanner/ready-to-use-ui/" rel="noopener noreferrer"&gt;Ready-to-Use UI&lt;/a&gt; page.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>android</category>
      <category>kotlin</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Integrating a barcode scanner into your iOS app – the easy way</title>
      <dc:creator>Kevin</dc:creator>
      <pubDate>Fri, 28 Jun 2024 11:25:46 +0000</pubDate>
      <link>https://forem.com/scanbot-sdk/integrating-a-barcode-scanner-sdk-into-your-ios-app-the-easy-way-18ei</link>
      <guid>https://forem.com/scanbot-sdk/integrating-a-barcode-scanner-sdk-into-your-ios-app-the-easy-way-18ei</guid>
      <description>&lt;p&gt;&lt;em&gt;Want to integrate barcode scanning functionalities into your iOS app? With our Ready-to-Use UI, this only takes you a few minutes. We’ll show you how it works!&lt;/em&gt;&lt;/p&gt;




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

&lt;p&gt;Setting up a simple app that can scan any 1D or 2D barcode is very straightforward – all we need are the following steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a project&lt;/li&gt;
&lt;li&gt;Install the SDK&lt;/li&gt;
&lt;li&gt;Set up the main screen&lt;/li&gt;
&lt;li&gt;Implement the scanning modes&lt;/li&gt;
&lt;li&gt;Build, run, and scan!&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After that, you will be scanning barcodes with lightning speed! ⚡&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Requirements
&lt;/h2&gt;

&lt;p&gt;All you need for this project is the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;macOS with Xcode 14.0 or higher&lt;/li&gt;
&lt;li&gt;A test device, since we’ll need to use the camera (minimum deployment target: iOS 13.0)&lt;/li&gt;
&lt;li&gt;Basic knowledge of how to build an iOS app&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 Our iOS Barcode Scanner SDK supports both Swift and Objective-C, but we’ll stick to Swift for this tutorial.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Preparing the project
&lt;/h2&gt;

&lt;p&gt;First, we’re going to create a new Swift project with the Storyboard interface.&lt;/p&gt;

&lt;p&gt;To create the project:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open Xcode and click on &lt;strong&gt;Create New Project…&lt;/strong&gt; (or go to &lt;strong&gt;File -&amp;gt; New -&amp;gt; Project…&lt;/strong&gt;)&lt;/li&gt;
&lt;li&gt;Make sure you have the &lt;strong&gt;iOS&lt;/strong&gt; tab selected, then choose &lt;strong&gt;App&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Give it a name, e.g., MyBarcodeScanningApp.&lt;/li&gt;
&lt;li&gt;Choose &lt;strong&gt;Storyboard&lt;/strong&gt; as the interface.&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;Swift&lt;/strong&gt; as the programming language.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Next, we’ll install the Barcode Scanner SDK package.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing the SDK
&lt;/h2&gt;

&lt;p&gt;For this tutorial, we will use the &lt;a href="https://developer.apple.com/documentation/xcode/swift-packages"&gt;Swift Package Manager&lt;/a&gt; to install the Barcode Scanner SDK.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In Xcode, go to &lt;strong&gt;File -&amp;gt; Add Package Dependencies…&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Paste the following package URL into the search bar: &lt;a href="https://github.com/doo/scanbot-barcode-scanner-sdk-ios-spm.git"&gt;https://github.com/doo/scanbot-barcode-scanner-sdk-ios-spm.git&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Select the package and click &lt;strong&gt;Add Package&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;In the pop-up that appears after verification, for “Add to Target”, choose your project.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Add Package&lt;/strong&gt; again.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jWcyrsNX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://scanbot.io/wp-content/uploads/2024/06/ios-barcode-scanner-sdk-integration-tutorial-add-package-gif-2.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jWcyrsNX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://scanbot.io/wp-content/uploads/2024/06/ios-barcode-scanner-sdk-integration-tutorial-add-package-gif-2.gif" alt="Image description" width="800" height="426"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 Without a license key, our SDK only runs for 60 seconds per session. This is more than enough for the purposes of our tutorial, but if you like, you can &lt;a href="https://scanbot.io/trial/"&gt;generate a license key here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For more information about where to put the license key in your code, please refer to &lt;a href="https://docs.scanbot.io/document-scanner-sdk/ios/sdk-initialization/"&gt;SDK Initialization&lt;/a&gt; in our docs.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We’ll need access to the device camera to scan barcodes, so let’s set the corresponding privacy property now.&lt;/p&gt;

&lt;p&gt;In the project navigator, select your project, then select your app under “TARGETS”. Open the tab &lt;strong&gt;Info&lt;/strong&gt;, add the target property &lt;strong&gt;Privacy – Camera Usage Description&lt;/strong&gt;, and type in a meaningful value, e.g., “Grant access to your camera to start scanning”.&lt;/p&gt;

&lt;h2&gt;
  
  
  Integrating the scanning functionalities
&lt;/h2&gt;

&lt;p&gt;Now that the project is set up, integrating barcode scanning into your app is very straightforward.&lt;/p&gt;

&lt;p&gt;In fact, it’s so straightforward that we’ll implement not just one, but &lt;strong&gt;three different scanning modes&lt;/strong&gt; in our project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Single-barcode scanning:&lt;/strong&gt; Read and show the value of a single barcode.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-barcode scanning:&lt;/strong&gt; Read as many barcodes as you like and display their values in a list.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AR overlay:&lt;/strong&gt; Display the values of the barcodes in the viewfinder on the screen in real time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here’s how we’re going to achieve that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Step 1:&lt;/strong&gt; Set up the main screen (create three buttons and connect them to our View Controller)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Step 2:&lt;/strong&gt; Implement the scanning modes (copy and paste the code for the three scanning modes into the @IBAction functions)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Step 2.5 (optional):&lt;/strong&gt; Customize the scanning UI&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Step 3:&lt;/strong&gt; Start scanning 🤳&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s dive right in!&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Setting up the main screen
&lt;/h2&gt;

&lt;p&gt;How exactly you approach this is up to you, but for the sake of this tutorial, we will just add three &lt;code&gt;UIButton&lt;/code&gt; elements (“Single-Barcode Scanning”, “Multi-Barcode Scanning”, and “AR Overlay”) onto the View Controller Scene in &lt;strong&gt;Main.storyboard&lt;/strong&gt;. Then, we connect them as an &lt;strong&gt;Action&lt;/strong&gt; to &lt;strong&gt;ViewController.swift&lt;/strong&gt; by dragging them while holding the &lt;strong&gt;control&lt;/strong&gt; key.&lt;/p&gt;

&lt;p&gt;For our purposes, let’s call the resulting &lt;code&gt;@IBAction&lt;/code&gt; functions “startSingleScanning”, “startMultiScanning”, and “startAROverlay”.&lt;/p&gt;

&lt;p&gt;We also need to import the ScanbotBarcodeScannerSDK package like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import ScanbotBarcodeScannerSDK

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

&lt;/div&gt;



&lt;p&gt;In the next step, we’ll add the code to get our scanning interface up and running into those functions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Implementing the scanning modes
&lt;/h2&gt;

&lt;p&gt;Thanks to our &lt;a href="https://docs.scanbot.io/barcode-scanner-sdk/ios/barcode-scanner/ready-to-use-ui/"&gt;RTU UI components&lt;/a&gt;, integrating our Barcode Scanner SDK’s features into your app is a breeze!&lt;/p&gt;

&lt;p&gt;In fact, you can just copy and paste the code from our &lt;a href="https://docs.scanbot.io/barcode-scanner-sdk/ios/barcode-scanner/ready-to-use-ui/#use-cases"&gt;documentation&lt;/a&gt; 🦥&lt;/p&gt;

&lt;p&gt;Here’s the code to put in each function for your convenience:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For single-barcode scanning:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
    @IBAction func startSingleScanning(_ sender: Any) {
        // Create the default configuration object.
        // Create the default configuration object.
        let configuration = SBSDKUI2BarcodeScannerConfiguration()

        // Initialize the single-scan use case.
        let singleUsecase = SBSDKUI2SingleScanningMode()

        // Enable and configure the confirmation sheet.
        singleUsecase.confirmationSheetEnabled = true
        singleUsecase.sheetColor = SBSDKUI2Color(colorString: "#FFFFFF")

        // Hide/unhide the barcode image of the confirmation sheet.
        singleUsecase.barcodeImageVisible = true

        // Configure the barcode title of the confirmation sheet.
        singleUsecase.barcodeTitle.visible = true
        singleUsecase.barcodeTitle.color = SBSDKUI2Color(colorString: "#000000")

        // Configure the barcode subtitle of the confirmation sheet.
        singleUsecase.barcodeSubtitle.visible = true
        singleUsecase.barcodeSubtitle.color = SBSDKUI2Color(colorString: "#000000")

        // Configure the cancel button of the confirmation sheet.
        singleUsecase.cancelButton.text = "Close"
        singleUsecase.cancelButton.foreground.color = SBSDKUI2Color(colorString: "#C8193C")
        singleUsecase.cancelButton.background.fillColor = SBSDKUI2Color(colorString: "#00000000")

        // Configure the submit button of the confirmation sheet.
        singleUsecase.submitButton.text = "Submit"
        singleUsecase.submitButton.foreground.color = SBSDKUI2Color(colorString: "#FFFFFF")
        singleUsecase.submitButton.background.fillColor = SBSDKUI2Color(colorString: "#C8193C")

        // Set the configured use case.
        configuration.useCase = singleUsecase

        // Create and set an array of accepted barcode formats.
        configuration.recognizerConfiguration.barcodeFormats = SBSDKUI2BarcodeFormat.twoDFormats

        // Present the recognizer view controller modally on this view controller.
        SBSDKUI2BarcodeScannerViewController.present(on: self,
                                                     configuration: configuration) { controller, cancelled, error, result in

            // Completion handler to process the result.
            // The `cancelled` parameter indicates whether the cancel button was tapped.

            controller.presentingViewController?.dismiss(animated: true)
        }
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;For multi-barcode scanning:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
    @IBAction func startMultiScanning(_ sender: Any) {
        // Create the default configuration object.
        let configuration = SBSDKUI2BarcodeScannerConfiguration()

        // Initialize the multi-scan use case.
        let multiUsecase = SBSDKUI2MultipleScanningMode()

        // Set the counting repeat delay.
        multiUsecase.countingRepeatDelay = 1000

        // Set the counting mode.
        multiUsecase.mode = .counting

        // Set the sheet mode of the barcodes preview.
        multiUsecase.sheet.mode = .collapsedSheet

        // Set the height of the collapsed sheet.
        multiUsecase.sheet.collapsedVisibleHeight = .large

        // Enable manual count change.
        multiUsecase.sheetContent.manualCountChangeEnabled = true

        // Configure the submit button.
        multiUsecase.sheetContent.submitButton.text = "Submit"
        multiUsecase.sheetContent.submitButton.foreground.color = SBSDKUI2Color(colorString: "#000000")

        // Set the configured use case.
        configuration.useCase = multiUsecase

        // Create and set an array of accepted barcode formats.
        configuration.recognizerConfiguration.barcodeFormats = SBSDKUI2BarcodeFormat.twoDFormats

        // Present the recognizer view controller modally on this view controller.
        SBSDKUI2BarcodeScannerViewController.present(on: self,
                                                     configuration: configuration) { controller, cancelled, error, result in

            // Completion handler to process the result.
            // The `cancelled` parameter indicates whether the cancel button was tapped.

            controller.presentingViewController?.dismiss(animated: true)
        }
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;For the AR overlay:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
    @IBAction func startAROverlay(_ sender: Any) {
        // Create the default configuration ject.
        let configuration = SBSDKUI2BarcodeScannerConfiguration()

        // Configure the usecase.
        let usecase = SBSDKUI2MultipleScanningMode()
        usecase.mode = .unique
        usecase.sheet.mode = .collapsedSheet
        usecase.sheet.collapsedVisibleHeight = .small

        // Configure AR Overlay.
        usecase.arOverlay.visible = true
        usecase.arOverlay.automaticSelectionEnabled = false

        // Set the configured usecase.
        configuration.useCase = usecase

        // Create and set an array of accepted barcode formats.
        configuration.recognizerConfiguration.barcodeFormats = SBSDKUI2BarcodeFormat.twoDFormats

        // Present the recognizer view controller modally on this view controller.
        SBSDKUI2BarcodeScannerViewController.present(on: self,
                                                     configuration: configuration) { controller, cancelled, error, result in

            // Completion handler to process the result.
            // The `cancelled` parameter indicates whether the cancel button was tapped.

            controller.presentingViewController?.dismiss(animated: true)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There’s one tiny thing we’ll change here right away, though: We want to be able to scan both 1D and 2D barcode formats. &lt;/p&gt;

&lt;p&gt;For this, let’s go to &lt;code&gt;configuration.recognizerConfiguration.barcodeFormats&lt;/code&gt; in each &lt;code&gt;@IBAction&lt;/code&gt; function and change &lt;code&gt;SBSDKUI2BarcodeFormat.twoDFormats&lt;/code&gt; to &lt;code&gt;SBSDKUI2BarcodeFormat.commonFormats&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Let’s now explore some of the other customization options.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2.5 (optional): Customizing the scanning UI
&lt;/h2&gt;

&lt;p&gt;Our RTU UI not only makes it very easy to integrate our SDK’s scanning functionalities, but also to style the UI elements in any way you like 🎨&lt;/p&gt;

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

&lt;p&gt;(Well, maybe not in ANY way. Sorry, Bob.)&lt;/p&gt;

&lt;p&gt;The comments already give you a hint about what’s possible to change:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hide or show specific UI elements&lt;/li&gt;
&lt;li&gt;Configure the active and inactive states of some UI elements&lt;/li&gt;
&lt;li&gt;Change colors&lt;/li&gt;
&lt;li&gt;Change titles and subtitles&lt;/li&gt;
&lt;li&gt;Change the accepted barcode formats&lt;/li&gt;
&lt;li&gt;Control the barcode sheet behavior for multi-scanning&lt;/li&gt;
&lt;li&gt;… and more&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As an example, let’s take a closer look at some of the configuration options of our multi-barcode scanning feature:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Set the counting mode.
multiUsecase.mode = .counting
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we can change &lt;code&gt;.counting&lt;/code&gt; to &lt;code&gt;.unique&lt;/code&gt;. This will prevent the SDK from scanning multiple barcodes with the same value, which is helpful if you only want to count the number of &lt;em&gt;unique&lt;/em&gt; barcodes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Set the sheet mode of the barcodes preview.
multiUsecase.sheet.mode = .collapsedSheet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Change &lt;code&gt;.collapsedSheet&lt;/code&gt; to &lt;code&gt;.button&lt;/code&gt; to not show the upper part of the barcode results sheet at the bottom of the screen and display a button that calls up the sheet instead.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 You can change the entire RTU UI’s color scheme using the &lt;a href="https://docs.scanbot.io/barcode-scanner-sdk/ios/barcode-scanner/ready-to-use-ui/#palette"&gt;palette feature&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once you’ve made your changes, build and run your app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Scanning some barcodes!
&lt;/h2&gt;

&lt;p&gt;Now you can go ahead and scan 1D and 2D barcodes – one after the other, many at the same time, and even with an AR overlay that lets you preview their values!&lt;/p&gt;

&lt;p&gt;If you’re in need of some barcodes, we’ve got you covered:&lt;/p&gt;

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

&lt;p&gt;If this tutorial has piqued your interest in integrating barcode scanning functionalities into your iOS app, make sure to take a look at our SDK’s other features in our &lt;a href="https://docs.scanbot.io/barcode-scanner-sdk/ios/barcode-scanner/ready-to-use-ui/"&gt;documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

</description>
      <category>ios</category>
      <category>swift</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Visual Studio alternatives for Mac – finding a new home for MAUI development</title>
      <dc:creator>Mayank Patil</dc:creator>
      <pubDate>Tue, 18 Jun 2024 12:05:26 +0000</pubDate>
      <link>https://forem.com/scanbot-sdk/visual-studio-alternatives-for-mac-finding-a-new-home-for-maui-development-l43</link>
      <guid>https://forem.com/scanbot-sdk/visual-studio-alternatives-for-mac-finding-a-new-home-for-maui-development-l43</guid>
      <description>&lt;p&gt;Support for Visual Studio for Mac will end on August 31, 2024. So, which tools should MAUI developers use when Visual Studio for Mac is no more? We’ve compared JetBrains Rider, the .NET MAUI extension for VS Code, and the CLI approach!&lt;/p&gt;




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

&lt;p&gt;Man, choosing a tool for developing cross-platform Xamarin apps on a Mac used to be so easy. Microsoft had our backs with Xamarin Studio, and later with Visual Studio for Mac.&lt;/p&gt;

&lt;p&gt;Recently though, Microsoft decided to add a little twist!&lt;/p&gt;

&lt;p&gt;Yes, the Xamarin MVP Monkey is giving us all a dramatic goodbye… 😢&lt;/p&gt;

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

&lt;p&gt;And Visual Studio for Mac is going with it. 🎹🎹 Yep, you heard that right: Visual Studio for Mac 17.6 will ride off into the sunset on August 31, 2024. &lt;/p&gt;

&lt;p&gt;Microsoft’s decision to retire the IDE isn’t unsurprising: The company is focusing on developing the .NET MAUI framework, the successor of Xamarin.Forms.&lt;/p&gt;

&lt;p&gt;But fear not, dear developers, because Microsoft has a plan. 😉&lt;/p&gt;

&lt;p&gt;They’re doubling down on Visual Studio Code as the go-to coding tool for Mac users. It’s like saying goodbye to your trusty old car and hopping into a shiny new rocket ship! &lt;/p&gt;

&lt;p&gt;But there are other options that might just become your preferred development environment. So in this article, we’ll explore these options. &lt;/p&gt;

&lt;p&gt;So strap in folks, because the MAUIverse is calling. 🚀&lt;/p&gt;

&lt;h2&gt;
  
  
  MAUI on Mac: your options
&lt;/h2&gt;

&lt;p&gt;Alright, it’s time to check out the tools!&lt;/p&gt;

&lt;p&gt;We’ll be covering three viable options for developing our .NET MAUI applications:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The &lt;strong&gt;.NET MAUI extension in Visual Studio Code&lt;/strong&gt; for Mac&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JetBrains Rider&lt;/strong&gt; for Mac&lt;/li&gt;
&lt;li&gt;Using the &lt;strong&gt;command line tools on Mac&lt;/strong&gt; (with an editor of your choice)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In this post, we’ll look at how to set up all options, their usage, and their pros and cons. After all, with great tools comes great responsibility (and maybe a few bugs too!).&lt;/p&gt;

&lt;p&gt;In particular, we’ll go through the following for each option:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Installation&lt;/li&gt;
&lt;li&gt;Creating a new project&lt;/li&gt;
&lt;li&gt;Running and debugging&lt;/li&gt;
&lt;li&gt;Using the NuGet package manager&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s start with the .NET MAUI extension in Visual Studio Code, which is the option recommended by Microsoft.&lt;/p&gt;

&lt;h2&gt;
  
  
  .NET MAUI extension in Visual Studio Code
&lt;/h2&gt;

&lt;p&gt;The .NET &lt;strong&gt;M&lt;/strong&gt;ulti-Platform &lt;strong&gt;A&lt;/strong&gt;pp &lt;strong&gt;UI&lt;/strong&gt; (MAUI) extension is the gateway to the MAUI realm for Visual Studio Code users on Mac. It comes with all the necessary tools for developing cross-platform apps.&lt;/p&gt;

&lt;p&gt;With the ability to develop and debug your creations on various devices, emulators, and simulators directly within VS Code, Microsoft aims to replicate the seamless experience of Visual Studio for Mac.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.dotnet-maui" rel="noopener noreferrer"&gt;.NET MAUI extension&lt;/a&gt; is built on top of the powerful &lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csharp" rel="noopener noreferrer"&gt;C#&lt;/a&gt;, &lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csdevkit" rel="noopener noreferrer"&gt;C# Dev Kit&lt;/a&gt; and &lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.vscode-dotnet-runtime" rel="noopener noreferrer"&gt;.NET Install Tool&lt;/a&gt; extensions, which gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;IntelliSense&lt;/li&gt;
&lt;li&gt;An intuitive solution explorer&lt;/li&gt;
&lt;li&gt;Package management and project references&lt;/li&gt;
&lt;li&gt;Debugging&lt;/li&gt;
&lt;li&gt;Unit tests&lt;/li&gt;
&lt;li&gt;…and more!&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 Installing the &lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.dotnet-maui" rel="noopener noreferrer"&gt;VS Code .NET MAUI&lt;/a&gt; extension also automatically installs the &lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csharp" rel="noopener noreferrer"&gt;C#&lt;/a&gt;, &lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csdevkit" rel="noopener noreferrer"&gt;C# Dev Kit&lt;/a&gt; and &lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.vscode-dotnet-runtime" rel="noopener noreferrer"&gt;.NET Install Tool&lt;/a&gt; extensions as dependencies.&lt;/p&gt;

&lt;p&gt;⚠️ Keep in mind that the .NET MAUI extension is still in preview. It therefore has some limitations and kinks to work out.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  C# Dev Kit and C# extensions
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csdevkit" rel="noopener noreferrer"&gt;C# Dev Kit&lt;/a&gt; is an extension designed to enhance your coding experience. Among other things, it gives you a solution explorer to navigate your code in the IDE as well as integrated unit test discovery and execution to ensure your code is test-driven.&lt;/p&gt;

&lt;p&gt;This extension builds on the powerful C# language capabilities provided by the &lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csharp" rel="noopener noreferrer"&gt;C# extension&lt;/a&gt;. C# Dev Kit’s tools and utilities integrate natively with VS Code and help developers to write and debug code faster, to avoid errors, and to simplify maintenance.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 C# Dev Kit is free for individuals, academia, and open-source development. For organizations, it’s included with Visual Studio Professional and Enterprise subscriptions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;

&lt;p&gt;Visual Studio Code is a lightweight but powerful source code editor. It comes with built-in support for JavaScript, TypeScript and Node.js and has a rich ecosystem of extensions for other languages and runtimes (such as C++, C#, Java, Python, PHP, Go, .NET).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://code.visualstudio.com/Download" rel="noopener noreferrer"&gt;Download VS Code&lt;/a&gt; and install it.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dotnet.microsoft.com/en-us/download" rel="noopener noreferrer"&gt;Download .NET&lt;/a&gt; and install it (make sure you download the right file for Apple Silicon machines).&lt;/li&gt;
&lt;li&gt;In Visual Studio Code, install the &lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.dotnet-maui" rel="noopener noreferrer"&gt;.NET MAUI extension&lt;/a&gt;. This will also install C#, C# Dev Kit and .NET Install Tool as dependencies.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdt04nyadgumppigsqj6c.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdt04nyadgumppigsqj6c.gif" alt="Installing the .NET MAUI extension in Visual Studio Code"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Installing the .NET MAUI extension in Visual Studio Code&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For iOS/macOS development:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We need to install &lt;a href="https://developer.apple.com/xcode/" rel="noopener noreferrer"&gt;Xcode&lt;/a&gt; for its SDK and simulators. Downloading Xcode requires an Apple ID.&lt;/li&gt;
&lt;li&gt;Download and install Xcode.&lt;/li&gt;
&lt;li&gt;Install the Xcode command line tools with the following command:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;xcode-select --install&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Launch Xcode. On the first open it will ask you to set up the iOS Simulator, so let’s do that.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your iOS setup is now ready to run on simulators. However, to run the app on your device, you will need a valid certificate and provisioning profile (you can find more information &lt;a href="https://developer.apple.com/help/account/manage-profiles/create-a-development-provisioning-profile/" rel="noopener noreferrer"&gt;here&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For Android development:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Install &lt;a href="https://learn.microsoft.com/en-us/java/openjdk/download#openjdk-17" rel="noopener noreferrer"&gt;Microsoft OpenJDK 17&lt;/a&gt;. Older JDK versions can cause issues and are therefore not recommended.&lt;/li&gt;
&lt;li&gt;During the installation, JDK will prompt you to install Rosetta (if not already installed).&lt;/li&gt;
&lt;li&gt;After installing JDK, create a new project and install the Android SDK using the following command:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo dotnet build -t:InstallAndroidDependencies
-f:net8.0-android
-p:AndroidSdkDirectory=/Users/MyLocalFolderName/Library/Android/sdk
-p:JavaSdkDirectory=/Library/Java/sdk
-p:AcceptAndroidSDKLicenses=True
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this command, &lt;code&gt;AndroidSdkDirectory&lt;/code&gt; specifies where to install the Android SDK (e.g., &lt;code&gt;/Users/MyLocalFolderName/Library/Android/sdk&lt;/code&gt;) and &lt;code&gt;JavaSdkDirectory&lt;/code&gt; where to install the Java SDK (e.g., &lt;code&gt;/Users/MyLocalFolderName/Library/Java/sdk&lt;/code&gt;).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 Another approach is to use &lt;a href="https://developer.android.com/studio" rel="noopener noreferrer"&gt;Android Studio&lt;/a&gt; to manage the SDK, APIs, tools, emulators, debug options, logs, etc. This requires some &lt;a href="https://developer.android.com/tools/variables" rel="noopener noreferrer"&gt;environment variable setup&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;Restart VS Code and check out the &lt;code&gt;Output&lt;/code&gt; tab, which gives you information about the Android and iOS/macOS SDK status.&lt;/li&gt;
&lt;li&gt;Now you have to accept the license for your Android SDK.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In VS Code, press &lt;strong&gt;Cmd + Shift + P&lt;/strong&gt;, select “&lt;code&gt;.NET MAUI: Configure Android&lt;/code&gt;”, and you should get an option to accept the license. Alternatively, restart your project, and you’ll be prompted to accept the license via the command line.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set the JDK and Android SDK paths as well. In VS Code, press &lt;strong&gt;Cmd + Shift + P&lt;/strong&gt; and select the option “&lt;code&gt;.NET MAUI: Configure Android&lt;/code&gt;”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now you should be able to run it on your Android device.&lt;/p&gt;

&lt;p&gt;Finally, we need to install the .NET MAUI workloads with the following command:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;dotnet workload install maui&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating a new project
&lt;/h3&gt;

&lt;p&gt;We will create our first Visual Studio Code demo application using the .NET MAUI extension as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click the &lt;code&gt;Create .NET Project&lt;/code&gt; button or press &lt;strong&gt;Cmd + Shift + P&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Select .NET: &lt;code&gt;New Project&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Select the project template &lt;code&gt;.NET MAUI App&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Select a project directory&lt;/li&gt;
&lt;li&gt;Enter the project name and submit&lt;/li&gt;
&lt;li&gt;Confirm &lt;code&gt;Create Project&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmsur4yfunqrnn2039htd.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmsur4yfunqrnn2039htd.gif" alt="Creating a new project in VS Code using the .NET MAUI extension"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Creating a new project in VS Code using the .NET MAUI extension&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If your project gets a build time error regarding the .NET MAUI tools, use the following command to install the necessary workloads:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;dotnet workload restore&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Running and debugging
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;On the bottom toolbar in Visual Studio Code, you will see the toolbar item  “{ }” . With this, you can check the current target configuration.&lt;/li&gt;
&lt;li&gt;Through the  “{ }”  button, you can also update the startup project and the target device options.&lt;/li&gt;
&lt;li&gt;After selecting the configurations, you can simply press &lt;strong&gt;F5&lt;/strong&gt; to run the project. You can also open the &lt;code&gt;RUN AND DEBUG&lt;/code&gt; tab from the side panel of VS Code and click on the &lt;code&gt;Run and Debug&lt;/code&gt; button.&lt;/li&gt;
&lt;li&gt;Debugging is similar to what we had in Visual Studio for Mac, like setting the breakpoints in the code.&lt;/li&gt;
&lt;li&gt;Changing the configuration from Debug to Release mode isn’t supported yet. However, you can do that by adding a custom configuration in a &lt;code&gt;launch.json&lt;/code&gt; file. After adding these configurations, you can use them from the &lt;code&gt;RUN AND DEBUG&lt;/code&gt; tab.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;launch.json&lt;/code&gt; file is created locally in the &lt;code&gt;.vscode&lt;/code&gt; folder. Here is what the configurations look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"configurations": [
    {
        "name": "Developer",
        "type": "maui",
        "request": "launch",
        "configuration": "Debug",
        "preLaunchTask": "maui: Build"
    },
    {
        "name": "Production",
        "type": "maui",
        "request": "launch",
        "configuration": "Release",
        "preLaunchTask": "maui: Build"
    }
  ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstagingscanbot.wpengine.com%2Fwp-content%2Fuploads%2F2024%2F06%2Fvisual-studio-alternatives-for-mac-gif-3.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstagingscanbot.wpengine.com%2Fwp-content%2Fuploads%2F2024%2F06%2Fvisual-studio-alternatives-for-mac-gif-3.gif" alt="Running and debugging in VS Code"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Running and debugging in VS Code&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Package management &amp;amp; project reference
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;NuGet package manager:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Visual Studio Code doesn’t really have a full-fledged NuGet package manager GUI like Visual Studio for Mac does. But there are some alternative options.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Restoring the packages:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You can set Automatic NuGet restore to ON. VS Code will now restore the packages automatically if the project is missing them.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Visual Studio Code -&amp;gt; Settings -&amp;gt; Extensions -&amp;gt; C# -&amp;gt; LSP Server -&amp;gt; Dotnet -&amp;gt; Enable Automatic Restore&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Alternatively, go to &lt;code&gt;Visual Studio Code -&amp;gt; Settings&lt;/code&gt; and search for the following text: ”&lt;code&gt;dotnet.projects.enableAutomaticRestore&lt;/code&gt;”&lt;/p&gt;

&lt;p&gt;This is the option we’re looking for:&lt;/p&gt;

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

&lt;p&gt;&lt;em&gt;“Enable Automatic Restore”&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The other option is to use the command line to restore the packages. We’ll look at this in the next section.&lt;/li&gt;
&lt;li&gt;To change or update the NuGet source, you have to modify the &lt;code&gt;Nuget.Config&lt;/code&gt; file directly, which you can find at &lt;code&gt;/Users/&amp;lt;username&amp;gt;/.config/NuGet.Config&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Adding NuGet packages and project references:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Adding, removing, and updating packages is easy. It’s not like the NuGet package manager window in Visual Studio for Mac, but it does the job.&lt;/p&gt;

&lt;p&gt;To add a NuGet package or a project reference, go through the Solution Explorer context menu like so:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;VS Code File Explorer -&amp;gt; Solution Explorer -&amp;gt; Expand Solution -&amp;gt; Right Click on the project&lt;/code&gt;&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;To remove/update a NuGet package, you can expand the project dependencies and navigate to the exact package.&lt;/li&gt;
&lt;li&gt;To remove a project reference, you have to directly edit the .csproj file, e.g.:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;ItemGroup&amp;gt;
    &amp;lt;ProjectReference Include="..\..\FirstClassLibrary.csproj" /&amp;gt;
&amp;lt;/ItemGroup&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  JetBrains Rider
&lt;/h2&gt;

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

&lt;p&gt;&lt;em&gt;I believe this GIF adequately portrays Rider’s power. But will you be able to handle it?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;JetBrains Rider is a full-fledged cross-platform .NET IDE providing a complete environment for developing .NET MAUI apps.&lt;/p&gt;

&lt;p&gt;Rider works with a wide variety of .NET Framework, Mono, and .NET Core project types. It supports most languages used in .NET development, including C#, VB.NET, F#, ASP.NET syntax, XAML, XML, JavaScript, TypeScript, JSON, HTML, CSS, and SQL. Its large set of powerful features that also work across different languages lets you deliver quality code faster than ever.&lt;/p&gt;

&lt;p&gt;All this makes it the best choice for replacing Visual Studio for Mac.&lt;/p&gt;

&lt;p&gt;One important thing to note, however, is that JetBrains Rider &lt;a href="https://www.jetbrains.com/rider/buy" rel="noopener noreferrer"&gt;requires a paid subscription&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Highlights:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More stable and reliable compared to Visual Studio for Mac.&lt;/li&gt;
&lt;li&gt;Eliminates a lot of the annoyances of developing .NET MAUI apps in Visual Studio for Mac.&lt;/li&gt;
&lt;li&gt;A full-fledged IDE, unlike the .NET MAUI extension for VS Code.&lt;/li&gt;
&lt;li&gt;The decompiler allows you to jump directly into the third-party package/code in your project, even from a particular XAML class implementation (e.g., Label).&lt;/li&gt;
&lt;li&gt;Powerful IntelliSense code completion, refactoring, debugging, version control and more.&lt;/li&gt;
&lt;li&gt;Convenient shortcuts that increase productivity, a bunch of different IDE settings, and more.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Download JetBrains Rider &lt;a href="https://www.jetbrains.com/rider/download/#section=mac" rel="noopener noreferrer"&gt;here&lt;/a&gt; and follow the general macOS application installation process.&lt;/li&gt;
&lt;li&gt;For iOS/macOS development, we must install &lt;a href="https://developer.apple.com/xcode/" rel="noopener noreferrer"&gt;Xcode&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;For Android, we need to install &lt;a href="https://learn.microsoft.com/en-us/java/openjdk/download#openjdk-17" rel="noopener noreferrer"&gt;Microsoft OpenJDK 17&lt;/a&gt; and &lt;a href="https://developer.android.com/studio" rel="noopener noreferrer"&gt;Android Studio&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Initial JetBrains Rider settings
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;For Android development:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;After installing Android Studio to get the Android SDK, launch JetBrains Rider.&lt;/li&gt;
&lt;li&gt;Install the &lt;code&gt;Rider Android Support&lt;/code&gt; plugin to get started with Android:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;JetBrains Rider -&amp;gt; Settings -&amp;gt; Plugins -&amp;gt; Install "Rider Android Support"&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;After installing this plugin, a new tab will show up in the JetBrains Rider settings:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;JetBrains Rider -&amp;gt; Settings -&amp;gt; Build, Execution, Deployment -&amp;gt; Android&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Here, set the paths for the &lt;code&gt;Android SDK&lt;/code&gt; and the &lt;code&gt;Microsoft Open JDK 17&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Make sure the developer options are enabled on your Android device. This completes the setup for Android development, so your app is ready to roll on your device.&lt;/p&gt;

&lt;p&gt;You also have the option to create and launch an emulator from Android Studio.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For iOS development:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Install Xcode, then launch it to download/install the iOS simulators.&lt;/li&gt;
&lt;li&gt;Now launch JetBrains Rider and set the Xcode path:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;JetBrains Rider -&amp;gt; Settings -&amp;gt; Build, Execution, Deployment -&amp;gt; Apple Platforms&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;After doing this, your iOS setup is complete, and your iOS app is also ready to roll on the simulator. To run the app on your iOS device, however, you will need a valid certificate and provisioning profile (you can find more information &lt;a href="https://developer.apple.com/help/account/manage-profiles/create-a-development-provisioning-profile/" rel="noopener noreferrer"&gt;here&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Alright! Then let’s dive into creating a new application with Rider.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating a new project
&lt;/h3&gt;

&lt;p&gt;Creating a new project is similar to Visual Studio for Mac: Just create a solution and select a project template.&lt;/p&gt;

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

&lt;p&gt;&lt;em&gt;Creating a new project in JetBrains Rider&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Running and debugging
&lt;/h3&gt;

&lt;p&gt;In Rider, there are separate sections for selecting a platform configuration, be it Android or iOS. The device and simulator/emulator lists are populated depending on the platform.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjhl94o0aptk7vigqssjk.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjhl94o0aptk7vigqssjk.gif" alt="Device and simulator/emulator lists in Rider"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Device and simulator/emulator lists in Rider&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For debugging, select the debug button in the bottom left of the IDE window.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  NuGet package manager
&lt;/h3&gt;

&lt;p&gt;Rider integrates a simple interface for the NuGet package manager. It provides a good interface for search filters, adding/selecting custom sources, and logs, installation of packages for multiple projects at once, and more.&lt;/p&gt;

&lt;p&gt;You can also right-click on the project and select Manage NuGet package, which will open this tab at the bottom.&lt;/p&gt;

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

&lt;p&gt;&lt;em&gt;Managing NuGet packages in Rider&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The buttons on the left allow you to restore/download, update, refresh packages and much more.&lt;/p&gt;

&lt;h2&gt;
  
  
  .NET CLI on Mac
&lt;/h2&gt;

&lt;p&gt;For the elite developers who prefer flexing their CLI muscle over relying on fancy IDE magic and crave results based solely on their precise inputs rather than any third-party influence, we raise a digital salute. 🫡&lt;/p&gt;

&lt;p&gt;I call those people Terminal Ninjas. With unwavering confidence in their commands, they bend the CLI to their will. If you’re among them: Kudos! Let’s get started.&lt;/p&gt;

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

&lt;p&gt;&lt;em&gt;Choosing the terminal&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Just like the other options, this one also has its pros and cons. It might take a bit of R&amp;amp;D until you find all the necessary commands needed for your development process.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Download the .NET SDK &lt;a href="https://dotnet.microsoft.com/en-us/download" rel="noopener noreferrer"&gt;here&lt;/a&gt; and install it.&lt;/li&gt;
&lt;li&gt;Install the .NET MAUI workload by using this command in the terminal (with or without &lt;code&gt;sudo&lt;/code&gt;, depending on your privileges):&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;dotnet workload install maui&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;To verify the workload installation, use the following command:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;dotnet workload list&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;For developing iOS/macOS and Android applications, we need to do the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For iOS/macOS development, install the &lt;a href="https://developer.apple.com/xcode/" rel="noopener noreferrer"&gt;Xcode&lt;/a&gt; tool for SDK and simulators.&lt;/li&gt;
&lt;li&gt;For Android development, install the &lt;a href="https://learn.microsoft.com/en-us/java/openjdk/download#openjdk-17" rel="noopener noreferrer"&gt;Microsoft OpenJDK&lt;/a&gt; and &lt;a href="https://developer.android.com/studio" rel="noopener noreferrer"&gt;Android Studio&lt;/a&gt; for the Android SDK and emulators.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the individual &lt;strong&gt;iOS&lt;/strong&gt; and &lt;strong&gt;Android&lt;/strong&gt; setup steps, please refer to the .NET MAUI extension section above.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating a new project
&lt;/h3&gt;

&lt;p&gt;Let’s jump into all the core commands you’ll need: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For creating a new project, the basic command is:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;dotnet new maui&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Here, &lt;code&gt;maui&lt;/code&gt; is the template for MAUI projects. Typically, you’ll do this after some basic preparation, like creating a directory for the project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir FirstCLIApp
cd FirstCLIApp
dotnet new maui
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This series of commands creates a new project based on the template &lt;code&gt;maui&lt;/code&gt; in the directory name &lt;code&gt;FirstCLIApp&lt;/code&gt;. So by default your solution will be created as &lt;code&gt;FirstCLIApp.sln&lt;/code&gt;, which will have a MAUI project named &lt;code&gt;FirstCLIApp.csproj&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For setting a project name, use the following command:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;dotnet new maui -n "FirstCLIApp"&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;To check out all available project templates for a project, you can use this command:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;dotnet new list&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;For more parameters you can use while creating a project, see below. You can find further details in the &lt;a href="https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-new" rel="noopener noreferrer"&gt;Microsoft docs&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet new &amp;lt;TEMPLATE&amp;gt;
    [--dry-run]
    [--force]
    [-lang|--language {"C#"|"F#"|VB}]
    [-n|--name &amp;lt;OUTPUT_NAME&amp;gt;]
    [-f|--framework &amp;lt;FRAMEWORK&amp;gt;]
    [--no-update-check]
    [-o|--output &amp;lt;OUTPUT_DIRECTORY&amp;gt;]
    [--project &amp;lt;PROJECT_PATH&amp;gt;]
    [-d|--diagnostics]
    [--verbosity &amp;lt;LEVEL&amp;gt;]
    [Template options]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Running and debugging
&lt;/h3&gt;

&lt;p&gt;In this section, we will look at the commands related to restoring, building and running the project. For the full command reference, check out the &lt;a href="https://learn.microsoft.com/en-us/dotnet/core/tools/" rel="noopener noreferrer"&gt;.NET CLI docs&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Restore packages:&lt;/strong&gt; Restores the dependencies and tools of a project.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;dotnet restore&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Build:&lt;/strong&gt; The following command will restore the project dependencies and build the app.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet build
    [&amp;lt;PROJECT&amp;gt;|&amp;lt;SOLUTION&amp;gt;]
    [-a|--arch &amp;lt;ARCHITECTURE&amp;gt;]
    [--artifacts-path &amp;lt;ARTIFACTS_DIR&amp;gt;]
    [-c|--configuration &amp;lt;CONFIGURATION&amp;gt;] 
    [-f|--framework &amp;lt;FRAMEWORK&amp;gt;]
    [--disable-build-servers]
    [--force] [--interactive] 
    [--no-dependencies] 
    [--no-incremental]
    [--no-restore] 
    [--nologo] 
    [--no-self-contained] 
    [--os &amp;lt;OS&amp;gt;]
    [-o|--output &amp;lt;OUTPUT_DIRECTORY&amp;gt;]
    [-p|--property:&amp;lt;PROPERTYNAME&amp;gt;=&amp;lt;VALUE&amp;gt;]
    [-r|--runtime &amp;lt;RUNTIME_IDENTIFIER&amp;gt;]
    [--self-contained [true|false]] 
    [--source &amp;lt;SOURCE&amp;gt;]
    [--tl:[auto|on|off]] 
    [--use-current-runtime, --ucr [true|false]]
    [-v|--verbosity &amp;lt;LEVEL&amp;gt;] 
    [--version-suffix &amp;lt;VERSION_SUFFIX&amp;gt;]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Run:&lt;/strong&gt; The following command will restore the project dependencies and build the app.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet run 
    [-a|--arch &amp;lt;ARCHITECTURE&amp;gt;] 
    [-c|--configuration &amp;lt;CONFIGURATION&amp;gt;]
    [-f|--framework &amp;lt;FRAMEWORK&amp;gt;] 
    [--force] [--interactive]
    [--launch-profile &amp;lt;NAME&amp;gt;] 
    [--no-build]
    [--no-dependencies] 
    [--no-launch-profile] 
    [--no-restore]
    [--os &amp;lt;OS&amp;gt;]
    [--project &amp;lt;PATH&amp;gt;]
    [-r|--runtime &amp;lt;RUNTIME_IDENTIFIER&amp;gt;]
    [--tl:[auto|on|off]]
    [-v|--verbosity &amp;lt;LEVEL&amp;gt;]
    [[--] [application arguments]]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can run the above commands without any parameters, but that sets them to their default values, which aren’t always what we need. So let’s jump into some use cases.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;iOS simulator:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet build
    -t:Run // target
    -f net8.0-ios // framework
    -p:_DeviceName=:v2:udid=&amp;lt;MY_SPECIFIC_UDID&amp;gt; // property
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, the .NET MAUI iOS app is launched on a particular simulator by passing the simulator’s unique identifier. There are two ways to get the identifier:&lt;/p&gt;

&lt;p&gt;Using Xcode:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Xcode -&amp;gt; Windows -&amp;gt; Devices and Simulators -&amp;gt; Right-click on the simulator -&amp;gt; Copy Identifier&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Or using the command line (the following command prints all the devices in the terminal along with their identifiers):&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/Applications/Xcode.app/Contents/Developer/usr/bin/simctl list&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;iOS device:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With the developer mode enabled on your iPhone or iPad, use the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet build
    -t:Run // target
    -f net8.0-ios // framework
    -p:RuntimeIdentifier=ios-arm64 // property
    -p:_DeviceName=&amp;lt;MY_SPECIFIC_UDID&amp;gt; // property
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can retrieve the device identifiers in the same way as the simulator identifiers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Android emulator/device:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The following command works for both devices and emulators. If you use a device, make sure that developer mode is enabled.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet build 
    -t:Run // target
    -f net8.0-android // framework
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, on the topic of debugging: When using the command line, debugging gets more complicated. It’s a huge topic that deserves an article of its own, so we won’t cover it here.&lt;/p&gt;

&lt;h3&gt;
  
  
  NuGet package manager
&lt;/h3&gt;

&lt;p&gt;These are the commands used frequently to manage NuGet packages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Restore the packages and tools for the project:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;dotnet restore | dotnet restore &amp;lt;PROJECT_FILE&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clear the local NuGet cache:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;dotnet nuget locals all --clear&lt;/code&gt; &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add/remove a package:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;dotnet remove [&amp;lt;PROJECT&amp;gt;] package &amp;lt;PACKAGE_NAME&amp;gt;&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet add [&amp;lt;PROJECT&amp;gt;] package &amp;lt;PACKAGE_NAME&amp;gt;
    [-f|--framework &amp;lt;FRAMEWORK&amp;gt;]
    [--interactive]
    [-n|--no-restore]
    [--package-directory &amp;lt;PACKAGE_DIRECTORY&amp;gt;]
    [--prerelease]
    [-s|--source &amp;lt;SOURCE&amp;gt;]
    [-v|--version &amp;lt;VERSION&amp;gt;]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are many other important aspects, such as adding/removing/updating/disabling a custom source.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add a NuGet source:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet nuget add source &amp;lt;PACKAGE_SOURCE_PATH&amp;gt;
    [--name &amp;lt;SOURCE_NAME&amp;gt;]
    [--username &amp;lt;USER&amp;gt;]
    [--password &amp;lt;PASSWORD&amp;gt;]
    [--store-password-in-clear-text]
    [--valid-authentication-types &amp;lt;TYPES&amp;gt;]
    [--configfile &amp;lt;FILE&amp;gt;]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Remove a NuGet source:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;dotnet nuget remove source &amp;lt;NAME&amp;gt; [--configfile &amp;lt;FILE&amp;gt;]&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enable or disable a NuGet source:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;dotnet nuget disable source &amp;lt;NAME&amp;gt; [--configfile &amp;lt;FILE&amp;gt;]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;dotnet nuget enable source &amp;lt;NAME&amp;gt; [--configfile &amp;lt;FILE&amp;gt;]&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Update a NuGet source:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet nuget update source &amp;lt;NAME&amp;gt;
    [--source &amp;lt;SOURCE&amp;gt;]
    [--username &amp;lt;USER&amp;gt;]
    [--password &amp;lt;PASSWORD&amp;gt;]
    [--store-password-in-clear-text]
    [--valid-authentication-types &amp;lt;TYPES&amp;gt;]
    [--configfile &amp;lt;FILE&amp;gt;]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Display a list of all available NuGet sources. It reads from the global Nuget.Config file:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;dotnet nuget list source&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Short comparison
&lt;/h2&gt;

&lt;p&gt;Based on our own usage, we would like to provide feedback for the tools discussed in this article. As the command line approach is not a tool per se, we will only compare the .NET MAUI extension for Visual Studio Code and JetBrains Rider.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Rating factors&lt;/th&gt;
&lt;th&gt;.NET MAUI extension&lt;/th&gt;
&lt;th&gt;JetBrains Rider&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;IntelliSense / editor&lt;/td&gt;
&lt;td&gt;★★★★★&lt;/td&gt;
&lt;td&gt;★★★★★&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Solution explorer&lt;/td&gt;
&lt;td&gt;★★★&lt;/td&gt;
&lt;td&gt;★★★★★&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Decompiler &amp;amp; navigation&lt;/td&gt;
&lt;td&gt;★★&lt;/td&gt;
&lt;td&gt;★★★★★&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Running and debugging&lt;/td&gt;
&lt;td&gt;★★★&lt;/td&gt;
&lt;td&gt;★★★★★&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Package manager&lt;/td&gt;
&lt;td&gt;★&lt;/td&gt;
&lt;td&gt;★★★★★&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Project ref. management&lt;/td&gt;
&lt;td&gt;★&lt;/td&gt;
&lt;td&gt;★★★★★&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Feature richness&lt;/td&gt;
&lt;td&gt;★★★&lt;/td&gt;
&lt;td&gt;★★★★★&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Free of charge?&lt;/td&gt;
&lt;td&gt;✔️*&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Elaboration on some points
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Solution explorer:&lt;/strong&gt;&lt;br&gt;
The solution explorer in Rider offers more features and settings compared to the VS Code .NET MAUI extension. It’s also more interactive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Running and debugging:&lt;/strong&gt;&lt;br&gt;
The run-and-debug experience in Rider is superior, with more configuration options and an easy-to-use interface. For example, you can see the list of simulators and devices separately, unlike in the VS Code .NET MAUI extension.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Decompiler &amp;amp; navigation:&lt;/strong&gt;&lt;br&gt;
One very helpful feature in Rider is that you can navigate to the class declaration directly, even if it is a third-party library. Other than that, code navigation is similar in both.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Package manager:&lt;/strong&gt;&lt;br&gt;
The package manager in the VS Code .NET MAUI extension is very basic: You can just add/update/remove a NuGet package. But inside Rider, you get a dedicated tool where you can add/update/remove NuGet packages as well as NuGet sources.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Project reference management:&lt;/strong&gt;&lt;br&gt;
Project reference management is very easy in Rider. If you’re using the VS Code .NET MAUI extension, you have to manually add and remove references.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Feature richness:&lt;/strong&gt;&lt;br&gt;
Rider is a full-fledged IDE, so it has many more features compared to the VS Code .NET MAUI extension.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing:&lt;/strong&gt;&lt;br&gt;
Rider is paid software. The VS Code .NET MAUI extension depends on the C# Dev Kit extension, which is free for individual, academic, and open-source developers. Enterprises will need a Visual Studio Professional subscription.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recommendation
&lt;/h2&gt;

&lt;p&gt;We hope this comparison of the currently available Visual Studio alternatives for Mac helped you choose the best option for developing .NET MAUI applications.&lt;/p&gt;

&lt;p&gt;We’d like to leave you with the following advice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you prefer a &lt;strong&gt;fully fledged, powerful IDE&lt;/strong&gt;, go with JetBrains Rider. &lt;/li&gt;
&lt;li&gt;If you prefer to have a &lt;strong&gt;combination of a lightweight IDE and the CLI&lt;/strong&gt;, then the .NET MAUI Extension in VS Code is your best bet. &lt;/li&gt;
&lt;li&gt;If you &lt;strong&gt;don’t want to rely on anything but your commands&lt;/strong&gt;, then go with the CLI. However, this option still requires a good editor, which in most cases will be VS Code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Good luck in selecting your approach and happy coding!&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>ios</category>
      <category>programming</category>
      <category>vscode</category>
    </item>
  </channel>
</rss>
