React Native in ClojureScript

David Goldfarb edited this page Feb 14, 2018 · 22 revisions

Editing React Native in ClojureScript

Introduction

Facebook’s React Native offers a powerful methodology for creating mobile applications. It combines the JavaScript ecosystem, access to most of the native APIs of Android and Apple’s iOS, and a react framework that is well-suited for ClojureScript development.

Several fantastic libraries and tools have been written that make it straightforward to create React Native apps in ClojureScript. However, when I started down this path last week, I discovered that the documentation is still fragmented and the tool chains are still evolving. This article is a work in progress; an attempt to capture what I’ve learned so far.

It is almost, but not quite, a tutorial on how to setup a development environment for this. Since the ground is still shifting, and I’m still learning, this is just the next best thing — a description of the tools and links to other documents that explain each area.

What I’ve learned is also reflected in receipts-native, a toy project in which I tried out these ideas.

My perspective

  • My main computer is a Linux desktop, and my mobile devices are mostly Android.

  • I code mostly in Clojure/ClojureScript and my current preferred ClojureScript tooling is re-frame. I’ve done a bit of pure JavaScript work recently, but not enough to be familiar with the constant growth of new and improved tools.

  • I last touched Android development back in the stone ages of Android 1.5, nearly a decade ago.

  • I am aware of earlier attempts to create Clojure/ClojureScript toolchains for Android and iOS, but I don’t know enough about them to say anything useful.

I won’t totally ignore the Mac/iOS side of things, or Om and other popular CLJS tools, but they won’t be my focus. Mostly, I’ll be looking at the tools I needed to learn in order to do a competent job of creating a re-frame ClojureScript React Native application in 2018.

Comments and corrections are invited. I would be thrilled to flesh out this article with more information.

The tooling hierarchy

ClojureScript React Native development relies on a hierarchy of tools. Few developers are fluent users of all these tools. You don’t require deep knowledge of most of them, but you do need to know what exists, how it can help you, and what bear-traps may catch you. To develop successfully, you will need some knowledge of the mobile platforms and development environments, of JavaScript tools, of React and React Native, of React Native libraries, and of ClojureScript libraries and tools. I’ll discuss each of these below.

Mobile platforms

Hardware

Android

Android is an open-source platform owned by Google. It powers most current mobile phones and tablets, including devices by Samsung, HTC, Huawei, OnePlus, Xaiomi, and many others.

Google releases new versions of Android roughly annually, with each version given a version number and a whimsical alphabetic name based on a snack food. The current version is 8.x (Oreo). Earlier versions remain widely used for several years after release.

iOS

iOS is Apple’s mobile operating system for iPhones and iPads.

Software emulators/simulators

Android development use emulators; iOS uses simulators. The difference is mostly just semantic, but there are some key differences. At the risk of offending some purists, I will mostly use the term emulator in this article.

When you are developing a CLJS React Native app, you can test on either the mobile hardware or on an emulator. For best accuracy, hardware testing is recommended, especially in final pre-release testing. On Android, indeed, you must test on multiple devices to ensure that your application will work for most users.

However, hardware development/testing is more cumbersome, and sometimes slower, than emulators. So, most of the time, you will first deploy to an emulator running on your development machine.

iOS

The iOS simulator runs only on Apple computers. (I have seen rumors of techniques that allow some degree of emulation on Windows or Linux computers, but I don’t know enough to say if this path is useful). It is probably impractical to develop for iOS on a Linux or Windows machine.

Android

I know of two emulators for Android. Both run on all three significant development platforms (Windows, Mac, and Linux).

:information_source:
Neither of these emulators will run in a virtual machine. Genymotion will not run at all. Android Studio (not tested by me) can be made to run but will be painfully slow.
Android Studio

Android Studio is Google’s official development tool. It bundles the Studio itself (an IDE built on IntelliJ), the SDKs for each Android version, and a set of Android emulators.

Genymotion

Genymotion Desktop is a commercial replacement for Google’s emulators. It is free only for personal use. I am currently using the free tier while I play and explore. I have not used Google’s recent tools, so I don’t know exactly what advantages are offered by Genymotion. They don’t have a comparison sheet, but did direct me to these lists of highlights and features.

It looks like Genymotion can be installed without first installing Android Studio. However, I installed Android Studio first. This required some minor fixups when I installed Expo (see below).

Genymotion tips
  • To bring up the developer menu: shake the device or type C-m on the Android emulator or Cmd-d on the iOS simulator.

  • If you are running with Figwheel, then you probably want to disable Live Reload and Hot Reloading from the developer menu.

:fire:
This tip appears in multiple tutorials and is almost certainly correct. But, I’ve seen situations where turning them off seems to prevent some updates. I’m not sure about this.

JavaScript tools

Node.js

node.js is a JavaScript runtime platform that runs directly on computers, rather than in a browser. It has evolved into a rich ecosystem with thousands of libraries and many development tools.

It is important for us as the platform that hosts most of the development tools we will need.

npm

npm is the primary package manager for node.js.

Yarn

Yarn is a relatively new package manager and competitor to npm, which shares its backend of libraries. This early article explains the rationale for it.

Yarn is claimed to be superior to npm, but probably not in ways that really matter for the small-scale ways it will be used in my ClojureScript tool chain. The more important issue is that some relevant tutorials suggest using npm, while others use Yarn. As we bring together a set of disparate tools, just be aware that npm and yarn both do about the same thing, and may occasionally step on each other’s toes.

:fire:
I’m handwaving here. I don’t really understand the pros and cons of npm and yarn.

React

React is a JavaScript library, created by Facebook, for building user interface components. The key idea is that it creates a "shadow DOM". This plays very nicely in ClojureScript, where Clojure’s immutable data structures allow for very efficient detection of parts of the tree that require redisplay.

React is the base for React Native, and also for much current ClojureScript development. ClojureScript libraries reagent, re-frame, Om next, Rum, and others are all based on it. (If you have not already drunk the React kool-aid, this entire article is probably not relevant for you).

React Native

React Native is a JavaScript framework, also created by Facebook, that lets you create native mobile applications in JavaScript. The key idea is that the framework wraps native functionality in a JavaScript API.

Its documentation starts here. This is also a great tutorial.

A nice article from 2016 by Dotan Nahum, that explains the usefulness of React Native relative to traditional mobile app development.

Expo

Expo is a free and open source toolchain built around React Native.

Expo augments React Native with support for systems devices such as the camera, contacts and local storage; UI components like icons and blur views; and services including asset management, push notification, and deployment.

I don’t know if Expo will continue to do everything I need in the future, but it definitely eased my entry into CLJS React Native development.

Installation:

Expo offers both a GUI (called the XDE) and a command line experience, exp. I’m using exp.

:information_source:
I did not find it very easy to find the instructions for installing the CLI (exp). They are at the very end of Expo’s instructions for Genymotion integration, where they discuss ensuring that both tools use the same version of adb. In short, run npm install -g exp. Then (for the adb issue) run exp path to save your path for the XDE.

With an app created with Expo-cljs-template (see below), my development-time tooling is simple. I open two terminal shells and invoke lein figwheel and exp start --android

:bulb:
You will probably need to change one of Expo’s settings. In your projects .expo/settings.json, change "hostType" to "lan".

To publish the app, for both Android and iOS, exp publish.

Side note: ADB and Node.js problems

I had some teething problems when I started, mostly due to working on a machine where I had installed a much earlier version of node.js and of Android Studio a long time ago. Mostly, the problems involved permissions, presumably because I had `sudo’d the old installation of node.js. This made it difficult to install the CLI version of Expo. This, in turn, made it difficult to prevent adb version clashes between Android Studio and node.js. Fixing the problems required some node.js-fu that I lacked. So, including some links here, in the hope that it will help the next person stuck with this problem:

React Native libraries

NativeBase

NativeBase offers a starter set of React Native UI components for Android and iOS. It comes with extensive documentation.

It includes most of the basic components needed to build an webpage-like app, including layout (headers, footers, animation, swipers, cards, tabs, drawers, etc.); basic controls (buttons, checkboxes, lists, icons, textboxes, text input, etc.); and many others.

(This GitHub search finds several projects that use NativeBase in ClojureScript).

:fire:
This was the first (and, so far, only) library that I installed into my app. I’m still not certain of the process. The following documents everything I did, starting with creating a new project. Many of the steps were certainly extraneous; I will clean this up after I install some more libraries.
  • Created a new project, with lein new expo-cljs-template my-project (see below)

  • Ran the project to ensure all was copacetic. (Started two terminal session. In one: lein figwheel; in the other: exp start --android

  • Killed both sessions

  • npm install native-base (This was probably a mistake; I should have used yarn).

  • For now, ignored instruction to install themes with node node_modules/native-base/ejectTheme.js Also, later, ignored more theme-related instructions that I don’t yet fully understand:

  • Ignored instruction to commit package-lock.json, as I realized I probably "done bad" by using npm instead of yarn

  • yarn install

  • yarn add native-base

  • Tested that all worked: exp start --android - gave error "Invalid Version: undefined" fail

  • Exp happened to be out of date, so did npm install -g exp

  • Tried exp start --android again

  • fail This time, failed with warning of uninstalled packages: create-react-class, expo, react-native, etc. Suggested running npm install

  • npm install (probably a mistake, since I had started the project with Yarn)

  • Got happy note that NativeBase 2.0 was installed, and another instruction (ignored still!) to install ejectTheme.js, this time with lots of details that I don’t yet fully grok:

  │  NativeBase theme has been copied at /home/deg/Documents/git/projects/receipts-native/native-base-theme              │
  │ Here's how to theme your app                                                                                         │
  │                                                                                                                      │
  │ import getTheme from './native-base-theme/components';                                                               │
  │ export default class ThemeExample extends Component {                                                                │
  │ render() {                                                                                                           │
  │   return (                                                                                                           │
  │     <StyleProvider  style={getTheme()}>                                                                              │
  │       <Container>                                                                                                    │
  │         <Content>                                                                                                    │
  │           ...                                                                                                        │
  │         </Content>                                                                                                   │
  │       </Container>                                                                                                   │
  │     </StyleProvider>                                                                                                 │
  │   );                                                                                                                 │
  │ }                                                                                                                    │
  │                                                                                                                      │
  │ Head over to the docs (http://docs.nativebase.io/CUSTOMIZE.html#Customize) for detailed information on customization |
  • exp start --android works, and I can use native-base components

  • Add needed boilerplate to ClojureScript code:

;;; Register library
(def native-base (js/require "native-base"))

;;; Support code (not specific to this library)
(ns ,,,
  (:require ,,,
            [oops.core :as oops]
            [reagent.core :as r]))

(defn adapt-class [class]
  (when class
    (r/adapt-react-class class)))

(defn get-class [module name]
  (adapt-class (oops/oget+ module name)))

;;; Define some components
(def Body      (get-class native-base "Body"))
(def Button    (get-class native-base "Button"))
(def Container (get-class native-base "Container"))
(def Content   (get-class native-base "Content"))
(def Footer    (get-class native-base "Footer"))
(def FooterTab (get-class native-base "FooterTab"))
(def Form      (get-class native-base "Form"))
(def Header    (get-class native-base "Header"))
(def Icon      (get-class native-base "Icon"))
(def Input     (get-class native-base "Input"))
(def Item      (get-class native-base "Item"))
(def Label     (get-class native-base "Label"))
(def ListNB    (get-class native-base "List"))     ;; (avoid name clash)
(def ListItem  (get-class native-base "ListItem"))
(def Left      (get-class native-base "Left"))
(def Right     (get-class native-base "Right"))
(def Tab       (get-class native-base "Tab"))
(def Tabs      (get-class native-base "Tabs"))
(def Text      (get-class native-base "Text"))
(def Title     (get-class native-base "Title"))

React Navigation

Components for page navigation: hierarchy, tabs, and drawers. Widely recommended as superior to the tab support in NativeBase.

:fire:
I’ve not yet installed this library.

date/calendar component

Neither React Native itself nor NativeBase includes a calendar widget. I plan to try one or more of the following:

others

Debugging React Native Apps

Debugging React Native Apps is still clumsy, and the extra complexity added by ClojureScript does not help.

In the event of a crash, stack traces are almost useless. They usually show only the React Native tooling, with few if any stack frames related to your code. Mostly, I find I do "printf debugging" (usually with js/console.log).

It is possible (see below) to view the React Native component tree, but the structure is very complex. I created a tabbed app with a simple form, and counted over forty levels of nesting before I reached the Text or Input components in my form.

Debug setup

In the emulator (I’ve tested only in Genymotion), type C-m to bring up the menu. From there, you can click Debug JS Remotely and Toggle Inspector. (Trivia bit: You can also cause the menu to appear by invoking adb shell input keyevent 82 in a terminal).

Debug JS Remotely opens a chrome window. In it, type Ctrl-Shift-J to open the Console pane. This will show any console output generated by your application.

:information_source:
Though I’ve not tested, it is apparently also possible to see console output in Android Studio, or by running react-native log-android in a terminal.

In Chrome, you can also open the Sources pane, where you can set breakpoints and step through your code. When you first start, a useful way to orient yourself is to write a line to the console: (js/console.log "Here I am"). The output will appear in the console pane, along with a clickable link to its line in the source code. Click on the link to open your source file.

Toggle Inspector allows you to inspect React components in your application. By default it overlays this info on the emulator window. But, for a better experience, you can use the standalone React Developer Tools. Install them with npm install -g react-devtools and run with react-devtools.

Debugging references

  • 2016 article summarizing React Native debugging, plus some monitoring and profiling tools built by the author.

ClojureScript libraries and tools.

Most ClojureScript developers of React Native applications start from one of two popular templates: Re-Natal or Expo-cljs-template.

Re-Natal

Re-Natal is a command-line utility that automates setting up a CLJS React Native app with Reagent
re-frame, Om.Next or Rum.

Gadfly (Mathew Jaoudi) has written a brilliant introduction to re-natal, with step-by-step installation instructions for Ubuntu Linux. This guide is now more than a year old, and perhaps slightly dated.

Expo-cljs-template

Expo-cljs-template integrates a CLJS base template with the usual Figwheel and re-base or Om Next, and also Expo, for a smooth, out-of-the-box ClojureScript React Native project start.

It requires that you install Expo and Yarn, and then everything just works.

Note: I see no reason not to use this template. But, if want a more bare bones approach, see Expo’s Using ClojureScript page.

Resources and examples

Unmerged tips

These need to be merged into the main flow of this doc.

  • When Figwheel fails to refresh after a crash, Reload

  • Fill in notes about two calendar controls (mention default, moment, ref)

  • Deployment: lein prod-build && exp publish

You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.