<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Chai Jia Xun</title>
    <description>The latest articles on DEV Community by Chai Jia Xun (@cjx3711).</description>
    <link>https://dev.to/cjx3711</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F585304%2Ffc5126de-17ac-4539-8b57-02e7a750ad0e.jpeg</url>
      <title>DEV Community: Chai Jia Xun</title>
      <link>https://dev.to/cjx3711</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/cjx3711"/>
    <language>en</language>
    <item>
      <title>Dockerising my ancient ghost instance and making a custom homepage with the latest post</title>
      <dc:creator>Chai Jia Xun</dc:creator>
      <pubDate>Fri, 13 Dec 2024 15:30:00 +0000</pubDate>
      <link>https://dev.to/cjx3711/dockerising-my-ancient-ghost-instance-and-making-a-custom-homepage-with-the-latest-post-13pd</link>
      <guid>https://dev.to/cjx3711/dockerising-my-ancient-ghost-instance-and-making-a-custom-homepage-with-the-latest-post-13pd</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.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%2Fxamnirzfiwd1eumsou8w.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fxamnirzfiwd1eumsou8w.jpg" alt="Dockerising my ancient ghost instance and making a custom homepage with the latest post" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's been another year since I've posted anything on my blog. Partly because I managed to burn myself out again, and partly because my website has been somewhat broken for more than half a year. So it seems apt that my first post is about upgrading Ghost yet again.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://chaijiaxun.com/blog/goodbye-ghost-hello-ghost/" rel="noopener noreferrer"&gt;My last post about upgrading Ghost was 5 years ago&lt;/a&gt;. That was about upgrading to version 2.0, with the promise that all future upgrades would be a simple cli command away. Despite that I never once bothered to update Ghost since it was working perfectly anyway. Unfortunately it was still running directly on my ubuntu 16 machine which is at least 8 ubuntus out of date at this point and it also reached end of life in 2021.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fo62mvr8gi25ccrgpjyww.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fo62mvr8gi25ccrgpjyww.png" alt="Dockerising my ancient ghost instance and making a custom homepage with the latest post" width="800" height="337"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;7 months ago, I wanted to deploy a new project, but the machine refused to let me set up SSL certs anymore. I use letsencrypt to manage my certificates but certbot also refused to work and I went down a rabbit hole of issues trying to get it running again, to no avail.&lt;br&gt;&lt;br&gt;
I wasn't in a particularly devop-sy mood, so the easiest thing I could do was to spin up a new machine running an updated version of ubuntu. My blog's SSL cert was also no longer working, but that wasn't important enough for me to deal with at the time. All it did was cause my blog to show a WEBSITE NOT SECURE message and reflect badly on me as a web developer.&lt;br&gt;&lt;br&gt;
In the 4 years since the previous upgrade, I finally figured out docker so all my projects are now dockerised. No more raw dogging the servers.&lt;/p&gt;
&lt;h1&gt;
  
  
  Spinning up the container
&lt;/h1&gt;

&lt;p&gt;This was one of the easiest parts. There's an &lt;a href="https://hub.docker.com/_/ghost/?ref=chaijiaxun.com" rel="noopener noreferrer"&gt;official ghost docker image&lt;/a&gt; which allowed me to get a ghost instance running locally within minutes.&lt;br&gt;&lt;br&gt;
I started by running a container with the development environment, which ran smoothly with no issues at all.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; docker run -d \
  -e url=http://localhost:3711 \
  -e NODE_ENV=development \
  -v ghost_content:/var/lib/ghost/content \
  -p 3711:3711 \
  --name ghost-dev\
  ghost:latest

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

&lt;/div&gt;



&lt;p&gt;Doing this also created the docker volume &lt;code&gt;ghost_content&lt;/code&gt; , which I will reuse later for my production container.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note that once you spin up a container, you can't change the environment variables. So to get it to run in production mode, you'll need to delete the container and create a new one. This is ok because all the data is stored in the volume which will persist outside of container creation and deletion.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Switching the database to SQLite
&lt;/h2&gt;

&lt;p&gt;Next, I tested running a container in production mode&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; docker run -d \
  -e url=https://chaijiaxun.com \
  -e NODE_ENV=production \
  -v ghost_content:/var/lib/ghost/content \
  -p 3711:3711 \
  --name ghost-prod\
  ghost:latest

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

&lt;/div&gt;



&lt;p&gt;It crashed.&lt;/p&gt;

&lt;p&gt;Which was to be expected.&lt;/p&gt;

&lt;p&gt;I did not have any SQL server running, or configured.&lt;/p&gt;

&lt;p&gt;If you look into the logs you'll see that's exactly what happened.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fu1fp8xzzl0wrpdth1ptw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fu1fp8xzzl0wrpdth1ptw.png" alt="Dockerising my ancient ghost instance and making a custom homepage with the latest post" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But I'm a lazy engineer and hosting a separate SQL server was not on my todo list, so instead I set up my prod configuration to also use sqlite and the same database as my development environment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "server": {
    "port": 3711, // Match the port to what you defined above
    "host": "::"
  },
  "database": { // Add this to use sqlite3
    "client": "sqlite3",
    "connection": {
      "filename": "/var/lib/ghost/content/data/ghost.db"
    }
  },
  "mail": {
    "transport": "Direct"
  },
  "logging": {
    "transports": ["file", "stdout"]
  },
  "process": "systemd",
  "paths": {
    "contentPath": "/var/lib/ghost/content"
  }
}

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

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;config.production.json&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; docker cp config.production.json ghost-prod:/var/lib/ghost/

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

&lt;/div&gt;



&lt;p&gt;After copying the config file into the container, it's just a matter of restarting the container and it'll start to use the sqlite database in the container itself.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; docker start ghost-prod

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

&lt;/div&gt;



&lt;p&gt;You can check this by running an exec into the container and checking that the db file exists&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; docker exec -it ghost-prod /bin/bash
&amp;gt; ls content/data
ghost.db

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

&lt;/div&gt;



&lt;p&gt;Now that we have verified we can get our ghost instance running in both development and production mode locally, we can move on to getting my data into this new instance.&lt;/p&gt;

&lt;h1&gt;
  
  
  Migrating the data
&lt;/h1&gt;

&lt;p&gt;Data migration came in parts.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Import posts and settings&lt;/li&gt;
&lt;li&gt;Import media&lt;/li&gt;
&lt;li&gt;Migrate theme&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Posts and Settings
&lt;/h2&gt;

&lt;p&gt;Ghost has always provided data export and import functionality, but it seems like it only allowed exporting a json file without any of the media. Maybe I was missing something but there didn't seem to be an easy way for me to download all my media via the interface.&lt;br&gt;&lt;br&gt;
Importing the posts was easy. First, I exported the json with all my posts and settings from my old blog. Then, I created a new account on my new ghost instance, and uploaded that json file to the new blog. This got all my posts and pages into the new site, but the images were still on the old site.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fqv065q71ubytbp4hbehl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fqv065q71ubytbp4hbehl.png" alt="Dockerising my ancient ghost instance and making a custom homepage with the latest post" width="800" height="487"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Media
&lt;/h2&gt;

&lt;p&gt;The next step was to copy all the data over. This was a matter of copying the data from my previous server to my local machine, then copying it into the docker container. In my last post, I had a lot of issues with the image urls because I migrated my blog from cjx3711.com to chaijiaxun.com. But I didn't have to worry about that issue this time. In the newer versions of Ghost (from at least 2.0), they no longer save the absolute URL of an image into the page content.&lt;br&gt;&lt;br&gt;
In the ghost folder on the server, all of the images are stored in the content folder. I had also stored my database in the data folder. Here's a peek into the folder structure of the content folder.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; ls content
apps data files images logs media public settings themes

&amp;gt; ls content/images
2017 2019 2020 2021 2022 2023 README.md

&amp;gt; ls content/data
ghost.db

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

&lt;/div&gt;



&lt;p&gt;After that it was a matter of copying the files around.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Download the images with rsync
rsync -azP username@remote_host:/path/to/ghost/content content

# Transfer this into the ghost container
docker cp content ghost-container:/var/lib/ghost/

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

&lt;/div&gt;



&lt;p&gt;And that was all. Since we mounted the volume with the flag &lt;code&gt;-v ghost\_content:/var/lib/ghost/content&lt;/code&gt;, this actually copies the data into the docker volume and not the container. Meaning we can now safely delete and boot up a new container without losing the data in the future.&lt;/p&gt;

&lt;h2&gt;
  
  
  Theme
&lt;/h2&gt;

&lt;p&gt;Luckily for me, ghost has been quite good about keeping their backwards compatibility for the themes. I barely had to make any changes to the theme for it to work. Though I ended up giving my theme a few upgrades since I was already at it.&lt;/p&gt;

&lt;h1&gt;
  
  
  Deploying it to my server
&lt;/h1&gt;

&lt;p&gt;This whole time I've been working off my local machine, but the whole point of dockerising it so I could easily run it on any server. To get it on my server, I had to do the following&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Transfer the volume to my server&lt;/li&gt;
&lt;li&gt;Transfer my config file to my server&lt;/li&gt;
&lt;li&gt;Spin up a ghost container on my server in production mode (this should crash since there's no connected MySQL server)&lt;/li&gt;
&lt;li&gt;Copy the config file into the container and restart it&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;This assumes your server already has docker installed and running, and that you know how to set up the nginx bits to link up the domain.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Transfer the volume to the server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# (Locally) Create a tar archive of your volume
# This creates a new container that backs up the volume and writes a tar file in your current directory
&amp;gt; docker run --rm \
  -v ghost_content:/volume \
  -v $(pwd):/backup \
  alpine tar cvf /backup/ghost_content_backup.tar /volume

# Transfer the tar archive using your favourite method.
&amp;gt; rsync -azP ghost_content_backup.tar username@remote_host:/any/temp/path/

# (Server side) Load the volume on the server
# Create a new volume on the server
&amp;gt; docker volume create ghost_content

# Navigate to your temp folder
&amp;gt; cd /any/temp/path

# Extract the backup into the new volume
&amp;gt; docker run --rm \
  -v ghost_content:/volume \
  -v $(pwd):/backup \
  alpine sh -c "cd /volume &amp;amp;&amp;amp; tar xvf /backup/ghost_content_backup.tar --strip 1"

# Delete the tar archive on the server if you want
&amp;gt; rm ghost_content_backup.tar

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

&lt;/div&gt;



&lt;p&gt;The rest of the steps are exactly the same as the local version in the previous section.&lt;/p&gt;

&lt;p&gt;And it's done. You should have the container running on your server.&lt;/p&gt;

&lt;h1&gt;
  
  
  Updating the homepage
&lt;/h1&gt;

&lt;p&gt;On top of the migration, I also wanted to change my homepage to something other than my latest posts. Ghost now has a way to directly edit the routes to make any page your homepage in the experimental settings and it &lt;a href="https://sprune.com/blog/custom-homepage-in-ghost/?ref=chaijiaxun.com" rel="noopener noreferrer"&gt;looks relatively straightforward&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fjuisdu8p3na9g32by0pl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fjuisdu8p3na9g32by0pl.png" alt="Dockerising my ancient ghost instance and making a custom homepage with the latest post" width="800" height="307"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Unfortunately, there was no way for me to retrieve and display the latest post using only the ghost editor, so I ended up having to edit my theme.&lt;br&gt;&lt;br&gt;
First, I updated my &lt;code&gt;index.hbs&lt;/code&gt; file to include the latest post.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; {{!-- Get the latest post --}}
    {{#get "posts" limit="1" as |latest_post|}}
      {{#foreach latest_post}}
        &amp;lt;article class="loopPost {{post_class}} latestPost"&amp;gt;
          &amp;lt;p class="latestMark"&amp;gt;Latest post&amp;lt;/p&amp;gt;
          &amp;lt;h2 class="loopPost-title post-title"&amp;gt;&amp;lt;a href="{{url}}"&amp;gt;{{{title}}}&amp;lt;/a&amp;gt;&amp;lt;/h2&amp;gt;
          {{#if feature_image}}
            &amp;lt;div class="postCover" style="background-image:url('{{img_url feature_image}}')"&amp;gt;
              &amp;lt;img class="invisibleImg" src="{{img_url feature_image}}"&amp;gt;
            &amp;lt;/div&amp;gt;
          {{/if}}
          &amp;lt;div class="postInfo"&amp;gt;
            &amp;lt;span&amp;gt;{{authors}}&amp;lt;/span&amp;gt; &amp;lt;span style="text-transform: lowercase"&amp;gt;on&amp;lt;/span&amp;gt; &amp;lt;span&amp;gt;{{tags}}&amp;lt;/span&amp;gt;
            &amp;lt;span class="post-date" datetime="{{date format='YYYY-MM-DD'}}"&amp;gt;| {{date format="DD MMMM YYYY"}}&amp;lt;/span&amp;gt;
          &amp;lt;/div&amp;gt;
          &amp;lt;div class="postExcerpt"&amp;gt;
            &amp;lt;p&amp;gt;{{excerpt words="25"}} ...&amp;lt;/p&amp;gt;
            &amp;lt;div class="bottomContainer"&amp;gt;
              &amp;lt;a class="readMore" title="{{title}}" href="{{url}}"&amp;gt;
                &amp;lt;span&amp;gt;Continue reading &amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;&amp;amp;#10142;&amp;lt;/span&amp;gt;
              &amp;lt;/a&amp;gt;
            &amp;lt;/div&amp;gt;
          &amp;lt;/div&amp;gt;
        &amp;lt;/article&amp;gt;
      {{/foreach}}
    {{/get}}

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

&lt;/div&gt;



&lt;p&gt;That settled my homepage, but now I needed a blog page. So I made a copy of my original &lt;code&gt;index.hbs&lt;/code&gt; , and named it &lt;code&gt;page-blog.hbs&lt;/code&gt; this allows me to create a blog page in the ghost backend.&lt;br&gt;&lt;br&gt;
Next, I updated the &lt;code&gt;routes.yml&lt;/code&gt; file to make the &lt;code&gt;/blog/\*&lt;/code&gt;my new blog list page.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;routes:
  /:
    data: page.home
    template: index

collections:
  /blog/:
    permalink: /blog/{slug}/
    template: page-blog

taxonomies:
  tag: /blog/topic/{slug}/
  author: /blog/author/{slug}/

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

&lt;/div&gt;



&lt;p&gt;One last thing I had to do was to find some way of redirecting anyone who accessed the old links. (Primarily for myself, searching for my own blog posts and search engines not updating the old results)&lt;br&gt;&lt;br&gt;
To achieve this, I decided to add a custom 404 page that would attempt to redirect the user to the correct url. This would ensure any working pages continue to work, but old pages should get automatically redirected to &lt;code&gt;/blog/&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;// Snippet from the 404 page script.
const currentPath = window.location.pathname;
if (!currentPath.includes('/blog')) {
    // Handle blog redirects
    const newPath = '/blog' + (currentPath.startsWith('/') ? currentPath : '/' + currentPath);
    document.getElementById('blog-link').setAttribute('href', newPath);
    document.getElementById('blog-message').style.display = 'block';
    setTimeout(() =&amp;gt; {
      window.location.href = newPath;
    }, 3000);
}

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

&lt;/div&gt;



&lt;p&gt;I just had to redirect any 404 url from &lt;code&gt;chaijiaxun.com/{route}&lt;/code&gt; to &lt;code&gt;chaijiaxun.com/blog/{route}&lt;/code&gt; and any tags from &lt;code&gt;chaijiaxun.com/tag/{tag}&lt;/code&gt; to &lt;code&gt;chaijiaxun.com/blog/topic/{tag}&lt;/code&gt; and that settled most of the cases (that I know of)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fkhkn5wz2yb8nqooypt5g.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fkhkn5wz2yb8nqooypt5g.gif" alt="Working site" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And that's it. The homepage you see now is the custom page and my blog is now on the /blog/ endpoint. This upgrade took a lot less time than my previous one, partly thanks to AI, but mostly thanks to Ghost's dev team for not breaking everything.&lt;/p&gt;

</description>
      <category>guides</category>
      <category>ghost</category>
      <category>docker</category>
    </item>
    <item>
      <title>Giving my Vacuum a "Laser" Upgrade</title>
      <dc:creator>Chai Jia Xun</dc:creator>
      <pubDate>Mon, 28 Jun 2021 03:21:09 +0000</pubDate>
      <link>https://dev.to/cjx3711/giving-my-vacuum-a-laser-upgrade-1h11</link>
      <guid>https://dev.to/cjx3711/giving-my-vacuum-a-laser-upgrade-1h11</guid>
      <description>&lt;p&gt;Dyson just announced the v15, an upgrade to their vacuum series, and one of the most notable additions was a new laser feature that would help you see all the dust that you were too lazy to vacuum up. Honestly this would have seemed like a really cool idea and somewhat magical to me. Except that a previous cheap vacuum I owned achieved this same effect by placing some LEDs on the front of the device and it worked pretty well too.&lt;/p&gt;

&lt;p&gt;Now I do own a Dyson v7, and I believe it’s possible to &lt;a href="https://www.dyson.com/support/journey/spare-details.971360-01.368340-01"&gt;upgrade just the head&lt;/a&gt; without having to fork over 700 bucks to Dyson for a completely new vacuum. It would only be 120, but that is still an expense that I wasn’t willing to pay for what was effectively a green LED. (Also it's sold out right now.) So I figured I’d do it myself. After all, if I insist that all it is is an LED, what’s stopping me from sticking an LED strip to my current vacuum?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2ktRVsDN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/06/thumbnail-1.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2ktRVsDN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/06/thumbnail-1.jpg" alt="Giving my Vacuum a"&gt;&lt;/a&gt;Left: official v15, Right: "v15" at home&lt;/p&gt;

&lt;h2&gt;
  
  
  The planning process
&lt;/h2&gt;

&lt;p&gt;Step 1: LED strip, batteries and wires I reckon.&lt;br&gt;&lt;br&gt;
Step 2: Hot glue&lt;br&gt;&lt;br&gt;
Step 3: ???&lt;br&gt;&lt;br&gt;
Step 4: Profit&lt;/p&gt;

&lt;h2&gt;
  
  
  Build process
&lt;/h2&gt;

&lt;p&gt;Not that I really needed to test this, but I threw some LEDs I had lying around onto a breadboard and wired them up in parallel. Then I found a spare battery housing I had lying around the house. Finally I stuck 4 rechargeable AA batteries in it and turned it on.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--RTMLT3AJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/06/pic1.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RTMLT3AJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/06/pic1.jpg" alt="Giving my Vacuum a"&gt;&lt;/a&gt;yep, electricity still works&lt;/p&gt;

&lt;p&gt;Next I needed to find a way to make a strip of LEDs. I could have just gotten a cheap 5050 5v LED strip off eBay or Aliexpress, but I was not in a spending money sort of mood today. Instead, I had this perfboard lying around which happened to be just about the right size for my vacuum head. The next step was just soldering the bits together. I used the excess of the LED legs as wires.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--v0uMU5Pc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/06/pic2.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--v0uMU5Pc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/06/pic2.jpg" alt="Giving my Vacuum a"&gt;&lt;/a&gt;LEDs, Resistors and Soldering&lt;/p&gt;

&lt;p&gt;Next up is to cut the perfboard to size and put some masking tape on the front of my dyson so that I don’t actually damage it by sticking this abomination to it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--oaapTGNr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/06/pic3.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--oaapTGNr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/06/pic3.jpg" alt="Giving my Vacuum a"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, we test the light and see how well it illuminates the dust.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GAFCC5MA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/06/pic4.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GAFCC5MA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/06/pic4.jpg" alt="Giving my Vacuum a"&gt;&lt;/a&gt;It works surprisingly well!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step ???, Hot glue gun&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YEztIQI6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/06/pic5.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YEztIQI6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/06/pic5.jpg" alt="Giving my Vacuum a"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Luckily for me, the battery holder had a built in switch, so I didn't need to install a switch, but that would have been pretty easy to fix as well.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2Lgj0ru0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://chaijiaxun.com/content/images/2021/06/vacuum.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2Lgj0ru0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://chaijiaxun.com/content/images/2021/06/vacuum.gif" alt="Giving my Vacuum a"&gt;&lt;/a&gt;And here's a gif of it in action&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Cost&lt;/strong&gt; : effectively $0 and 1 hour.&lt;/p&gt;

&lt;p&gt;This works just as well as the $120 version that Dyson provides on their website. I guess I get to keep my money.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8g5Giq6A--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/06/image.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8g5Giq6A--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/06/image.png" alt="Giving my Vacuum a"&gt;&lt;/a&gt;Seems like it's pretty popular too&lt;/p&gt;

</description>
      <category>making</category>
      <category>diy</category>
    </item>
    <item>
      <title>Creating an Android App with React Native, NativeBase and WatermelonDB</title>
      <dc:creator>Chai Jia Xun</dc:creator>
      <pubDate>Sat, 06 Mar 2021 07:50:00 +0000</pubDate>
      <link>https://dev.to/cjx3711/creating-an-android-app-with-react-native-nativebase-and-watermelondb-58im</link>
      <guid>https://dev.to/cjx3711/creating-an-android-app-with-react-native-nativebase-and-watermelondb-58im</guid>
      <description>&lt;h2&gt;
  
  
  My motivation
&lt;/h2&gt;

&lt;p&gt;I used to code android apps 10 years ago. Back then Google had not much opinion on how apps should be coded and I was very happy to code terrible apps with eclipse and Java. About 3 years ago, I wanted to code another app, after years in web development. I figured I’d try this newfangled Kotlin thing with the cool new recommended MVVC and Room ORM that Google has been trying to push. It was a terrible mistake. It was so painfully slow to make any progress on my app at all, and I spent so much of my time trying to figure out how to implement everything. So much so that each time I found the motivation to start work on the project, I would lose it again after spending a whole weekend on it and barely making any progress. To be fair to Kotlin and native development, this is mostly because I am primarily a web developer and didn’t have someone to mentor me on Android dev. So it was a very different paradigm for me.&lt;/p&gt;

&lt;p&gt;On multiple occasions, I considered switching to React Native or some other cross-platform framework. But the issue with those is that they didn’t come with the default android app feel, and would look a lot more like a bootstrap web app unless I hired a designer. Also, I had already sunk so much effort into my app, why would I want to recode the entire thing?&lt;/p&gt;

&lt;p&gt;Recently, I had found my motivation again to start working on my app. But just thinking of having to figure out Kotlin again put me off. Then I decided to instead look into the feasibility of React Native once again, and I was taken by surprise. The developer community around the framework has grown significantly with libraries like NativeBase and WatermelonDB since I last looked at it. This alleviated many concerns I had with the feasibility of coding an app in React Native. This meant that redoing my app in react native would probably only take a week or so to get to the point I was already at.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Technologies
&lt;/h2&gt;

&lt;p&gt;If you are reading this, I assume you’d most likely know what these technologies are, but here’s a quick overview anyway.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;React Native&lt;/strong&gt; is a way to code native apps with react code, making it easy for web developers to make native apps. It allows deploying to both android and iOS with a single codebase (for the most part)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Native Base&lt;/strong&gt; is a UI library that sits on top of React Native to make the UI coding easier, and more consistent. Instead of having to define different styles for both iOS and Android, you just need to code with their set of components and it’ll automatically take on each OSes native look.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;WatermelonDB&lt;/strong&gt; is an observable lazy loading database for web or mobile applications that are designed to be offline-first. This also ensures the app is performant even with a large amount of data. It comes with functionality to make syncing with a server easy if it is ever required.&lt;/p&gt;

&lt;p&gt;It comes with functionality to make syncing with a server easy if it is ever required.&lt;/p&gt;

&lt;p&gt;All these projects are open-sourced too, which is great.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Project and Requirements
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What this project is&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;To keep things simple, we’ll make a classic todo app.&lt;/li&gt;
&lt;li&gt;The focus on this tutorial is to get a basic app that creates a basic UI, and touches on the database.&lt;/li&gt;
&lt;li&gt;We will have only a single page&lt;/li&gt;
&lt;li&gt;The app will be persistent, and will save the data to the database.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What this is not&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This is not a UI/UX tutorial, you will see some terrible UI, you have been warned.&lt;/li&gt;
&lt;li&gt;This will not be using multiple screens as that requires a navigation library like &lt;a href="https://reactnavigation.org/"&gt;React Navigation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;This will not be using complex forms as that will require a good form handling library like &lt;a href="https://formik.org/"&gt;formik&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;This will have no state management like &lt;a href="https://redux.js.org/"&gt;Redux&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;you are making a real app, I will provide additional recommended libraries at the end of this post. For this tutorial, I will stick to React Native, NativeBase, and WatermelonDB.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Platform&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
I will be coding this on macOS, and targeting the Android platform. Since this is React Native, you could code on either Linux or Windows and target iOS or web as well. You would probably just need to change some configurations, but coding on MacOS for Android is the configuration I have chosen to use. I have also tested this on Linux and it seems to work fine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Required Software&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Android Studio with Android SDK &amp;gt;= 28&lt;br&gt;&lt;br&gt;
node &amp;gt;= 10.21&lt;br&gt;&lt;br&gt;
npm &amp;gt;= 6.9.0&lt;br&gt;&lt;br&gt;
JDK &amp;gt;= 1.8&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recommended Knowledge&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
How to code react (not strictly necessary)&lt;br&gt;&lt;br&gt;
How to use git (also not really necessary)&lt;br&gt;&lt;br&gt;
How to use the terminal&lt;br&gt;&lt;br&gt;
How databases work&lt;br&gt;&lt;br&gt;
What an app is&lt;br&gt;&lt;br&gt;
How to use a text editor&lt;br&gt;&lt;br&gt;
How to use a keyboard&lt;br&gt;&lt;br&gt;
How to use your computer&lt;/p&gt;
&lt;h2&gt;
  
  
  The Process
&lt;/h2&gt;

&lt;p&gt;For each of these sections, I will be providing a link to the relevant commit so that you can see the changes from the previous state.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/cjx3711/ReactNative-BadTodo"&gt;Here's a link to the repo if that's all you want.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There will be a total of 5 sections to this tutorial:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Starting a basic react native app&lt;/li&gt;
&lt;li&gt;Getting started with NativeBase&lt;/li&gt;
&lt;li&gt;Installing WatermelonDB&lt;/li&gt;
&lt;li&gt;Creating the schema and tables&lt;/li&gt;
&lt;li&gt;Hooking everything up&lt;/li&gt;
&lt;/ol&gt;


&lt;h3&gt;
  
  
  Section 1: Starting a basic react native app
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/cjx3711/ReactNative-BadTodo/commit/daedcb9d743e47a47c8820488e64bf6746376608"&gt;💡 Code changes for this section 💡&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To start off, make sure you have the required software installed. Since this is an Android tutorial, you will also need to create an Android Virtual Device to run your app on. You can follow the instructions on the &lt;a href="https://reactnative.dev/docs/environment-setup"&gt;official react-native documentation&lt;/a&gt; here.&lt;/p&gt;

&lt;p&gt;Once you have node, watchman, jdk, and the Android Virtual Device set up, we will create a new app.&lt;/p&gt;

&lt;p&gt;We’ll use the built-in react-native cli to create the app. You can call this app whatever you want but we’ll call ours BadTodo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx react-native init BadTodo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sT8MOYzu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/03/init-app.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sT8MOYzu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/03/init-app.png" alt="Creating an Android App with React Native, NativeBase and WatermelonDB"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice there are a bunch of errors related to iOS. Well since I am not coding for iOS, I am going to ignore these errors.&lt;/p&gt;

&lt;p&gt;After that, we need to run the metro js server that the app will use to get the javascript code. First, navigate to the new folder cd BadTodo and run the following command,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx react-native start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we open a new terminal and run the android app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx react-native run-android
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should end up with something that looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NnQse6T5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/03/template-app.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NnQse6T5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/03/template-app.png" alt="Creating an Android App with React Native, NativeBase and WatermelonDB"&gt;&lt;/a&gt;Left: metro server, Middle: run-android, Right: Android emulator&lt;/p&gt;

&lt;p&gt;We now have a basic react native app running in the android emulator. This is the point where you should probably make this a git repo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git init
git add &lt;span class="nt"&gt;-A&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; “Initial React Native Commit”
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the next section, we’ll install NativeBase and create a basic todo page.&lt;/p&gt;




&lt;h3&gt;
  
  
  Section 2: Getting started with NativeBase
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/cjx3711/ReactNative-BadTodo/commit/2ac5d0141b89222c9386d5c832f655afa49502f1"&gt;💡 Code changes for this section 💡&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First, we’re going to install nativebase, simply run this command in the same repo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;native-base &lt;span class="nt"&gt;--save&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tar3l8wm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/03/happypath-nativebase.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tar3l8wm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/03/happypath-nativebase.png" alt="Creating an Android App with React Native, NativeBase and WatermelonDB"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;NativeBase does not automatically link the peer dependencies, so run this to link them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx react-native &lt;span class="nb"&gt;link&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will see that native-base has been added to your package.json. &lt;a href="https://github.com/cjx3711/ReactNative-BadTodo/commit/4c350a58e76ce7b909782fb6d1a068c1e963cd26"&gt;Here’s the commit where that happens&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UdldWzWu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/03/nativebase-dependency.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UdldWzWu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/03/nativebase-dependency.png" alt="Creating an Android App with React Native, NativeBase and WatermelonDB"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now that we have that up, we’re going to change the home screen to something less template looking and more bad-todo looking.&lt;/p&gt;

&lt;p&gt;Open up &lt;code&gt;App.js&lt;/code&gt; and replace the entire thing with the following code&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="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Component&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="s1"&gt;react&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Container&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Footer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;FooterTab&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Right&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ListItem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;CheckBox&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Grid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Form&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;Item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Label&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="s1"&gt;native-base&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="nx"&gt;BadTodo&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Component&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
     &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Container&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
       &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Header&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
         &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Body&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
           &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Title&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Really&lt;/span&gt; &lt;span class="nx"&gt;Bad&lt;/span&gt; &lt;span class="nx"&gt;Todo&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Title&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;         &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Body&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;         &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Right&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
       &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Header&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;       &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Content&lt;/span&gt; &lt;span class="nx"&gt;padder&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
         &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Text&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
           &lt;span class="nx"&gt;There&lt;/span&gt; &lt;span class="nx"&gt;are&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="nx"&gt;tasks&lt;/span&gt;
         &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Text&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;         &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ListItem&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
           &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Grid&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
             &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Row&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
               &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CheckBox&lt;/span&gt; &lt;span class="nx"&gt;checked&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;               &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Body&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                 &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Text&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Need&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Text&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;               &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Body&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;             &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Row&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;           &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Grid&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;         &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/ListItem&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;         &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ListItem&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
           &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Grid&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
             &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Row&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
               &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CheckBox&lt;/span&gt; &lt;span class="nx"&gt;checked&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;               &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Body&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                 &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Text&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Need&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="nx"&gt;that&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Text&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;               &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Body&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;             &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Row&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;           &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Grid&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;         &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/ListItem&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;         &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Form&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
           &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Item&lt;/span&gt; &lt;span class="nx"&gt;floatingLabel&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
             &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Label&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;New&lt;/span&gt; &lt;span class="nx"&gt;Task&lt;/span&gt; &lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Label&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;             &lt;span class="o"&gt;&amp;lt;&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Item&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;         &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Form&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;       &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Content&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;       &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Footer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
         &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;FooterTab&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
           &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="nx"&gt;full&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
             &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Text&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Add&lt;/span&gt; &lt;span class="nx"&gt;Task&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Text&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;           &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;         &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/FooterTab&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;       &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Footer&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;     &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Container&lt;/span&gt;&lt;span class="err"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can then reload the app (press &lt;code&gt;r&lt;/code&gt; on the metro window) and you should get something like this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2TZbehth--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/03/simple-todo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2TZbehth--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/03/simple-todo.png" alt="Creating an Android App with React Native, NativeBase and WatermelonDB"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I hope that the code layout and the results are self-explanatory. You can see that NativeBase takes all the pain of styling out of your code and it looks somewhat like native android with very little code. The best part is that if you were coding for the iPhone as well, it would be the same code! For a list of components and example code, you can check out their &lt;a href="https://docs.nativebase.io/Components.html#anatomy-headref"&gt;official documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We’ll hook this up to something more dynamic in section 5, after we set up WatermelonDB.&lt;/p&gt;




&lt;h3&gt;
  
  
  Section 3: Installing WatermelonDB
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/cjx3711/ReactNative-BadTodo/commit/2ac5d0141b89222c9386d5c832f655afa49502f1"&gt;💡 Code changes for this section 💡&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can follow the &lt;a href="https://nozbe.github.io/WatermelonDB/Installation.html"&gt;official WatermelonDB docs&lt;/a&gt; to get it installed, but I will be covering the steps here as well.&lt;/p&gt;

&lt;p&gt;First, install WatermelonDB with these commands. You should still be in the root directory of your app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn add @nozbe/watermelondb
yarn add @nozbe/with-observables
yarn add &lt;span class="nt"&gt;--dev&lt;/span&gt; @babel/plugin-proposal-decorators

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

&lt;/div&gt;



&lt;p&gt;Next, create a &lt;code&gt;.babelrc&lt;/code&gt; file in the root directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;touch&lt;/span&gt; .babelrc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then paste the following snippet in the newly created 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="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;presets&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;module:metro-react-native-babel-preset&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;plugins&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@babel/plugin-proposal-decorators&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;legacy&lt;/span&gt;&lt;span class="dl"&gt;"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;❓ If you are confused as to where to put the code, &lt;a href="https://github.com/cjx3711/ReactNative-BadTodo/commit/7be17add6fef0d1a1f89db7f96504d04c5c52539"&gt;check out this commit on github&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the &lt;code&gt;android/settings.gradle&lt;/code&gt;, add the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gradle"&gt;&lt;code&gt;&lt;span class="n"&gt;include&lt;/span&gt; &lt;span class="s1"&gt;':watermelondb'&lt;/span&gt;
&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;':watermelondb'&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;projectDir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;File&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rootProject&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;projectDir&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'../node_modules/@nozbe/watermelondb/native/android'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;In &lt;code&gt;android/app/build.gradle&lt;/code&gt;, add the following.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;❗ Note that there are 2 build.gradle files, please make sure you are editing the correct one.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gradle"&gt;&lt;code&gt;&lt;span class="n"&gt;apply&lt;/span&gt; &lt;span class="nl"&gt;plugin:&lt;/span&gt; &lt;span class="s1"&gt;'kotlin-android'&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="k"&gt;dependencies&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="nf"&gt;project&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;':watermelondb'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;In &lt;code&gt;android/build.gradle&lt;/code&gt;, add Kotlin support to the project&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gradle"&gt;&lt;code&gt;&lt;span class="k"&gt;buildscript&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ext&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// ...&lt;/span&gt;
        &lt;span class="n"&gt;kotlin_version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'1.3.21'&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;dependencies&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// ...&lt;/span&gt;
        &lt;span class="n"&gt;classpath&lt;/span&gt; &lt;span class="s2"&gt;"org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;In &lt;code&gt;android/app/src/main/java/{YOUR_APP_PACKAGE}/MainApplication.java&lt;/code&gt;, add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.nozbe.watermelondb.WatermelonDBPackage&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// This line&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ReactPackage&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;packages&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;PackageList&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;getPackages&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&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;WatermelonDBPackage&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// This line&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that this is done, you should rerun your android app by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx react-native run-android
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything should compile without errors. If not, please refer to the troubleshooting section at the bottom of this post&lt;/p&gt;




&lt;h3&gt;
  
  
  Section 4: Creating the schema and tables
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/cjx3711/ReactNative-BadTodo/commit/2ac5d0141b89222c9386d5c832f655afa49502f1"&gt;💡 Code changes for this section 💡&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you’ve used an ORM before, WatermelonDB should be pretty familiar to use. If not, here are some basic concepts.&lt;/p&gt;

&lt;p&gt;There are two main things you will need to create, the &lt;em&gt;schema&lt;/em&gt;, and the &lt;em&gt;models&lt;/em&gt;. The schema is usually the thing that generates the database tables and fields, while the model is the object that you get when you query the database from the ORM. Some ORMs automatically create the schema for you given the model file, but watermelon does not.&lt;/p&gt;

&lt;p&gt;Watermelon only has three column types. &lt;em&gt;Boolean&lt;/em&gt;, &lt;em&gt;Number&lt;/em&gt;, and &lt;em&gt;String&lt;/em&gt;. Dates are stored as numbers. IDs are stored as strings. You can store json objects as strings as well, but it’s not generally recommended as WatermelonDB is unable to index json keys. (Some DBs like Postgres can though!)&lt;/p&gt;

&lt;p&gt;Let’s begin by creating a new folder &lt;code&gt;src/db&lt;/code&gt;. This is where our database stuff will go.&lt;/p&gt;

&lt;p&gt;Next, we’ll create our schema in this file &lt;code&gt;src/db/schema.js&lt;/code&gt;&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;appSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tableSchema&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="s1"&gt;@nozbe/watermelondb&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mySchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;appSchema&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
 &lt;span class="na"&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="c1"&gt;// Used for migrations&lt;/span&gt;
 &lt;span class="na"&gt;tables&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
   &lt;span class="nx"&gt;tableSchema&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;tasks&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Convention is to be plural lowercase of the model&lt;/span&gt;
     &lt;span class="na"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
       &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;description&lt;/span&gt;&lt;span class="dl"&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;string&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;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;is_complete&lt;/span&gt;&lt;span class="dl"&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;boolean&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
       &lt;span class="c1"&gt;// These will be dates but we define it as numbers&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;created_at&lt;/span&gt;&lt;span class="dl"&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;number&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;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;updated_at&lt;/span&gt;&lt;span class="dl"&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;number&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="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;Then we’ll create the Task model under &lt;code&gt;src/db/Task.js&lt;/code&gt;&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;Model&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="s1"&gt;@nozbe/watermelondb&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;readonly&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;action&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="s1"&gt;@nozbe/watermelondb/decorators&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="c1"&gt;// Naming convention is CamelCase singular&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="nx"&gt;Task&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Model&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;table&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tasks&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

 &lt;span class="c1"&gt;// These are our own fields&lt;/span&gt;
 &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;description&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;description&lt;/span&gt;
 &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;is_complete&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;isComplete&lt;/span&gt;

 &lt;span class="c1"&gt;// These are special fields that will automatically update when the&lt;/span&gt;
 &lt;span class="c1"&gt;// record is created and updated&lt;/span&gt;
 &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;readonly&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;created_at&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;createdAt&lt;/span&gt;
 &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;readonly&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;updated_at&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;updatedAt&lt;/span&gt;

 &lt;span class="c1"&gt;// Actions are functions that you can call on the database object&lt;/span&gt;
 &lt;span class="c1"&gt;// These can be something like calculating a new field, but in this&lt;/span&gt;
 &lt;span class="c1"&gt;// case we're using them to modify the database object directly.&lt;/span&gt;
 &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;action&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;rename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&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;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;newName&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="nd"&gt;action&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;markAsDeleted&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;Following that, we need to register the model with the database. In your &lt;code&gt;index.js&lt;/code&gt; file, add these lines of code just before the register component line.&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="nx"&gt;Task&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;./src/model/Task&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;mySchema&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="s1"&gt;./src/db/schema&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;SQLiteAdapter&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;@nozbe/watermelondb/adapters/sqlite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Database&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="s1"&gt;@nozbe/watermelondb&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;adapter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;SQLiteAdapter&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
 &lt;span class="na"&gt;dbName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;BadTodo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;mySchema&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;database&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
 &lt;span class="nx"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;modelClasses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
 &lt;span class="na"&gt;actionsEnabled&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we run the android app again. &lt;code&gt;npx react-native run-android&lt;/code&gt;. If you’ve set up everything correctly, you will see the database being set up in the metro window.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--AnBr1LNs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/03/db-setup.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AnBr1LNs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/03/db-setup.png" alt="Creating an Android App with React Native, NativeBase and WatermelonDB"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;❗ Note that each time you make any changes to the database schema, you will need to uninstall the app or clear the app cache. You can do this by long-pressing the app and clicking on app info, then uninstalling it. After that, all you need to do is to run the run-android command again to reinstall the application.&lt;/p&gt;

&lt;p&gt;🧡 Bonus: If you want to know how to do relations and indices, &lt;a href="https://github.com/cjx3711/ReactNative-BadTodo/commit/2ac5d0141b89222c9386d5c832f655afa49502f1"&gt;check out github&lt;/a&gt; where I added the code for that.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We’re almost there!&lt;/p&gt;




&lt;h3&gt;
  
  
  Section 5: Hooking everything up
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/cjx3711/ReactNative-BadTodo/commit/2ac5d0141b89222c9386d5c832f655afa49502f1"&gt;💡 Code changes for this section 💡&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now comes the best part, hooking everything up so that we have a working todo app.&lt;/p&gt;

&lt;p&gt;In your &lt;code&gt;App.js&lt;/code&gt; file, add this code just above the &lt;code&gt;render()&lt;/code&gt; method.&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;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

   &lt;span class="c1"&gt;// A better way to do this would be to use observables.&lt;/span&gt;
   &lt;span class="c1"&gt;// You can find out more about this in the watermelondb docs.&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;updatePage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

   &lt;span class="c1"&gt;// We're just using the react basic state.&lt;/span&gt;
   &lt;span class="c1"&gt;// For a real app, it's recommended to use something like redux&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;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="na"&gt;taskCount&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="na"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
     &lt;span class="na"&gt;newTaskDescription&lt;/span&gt;&lt;span class="p"&gt;:&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="c1"&gt;// Refreshes the page&lt;/span&gt;
 &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;updatePage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="c1"&gt;// Note this is the table name 'tasks' and not the model name 'Task'&lt;/span&gt;
   &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tasksCollection&lt;/span&gt; &lt;span class="o"&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;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tasks&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="c1"&gt;// query() without any parameters will get all the records in the table&lt;/span&gt;
   &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;taskCount&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;tasksCollection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;fetchCount&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;tasks&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;tasksCollection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&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="nx"&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;Tasks count&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;taskCount&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;setState&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;taskCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;taskCount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;


 &lt;span class="c1"&gt;// Adds a new task from the newTaskDescription varaible in the component state&lt;/span&gt;
 &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;add&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;tasksCollection&lt;/span&gt; &lt;span class="o"&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;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tasks&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newTaskDescription&lt;/span&gt; &lt;span class="o"&gt;==&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;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Cannot add a blank task&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="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
   &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newTask&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;tasksCollection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;task&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;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="o"&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;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newTaskDescription&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       &lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isComplete&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&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="nx"&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;Adding new task&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;newTaskDescription&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&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="nx"&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 new task&lt;/span&gt;&lt;span class="dl"&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;updatePage&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 should actually not delete the task, but instead set the&lt;/span&gt;
 &lt;span class="c1"&gt;// is_complete variable to true. But this is just a proof of concept.&lt;/span&gt;
 &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;setChecked&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;task&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="nx"&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;Setting checked&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
   &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;updatePage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;

 &lt;span class="c1"&gt;// Renames a task. It's currently hardcoded to Renamed!&lt;/span&gt;
 &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;rename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;task&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="nx"&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;Renaming&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Renamed!&lt;/span&gt;&lt;span class="dl"&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;updatePage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;

 &lt;span class="c1"&gt;// This is called when the text is changed.&lt;/span&gt;
 &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;onChangeText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="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;setState&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;newTaskDescription&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
   &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that the code assumes there is a database object passed in from the props? Well, let’s pass the database object into the props then. In &lt;code&gt;index.js&lt;/code&gt;, replace the line:&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;AppRegistry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;registerComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;appName&lt;/span&gt;&lt;span class="p"&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="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;with&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="nx"&gt;React&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;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;AppWrapper&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&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;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;App&lt;/span&gt; &lt;span class="p"&gt;{...{&lt;/span&gt;
   &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="sr"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;AppRegistry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;registerComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;appName&lt;/span&gt;&lt;span class="p"&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="nx"&gt;AppWrapper&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a wrapper where we get to pass in the database object.&lt;/p&gt;

&lt;p&gt;Now we need to make sure our render method actually makes use of the component state. Replace the whole render method with the code below:&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;render&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
     &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Container&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
       &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Header&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
         &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Body&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
           &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Title&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Really&lt;/span&gt; &lt;span class="nx"&gt;Bad&lt;/span&gt; &lt;span class="nx"&gt;Todo&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Title&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;         &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Body&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;         &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Right&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
       &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Header&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;       &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Content&lt;/span&gt; &lt;span class="nx"&gt;padder&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
         &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Text&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
           &lt;span class="nx"&gt;There&lt;/span&gt; &lt;span class="nx"&gt;are&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;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;taskCount&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;tasks&lt;/span&gt;
         &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Text&lt;/span&gt;&lt;span class="err"&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;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&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;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
               &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ListItem&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                 &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Grid&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                   &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Row&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                     &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CheckBox&lt;/span&gt; &lt;span class="nx"&gt;checked&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isComplete&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;onPress&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setChecked&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                     &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Body&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                       &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Text&lt;/span&gt; &lt;span class="nx"&gt;onPress&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&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="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Text&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                     &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Body&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                   &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Row&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                 &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Grid&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;               &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/ListItem&lt;/span&gt;&lt;span class="err"&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Form&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
           &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Item&lt;/span&gt; &lt;span class="nx"&gt;floatingLabel&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
             &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Label&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;New&lt;/span&gt; &lt;span class="nx"&gt;Task&lt;/span&gt; &lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Label&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;             &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Input&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&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;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newTaskDescription&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;onChangeText&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;onChangeText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;           &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Item&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;         &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Form&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;       &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Content&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;       &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Footer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
         &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;FooterTab&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
           &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="nx"&gt;full&lt;/span&gt; &lt;span class="nx"&gt;onPress&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
             &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Text&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Add&lt;/span&gt; &lt;span class="nx"&gt;Task&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Text&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;           &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;         &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/FooterTab&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;       &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Footer&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;     &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Container&lt;/span&gt;&lt;span class="err"&gt;&amp;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;Once done, you should have a fully* functional working todo app!&lt;/p&gt;

&lt;p&gt;&amp;lt;!--kg-card-begin: html--&amp;gt;&lt;small&gt;* definition of fully may vary.&lt;/small&gt;&amp;lt;!--kg-card-end: html--&amp;gt;&lt;/p&gt;

&lt;p&gt;If you have issues, check the troubleshooting section.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Result
&lt;/h2&gt;

&lt;p&gt;We now have a functional CRUD app. 🎉🎉🎉&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Creation&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
To add a task, type a description in the text field, and click on Add Task at the bottom&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Retrieval&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Well, the task list itself is an example of retrieval&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Tapping on the task name will rename it to Renamed!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Delete&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Checking the task will delete it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6MfikzPj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/03/final-app.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6MfikzPj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/03/final-app.png" alt="Creating an Android App with React Native, NativeBase and WatermelonDB"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Further Improvements
&lt;/h2&gt;

&lt;p&gt;This app is nowhere near what you would consider complete or even production-ready, but I hope it helped you to understand the basics of an app with a database.&lt;/p&gt;

&lt;p&gt;For further reading, please take a look at the &lt;a href="https://docs.nativebase.io/"&gt;NativeBase&lt;/a&gt; and &lt;a href="https://nozbe.github.io/WatermelonDB/index.html"&gt;WatermelonDB&lt;/a&gt; documentation, they are pretty detailed.&lt;/p&gt;

&lt;p&gt;These are things that should have been done but aren’t.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Using a router for page navigation&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
An app does not exist as a single page. We definitely need more than one page. In this case, we need a routing library. One that I see come up pretty often is &lt;a href="https://reactnavigation.org/"&gt;React Navigation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Using a library for state management&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
As apps get much bigger, allowing your state to be modified willy nilly by anyone is a recipe for many many bugs. A state management library like &lt;a href="https://redux.js.org/"&gt;Redux&lt;/a&gt; will help you keep your app state sane.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Using observables for the page updates&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Needing to call the refresh method to update your page is fine, but that sometimes leads to unnecessary updates. You should instead use the observable paradigm which is described in &lt;a href="https://nozbe.github.io/WatermelonDB/Components.html#install-withobservables"&gt;this section of the watermelon docs&lt;/a&gt;. Observables mean that a list or text field will automatically update when the underlying database updates. That’s the whole point of using WatermelonDB as opposed to other React Native databases.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Setting this up for iOS as well&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
I don’t like Apple so I didn’t bother (yes I know I’m using a Mac, it was required for my job okay?), but the whole point of React Native and NativeBase is to allow deploying to multiple devices with a single code base.&lt;/p&gt;

&lt;p&gt;It’s probably better to start your project off testing with both iOS and Android, instead of trying to get it running on iOS once your app is complete. You might end up finding out that some things you did would not work on iOS and you might spend way more time than necessary trying to “port” it over to iOS.&lt;/p&gt;



&lt;p&gt;As you can see, creating an app is pretty easy for a web developer these days. With so many libraries out there you don't even need to learn a new language to code a simple app. If you do it right, you can even use the same codebase for your website since WatermelonDB supports web apps as well.&lt;/p&gt;


&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Troubleshooting Section 3: Installing WatermelonDB
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gvkFHCv---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/03/watermelon-trouble.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gvkFHCv---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/03/watermelon-trouble.png" alt="Creating an Android App with React Native, NativeBase and WatermelonDB"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I ran into some issues when trying to run the android app. The error message is pretty misleading. It tells you that you should check if your Android development environment is set up, but we already know that is working since we’ve been able to run the app before. The real error is above. In this case it was simply that my android virtual device had run out of storage space.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Could not resolve project :watermelondb.
     Required by:
         project :app
      &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Unable to find a matching configuration of project :watermelondb:
          - None of the consumable configurations have attributes.

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

&lt;/div&gt;



&lt;p&gt;In this case, this means that the WatermelonDB installation likely doesn’t exist for some reason. If you take a look at the &lt;code&gt;settings.gradle&lt;/code&gt; you will see that we are sourcing the watermelon db installation from this folder: &lt;code&gt;../node_modules/@nozbe/watermelondb/native/android&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;So you should check if that folder exists. In my case it did not and I could not seem to install it. So I deleted the &lt;code&gt;node_modules&lt;/code&gt; folder, updated my node to a newer version and tried to reinstall WatermelonDB again. Then it worked.&lt;/p&gt;

&lt;h3&gt;
  
  
  Troubleshooting Section 5: Hooking everything up
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;WARN Possible Unhandled Promise Rejection &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;: 0&lt;span class="o"&gt;)&lt;/span&gt;:
TypeError: undefined is not an object &lt;span class="o"&gt;(&lt;/span&gt;evaluating &lt;span class="s1"&gt;'columnSchema.type'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means a column is named wrongly, check the column names in the &lt;code&gt;Task.js&lt;/code&gt; file and in the &lt;code&gt;schema.js&lt;/code&gt; file&lt;/p&gt;




</description>
      <category>reactnative</category>
      <category>android</category>
      <category>mobile</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Tracking my Expenses</title>
      <dc:creator>Chai Jia Xun</dc:creator>
      <pubDate>Tue, 02 Mar 2021 09:52:02 +0000</pubDate>
      <link>https://dev.to/cjx3711/tracking-my-expenses-f29</link>
      <guid>https://dev.to/cjx3711/tracking-my-expenses-f29</guid>
      <description>&lt;h2&gt;
  
  
  10 years of expense tracking
&lt;/h2&gt;

&lt;p&gt;I began tracking my expenses since 2009 or so. At that time, I was given pocket money monthly, but most of it disappeared pretty much instantly. I had so many wants and buying things gave me a small dopamine hit. Insert something about capitalism here I guess. I didn’t even have a credit card yet, and here I was needing to borrow money from my parents just to be able to buy food. After getting one scolding too much, I decided to start tracking my expenses.&lt;/p&gt;

&lt;p&gt;It began as a tedious manual entry process into the budget app on my windows mobile. Then I moved to an app called moBudget on Windows Phone 7. Then I realised it was not great to keep everything on a single phone in case it’s all lost, and also Windows Phone was pretty much dying at that point. So I searched for a cross platform cloud synced alternative, and discovered Toshl (they used to support windows phone!). I’ve been using it ever since.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--PZA4rEOq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/EMW7TaQr5xESjYDfHAxfYcxTuiqr3RiYhGD8jU2dFjx26ngeW-av9i4SYh8rGORRRLvzrbBHu3hWbIzuGwgpzu1TF_YZD4nToCEXSUQDO9cPYE4TIcuHJlLu1pKsUsOcF8mTUwK5" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--PZA4rEOq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/EMW7TaQr5xESjYDfHAxfYcxTuiqr3RiYhGD8jU2dFjx26ngeW-av9i4SYh8rGORRRLvzrbBHu3hWbIzuGwgpzu1TF_YZD4nToCEXSUQDO9cPYE4TIcuHJlLu1pKsUsOcF8mTUwK5" alt="Tracking my Expenses"&gt;&lt;/a&gt;Interesting fact: Somehow moBudget still exists on the Microsoft Store.&lt;/p&gt;

&lt;h2&gt;
  
  
  My current tracking system
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://toshl.com/"&gt;Toshl&lt;/a&gt; &amp;amp; &lt;a href="https://www.splitwise.com/"&gt;Splitwise&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--w81mb9Qn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/03/appss.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--w81mb9Qn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/03/appss.png" alt="Tracking my Expenses"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://toshl.com/"&gt;Toshl&lt;/a&gt;&lt;/strong&gt; is a free cross platform personal finance tracking app that I really like. It’s simple and does the job well, even comes with an API. Paid features exist and I think it’s well worth the money. But even without the paid features, they have the essential functionality provided for free.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.splitwise.com/"&gt;Splitwise&lt;/a&gt;&lt;/strong&gt; is a free cross platform debt tracking app that keeps tabs on how much you owe your friends and vice versa. Also comes with an API. I have not seen the need to pay for this yet since the free features do everything I need it to do.&lt;/p&gt;

&lt;p&gt;This is the system I’ve been using for the past 5 years.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Before the end of each pay cycle, I’ll go through my bank statements and input all the expenses into either Toshl or Splitwise. Toshl is where all my own expenses go. Splitwise is when I need to split bills with friends. If I paid in cash, I would input it immediately so I don’t forget. (In an ideal world, I would input expenses as I spent them but procrastination.)&lt;/li&gt;
&lt;li&gt;The next thing I have to do is to copy my share of each expense from Splitwise into Toshl. This part might seem tedious, but it was necessary to do since it would not be logged in one place otherwise. This allows me to use toshl for my expenses only and splitwise to track debts to friends. It also supports balances in multiple currencies.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I would typically do these things while on the train, or during commute times so I don’t really waste too much time. To further motivate me to do this, I have a rule that I can only pay off my credit cards once I have audited my expenses. So when my credit card payments are due, I'd be forced to audit and log my expenses.&lt;/p&gt;

&lt;h3&gt;
  
  
  Downsides
&lt;/h3&gt;

&lt;p&gt;This system does not calculate the actual cash I have in my bank or my net worth, in fact it's designed to be detached from that number. This only calculates how much I spent, even if the money I spent was borrowed from a friend. It doesn’t consider lending my friends money an expense either. So if I were to pay for a $100 meal for my friend, it would just go into splitwise as something they owe me. My expense tracking would not reflect it, unless I log it as a treat in Toshl (then they wouldn’t owe it to me anymore).&lt;/p&gt;

&lt;p&gt;The idea behind this is to get a sense of exactly how much I spend, and not to worry about other things like cashflow. This is based on the assumption of eventual consistency, meaning if I were to key everything in and settle all my debts, the numbers would match up. My bank balance never reflects the actual amount of money I have left. In fact, I typically keep most of my money in investments or savings accounts which are more tedious to access. My main bank balance typically remains in the low hundreds to keep me from spending it. Which brings me to my next point.&lt;/p&gt;

&lt;h2&gt;
  
  
  The reason I track everything
&lt;/h2&gt;

&lt;p&gt;The main reason is making sure I feel the pain of spending money. As mentioned earlier I used to have problems controlling my spending. This was made worse with credit cards, and recently the ability to simply tap my watch to make payments. When I'm auditing all my expenditures, I remind myself again about all the money I've spent and then I lose my desire to spend more. It makes me think multiple times about new purchases and provides motivation not to buy stuff.&lt;/p&gt;

&lt;p&gt;I also really like having data, and the ability to run an analysis on it sometime in the future. This also helps me reduce my spending because when I buy stuff I know that future-me will look at all the money I spent and judge current-me. And who wants to be judged by future-me? That guy is probably being all smug and I-told-you-so about the things that I bought and will never touch again.&lt;/p&gt;

&lt;p&gt;This also allows me to keep my bank balance artificially low so that I don't have any excuses to spend money.  I used to look at my bank balance to get an idea of how much I've spent that month. But more often than not, when I see a number that's slightly higher than usual in there, I feel compelled to spend it all on the next shiny new thing I see. Using this system detaches the actual amount I have from the money I've spent.&lt;/p&gt;

&lt;p&gt;Lastly, it gives me some time for meditation and reflection on how much money I’ve spent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fully Automated Expense Tracking Does not Work
&lt;/h2&gt;

&lt;p&gt;Toshl and many other finance apps provide some tier that allows you to link your bank accounts and credit cards and they’ll automatically add and categorise all your expenses for you. This might sound great at first glance, but there are so many underlying issues with it.&lt;/p&gt;

&lt;p&gt;Not everyone uses credit cards, except maybe in America. No matter how good your automation is, it obviously wouldn’t be able to track cash purchases.&lt;/p&gt;

&lt;p&gt;When you make a purchase on someone's behalf or if you want to split a purchase, it won't be able to categorise it properly. Also there are some transactions that I would want to split into separate entry.&lt;/p&gt;

&lt;p&gt;I tried the automated tracking for a while, but it created additional entries and it messed up the system I was already using. In the end, because there were way too many edge cases, I ended up reverting to manual entry. In any case, the time I spent doing data entry was a pretty good time for self reflection so it wasn’t that bad.&lt;/p&gt;

&lt;p&gt;It's not all manual labour though, I recently wrote a script to transfer data from splitwise to toshl. So at least that's one part automated away.&lt;/p&gt;

&lt;h2&gt;
  
  
  Some automation can work
&lt;/h2&gt;

&lt;p&gt;Before this script existed, I was spending about an hour a month manually copying and auditing my transactions from splitwise and toshl. It took me about 3 years of doing this before I finally &lt;a href="https://github.com/Honoo/splitwise-to-toshl"&gt;got around to automating this&lt;/a&gt;. Writing this script was easy, it simply required hooking up both platforms' apis. The only concern I have is that this depends on the companies to survive, and I've learnt that I can't take that for granted. But if any platform were to go down I have more important things to worry about, like migrating my data out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;I think expense tracking is an extremely important thing to do to help people manage their finances. Many banks have been building expense analysis features into their apps and websites for this reason. But unless you keep all your expenditures on cards from one bank, you'll never really have a good overview of all your expenses and can't make good spending decisions. This is still a problem that won't be solved unless a single company/bank dominates all transactions and physical cash becomes a thing of the past. That's not happening anytime soon, so this is the next best thing.&lt;/p&gt;

&lt;p&gt;In a future post I plan on running some analyses on my expenditures over the past decade.&lt;/p&gt;

</description>
      <category>tracking</category>
      <category>finances</category>
      <category>automation</category>
    </item>
    <item>
      <title>Automatically Posting to dev.to from Ghost</title>
      <dc:creator>Chai Jia Xun</dc:creator>
      <pubDate>Wed, 24 Feb 2021 10:48:25 +0000</pubDate>
      <link>https://dev.to/cjx3711/automatically-posting-to-dev-to-from-ghost-3hc8</link>
      <guid>https://dev.to/cjx3711/automatically-posting-to-dev-to-from-ghost-3hc8</guid>
      <description>&lt;p&gt;I wouldn't typically bother posting something so short, but this is really more to test how well the syndication works.&lt;/p&gt;

&lt;p&gt;I've recently heard of dev.to from my friend &lt;a href="https://tenzhiyang.com/"&gt;Ten&lt;/a&gt;. Seemed like a cool place to hang out and I figure I've nothing to lose anyway. So I tried to use the auto syndication feature to port my blog over to dev.to.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This does not just apply to Ghost but any website that supports and rss feed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Even though I've been using ghost for years, I only learnt today that my blog has an rss feed. To be fair I've always known that platforms provide rss feeds but I've never tried it on my blog. Well, it turns out it's just chaijiaxun.com/rss.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Vb4En9wG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/02/rss.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Vb4En9wG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/02/rss.png" alt="Automatically Posting to dev.to from Ghost"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Porting the site over
&lt;/h1&gt;

&lt;p&gt;Syndicating the site is just a matter of going under &lt;code&gt;settings &amp;gt; extensions &amp;gt; Publishing to DEV Community from RSS&lt;/code&gt; and filling up the one field. Not having to spend a long time writing scripts felt so great.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5Lkfs9_o--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/02/settings.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5Lkfs9_o--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/02/settings.png" alt="Automatically Posting to dev.to from Ghost"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Publishing the posts
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SeVwv7xm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/02/post_list.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SeVwv7xm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/02/post_list.png" alt="Automatically Posting to dev.to from Ghost"&gt;&lt;/a&gt;Yay my posts are all there&lt;/p&gt;

&lt;p&gt;Now I feel really dumb having to say this, but in order to publish a post, you need to edit the text of the post. I spent a good 10 minutes searching the website for a publish button.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CH2AsgPj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/02/publishing.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CH2AsgPj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/02/publishing.png" alt="Automatically Posting to dev.to from Ghost"&gt;&lt;/a&gt;&lt;/p&gt;
This is the no frills basic editor. You need to change the false to true to publish the post.


 

&lt;p&gt;And finally, we're done. &lt;a href="https://dev.to/cjx3711/my-kickstarter-journey-1nci"&gt;Here's my previous post on dev.to&lt;/a&gt;. Maybe I'll post to medium next.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--WQOBbHpi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/02/image.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--WQOBbHpi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/02/image.png" alt="Automatically Posting to dev.to from Ghost"&gt;&lt;/a&gt;&lt;/p&gt;
Both websites side by side






&lt;h1&gt;
  
  
  Quirks and Caveats
&lt;/h1&gt;

&lt;h2&gt;
  
  
  New Posts
&lt;/h2&gt;

&lt;p&gt;You have to set the post on dev.to to published for every new one that's posted. This is a bit of a pain as you have to go into the settings each time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Updating Posts
&lt;/h2&gt;

&lt;p&gt;Unfortunately, it does not seem like a post is automatically updated if you update it on the main site. But you can just delete it and resyndicate it in the settings and it should reappear as a draft with the new updates. You do lose the unique URL though, so you might end up just editing both posts side by side.&lt;/p&gt;

&lt;p&gt;Right now, I just want to say that the old ghost was better when it was all in markdown. It would have been easier to simply copy paste from one to the other.&lt;/p&gt;

&lt;h2&gt;
  
  
  Editor
&lt;/h2&gt;

&lt;p&gt;The editor on dev.to seems to limit me to only the basic editor for all my imported posts, even though I have my settings on the rich text one. But when I create posts directly in dev.to, I am able to use the rich text editor.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--STELkDfY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dbrr18doxcwgd35kz0kx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--STELkDfY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dbrr18doxcwgd35kz0kx.png" alt="rich_text"&gt;&lt;/a&gt;&lt;/p&gt;
In the picture before this, it is limited to the basic editor and there is no option to set the cover image.


 

&lt;h2&gt;
  
  
  Cover images
&lt;/h2&gt;

&lt;p&gt;The cover image on the ghost blog just appears as a regular image at the top of the post. I have not figured out how to update the cover image without the rich text editor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Previews
&lt;/h2&gt;

&lt;p&gt;Since there are no cover images, the link previews don't really show the cover image picture. I also don't get to edit the preview excerpt. (Or maybe I do and I haven't figured that out)&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DUWQG89S--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tai611022q5lcb00irlj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DUWQG89S--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tai611022q5lcb00irlj.png" alt="previews"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>My Kickstarter Journey</title>
      <dc:creator>Chai Jia Xun</dc:creator>
      <pubDate>Tue, 12 Jan 2021 16:10:49 +0000</pubDate>
      <link>https://dev.to/cjx3711/my-kickstarter-journey-1nci</link>
      <guid>https://dev.to/cjx3711/my-kickstarter-journey-1nci</guid>
      <description>&lt;p&gt;It's been a year since I started working on my Kickstarter project. Kickstarter is running their Make100 campaign again and I have finally gotten around to finishing up this post. It is one of the most fulfilling things I’ve done in a long time. You can read about the creation of the product on these two blog posts &lt;a href="https://dev.to/cjx3711/how-did-i-build-lifeclocc-4g7f-temp-slug-5640012"&gt;here&lt;/a&gt; and &lt;a href="https://dev.to/cjx3711/creating-a-life-clock-kh8-temp-slug-1000137"&gt;here&lt;/a&gt;, but this post will be focused on my experience with crowdfunding and the issues I faced during the fulfilment process.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Disclaimer: While this is a success story, I attribute a lot of it to luck as well. This is not exactly a guide on how to make a successful kickstarter project. Take it as just another story to influence your decision making and discover the different considerations needed to make a kickstarter project work.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  Context
&lt;/h1&gt;

&lt;p&gt;Before we start, I figured I should give some context to this campaign so you know what to expect. This is &lt;strong&gt;not&lt;/strong&gt; a hugely successful campaign that raised hundreds of thousands and has tens of thousands of backers. It’s a small electronics project that had about 90 backers and raised about $4000. The goal was never to open a huge production warehouse in China and make a thousand units. I wanted to share my creation with the world, and I had planned from the start to assemble everything myself.&lt;/p&gt;

&lt;p&gt;Initially, I wasn’t even going to crowdfund the product, if it wasn’t for &lt;a href="https://www.kickstarter.com/discover/advanced?page=1&amp;amp;sort=magic&amp;amp;tag_id=218"&gt;Kickstarter’s Make100 campaign&lt;/a&gt;, I wouldn’t even have bothered to make a Kickstarter project. Make100 is a campaign which encourages small time creators like me to make a small production run of things, up to 100 pieces. In fact the campaign is running right now (assuming you’re reading this in Jan 2021). If not they seem to run it once a year in Jan.&lt;/p&gt;

&lt;p&gt;That said, this still worked out way better than I thought it would. I was expecting maybe 10 people to notice it and I would get about 10 backers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BGZpMCIb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/01/thumb-1.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BGZpMCIb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/01/thumb-1.jpg" alt="My Kickstarter Journey"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Before the Campaign
&lt;/h1&gt;

&lt;p&gt;This part focuses on building the crowdfunding part of the campaign. Needless to say, this assumes that you have figured out the finances, supply chains, and have a good idea of how you would fulfil the project if the crowdfunding campaign is successful. You should include all this information in the campaign, not only to build trust in a would be backer, but also as a mental exercise to make sure you didn't miss out on anything important. For my campaign specifically, I had planned to purchase everything from aliexpress.com, the same place I bought all the parts for my prototypes. For the PCB, I used jlcpcb.com, which is also where I bought my PCB prototypes. Since I was planning on assembling it myself, I did not have to worry about the assembly part of the factory.&lt;/p&gt;

&lt;p&gt;The primary reason for choosing Kickstarter was their make100 campaign. I also am more familiar with them having backed a whole bunch of projects before. The first thing I did was to look at other kickstarter campaigns to get an idea of how to structure campaigns. I recorded a list of campaigns that were similar to my own in terms of campaign size as well as product category. I also had another list of campaigns that I definitely did not want to follow. These included those that looked like they put very little effort into the campaign or video. I then used these to guide the creation of the story part of my campaign.&lt;/p&gt;

&lt;p&gt;I originally had no plans for making a video either, but for all the Kickstarter guides I looked at said that having a video improves the chances of a successful campaign. So I ended up creating one. For that, I did the same thing and watched a bunch of other kickstarter videos to figure out what I wanted to include in mine. I am embarrassed to admit that I ended up backing a few additional projects in the course of my research.&lt;/p&gt;

&lt;p&gt;One thing that is usually mentioned in crowdfunding guides is pre-marketing your product, and having a mailing list. This is something that I did not do at all as I was banking on kickstarter being my main source of marketing. I did try to look for communities that I could market to, but at the same time, unsolicited marketing is usually unwelcome in many places. I posted it on the kickstarter subreddit after launch, but I don’t think it helped much. I guess that advice is usually targeted at kickstarters that aim to get thousands of backers, and not a small time creator like me. One more successful one was me sending an email to Hackaday. They featured my project on their site and I'm pretty sure I managed to get at least 2 backers from them.&lt;/p&gt;

&lt;p&gt;Another trick I read somewhere was to keep your goal low. This is because people are more likely to back a kickstarter that has met their goal, and having your campaign reach more than a few hundred percent of the goal is much more impressive. While I don’t typically like to resort to marketing tricks like this, I do have to agree that having a large % is much nicer to look at. Taking this to the extreme, I have seen kickstarters that set their goal to be $1. So even $100 in pledges will result in 10000% of the goal. This might sound like a great idea, and slightly shammy, but one thing to remember is you should always expect the worst case. You should assume that you only get the goal amount. And if that were to happen, can you still fulfil the rewards without making a loss? For me, that amount was $200. Meaning even if only 5 people backed my project, I would still be able to fulfil those orders without making any loss.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1svQiAQv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/01/large_funds.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1svQiAQv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/01/large_funds.png" alt="My Kickstarter Journey"&gt;&lt;/a&gt;Looks real nice at first glance till you do the math&lt;/p&gt;




&lt;h1&gt;
  
  
  During The Campaign
&lt;/h1&gt;

&lt;p&gt;The campaign was set to last a month. This is naturally one of the most stressful times and you get to see your numbers slowly rising (or falling).&lt;/p&gt;

&lt;p&gt;Kickstarter provides a good project funding report page that you can use to track your progress. But there is always space for more graphs. One such resource is kicktraq. They provide so much more information than Kickstarter and the best part is that it happens automatically. They scrape and track every project on Kickstarter and even provide the projected end amount so you can see how your project is trending.&lt;/p&gt;

&lt;p&gt;&lt;a href="http://www.kicktraq.com/projects/cjx3711/lifeclocc-your-life-countdown-and-motivational-companion/#chart-daily"&gt;http://www.kicktraq.com/projects/cjx3711/lifeclocc-your-life-countdown-and-motivational-companion/#chart-daily&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--WDZckmq4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/01/kicktraq1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--WDZckmq4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/01/kicktraq1.png" alt="My Kickstarter Journey"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1DUwZBiz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/01/kicktraq2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1DUwZBiz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/01/kicktraq2.png" alt="My Kickstarter Journey"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Only 2 backers came from my website link. Which was surprising since I wasn’t doing anything to actively advertise my website. I guess those people saw the project on another site, then Googled lifeclo.cc and got my website.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bbhdJI23--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/01/referrers.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bbhdJI23--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/01/referrers.png" alt="My Kickstarter Journey"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stretch Goals&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To be honest I had no plans for stretch goals, but I felt like something that everyone else was doing and I wanted to be part of the party. I ended up putting various colour combinations and a webapp. I will come to regret that decision later as it added a lot more logistical overhead, but we will touch on that part later.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Follow up campaign&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One thing I could have done but did not do was to create a follow up campaign on Indiegogo. From what I read, this is a strategy to get more exposure for the campaign. Indiegogo allows you to import your goal and funding progress from kickstarter so you won't have to start from 0. In my case I just wanted to finish my project and needed a fixed number of units to manufacture.&lt;/p&gt;

&lt;p&gt;Other than that and keeping my backers up to date. I didn't do much else for the duration of the campaign. I didn't run any ads or post it anywhere else. This was also an experiment to see how a campaign performs with only Kickstarter’s discovery algorithm.&lt;/p&gt;




&lt;h1&gt;
  
  
  After the Campaign: Logistics and Fulfilment
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Fulfilment Logistics and Spreadsheets
&lt;/h2&gt;

&lt;p&gt;Even though fulfilment logistic platforms such as BackerKit exists, I chose not to use any. Those help you with order management and automates some communication with the backers . For my project, I felt that since I was only going to deal with 100 backers max, it wouldn’t be too difficult to figure out the quantities for what I needed to buy from the spreadsheet Kickstarter provides.&lt;/p&gt;

&lt;p&gt;I was wrong. I ended up having some spaghetti-code-like Google Sheets document with too many formulae and queries just to figure out the number of parts I needed to buy in total. In addition, I had to make sure that each backer received the correct order and that it was shipped to the right address.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--B7aGrtl9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/01/parts-sheet.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--B7aGrtl9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/01/parts-sheet.png" alt="My Kickstarter Journey"&gt;&lt;/a&gt;Spreadsheet for buying the correct number of parts.&lt;/p&gt;

&lt;p&gt;One huge thing I overlooked was the fact that I gave my backers many different options. They could choose the edition, the board colour, the light colour, and if they wanted it assembled or not. This did not seem like a lot, but that is 2 ^ 4 options, giving me 16 unique products to prepare. This did not even take into account special orders with a mix of colours or multiple items. Since I was not using any order management system, this also meant that whenever people wanted to add on more units, I would have to correspond with them via email or the messaging system, and then put a new row on my spreadsheet.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kpyk5KVo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/01/fulfilment_sheet.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kpyk5KVo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/01/fulfilment_sheet.png" alt="My Kickstarter Journey"&gt;&lt;/a&gt;Fulfilment spreadsheet horrors&lt;/p&gt;

&lt;p&gt;You also only get to send out the backer survey after the campaign ends. This means that you can only start planning the logistics after the project is complete. I notice that some bigger Kickstarter projects tend to send a message asking their backers for what options they want before the project ends to get an idea of roughly how many units they need to manufacture.&lt;/p&gt;

&lt;p&gt;In my case, I made the wrong assumption that I could start planning immediately after the Kickstarter campaign ended. What I did not consider was that people generally take some time to respond to the survey. I ended up waiting 2 weeks before extrapolating the units and adding some buffer to the final order amount before placing the order.&lt;/p&gt;

&lt;p&gt;At this point, I still had plenty of time to fulfil my project on time. I had added 4 weeks of buffer time thinking it would be enough. It wasn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  Covid Issues
&lt;/h2&gt;

&lt;p&gt;The campaign ended at the beginning of February. I only managed to place my orders in the middle of February. Based on previous shipments, orders took about 4 - 6 weeks to arrive, so I was expecting the items to arrive mid to end of March. I would then be able to assemble them and ship them out.&lt;/p&gt;

&lt;p&gt;As we all know, covid happened to hit China and the US pretty hard during that time, and that completely destroyed the supply chain. I did end up having pretty large delays due to that. I even put in a second order of some components after USPS failed to deliver them. Due to that, I now have an additional 100 micro USB cables lying around at home that I can’t bear to throw away.&lt;/p&gt;

&lt;p&gt;Every 2 weeks, I would &lt;a href="https://www.kickstarter.com/projects/cjx3711/lifeclocc-your-life-countdown-and-motivational-companion/posts/2833400"&gt;update my backers&lt;/a&gt; on the progress of various other things related to the project. Throughout the delays, no one seemed particularly upset about it, and were all pretty understanding. I’m happy to have gotten such nice backers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Production Logistics
&lt;/h2&gt;

&lt;p&gt;This caught me entirely by surprise. I had planned for the assembling and soldering of the Lifecloccs to take about 1 hour per item. That's how long it took me to assemble one unit anyway.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6j91WM8N--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://chaijiaxun.com/content/images/2021/01/timelapse.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6j91WM8N--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://chaijiaxun.com/content/images/2021/01/timelapse.gif" alt="My Kickstarter Journey"&gt;&lt;/a&gt;Timelapse of the assembly process&lt;/p&gt;

&lt;p&gt;That meant I would only need about 20 hours so solder and pack the units since I had 20 orders right?&lt;/p&gt;

&lt;p&gt;This turned out to be completely wrong. Every time I started my packing line, it took me 30 minutes just to prepare everything. Then it took about 20 minutes per order to do the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Check the order&lt;/li&gt;
&lt;li&gt;Print the shipping label&lt;/li&gt;
&lt;li&gt;Pick out the correct parts&lt;/li&gt;
&lt;li&gt;Double check the parts were correct&lt;/li&gt;
&lt;li&gt;Pack them nicely, and seal the envelope&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This meant that for the remaining 60 or so orders that weren’t assembled, I still took about 2 weeks working nonstop after work to complete.&lt;/p&gt;

&lt;p&gt;All this did not include the couple of weeks I took to figure out the spreadsheets and where and how to ship things. I now see why people use crowdfunding management platforms.&lt;/p&gt;

&lt;p&gt;All in all, I feel like while my original estimates of being able to ship it in May would have been met if it were not for covid. Having the covid delays definitely helped by giving more time to sort things out. It also helped that during this time, I was working from home, as many other companies have been. That meant that I did not have to deal with commuting, and I could reset my 3D printer every 4 hours.&lt;/p&gt;

&lt;p&gt;One worry I had was that some of the packages would be mispacked or wrongly sent. This was especially true for the DIY ones where I would have no way of testing them. I’m happy to say that I came up with a pretty cool solution for this. In the picture below, on the left, I used a portable whiteboard to create a spatial packing list. On the right, I have one DIY lifeclo.cc ready to be packed. This way, I can tell what’s missing at a glance without having to do an O(n) search through the list.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TwGeiuWm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/01/packing_list.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TwGeiuWm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/01/packing_list.jpg" alt="My Kickstarter Journey"&gt;&lt;/a&gt;My visual packing list&lt;/p&gt;




&lt;h1&gt;
  
  
  Shipping Logistics
&lt;/h1&gt;

&lt;p&gt;This was figured out before the campaign even started. So when it came down to it, shipping was one of the easiest parts of my campaign fulfilment.&lt;/p&gt;

&lt;p&gt;I used easyship to handle my shipping logistics (mainly because their interface looked the nicest). Kickstarter also provides a handy guide of different companies that you can use for this purpose. In my case, I simply shipped all the units out of my house as I packed them, so I didn’t need a fulfilment company.&lt;/p&gt;

&lt;p&gt;The shipping itself was really straightforward. I just needed to import the addresses into easyship, and they would give me some shipping options. Once I made a purchase, I could just print it out and stick it onto the package. I could then drop it off at any post box near my house.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xSJ2KgVc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/01/shipment.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xSJ2KgVc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/01/shipment.jpg" alt="My Kickstarter Journey"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A convenient thing I only realised later on is that I could also get a free pickup if you are shipping it with USPS. Since they deliver mail to you anyway and the package is prepaid, the postman can just pick up the package if you tell them to and leave it in your mailbox. Again, something that I am fortunate to be in the US for.&lt;/p&gt;

&lt;p&gt;I wanted to keep a log of all the shipments I made. This was done by taking pictures of all the parts and the shipping label before I packaged them. By doing this, I would have something to reference if I get reports of missing pieces or if I packed wrong parts. To be honest I have no idea how larger projects handle this part, but here’s an idea for anyone who is doing this by themselves.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--C7RlhXUJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/01/logging.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--C7RlhXUJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/01/logging.jpg" alt="My Kickstarter Journey"&gt;&lt;/a&gt;Logging shipments with pictures&lt;/p&gt;

&lt;p&gt;I feel fortunate to have been in the United States at the time of my campaign. Shipping internationally from the US is surprisingly cheap. Many of my backers were from the US as well. I’m not sure if this is because I indicated that I was in the US, or if most of Kickstarter’s demographic is naturally in the US.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XO65Ullp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/01/backer_locations.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XO65Ullp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2021/01/backer_locations.png" alt="My Kickstarter Journey"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Shipping within the US costs only $5 a package, and shipping anywhere else in the world costs about $10 - $20. This might not sound cheap, but if I was shipping my project out of Singapore for instance, it would easily be $30 - $50 per piece.&lt;/p&gt;

&lt;p&gt;If I was based elsewhere, I guess I would have to go through some bulk shipping to a fulfilment centre in the US (or wherever my backers are located) before shipping them out from there. The US has quite an extensive infrastructure of export shipping that’s baked directly into their low international shipping prices.&lt;/p&gt;




&lt;h1&gt;
  
  
  Defects and Reshipping
&lt;/h1&gt;

&lt;p&gt;It would be naïve to expect that every single package would arrive in perfect condition and undamaged. I had a couple of units lost in transit as well. For those packages, I simply sent out another shipment while I tried to claim the shipping insurance for the missing pieces.&lt;/p&gt;

&lt;p&gt;One thing to note is that shipping insurance only works under specific conditions. When I claimed the insurance for a damaged item it was rejected because they deemed my packaging insufficient. But for 2 other packages that were lost in transit, they accepted the claims since the tracking clearly showed that it hadn't arrived yet after a long time.&lt;/p&gt;

&lt;p&gt;I even received a missing package complaint 6 months after I shipped everything. Sometimes people simply forget about the Kickstarters they back.&lt;/p&gt;




&lt;h1&gt;
  
  
  Main Takeaways
&lt;/h1&gt;

&lt;p&gt;Make sure you have plans for every step of the manufacturing and fulfilment process, and document that in the campaign as much as possible. It gives backers confidence and also works as a mental exercise so you can see if you have missed anything out.&lt;/p&gt;

&lt;p&gt;Keep your backers up to date and be completely honest with them, even if there’s bad news. Kickstarter backers are backing an idea, not a product (at least that’s how it should be) so they’ll want to see that you are doing your best to get it working.&lt;/p&gt;

&lt;p&gt;Overall I’m really glad to have had the opportunity to do this. As a software guy, I have never had the opportunity to work on a proper hardware project before. I didn't get the chance to do another Make100 this year, but I probably create another crowdfunding project sometime in the future.&lt;/p&gt;

</description>
      <category>guides</category>
      <category>making</category>
    </item>
    <item>
      <title>4 Day Water Fast Experiment</title>
      <dc:creator>Chai Jia Xun</dc:creator>
      <pubDate>Sun, 27 Dec 2020 17:13:53 +0000</pubDate>
      <link>https://dev.to/cjx3711/4-day-water-fast-experiment-1io1</link>
      <guid>https://dev.to/cjx3711/4-day-water-fast-experiment-1io1</guid>
      <description>&lt;p&gt;Since coming back to the glorious food paradise that is Singapore, I have been inhaling food non-stop, and as a result have gained 5kg. On one hand, I could exercise and diet and do all that good stuff to try and lose the weight, on the other hand, I could take it to the extreme and simply stop eating for a whole week. I chose the latter.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0FXdV3B8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2020/12/thumb-1.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0FXdV3B8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2020/12/thumb-1.jpg" alt="4 Day Water Fast Experiment"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fasting for a week was not simply a ploy to lose weight (though that was definitely a plus point). I had watched a &lt;a href="https://youtu.be/APZCfmgzoS0"&gt;youtube video&lt;/a&gt; that talked about fasting, and how if done correctly, would not be *too* detrimental. The thing that stood out to me most was the claim that after about 3 days of not eating, you stop feeling hunger pangs. That was pretty hard to believe, and I had to try it for myself. Losing weight, and saving money would merely be happy side effects. Granted I could also get terribly sick and die, but that’s a risk I was willing to take. For science.&lt;/p&gt;

&lt;p&gt;This is an account of how I did it and how it affected me.&lt;/p&gt;

&lt;h1&gt;
  
  
  Pre planning?
&lt;/h1&gt;

&lt;p&gt;I watched a single YouTube video. That’s all the actual research I intended on doing.&lt;/p&gt;

&lt;p&gt;I also mentally prepared myself, and tried to make my environment more conducive for fasting. To achieve this, I had to stop thinking about food entirely. That meant that I could not have any plans with friends, and work my schedule such that I avoided food entirely if possible. That way I would not be tempted by food and hopefully completely forget about it. That was my theory anyway.&lt;/p&gt;

&lt;p&gt;The plan was to do a 3 to 5 day fast. The first 3 days was to see how the hunger pangs affected me, and if I really couldn’t take it anymore, I would eat on the fourth day. But if everything went well and I truly stopped needing food, then I would carry on for another 2 days.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rules:
&lt;/h2&gt;

&lt;p&gt;No calories at all. Meaning that only water and unsweetened tea would be allowed. Any supplements would be a pill with only the necessary ingredients and not a sugary vitamin candy.&lt;/p&gt;

&lt;h1&gt;
  
  
  Log
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Day -1 (Sunday)
&lt;/h2&gt;

&lt;p&gt;I still ate as per normal. In fact, I had a buffet the night before my fast. It made me feel so sick of food that I did not have much issue avoiding food the following day.&lt;/p&gt;

&lt;p&gt;In hindsight eating a buffet before fasting is a terrible idea. But also I had already planned that meeting a while ago and didn’t want to cancel it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Day 1 (Monday)
&lt;/h2&gt;

&lt;p&gt;I brought 2 huge bottles of ice cold water to work with me. If I drank a lot of water, I wouldn't be hungry. At least that's what my mum kept telling me as a kid when I kept snacking and she was too lazy to prepare food for me.&lt;/p&gt;

&lt;p&gt;I told my colleagues and friends about this so that they wouldn’t ask me to go to lunch and hopefully avoid doing foodish things around me.&lt;/p&gt;

&lt;p&gt;That day, I ended up staying in the office till 11pm so that I would avoid being around my family eating.&lt;/p&gt;

&lt;p&gt;I was feeling slightly grumpy at the end of the day. On the bright side, I did not feel hungry overall since I avoided food entirely. Food directly contributes to my happiness though, and not having any was a bit irritating. At the very least, I know that I would be able to do a 1 day on 1 day off intermittent fasting thing if I wanted to.&lt;/p&gt;

&lt;p&gt;I ended off the day with a Ring Fit session to ensure I keep my metabolism up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Day 2 (Tuesday)
&lt;/h2&gt;

&lt;p&gt;I woke up pretty hungry. I don’t typically have breakfast, and I very rarely wake up wanting to eat, but today was different. My stomach was complaining from the moment I woke up. I mostly ignored it and carried on with my work day. I felt hungry again at about 1pm and 7pm, which is just around my lunch and dinner times.&lt;/p&gt;

&lt;p&gt;I also felt pretty sleepy in the middle of the day so I took a nap. I wasn't sure if it was due to the lack of food, or my usual lack of sleep.&lt;/p&gt;

&lt;p&gt;Today was also a day I discovered I needed more nutrients than just water to survive. As it turns out, you need sodium, magnesium, and potassium to function. This should have probably been discovered during the research phase, but eh. I took some sea salt, and a random pill supplement that I found that happened to say magnesium in it.&lt;/p&gt;

&lt;p&gt;The level of hunger I felt throughout the day was still bearable. I spent most of the day trying to convince my brain that I don’t need food in an attempt to trick my body into not making me feel hungry.&lt;/p&gt;

&lt;p&gt;My mood was still not great. I also started to notice food everywhere. There were ads, pictures or just mentions of food all over the place. Even my bank home page had a picture of a burger on it. The best part is that the picture is no longer there. It’s like they timed it to coincide with my fast.&lt;/p&gt;

&lt;h2&gt;
  
  
  Day 3 (Wednesday)
&lt;/h2&gt;

&lt;p&gt;I needed to attend a metalworking workshop and though I felt fine enough to drive, to be on the safe side I decided not to. Instead I had my brother drive me. I was alert enough to operate machinery during the workshop without any issue.&lt;/p&gt;

&lt;p&gt;After getting home I did crash for a couple of hours. Then I started doing work. While I felt alert enough to do work without much hindrance, I did feel sleepy throughout the day. At this point I was pretty sure it was due to the lack of food. I really just wanted to crawl into bed and sleep the week away.&lt;/p&gt;

&lt;p&gt;I willed myself to go for a run that night. It felt like shit. I usually run 5km without feeling too bad, but this time I only did 2.5 and felt way worse than usual. I'm guessing if I were to do a 2 week fast and my body were to get used to the lack of food, this would be better.&lt;/p&gt;

&lt;h2&gt;
  
  
  Day 4 (Thursday)
&lt;/h2&gt;

&lt;p&gt;Today was the day that I was supposed to forget that hunger is a thing. True enough, it happened somewhat. I did not really feel hungry throughout and didn't really have many food cravings. In fact for most of the day I felt bloated after drinking a little bit of water. I did have a dull stomach ache throughout the day though.&lt;/p&gt;

&lt;p&gt;Also I could not stop sneezing throughout the day. Is this the keto flu that people speak of? According to the Internet that's not the case, keto flu is headaches and lerthagy. Maybe I caught covid. (I didn't. I was just having weird allergies, or it could be the lack of food).&lt;/p&gt;

&lt;p&gt;Other than the constant dull stomach ache and general mood debuff I felt fine and was able to do work throughout the day. I did take a 3 hour nap in the evening before getting up and continuing my work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Day 5 (Friday)
&lt;/h2&gt;

&lt;p&gt;I decided to have dinner today. This was mainly because I had a buffet reservation for Saturday night and wanted to make sure I ease myself into eating food again. I did not want to go for a large meal after not having eaten in a week and then throw everything up. In the end, I succeeded in completing a 4.5 day fast.&lt;/p&gt;

&lt;p&gt;I had a light dinner. It did not taste as heavenly as I expected from not having eaten for a week. It felt like a perfectly normal meal, with the exception of the dull stomach ache I had been having for the past days. The only ill effect noticed was that my stomach could no longer take spicy foods. A chilli that I could typically consume without issue caused my stomach to burn.&lt;/p&gt;

&lt;h1&gt;
  
  
  The aftermath:
&lt;/h1&gt;

&lt;p&gt;Right before I broke my fast on Friday. I checked my weight, and I had lost a total of 4kg. While this sounds impressive (1kg per day) it does not take into account the food that I would have to put back into my system.&lt;/p&gt;

&lt;p&gt;Over the weekend, after a normal eating cycle, I ended up putting back on 2kg. So overall, I had a net loss of 2kg.&lt;/p&gt;

&lt;p&gt;I maintained this weight loss for a couple more weeks, confirming that I did indeed lose 2kg in body mass and not just from not having food. Unfortunately, the land of the food beckoned and I ended up putting the weight back on after a month by consuming a daily bubble tea.&lt;/p&gt;

&lt;h2&gt;
  
  
  Side effects:
&lt;/h2&gt;

&lt;p&gt;I don’t know if this was caused directly by the fasting, but it happened on the Sunday after the fast. I woke up with an extremely sharp pain in my neck and was unable to cough, sneeze, laugh, walk, turn my head, or generally function as a human for a whole week without agonising pain in my neck. I ended up getting a neck brace to keep my head in place so I could at least work. I suspect it could be potassium deficiency, or it could just be me getting really old.&lt;/p&gt;

&lt;p&gt;I also felt terribly sleepy and unmotivated throughout the week. During work I could not do tasks that required too much thinking and planning. While I tried to do other things to keep my mind off food, I found it hard to concentrate on the task at hand.&lt;/p&gt;

&lt;h2&gt;
  
  
  Learnings:
&lt;/h2&gt;

&lt;p&gt;Water fasting is completely doable if you have the moral support of your peers and have the correct mindset. In my case, my mum kept trying to feed me food and the only thing that kept me going was pure spite.&lt;/p&gt;

&lt;p&gt;One thing I noticed is that a lot of social gatherings, if not all of them, revolve around food. Short of having some unbreakable willpower, this also means that you will no longer be able to attend any social events. (Not that you should, what with covid happening anyway.)&lt;/p&gt;

&lt;p&gt;You still need some minerals, namely, sodium, potassium, magnesium. (Citation needed, that’s what I got from a few internet articles).&lt;/p&gt;

&lt;p&gt;Drinking salt water is not enough, and is also pretty disgusting.&lt;/p&gt;

&lt;p&gt;The upside is that not having to deal with anything food related meant that I did end up having more time to be productive or do other things. This was cancelled out by the naps I had to take and me generally being out of it. I suppose I was only very sleepy because my body wasn't used to having no food. I suspect that if I kept this up for more time, I would eventually adapt to it. But that is an experiment for when I go back to the US and am no longer surrounded by all the temptations.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Fasting is not simply halting food intake. You need to prepare actual minerals and keep up exercise so you don't wither away. The health benefits it provides is questionable but at least I got to write a blog post about it. I would probably do this again when I go back to US, a place where I have expressly stated my dislike for the food there. Not liking the food helps a lot in controlling my desire to consume food. Maybe I can finally lose weight permanently this time.&lt;/p&gt;

</description>
      <category>experiments</category>
    </item>
    <item>
      <title>Apple, the Esc Key and Keyboard Remappings</title>
      <dc:creator>Chai Jia Xun</dc:creator>
      <pubDate>Sun, 21 Jun 2020 05:45:10 +0000</pubDate>
      <link>https://dev.to/cjx3711/apple-the-esc-key-and-keyboard-remappings-4ipl</link>
      <guid>https://dev.to/cjx3711/apple-the-esc-key-and-keyboard-remappings-4ipl</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BjqaE8Zn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2020/06/thumb-1.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BjqaE8Zn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://chaijiaxun.com/content/images/2020/06/thumb-1.jpg" alt="Apple, the Esc Key and Keyboard Remappings"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I meant to write this post in 2017, I have managed to procrastinate 3 years before finally getting around to it.&lt;/p&gt;

&lt;h1&gt;
  
  
  Apple being Apple and pulling an Apple
&lt;/h1&gt;

&lt;p&gt;The year is 2017, and I had just started my internship at Indeed. Apple had just released their new, bold, courageous, Macbook Pro that decided that ports and physical buttons were a thing of the past. I found the backlash pretty amusing at that time. “That’s what you get for choosing to use Apple I guess.”, I mused hypocritically, using the 2015 Macbook Pro (with physical buttons) that my company gave me to work on. It became much less amusing when I was provided with the 2017 Macbook Pro (with no esc key and a really useless touch bar) on my first day of work. It dawned on me that this would be my reality moving forward, that I am subject to the whims of that big corporation that I dislike so much.&lt;/p&gt;

&lt;p&gt;Indeed would not be the first company that will insist I use a Mac to code. True enough, here I am typing this on yet another Macbook with no escape key, because everyone in Silicon Valley seems to be using a Macbook to develop websites for some reason, but I digress.&lt;/p&gt;

&lt;p&gt;As I was grappling with the loss of my escape key, I tried to convince myself that I didn't really need that key. I do not code much on vim, and I could not think of any use of the escape key in my day to day off the top of my head. Boy was I wrong. I spent the first couple of days hitting (and missing) the non existent escape key on my mac. It got to a point where I started to notice all the times where I do use the escape key that I never noticed before. Here’s the non exhaustive list that no one asked for.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Closing a dialog box in browsers.&lt;/li&gt;
&lt;li&gt;Closing dialog boxes in Slack&lt;/li&gt;
&lt;li&gt;Closing any dialog box in rubymine/android studio/intellij&lt;/li&gt;
&lt;li&gt;Closing any dialog box in VS code&lt;/li&gt;
&lt;li&gt;Did I mention closing dialog boxes?&lt;/li&gt;
&lt;li&gt;Cancelling a find and replace in code editors&lt;/li&gt;
&lt;li&gt;Cancelling multi cursors in code editors&lt;/li&gt;
&lt;li&gt;Cancelling code completion in code editors&lt;/li&gt;
&lt;li&gt;Closing the spotlight search / start menu&lt;/li&gt;
&lt;li&gt;Quitting a game or getting the menu&lt;/li&gt;
&lt;li&gt;Doing anything in vim&lt;/li&gt;
&lt;li&gt;Closing a chat in Telegram&lt;/li&gt;
&lt;li&gt;Closing the context menu when I accidentally press alt or click the wrong thing.&lt;/li&gt;
&lt;li&gt;Rage mashing the escape key when the computer is jammed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In theory, I could use my mouse to do all of the above actions, except the last one and that was simply unacceptable.&lt;/p&gt;

&lt;h1&gt;
  
  
  Possible solutions
&lt;/h1&gt;

&lt;p&gt;One solution was to get used to the touch bar and accept it as a fact of life. But this would mean that when I switch back to a real keyboard on a real computer, I would end up having to deal with an escape key that felt different.&lt;/p&gt;

&lt;p&gt;Another solution was to simply use an external keyboard at all times. But this meant that I would not be able to use my laptop in bed. (Incidentally, I have solved this problem and I will be writing another post about it. Maybe it’ll take another 3 years for me to do so.)&lt;/p&gt;

&lt;p&gt;The solution that I arrived at was to remap my caps lock key to escape. It actually made the most sense and I’m surprised that I did not think of doing that much earlier. I guess I never needed a solution to a problem I never really had.&lt;/p&gt;

&lt;p&gt;On a tangential note, keys that change the state of the keyboard are terrible. I strongly believe that a keyboard should be stateless. Meaning that you should never have to keep track mentally or visually which state your keyboard is in if you aren’t actively pressing anything. The caps lock key has done nothing for me except cause typos. I can very confidently say that I have only  enabled caps lock lock key by mistake. 3 years later, I have not once missed having caps lock.&lt;/p&gt;

&lt;h2&gt;
  
  
  Remapping on a Macbook
&lt;/h2&gt;

&lt;p&gt;Remapping the key on a mac was straightforward enough. Apple had apparently decided that some people might want to remap the escape key and had included it as an option in the latest MacOS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Remapping on Windows with Software.
&lt;/h2&gt;

&lt;p&gt;On Windows, the remapping was also straightforward enough. I was using a Razer BlackWidow at the time. It allowed me to remap any key on my keyboard using their Razer Sypnapse software. Even if I didn't have Razer Sypnapse, there are plenty more software that allow remapping on Windows. One such software is &lt;a href="https://www.autohotkey.com/"&gt;AutoHotkey&lt;/a&gt;. The benefit of software remapping is that you can also do per application key maps. But that was not a feature that I needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Remapping on MacOS with Software
&lt;/h2&gt;

&lt;p&gt;Now if I wanted to use my keyboard on my mac, I would need to remap the caps lock to escape via a software solution. I am not sure if Razer Synapse existed on a mac, and I wasn’t keen on installing it on my work laptop anyway. It wasn’t necessary, it turns out. There are plenty of key remapping software that exist on mac as well. I used Karabiner Elements to remap my caps lock key to the escape key for that particular keyboard.&lt;/p&gt;

&lt;h2&gt;
  
  
  Remapping on Windows with the Registry
&lt;/h2&gt;

&lt;p&gt;I also had a surface book at the time. The surface book was a little harder to remap. I was considering installing some software, but I came across this neat little registry entry that I could update to make all keyboards remap caps lock to escape on that computer.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;REGEDIT4
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layout]
"Scancode Map"=hex:00,00,00,00,00,00,00,00,02,00,00,00,01,00,3a,00,00,00,00,00

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

&lt;/div&gt;



&lt;p&gt;Now I realise I could easily do this on my desktop as well without using Razer’s proprietary software, but I had already set that up and was too lazy to redo that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Remapping my brains
&lt;/h2&gt;

&lt;p&gt;Now that the remapping on the computers was done, I just had to remap my brains to remove the muscle memory of hitting the non existent key. It was fairly easy considering that each time I hit the escape key on my mac, I was physically reminded that the button did not exist and I would press the caps lock button.&lt;/p&gt;

&lt;h1&gt;
  
  
  Pros and cons
&lt;/h1&gt;

&lt;p&gt;The pros of this method were obvious. In fact a quick search of &lt;em&gt;"remap caps lock to xxx"&lt;/em&gt; would return a lot of results showing you all sorts of ways to do this. The caps lock key takes up a lot of real estate on the keyboard and I think there are many people who find that it’s nothing but a nuisance. This would also make sure that I would not have to worry about Apple’s decisions around the escape key in the future. As it turns out, the backlash was strong enough that they did put the escape key back in the 2019 edition. But the damage was done and probably millions of people were left with a keyboard without an escape key.&lt;/p&gt;

&lt;p&gt;The cons are that I would have some trouble adapting to using other peoples' keyboards if they don’t remap their keys. I’ve had plenty of instances where I kept hitting caps lock on someone’s computer when I was using it. But I guess I’m optimising for the usage of my computer, and not the usage of other computers. If anything I should probably be helping them remap their caps lock keys anyway. I’m sure not many people would miss it much.&lt;/p&gt;

&lt;h1&gt;
  
  
  Future Endeavours
&lt;/h1&gt;

&lt;p&gt;It only took me a week before I got used to the new position of the escape key. That’s when I started getting interested in remapping my keyboard to be more efficient. If I could remap my brain like that, why not try one of those cool layouts like dvorak and make my typing even more efficient?&lt;/p&gt;

&lt;p&gt;I spent a month or so trying to get used to dvorak, even going as far as to change my phone’s keyboard layouts to it so that I would memorise the new key positions. In the end, too many programs use the qwerty layout to make it worth the change. Playing games will definitely require changing back to qwerty. Imagine trying to use wasd in the dvorak layout. (It’s the ,;ah keys on the regular qwerty layout.)&lt;/p&gt;

&lt;p&gt;I eventually switched back to the more familiar qwerty layout, much to the relief of my productivity. While it supposedly makes it easier to type by putting the frequently accessed keys on the home row, there are &lt;a href="https://www.theverge.com/2019/2/17/18223384/dvorak-qwerty-keyboard-layout-10-years-speed-ergonomics"&gt;many&lt;/a&gt; &lt;a href="https://dossy.org/2006/01/is-the-dvorak-keyboard-layout-faster-than-qwerty/"&gt;sources&lt;/a&gt; &lt;a href="https://dossy.org/2006/01/is-the-dvorak-keyboard-layout-faster-than-qwerty/"&gt;that&lt;/a&gt; &lt;a href="https://www.quora.com/Are-Dvorak-keyboards-really-that-much-better"&gt;say&lt;/a&gt; that it’s not that much faster than using qwerty anyway. The speed increase is mostly from the deliberate practice of good typing habits that you wouldn’t otherwise have bothered to do. Other health benefits like proper typing posture would come from an ergonomic keyboard layout and not the key layout. But that’s something that I would not discover till 2020. That’s the topic of another blog post.&lt;/p&gt;

</description>
      <category>keyboards</category>
      <category>guides</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
