Travellers' community. Sharing, hosting and getting people together.
- React Native 0.30
- Re-natal
- Clojure/ClojureScript (1.9.x)
- Reagent
- Re-frame
- Prismatic/schema
- For testing:
- mocha.js and chai.js (with a spec wrapper helper in tests)
See Archictecture in nutshell section for more details on stack.
- Install React Native and everything that react native requires for development.
- Install Leiningen
- Install re-natal:
npm install -g re-natal
- Install the Android SDK
- Create an Android Virtual Device. You can use the build-in emulator from the Android SDK.
In working directory:
- Install npm dependencies:
npm install
First start up your android virtual device. (e.g if you're using the android sdk emulator, Android/Sdk/tools/emulator -avd [emulator name]
)
If you've installed a different version of re-natal than the one listed packages.json
, then use the local version in the ./node_modules
folder (e.g ./node_modules/.bin/
) when running the following:
re-natal use-android-device avd
re-natal enable-source-maps
re-natal use-figwheel
lein figwheel android
Start your development server:
npm start
Install the app to the device:
react-native run-android
Enable remote debugging:
ctrl-m
, click "start remote debugging"- this will open [http://localhost:8081/debugger-ui] in your browser
- you might need to also open dev tools
For more comprehensive instructions see: Re-natal's readme
Run tests once by running:
lein cljsbuild test
Start auto test runner:
lein cljsbuild auto test
TODO: use Enzyme to mock React Native
- After you have added a clojure dependency:
- Restart figwheel
lein figwheel android
and restart React Packagerreact-native run-android
- Restart figwheel
- After you have added a npm dependency:
- Add dependency to .re-natal file in modules array. Otherwise it won't be included in the package when using figwheel.
- Run
re-natal use-figwheel
- Restart figwheel
lein figwheel android
and restart React Packagerreact-native run-android
- After you have added an image to images folder:
- Run
re-natal use-figwheel
- Restart figwheel
lein figwheel android
and restart React Packagerreact-native run-android
- Run
Solution:
Patches RN packager to server *.map files from filesystem, so that chrome can download them by running:
re-natal enable-source-maps
re-natal use-figwheel
lein figwheel android
And once figwheel have started:
react-native run-android
Solution 1: In figwheel REPL run:
(build-once)
You should get more informative error message.
Solution 2: Open develper menu by keyboard short cut Command ⌘ + M
and click "Enable remote debuging" in Android emulator. This opens browser. Open console in browser - it often gives you better idea what is happening.
Solution 3: Check Android logs using command adb logcat
. (You probable get insane amount of log entiries, and thus you might want to clear log by adb logcat -c
Re-frame is a architectural framework that is similar to Redux and other Flux implementations.
The idea is simple:
- In re-frame, application state is stored in an ratom called db (database). Ratom (reactive atom) is an observable data structure that ensure atomic modifications (i.e. only one change at time to the latest version of data it contains). The structure of db is constrained by a schema. Currently plumatic schema is used, but it will change to core.spec at some point.
- The database is modified by events. The logic of how an event changes db is specifed by a handler.
- UI component listens for re-frame subscriptions. Each subscription is an observable view to db. You need to specify view to data in subs.cljs first, then you can subscribe it in UI component by using
re-frame.core/subscribe
function that returns anratom
. - UI component will dispatch events to re-frame dispatch. The events are handled by a handler in the order they arrive to the dispatcher sequentially. If you dispatch an event in a handler, it will be handled after the current handler function call is finished. (I.e. you cannot dispatch event synchronously in a handler.) Each handler must return an updated version of db, and as they're called they get curent db as parameter. After a handler returns, subscriptions trigger.
In most cases, you need to answer these question when you develop a new feature:
- What data does the feature need?
#. You may need to extend the schema and db. (in the
/domain
folder) #. You need to write a subscrition or maybe two. UI components should not depend directly on the db's structure. - How does the data change?
#. Implement a handler for each reason to change. (
handlers.cljs
) #. Re-frame encourages you to think about how the data changes. If the data should be changed by a REST call, you probably need a handler for a successful call and one for an unsuccessful call. That's where you start. Then you probably add a handler that does the REST call and changes db so that users know that something is happening. Handlers for successful or erroneous events do not know where the data related to an event comes from. Handlers that do the REST request don't know how the response is used. Pros of this approach is that you can easily change any of the parts without modifying other parts (open/closed principle) and easily trigger any change from the REPL or in tests; cons for this this approach is that it can be hard to understand how the data flows and where it comes. - When should data be fetched? There are three options:
#. When the app starts. In this case you dispatch fetch events in
[platform]/core.clsj
init function. There are separatecore.cljs
files for Androind and iOS. #. When the user does something, you dispatch the event from a react-native UI component event handler. #. Periodically (e.g. once per 1 minute). You can use JavaScript setTimeout and put it in thehandler.cljs
file. Currently this is a bit clumsy and probably a helper function should be created for it. - How does the user interact with the data (in the UI)?
#. The goal is that there are as much shared components as possible. UI components are located in
shared
,android
andios
directories. In addition to documentation there is no way to know what component works in which framework. Not all components support all features in React Native and components may behave differently on differnt platforms. This is a React Native design decision. - What should the components look like?
#. Until now we have used a lot of inline style. They should be refactored to platform specific
styles.cljs
files.
Actual source code you should modify are in the ./src/trustroots/
and ./test/
directories, and images are in the /images/
directory. In addition to those there are two files you need to modify:
package.json
contains npm packages..re-natal
is used by leiningen to bundle JavaScript source for react-native. You need to list there all npm packages that should be included in the react native app. Leiningen does not usepackage.json
for this. I.e. if you want to use a 3rd party library you need install it first by using npm and then add the name of packages to.re-natal
. This file also contains some other build-related information, and it is modified e.g. when you swap between emulator and physical machine.
- android: android specific UI components goes in this folder
- domain: contains schema definitions.
- ios: iOS specific UI components goes to this folder. Currently the app does not support iOS so there's not much here.
- shared: all component that should work both in android and iOS are located here.
api.cljs
: REST api call helpers goes here.db.cljs
: This file contains helpers for persisting data, including caching mechanims.fetch_helper.cljs
: contains wrapper for react native fetch function. This is used for REST calls.handlers.clsj
: contains all re-frame handlers. Currently this file is rather long and it might be split to many files.subs.cljs
: contains observable re-frame subscriptions.
Currently the domain model closely reflects what the REST APIs return. This needs to be changed as the app should support offline use and rest APIs may not return all data at once (they page data).
- users: map of all users the current user has had a conversation with, or that are their friends.
- { : }
- me: Current user id
- last-sync-time: last change timestamp (this should come form server)
- inbox: { thread: list of messages }
- current-page: vector, e.g. [:discussion thread]
- errors: vector of errors
- services: { :toaster obj :fetch obj }
- In future Plumatic schema should be changed to core.spec.
-
Initialization
[:initialize-db]
loads intial version of database.[:register-service service-obj]
Register helper function for dependency injection.
-
Navigation
[:set-page page]
Change UI view.
-
Authentication related
[:auth/login {:user username :pwd password}]
[:auth/login-success user-obj]
[:auth/login-error error-obj]
-
[:message/send-to to-user-id content]
[:message/send-to-success message]
[:message/send-to-fail error]
Currently support only one page and are clumsy to use.
[:inbox/fetch]
get users discussion threads form REST API[:inbox/fetch-success data]
[:inbox/fetch-failed error-obj]
[:conversation/fetch user-id]
get conversion with given username[:conversation/fetch-success data]
[:conversation/fetch-fail]
[:show/conversation-with user-id]
Fetch discusison, move to conversation page and set current conversation (that should be shown to given user id).
[:load-db]
load whole db form local storage.[:save-db]
save whole db to local storage.[:set-db]
Helper for setting whole database.[:storage-error]
error in loading or saving data to local storage.[:logout]
Should be :auth/logout[:login user-pwd-obj]
us :auth/login instead.
[:sync/from-remote timestamp]
get all users, conversations related current user. This handler replaces `:inbox and :conversation handlers[:sync/from-remote-success data]
[:sync/from-remote-failed data]
[:sync/from-local]
load data from local storage. This is run always when user starts the app.[:sync/from-local-success data]
[:sync/from-local-fail error-data]
(possibly user has never logged in)
[:sync/to-local]
save users, last-sync-timestamp and inbox to local storage.[:sync/to-local-success]
[:sync/to-local-fail]
- In new version of re-frame events with and without side-effects are separated more in more elegent way. This should be upgraded at somepoint to never version.
[:get-db]
returns db as is. This is mainly for testing in REPL, don't use it in any component.[:inbox/get]
Returns user conversation listing.[:get-page]
Get currently active page.[:current-conversation]
Return currently active conversation (or nil)[:get-user-of-current-converation]
Get user id of person with whom user have conversation with.[:auth-status]
Return object that informs if authentication is in progress and succeed and possible error, if authentication have failed.
Naming should be consistent with handlers. E.g. :current-conversation should be :conversation/current.