Building mobile apps using Elm and Capacitor
Tags: elm, functional, capacitor, mobile
Published: 2023-08-18 17:08:28

---
title: Building mobile apps using Elm and Capacitor
published: true
date: 2023-08-15 06:30:00 UTC
tags: elm, functionalprogramming, capacitor, mobile
canonical_url: https://code.druchan.com/elm-capacitor
---

Native mobile app dev is still largely object-oriented driven (Kotlin, Java etc). Languages like Kotlin now have a lot of functional programming paradigms supported out of the box (e.g. Arrow).

About two years ago, I wanted to see if [Elm](https://elm-lang.com) can be used to build a meaningful Android app.

I manage my expenses on a simple Google spreadsheet. When I'm on the move, I'd jot down the expenses as notes and then type them out on the spreadsheet once I had access to my laptop. I wanted a way to add expenses directly to my spreadsheet through my phone. The Google Sheets mobile app does not offer a great UX for this.

I ended up building a (highly-personalized) [expense tracking Android app using Elm](https://github.com/chandru89new/xpns). I've been using this app ever since and has hardly needed a few updates in all this time.

Recently, I updated the "bootstrap" repo that I use to build Elm-based Android apps. (These can be used to build iOS apps as well). [You can grab the source-code / clone the repo from here](https://github.com/chandru89new/elm-capacitor-bootstrap).

But if you're interested in setting up an Elm-based Android app project yourself, here's my notes:

1. Core dependencies
2. Bundling logic – how is the project built?

#### 1. Core dependencies

For this project, I use CapacitorJS. **This means the whole application runs inside a web-view.**

Things have improved quite a bit in the web-view (and mobile browser engine) space in the recent years so building apps that run on web-views is not really a bad thing now.

For styling, I use [TailwindCSS](https://tailwindcss.com/). It's simple, clean and has one of the best styling ecosystems that one can ask for.

Custom JS glue-code has to be written to make your Elm app interact with native things (via CapacitorJS) and you'd ideally write these in ES6 or later. So, the app would need a way to be "transpiled" and/or bundled. To do this, I use [Parcel](https://parceljs.org). Not exactly the _best_ solution out there but it's far more than enough for a bootstrap. Eventually, I'd like to swap this out with [Vite](https://vitejs.dev/) to see how things work.

#### 2. Bundling logic – How is the project built?

If we were building an Elm app, this is broadly the workflow:

- write Elm code
- compile Elm to JS
- include the compiled JS in an `index.html` file and init the Elm app via `Elm.Main.init()`
- serve the `html` file and JS assets.

Building a mobile app is _almost_ the same, except for a couple of steps:

- include Capacitor-JS related code (in Javascript) – typically, Elm and Capacitor would communicate via ports,
- and because Capacitor-related code will end up being ES6 or later, use a bundler like Parcel

The bootstrap's project structure is simple:

```
public
├── css
│   ├── index.css
│   └── style.css
├── index.html
└── js
    ├── elm.js
    └── index.js

```

Here's more info:

```
public
├── css
│   ├── index.css <-- all your custom CSS goes here
│   └── style.css <-- this file gets auto-generated by the `yarn build` command
├── index.html <-- main entry-point for the project. Parcel will use this file to build/bundle the project.
└── js
    ├── elm.js <-- this file is auto-generated by Elm during the `yarn build` process
    └── index.js <-- all your custom Capacitor / other JS can go in this file (and other JS files)

```

The build step does these things:

- it compiles Elm code to JS
- it builds a minified CSS file
- then it lets Parcel bundle the whole project into a separate directory (`web`)
- and finally, it lets Capacitor "[sync](https://capacitorjs.com/docs/cli/commands/sync)" the project which is basically Capacitor copying over the `web` into the Android/iOS project folder.

#### 3. Caveats and explorations

One of the things I realized while building the personal expense tracking app was that Elm routing doesn't work. So I had to resort to using `Browser.element` and using a custom `Page` type as part of the `Model`.

Elm ports _can_ feel a little tedious to hook up with the Capacitor bridge. However, with some good [abstractions](https://github.com/chandru89new/harbor), using ports can be more streamlined.

Capacitor is not the only JS-native bridge. There are also other tools like [NativeScript](https://github.com/NativeScript/NativeScript) and it could be worth exploring how that plays with an Elm project.