Skip to content

gphilipp/react-native-with-clojurescript

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

52 Commits
 
 

Repository files navigation

React Native With ClojureScript

This repo is the result from my experimentations writing a native iOS application using react-native with Clojurescript. I've tried re-natal with om-next first, but found it quite difficult to use due to the lack of documentation. Then I tried re-natal with re-frame and found that I got something working more rapidly.

Below is a collection of links, interesting ramblings on Slack from the #cljsrn channel. I'll try to add simple examples to get started and keep it up-to-date with newest versions of react-native and re-natal.

Real apps written in React-Native

Interop with JavaScript

It's important to understand how to do proper interop between ClojureScript and JavaScript, both ways.

Type Op
ILookup get or get-in
js/Array aget
js/Object goog.object/get or goog.object/getValueByKeys

Tutorials

Example apps

nice components

Frontend libraries/frameworks

You can use om-next,re-frame, or rum.

Om-next

If you use om-next, check out this blog post on writing om-next reloadable code : https://anmonteiro.com/2016/01/writing-om-next-reloadable-code-a-checklist/.

Re-Frame

Tooling

Re-natal

How to add an external library

  1. re-natal use-component react-native-store re-natal use-figwheel
  2. Check component is listed in .re-natal and in package.json
  3. re-natal use-figwheel
  4. lein figwheel ios since re-natal use-figwheel does a clean first.
  5. Check that localhost:8081/index.ios.bundle lists the component.
  6. Run (js/require "react-native-store") in the repl.

Boot-react-native

Source maps have been disabled for a while : mjmeintjes/boot-react-native#58

@pesterhazy: one thing that is great is that brn integrates with RN's packager directly, so you get all its features. including pretty fast reloading, requiring images just works @pesterhazy: boot-react-native badly needs updating, code and docs

Blog post of Aug 2016: http://presumably.de/boot-react-native.html

Troubleshooting: https://github.com/mjmeintjes/boot-react-native/wiki/Troubleshooting

Proper restart

  • restart the app (not relying on boot-reload)
  • clearing the packager cache react-native start --refresh-cache true
  • check the bundle output http://localhost:8081/index.ios.bundle?platform=ios&dev=true&hot=true

Navigation

Facebook has deprecated all pre-existing navigation mechanism in favor of their new contribution: react-navigation. Its run by the react community, so not by Facebook directly, but it's endorsed: https://github.com/react-community/react-navigation.

Sean Tempesta has written and published an excellent starter lib which specced out react-navigation. You can use whatever your wrapper is (currently it only provides out-of-the-box bindings for reagent and re-frame but it can be extended to support Rum and Om-Next: https://github.com/seantempesta/cljs-react-navigation

Two other examples of using it is

So, these libraries aren't useful anymore :

Customize navigation

gphilipp [16:12] @seantempesta do you know how to customize the header from the screen ? Basically, replicate this example from the react-navigation doc: https://reactnavigation.org/docs/intro/headers

seantempesta @gphilipp: So, you’ve got two options. You can statically assign navigationOptions or you can use a function and dynamically return the navigationOptions. Here are some examples from the login screen in my re-frame example:

(def static-navigationOptions {:headerTitle "Login"
                               :headerRight (fn []
                                              [:> Button {:title   "Sign In"
                                                          :onPress #(dispatch [:login])}])})

(defn dynamic-navigationOptions [{:keys [navigation] :as props}]
  (let [navigate (:navigate navigation)]
    {:headerTitle "Login"
     :headerRight (fn []
                    [:> Button {:title   "Sign In"
                                :onPress #(navigate "Loading")}])}))
                                

High-level design

  • Routers define the relationship between URIs, actions, and navigation state. They allow to share navigation logic between mobile apps, web apps, and server rendering.
  • Navigators allow you to define your application's navigation structure. Navigators also render common elements such as headers and tab bars which you can configure. Under the hood, navigators are plain React components.

Components

Lists

Facebook has implemented a virtualized list to reduce memory consumption: https://facebook.github.io/react-native/blog/2017/03/13/better-list-views.html

@raspasov on 2017-03-30: ...I’ve replaced all my old ListView cases with VirtualizedList, works very well so far.

Running on device

  1. Get IP address from iphone settings
  2. Run re-natal use-ios-device with that IP address.
  3. Run lein prod-build
  4. Open Xcode, changed scheme configuration from Debug to Release, change platform to physical device
  5. Hit Run on Xcode.

Debugging

Frisk to visualize state

Re-natal Hack

See the support directory and the build.boot file: https://github.com/kennyjwilli/postal-app

Re-natal import external components

  • react-native link react-native-sound && npm install react-native-sound —save && re-natal use-component react-native-sound
  • Optionally : re-natal use-figwheel

Shipping

@savelichalex: continuous integration that pushes every commit to master to testflight is a total life-saver you can build the archive e.g. using xctool

Testing

(defmacro generative-tests
  "Takes a list of fn symbols and runs generative tests on them"
  [fn-syms]
  (let [opts#     {:clojure.test.check/opts {:num-tests 1}}
        long-res# (cljs.spec.test/check ~fn-syms opts#)
        res#      (cljs.spec.test/summarize-results long-res#)]
    (cljs.test/is (-> ~fn-syms empty? not) "A namespace was empty")
    (cljs.test/is
      (not (or (:check-failed res#)
             (:check-threw res#)
             (:instrument res#)))
      (str "Spec failed for " (map :sym (filter :failure long-res#))))))

(s/fdef generative-tests :args (s/cat :syms (s/coll-of any?)))

(defn- gen-test ([n] (gen-test n #{}))
  ([n {:keys [exclude]}] `(generative-tests
                            (clojure.set/difference
                              (enumerate-namespace ~n) ~exclude))))

(defmacro gen-test-ns
  "Takes a ns and optionally excluded syms and runs generative tests for all the functions"
  [& args]
  (apply gen-test args))

Logging

  • Use react-native log-ios in a separate terminal (cluttered with random, useless messages that it's really hard to read says @pesterhazy)
  • You can use timbre in conjunction with cljs-devtools, you can use this code taoensso/timbre#132 (comment)

Building

Avanced compilation for production requires rn-externs: https://github.com/artemyarulin/react-native-externs

@artemyarulin: well, this is a way how advanced compilation works - without externs file Google Closure rename a lot of important staff Then @pesterhazy says that advanced compilation is not necessarily worth the effort on react native because bundle size doesn't matter as much as on the web. @artemyarulin replies that there are still to cases for that: 1) If you want to do hot deploys and do it daily or more often (like we used to do in web) and 2) GC is best obfuscator - not a big deal for certain cases, but it drives me crazy a bit that somebody can unpack bundle and recreate the app in 10 minutes. @pesterhazy agrees and adds that there might be speed bumps too. (https://clojurians-log.clojureverse.org/cljsrn/2016-08-05.html)

Fetching Data

(def fetch (.-fetch js/window)) (register-handler :load-data (fn [db _] (.then (fetch "[https://api.github.com/repositories](https://api.github.com/repositories)") #((.warn js/console (.stringify (.-JSON js/window) %1)(dispatch :process-data %1))))))

pesterhazy 12:02:06 @debug, now what you mention it, I saw a similar issue when porting my code to Android, and also ended up using js/fetch directly

(.then 
  (js/window.fetch 
    "https://api.github.com/repositories") 
    #(println %) 
    #(println "rejected" %))
(defn request*
  [uri {:keys [request-method on-success on-error retry? retries params]
        :or {retries default-retries
             request-method :get}
        :as opts}]
  (assert (#{:get :post} request-method) (str "invalid request method " request-method))
  (let [uri* (str (client-conf-api)
                  (to-uri uri)
                  (when (= request-method :get)
                    (str "?" (query-string params))))]
    (log/info "fetch:" uri*)
    (-> uri*
        (js/fetch (clj->js (merge {:method ({:post "POST" :get "GET"} request-method)
                                   :headers (if (= request-method :post)
                                              {"Accept" "application/transit+json"
                                               "Content-Type" "application/transit+json"}
                                              {"Accept" "application/transit+json"})}
                                  (when (= request-method :post)
                                    {:body (transit/write params)}))))
        (.then (fn [response]
                 (log/info "got resonse")
                 (.text response)))
        (.then (fn [text]
                 (log/info "got text")
                 (log/info "decoded:" (transit/read text))
                 (on-success (transit/read text))))
        (.catch (fn [err]
                  (log/error "Oops, an error occurred:" err))))))

Issues

Animations

oh my! 60 fps UI on iphone 5 device :aw_yeah:

(def ReactNative (js/require "react-native")) (def interaction-manager (.-InteractionManager ReactNative))
(defn on-click [f] (fn [] (.-requestAnimationFrame js/window #(.runAfterInteractions interaction-manager f))))
... (touchable-highlight {:onPress (on-click #(...))}

Disable Logs

  1. You can disable painful messages in XCode like "nw_connection_get_connected_socket_block_invoke 476 Connection has no connected handler with this trick facebook/react-native#10027 (comment)

  2. Until it's done properly, you can also disable re-frame warning messages caused by figwheel reloads : day8/re-frame#204 (comment)

Tips

  1. you can enable custom formatters in Chrome with cljs-devtools

  2. Recently many dot-forms did not work for me because those required symbol instead of expression as a first argument in things like:

    (.. (expr) -someAttr -anotherAttr)

    so I used let, and it worked

    (let [s (expr)] (.. s -someAttr -anotherAttr)
  3. use refs w/ reagent : example1, example 2

Build plugin offline

https://github.com/vikeri/rn-cljs-tools

CI

https://pilloxa.gitlab.io/posts/ci-with-gitlab-and-docker/ @vikeri: You only have to install gitlab-ci-muti-runner not a full Gitlab instance. Actually I’m just doing nodejs tests at the moment. No integration tests with an iOS simulator yet. But yeah that should work in the future as well: https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/

Local storage

https://realm.io/docs/react-native/latest/#getting-started

@tiensonqin: I used Realm.js. I have to store contacts and chat history in disk, so datascript along not works for me.

Custom Native Components

another warn is file and properties names, don't use names that start with RTC, use another namespace, don't use properties like margin, font-size and so one, this is cause conflicts and RN overwrite it. Also remember that if you write your file with Manager at the end, then in your code you don't need to write it, i.e file name - MyViewManager in cljs (require-native-component "MyView") (https://clojurians-log.clojureverse.org/cljsrn/2016-11-10.html#inst-2016-11-10T08:59:13.002600Z)

Troubleshooting

Memory issues

Sometimes the node packager will run out of memory.

One solution for iOS is to edit the following file: ios/YourProjectName.xcodeproj/project.pbxproj and change the following line (~600)

shellScript = "export NODE_BINARY=node\n../node_modules/react-native/packager/react-native-xcode.sh"; to

shellScript = "export NODE_BINARY='node --max_old_space_size=4092'\n../node_modules/react-native/packager/react-native-xcode.sh";

Update 8 Jul 2017

It seems the memory problem comes from the SourceMapGenerator.toJSON function https://gist.github.com/gphilipp/10cebb3aba7afbafbc1cca7b265a7b7e#file-short_build_rn_memory-log-L108-L109. the log includes gc traces. I can’t find any settings which can effectively increase the maximum memory allocated to the node process, and I’ve tried them all, with various versions of node. The trick above sort of worked ok with RN 0.44 but falls short with RN 0.45. On my desktop, the --max_old_space_size=4092 has no effect on the maximum memory of the node process. It will not go higher than 1.5 gb. My theory is that the packager uses more memory in RN 0.45 than in RN 0.44 or there might be a leak, so the node process will OOM with 1.5 gb only. The good news it that there might be some hope, as we have the exact same issue than someone who filed it against https://github.com/facebookincubator/create-react-app. Watch this issue: facebook/create-react-app#2555 And since someone uploaded a project to reproduce it reliably, the issue will most likely be fixed in the near future: facebook/create-react-app#2555 (comment)

Other tricks

(thx @ronb : https://clojurians.slack.com/archives/C0E1SN0NM/p1498904841241132):

https://stackoverflow.com/a/38198512/50114

Other issues

Useful Code

(defn obj->vec [obj]
  "Put object properties into a vector"
  (vec (map (fn [k] {:key k :val (aget obj k)}) (.keys js/Object obj))))

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published