« Home

Elm Native UI

Writing a React Native app in Elm

Ossi Hanhinen @ohanhi

Read this to

I expect you to know

We were at the Reactive 2015 conference in Bratislava in November. A lot of people I talked with were interested in Elm, and many asked whether it could be used with a) Node and b) React Native. I knew there was progress on the Node front, but at least Richard Feldman hadn’t heard anything about React Native being experimented with.

Me and André wanted to prove it was possible to write a React Native app using Elm. A weekend of hacking lead to Elm Native UI being born.

Elm Native UI logo by Paul Young

What we wanted to achieve

We wanted to start with something that would be simple enough, but would have at least some of the characteristics of a real application. From the Elm application, we need to output a tree representing React Native components (View and Text). From the React Native side, we need to send events that the Elm app wants to listen to.

In the end, we opted for the traditional example of increment and decrement buttons and a counter to display the current value. This would demonstrate that both of the vital parts, the output and the input, work as intended.

This diagram shows how the whole thing works. Ideally, the Elm application is the only thing a developer needs to work on. React Native works as a “backend” of sorts, relaying messages to and from the native app side to the Elm side.

Overcoming the Elm border control

Elm’s main function is special in that it only allows a handful of types, namely Element, Html, or a signal of either. This means we can’t easily have the Elm application produce a component tree (like the virtual DOM) for React Native to pick up as the rendered component tree. Unless we use ports, of course.

If you’ve worked with ports before, you may know that they have a predefined set of allowed types themselves. The listing mentions Records as one of the allowed types, so my first intuition was to create a self-referential type for the VTree. A Node can have a list of Nodes for children. Sadly, this is forbidden in Elm. Records need to have finite boundaries, and a tree structure is obviously not finite. So instead, I created a tree structure as a union type:

type VTree
  = VNode String (List RnStyle.Style) (List VTree)
  | VText (List RnStyle.Style) (String, EventHandlerRef) String

To pass the thing to the React Native side, we decided to go with plain JSON values, since they exhibit all the qualities we needed and are allowed through ports:

-- "main"
port viewTree : Signal Json.Encode.Value
port viewTree =
  NativeApp.start { model = model, view = view, update = update, init = init }

Note: Evan Czaplicki, the creator of Elm, contacted me after this post was first published. He said “Supporting a new platform is something that can get special help from the language”, so I am hopeful this will become even nicer in the future!

Ping back

The second big challenge was user interaction. An application ain’t much if it cannot react to any user input, after all. In elm-html you set up bindings in the view like so:

  [ someAttribute
  , Html.Events.onClick address
  [ Html.text "Click me!" ]

We figured we should try and reproduce a somewhat similar API, but couldn’t quite get there. We have:

  [ someAttribute ]
  (RN.onPress address action)
  "Press me!"

The reason why the event handler is separate from other attributes is that the elm-html Attribute is a Virtual DOM JavaScript property under the hood. Our attributes are (for now) simply styles, as you could see from the type definition:

type VTree
  = VNode String (List RnStyle.Style) (List VTree)
  | VText (List RnStyle.Style) (String, EventHandlerRef) String

Also, since the React Native Text component only allows a single text child, we reflect that in the API.

To bridge the gap between the Elm generated VTree and React Native, there are two things at play:

  1. a React Native app that initializes the Elm app with Elm.worker and subscribes to its vtreeOutput port, and
  2. an “Elm Native” JS module that sets up the listeners for the Elm runtime.

Now the “Elm Native” is something you would generally try to avoid when coding Elm, since there are no guarantees of safety. Also, the way they work is supposed to change in the very near future. But just to clear it up, from the Elm point of view, native refers to JavaScript, which is what the Elm runtime runs on. From the React Native point of view however, native is the Objective-C/Java code that runs on a mobile device. This naming thing is also the reason the project is called Elm Native UI and not simply Elm Native.

How the event listeners work under the hood is a bit funky at the moment. The Elm Native module utilizes handler IDs to keep track of them and pass events to the Elm runtime. That is why the on function returns an EventHandlerRef, which is essentially an integer.

on : Json.Decode.Decoder a -> (a -> Signal.Message) -> EventHandlerRef
on decoder toMessage =
    Native.ReactNative.on decoder toMessage

We are looking to improve this in the future.

Did it work out?

Yes it did. There are still big things to overcome, such as the Navigator (though that should change soon) and event handler thing. But all in all it feels like Elm Native UI could actually be a thing somewhere down the line.

A resounding thank you goes to all the contributors!

Here’s an internetsy representation of what we’ve accomplished thus far: a GIF.

The repository is open source on GitHub: Elm Native UI

What’s next

I will write another post later on about the future of Elm Native UI. There are some interesting developments, as with React Native 0.21 the much more reactive NavigationExperimental is becoming a reality.

Closes #2

Maybe your followers would be interested in this post? Get tweety with it!