/fulcro-native

A small library of helpers for using React Native with Fulcro

Primary LanguageClojure

fulcro native

A small set of helpers for writing with React Native apps with Fulcro.

Fulcro itself works fine with React Native, but there are a few small configuration details that this library does for you. Additionally, it includes some things like factory helpers and wrappers for the standard React Native components.

This library is in active development. It is useful, but incomplete.

Warning
If you are using expo SDK +40 version, use expo-application-40 as there were breaking changes. For older expo versions use expo-application

Add the latest version to your project dependencies. At the moment the helper code only supports apps written with Expo, but if you look in expo-application-40 you’ll see there isn’t much to change to work with non-expo apps.

We only test against the shadow-cljs build tool. If you’re using standard cljs compiler for npm deps then you’ll have to manage making sure react-native is available.

You will need the following NPM dependencies:

  • expo

  • react

  • react-native

  • create-react-class

The standard com.fulcrologic.fulcro.expo-application/fulcro-app function needs a few configuration parameters to be changed to work with Native. This is done for you if you instead use the com.fulcrologic.fulcro-native.application/fulcro-app function, which is otherwise identical (it just passes your other options through).

(ns
  (:require
    [com.fulcrologic.fulcro-native.expo-application :as expoapp]
    [com.fulcrologic.fulcro.application :as app]))

(defonce app (expoapp/fulcro-app {}))


;; later (in entry point):

(app/mount! app RootComponent :native)

Calling mount! again will just refresh the UI.

Be sure to disable both live reload and hot reload in the Expo system. Shadow-cljs will do hot code reload without them, and if they are enabled they will fight.

You’ll want your shadow-cljs config to include an after-load function that calls app/mount! to re-render the UI.

Expo provides a mechanism to pre-load and cache fonts and static images during app startup. If you use the com.fulcrologic.fulcro-native.expo-application-40/fulcro-app function, this is taken care of for you. All you need to do is specify the images and/or fonts you want to cache in the app configuration, as follows:

(ns my.app.root
  (:require
    [com.fulcrologic.fulcro-native.expo-application-40 :as expo]))

; require images individually, so you can reference them when needed in the app
(defonce profile-blank (js/require "../assets/images/profile.png"))
(defonce background (js/require "../assets/images/background.png"))

(defonce images [profile-blank background])

(defonce fonts [["Roboto" (js/require "../node_modules/native-base/Fonts/Roboto.ttf")]
                ["Roboto_medium" (js/require "../node_modules/native-base/Fonts/Roboto_medium.ttf")]
                ["Avenir Next" (js/require "../assets/fonts/AvenirNext-Regular-08.ttf")]])


(defonce app (expo/fulcro-app {:cached-images images
                               :cached-fonts fonts}))

Note that the asset paths are relative to the /app directory, or wherever you have set the output directory for the mobile app build.

The com.fulcrologic.fulcro-native.alpha.components namespace includes functions that properly wrap many of the predefined components (if you want to add some, please feel free to send a PR). There is also a react-factory function in that namespace that can properly wrap React Native components, and does some nice automatic argument conversion for you: In particular if you use a raw string as a child, it will be auto-wrapped in a ui-text component with empty props:

;; You can write
(ui-button {} (ui-text {} "Hello"))
;; OR
(ui-button {} "Hello")
-----

This namespace has an alpha component to it because we do want to evolve this API to do some of the same nice things that the DOM macros do, like making the props optional. Currently that is not supported.

The element factory support includes component-local style support. This is a feature that we’re playing with while in Alpha, and will probably evolve. It allows you to co-locate style declarations in component options, and then apply them to the elements within. It uses the current value of Fulcro’s comp/parent to fine the styles. So, the basic usage right now is:

(ns my.app.ui
  (:require
    ["some-native-lib" :refer [FancyThing]]
    [com.fulcrologic.fulcro.components :as comp :refer [defsc]]
    [com.fulcrologic.fulcro-native.alpha.components :as nc :refer [react-factory ui-text ui-view]]
    ...))

(def ui-fancy-thing (react-factory FancyThing))

(defsc SomeComponent [this props]
  {:style {:bold {:fontWeight "bold"}
           :warning {:color "black"
                     :backgroundColor "red"}}
   ...other defsc options...}
  ;; normal style
  (ui-view {:style {:color "black"}}
    ;; Look up style in current component's options
    (ui-fancy-thing {:style :.bold}
       ;; Combine together styles from component's options with some optional normal ones
       (ui-text {:style {:marginLeft 10}
                 :styles [:.bold (when warning? :.warning)]} "Text"))))

If you want to use some other component’s style data, simply wrap it with a binding (NOTE: don’t render data-driven Fulcro components inside of such a binding or you’ll confuse things):

  (binding [comp/*parent* SomeComponent]
    (ui-text {:style :.bold} "Hi"))

You’ll need to download the Fulcro Inspect electon app, which can be found in the release section of the Fulcro Inspect repository. You’ll also need to configure shadow-cljs.edn to use the websocket preload.

...
             :devtools   {:preloads   [... com.fulcrologic.fulcro.inspect.websocket-preload]}
...

This will attempt to contact inspect via a websocket on localhost. For everything except iOS Simulator this won’t work, and you’ll also have to deal with networking setup, as described next.

Warning
Using Inspect on anything but iOS Sim requires Fulcro 3.0.17+

The iOS simulator will generally let you use localhost, but devices and Android simulator will think of localhost as the handheld device (sim) itself. Therefore, you’ll have to find your machine’s IP address (e.g. via ifconfig), make sure the device is on the same LAN, and configure that IP into your application’s API URL, and also Inspect’s websocket.

Your application startup code will need to explicitly set the URL of your server API (even if running locally). Something like this should work (pay attention to the goog-define):

(ns app.client-native
  "Entry point for native client."
  (:require
    [app.application :refer [SPA]]
    [com.fulcrologic.fulcro-native.expo-application-40 :as expo]
    [app.mobile-ui.root :as root]
    [taoensso.timbre :as log]
    [com.fulcrologic.fulcro.networking.http-remote :as net]
    [com.fulcrologic.fulcro.application :as app]))

;; Allow defines in shadow-cljs to define API endpoint for dev mode (and even prod mode)
(goog-define SERVER_URL "http://production.server.com/api")

(defn ^:export start
  {:dev/after-load true}
  []
  (log/info "Re-mounting")
  (app/mount! @SPA root/Root :i-got-no-dom-node))

(defn init []
  (reset! SPA (expo/fulcro-app
                {:remotes          {:remote (net/fulcro-http-remote {:url SERVER_URL})}}))
  (start))

Inspect has a similar goog define for the inspect server host. So, you can define both of these in the dev section of your shadow-cljs build config:

{:deps     {:aliases [:cljs :dev]}
 :nrepl    {:port 9000}
 :jvm-opts ["-Xmx2G"]
 :builds   {:native
            {:target     :react-native
             ;; entry point
             :init-fn    app.client-native/init
             ;; Use closure defines to point things at your correct server URL, and same with websockets inspect.
             ;; In this example 192.168.1.3 is the IP of your machine on your dev LAN (visible in ifconfig).
             :dev        {:closure-defines {app.client-native/SERVER_URL "http://192.168.1.3:3000/api"
                                            com.fulcrologic.fulcro.inspect.inspect_ws/SERVER_HOST "192.168.1.3"}}
             :devtools   {:autoload   true
                          :after-load app.client-native/start
                          :preloads   [shadow.expo.keep-awake
                                       com.fulcrologic.fulcro.inspect.websocket-preload]}}}}

See the fulcro native template for a working example: https://github.com/fulcrologic/fulcro-native-template