ARMINCERF.com

July 18, 2021Last Updated: December 24, 2024

Making a mobile app with ClojureScript in 2021

The below 'blog' is really just an incoherent ramble from when I was making my app, if you want to read my actual post about this, go here. If you want to play with the app I made, the links are here. Also there's a playlist of various videos here, including a demo of the app.

I plan to do a 'part 2' where I move my app to krell (or perhaps make a new one). I did give it a quick go but found too many issues for my liking (js require doesn't work, can't import js files without making them into node modules, repl needs restarting when requires change). David Nolan has said before that Krell is made specifically for his company (vouch) to make their app, and his aim isn't for people like me to have the best experience making toy apps. Though I will try again in a month or so and see where it's at.

In the meantime I am working on 'site', a Clojure platform built on Crux for making webapps without writing backend code (instead you write an openAPI spec with datalog queries/rules). Me and Malcolm are posting videos of the progress here, though they are just raw streams so nothing polished.

Intro

I started working for JUXT around 4 years ago now, and on my first project we made a mobile app using ClojureScript. At the time, I was told this was a groundbreaking technological achievement, though to be honest, it didn't really feel like it. Doing simple things could sometimes take days due to bugs in the underlying rats nest of tooling, and after 6 months we couldn't even publish any updates because the version of expo we were using was no longer supported, and the new ones used a new version of react native which had too many breaking changes to justify updating everything. 🤮

We abandoned the project after that and never touched mobile apps again, but after reading about Krell, written by the core maintainer of ClojureScript, I became interested again and decided to give it another go.

State of tooling

There are a few ways to make a mobile app with Clojure. This approach uses Graalvm and Sci, and this one uses ClojureScript to build views for an otherwise native app, but currently React Native is the only realistic option if you want something 'stable'. It still won't be as stable as a native app, and nowhere near as stable as a web app, but that is just something that comes with the territory in the native app world.

React Native compiles JS into native code for either iOS or Android targets, so to use ClojureScript, we just need something to turn Clojure into JS that the React Native tools can understand.

💡 You can build RN apps for the web too, but don't go thinking that you can easily copy and paste code between 'standard' React and React Native, because React Native uses native mobile components rather than web components. There's no concept of a div, things like buttons are imported from React Native and generally, everything in your views will look pretty different if you're coming from web apps.

Here are the current options for tooling then:

  • Krell - Very simple, very few external dependencies. Designed to get the CLJS into React Native as 'natively' as possible with no added frills. You will need a Mac and Xcode to test your apps on iOS.
  • Shadow-Cljs - Many CLJS developers will already be using this for web projects, but it can build code for a React Native target too. You can either use it with raw React Native or Expo.
    • Expo - A layer on top of React Native that aims to solve many of the more frustrating parts with cross-platform mobile development. With Expo, you don't need to set up Xcode or Android studio. You can test apps on your actual phone just by scanning a QR code, and with Shadow, you will still get a REPL and hot code reloading! You can push code updates instantly without needing Apple or Google to approve them, and you can even develop and deploy an iOS app to the App Store without owning a Mac. However all of this magic comes at a cost, complexity. The main focus of this article will be evaluating the Shadow + Expo combination to see how many issues I run into.
  • Re-Natal - uses figwheel to compile the CLJS and adds some extra features for a more 'Clojure like' experience with React Native. However, the cost of these abstractions is that maintaining such a project is time-consuming, and as such, this project is now very outdated (in mobile terms at least).

What's the app?

I wanted the app to be simple enough that I could build the logic for it in a day or two, but interesting enough that I wouldn't lose motivation. I've been getting into meditation recently and thought I would make yet another meditation app, though it does have a somewhat unique twist.

To give an example, there is a popular set of meditation/breathing exercises known as 'The Wim Hof Method'. The main exercise being the following:

Wim Hof Method

Now this can be pretty daunting and difficult to remember for some, and others may find it difficult to stick to without a reminder, so Wim Hof has an app that will play audio clips of each step and keep track of things like how many cycles you have done.

This is also the case for many other activities such as yoga. An app will be made with a series of 'activities' such as 'tree position' with maybe an image showing the position and a voice explaining it. After some duration, the next step will start.

But I think all of these routines can be condensed into a simple data structure like so:

(def power-breath
  \\\{:name "Wim Hof Power Breath"
   :description "Active Deep inhale, passive exhale "
   :activities [\\{:title "Breath in"
                 :duration 2000\\\}
                \\\{:title "Breath out"
                 :duration 4000\\\}]\\})

(def exhale-hold-breath
  \\\{:name "Hold your breath"
   :description "Exhale completely and retain as long as possible"
   :activities [\\{:title "Exhale fully"
                 :duration 2000\\\}
                \\\{:title "Hold your breath for as long as possible"
                 :duration nil\\\}]\\})

(def inhale-hold-breath
  \\\{:name "Hold your breath"
   :description "Inhale deeply"
   :activities [\\{:title "Inhale deeply"
                 :duration 3000\\\}
                \\\{:title "Hold your breath"
                 :duration 15000\\\}
                \\\{:title "Exhale"
                 :duration 3000\\\}]\\})

(def wim-hof-round
  \\\{:name "Wim Hof Method"
   :description "30 cycles of power breaths and breath holding"
   :activities [\\{:title "Power Breaths"
                 :cycle-count 30
                 :activity power-breath\\\}
                exhale-hold-breath
                inhale-hold-breath
                \\\{:title "Meditation"
                 :duration nil\\\}]\\})

As you can see, this is a very flexible structure, if there is a duration a timer could show you how much time was left, if no duration exists, it relies on the user to click a button to move on. There could easily be an audio recording or image for each step etc. So all the app needs to do is:

  • Rotate through each activity and keep track of which step is the 'current activity'
  • Render each 'current activity' map (have a render component for duration, title, description etc)

I managed to set up a basic mobile app that does all of this logic in under an hour. You can see the recorded live stream of me doing so below, and the commit is here.

https://youtu.be/3HvhP2NImOQ

To set up starting point seen in the beginning of this video:

  • You can start from here. I made some initial modifications to disable hot reload in expo web, as well as tidy the indentation to my preference and remove some unwanted code. I also added some setup for clj-kondo which I would recommend to anyone. You can checkout my initial commit here for details.

Next episode I will add a nicer UI, follow me on twitch to get updated when I stream, or check this page in a few days as I will be keeping it updated. Feel free to comment on the youtube videos or here.

Detour into the land of Krell

Before I jumped into making my UI, I had a bit of a research around what I would need to do to get two important features working: HealthKit integration (to save mindfulness or fitness minutes), and background audio (Otherwise the app stops working if the user locks there phone, preferably it would act like the music app and show the 'now playing' screen).

However it seemed that both of these things could only be done if I ejected from expo. Because of this, and also some small but annoying issues with the shadow+expo setup where errors would be invisible, I decided to try and build what I have in Krell instead.

React Native without Expo

To get started with krell, you first have to setup a proper react native environment, I followed this guide. After an hour I still couldn't get Xcode to build my project, apparently many other people are also having issues but even after following all the steps in this thread, nothing was working. Eventually I figured out the issue, my dependencies were installed on an M1 Mac and something in there was not happy with that (see here if you have the same issue). I was later told that such events are 'just part of the mobile