Android app in Haskell, six months, no previous Android experience

Posted on 2023-07-25

What did you do?

We built an open source Android app for Private Storage, with Haskell, in six months.

Why?

When I joined in October 2022, PrivateStorage had an existing grant deadline to ship an Android app by the end of July 2023. This seemed like the most important thing to do, so we did. We also improved the underlying layers of the tech stack in the process.

Who?

I work for PrivateStorage, a secure file sharing system on top of Tahoe-LAFS.

Success?

YES! As of <2023-07-25> the app is submitted but not yet through the Google Play Store review process.

When released, the app will be available on the Google Play store

Who worked on this?

Useful strategies?

Most software dev jobs I’ve had want me to “do the thing” while spending zero time teaching or training others. I wanted to take the opposite approach and this paid off far more than my already wild hopes.

What tech did you use to get Haskell running on Android?

We used reflex-frp, meaning the app was a webview component that worked on Android and also on localhost.

First we learned about functional reactive programming, then specifically reflex-frp, then we got a one hour a week contract with Obsidian Systems ( creators of reflex-frp ) where we could ask questions when stuck.

Why didn’t you use Kotlin, Java, or port the Python codebase to Android?

PrivateStorage is a thin layer on top of gridsync, which is a layer on top of Tahoe-LAFS, and it’s almost entirely written in Python.

One of these ingredients, zfec, is a forward error correction layer that splits your saved file into five pieces, allowing you to recover the data from any three of those pieces. Tahoe’s ZFEC is a 17 year old C file that compiles to a shared object that dynamically loads into our application. Jean-Paul figured out the magic to cross compile this shared object to Android. :tada:

I spent eight weeks failing to load the shared object into a Python on Android.

When we hit the point where we could not find anyone who would take our money to show us how to do it, I tried Haskell.

I was able to load the shared object into ghci on termux in two days. So we tried building an Android app in Haskell.

Here’s demo code for using Haskell’s foreign function interface from inside GHCi:

-- this works great in ghci:
foreign import ccall "math.h sin" c_sin :: Double -> Double
c_sin 1.0

Once the simple demo code above worked, I wrote this code to call into our cross-compiled shared object:

{-# LANGUAGE ForeignFunctionInterface #-}
import Foreign.C.String
import Foreign.Ptr
data C_Token

foreign import ccall "lib.h token_random" tokenRandom :: IO (Ptr C_Token)
foreign import ccall "lib.h token_encode_base64" tokenEncodeBase64 :: (Ptr C_Token) -> IO CString
do tr <- tokenRandom
   btr <- tokenEncodeBase64 tr
   res <- peekCAString btr
   print res

This was more work than it seems, I had to install the latest version of termux from the F-Droid store due to changes in Android permissions.

I don’t remember where I got a version of GHC built for termux, but if you want to know, I can check my notes.

No really, why not Java or Kotlin?

Jean-Paul and I had previous Java experience, but didn’t really want to write it in Java if possible.

Why Haskell?

My previous job was Haskell, Jean-Paul wanted to use Haskell in anger.

In my experience, Haskell is more fun to write than Java or Kotlin.

Random useful bits

Using scrcpy to interact with my phone from my desktop was good for pairing, team demos, and the screenshots in this blog post.