About me
Creating magic with React Native
in Debitoor/Ciklum :)
What are we doing?
Goals before development started
+ Fast to develop with existing JavaScript team
+ Firstly for iOS, then for Android
+ Offline first
+ Mostly native look and feel - animations, visual
effects, etc...
Sharing code between
platforms
It’s possible to share 70-80-90% of code between platforms
Depending on:
+ Architecture
+ Functionality
+ Third-party plugins usage
Sharing code between platforms
Can be shared
+ Business logic
+ Container components
Sharing code between platforms
Can’t be shared
- Platform specific stuff
- Different interface
patterns
Can be shared
+ Business logic
+ Container components
Sharing code between platforms
Can’t be shared
- Platform specific stuff
- Different interface
patterns
It depends...
● Code that works with plugins
● Presentational Components
(depending on design)
Let’s make app architecture
* That’s how React/React Native ecosystem looks like :)
Let’s start from basement
View and Business Logic separation with Redux
tap tap...
User
Container
Component
Thanks @andrestaltz for inspiration
State
Reducer
Middleware
Store
Presentational
Components
View
Actions
Reducer
API
Render data
But…
Sometimes...
But not all apps are working offline...
And even don’t forget your maps route screenshot
...we are offline
tap tap...
User
Container
Component
State
Reducer
Middleware
Store
Presentational
Components
View
Actions
Reducer
API
Render
?
?
We can solve a lot of problems it in few lines of code
+ Persist store state on each change to AsyncStorage
+ Rehydrate store on App Start
const store = createStore(reducer, compose(
applyMiddleware(/* ... some middleware */),
autoRehydrate()
));
Using redux-persist enhancer
persistStore(store, {storage: AsyncStorage});
Redux-persist
Persist store state on each change to AsyncStorage
Container
Component
State
Reducer
Middleware
Store
Presentational
Components
View
Reducer
Device
AsyncStorage
Render
Actions
Redux-persist
Auto rehydrate store on Application start
Container
Component
State
Reducer
Middleware
Store
Presentational
Components
View
Reducer
Device
AsyncStorage
Initial
Render
Actually we received
offline-first for viewing data
Let’s improve :)
Viewing data offline
tap tap...
User
Container
Component
Store
Presentational
Components
View
Actions
API
Render
existing data
Adding
“offline”
label
Redux-persist
State
Reducer
Middleware
Reducer
Marking
data as
outdated
Optimistically
render
new data
Creating data offline (optimistic ui)
tap tap...
User
Container
Component
Store
Presentational
Components
View
Actions
API
Adding
“not sync”
label
Redux-persist
State
Reducer
Middleware
Reducer
Marking data
as not
synchronised
Hints
Generate item Ids on client
Hints
If you have some data dependencies
invoice depending on client and product
make simple dependency tree for
synchronisation order
Hints
Use storage version to not break anything
while updating app
Hints
Offline-first editing of existing data is much more
complicated:
+ Deal with conflicts
+ Sync challenges
+ Multiple users challenges
* Do it only if it’s one of the main selling points of your app
Navigation
Navigator
NavigatorIOS
NavigationExperimental
react-native-router-flux
react-native-navigation
ex-navigation
ex-navigator
react-native-router
react-router-native
react-native-simple-router
react-native-controllers
Navigation approaches
JS-based Navigation
● Navigator
● NavigationExperimental
● react-native-router-flux
● ex-navigation
● ...
Native Navigation
● NavigatorIOS
● react-native-navigation
(wix)
● airbnb solution (not yet
opensourced)
Pros:
+ Works with all latest RN versions
+ Declarative approach:
<Router createReducer={reducerCreate}>
<Scene key="loginPage" component={LoginPage} title="Login"/>
...
</Router>
+ Can be easily debugged (it’s just JS code)
+ Has good custom extensions support
React-native-router-flux
Cons:
- Bad documentation
- Non native JS animations, non native styles
- Unable to smoothly integrate with existing IOS/Android apps
React-native-router-flux
Pros:
+ Native user experience for Android and IOS platforms
+ Developed and supported by Wix
Cons:
- You need to know Objective C/Java to debug it/fix issue
- Hard to implement something really custom
- Limited React Native version support
(stable version supports 0.25.1 and experimental 0.37.0)
React-native-navigation
Performance hints
React Native Threads Communication
JS Thread
(JavaScriptCore)
Native Thread UI (Main) Thread
Async
Bridge
Process Serialize Deserialize
Process
Render
Long
process
Becomes
not
responsive
Process
How to improve
+ Use shouldComponentUpdate - avoid not needed rendering
(and bridging from JS to UI thread)
+ Run calculations after animations finish with
InteractionManager.runAfterInteractions
+ Offload animations to UI Thread with Layout Animation
+ If everything is completely wrong - write Native code (we
haven’t experienced such situations)
Going to production - expected path
Write some JS code
Going to production - actually
Write some JS code
Tests and CI setup example at: https://git.io/rnstk
What we need besides JS code
● Javascript tests: Unit and components
Unit tests - example
describe('auth.reducer', () => {
it('should set token on login success', () => {
const action = {
type: types.AUTH_LOGIN_SUCCESS,
payload: {token: '111'}
};
expect(auth(INITIAL_STATE, action)).to.be.deep.equal({
...INITIAL_STATE,
token: '111',
loading: false
});
});
});
Components testing
+ enzyme
+ react-native-mock
+ chai-enzyme
Components tests with Enzyme - example
describe('Button.spec.js', () => {
let wrapper, pressHandler;
beforeEach(() => {
pressHandler = sinon.stub();
wrapper = shallow(<Button onPress={pressHandler}>text</Button>);
});
it('should render text in button', () => {
const text = wrapper.find(Text).first();
expect(text).to.have.prop('children', 'text');
});
it('should handle press', () => {
wrapper.simulate('Press');
expect(pressHandler).to.have.been.calledOnce;
});
});
Integrational (end to end) tests
● Javascript tests: Unit and components
● Integrational (end to end) tests:
○ Write in JavaScript - yeah, we are JS developers! :)
○ Run app on simulator
○ Login (check API work)
○ Went through main functionality and check that it works
Appium - architecture
● Uses Selenium Webdriver
● Appium is an HTTP server that creates and handles WebDriver sessions
● Appium starts “test case” on device that spawns a server and listen for
proxied commands
Appium test
The same tests crossplatform
The same tests crossplatform
React Native uses different props
to access elements on iOS and
Android.
Hope will be fixed soon:
https://github.com/facebook/react-
native/pull/9942
Test Example
it('should login', () => getDriver()
.waitAndClickElement('SignIn')
.waitForElement('Login')
.setElementValue('Email', EMAIL)
.setElementValue('Password', PASSWORD)
.clickElement('Login')
.waitForElement('Dashboard')
)
* With using of custom methods created with wd.addPromiseChainMethod
What about automating custom actions?
Like:
+ Signin
+ Signout
+ Redirect to page
example at: https://git.io/rnstk
Automate commands
Appium
test
Mobile
TestRunner
Action Server
(serves actions)
Push action
Press execute
button
fetch action
get action
dispatch action
example at: https://git.io/rnstk
Continuous delivery
Continuous delivery with Fastlane
+ Manage certificates and provision profiles (for iOS)
+ Make app builds (for iOS)
+ Submits Beta builds to TestFlight / PlayStore alpha track
+ Submits Release builds to AppStore / PlayStore
but...
- We need to wait before Apple approves our app
- Users do not update their apps for a long time
- If you update app through appstore it resets user reviews :(
- It’s harder to support different versions of app - especially for
supporters :)
We need to be able to update javascript immediately (like on web)
CodePush
Cloud service with React Native support
CodePush
Deploy Code updates directly to
users:
code-push release-react MyApp ios
● JS Bundle
● Image assets
CodePush features
● Separate Staging and Production versions
● Promoting updates
○ Partially promoting (like 20%)
○ A/B testing
● Rollback updates
○ Auto-rollback if app crashes
Links
React Native Starter Kit:
https://github.com/philipshurpik/react-native-starter-kit
Performance tuning:
https://medium.com/@talkol/performance-limitations-of-react-native-an
d-how-to-overcome-them-947630d7f440#.2dsdud7yr
https://facebook.github.io/react-native/docs/android-ui-performance.ht
ml
Questions
skype: philipshurpik
twitter: philipshurpik
https://github.com/philipshurpik

Philip Shurpik "Architecting React Native app"

  • 2.
    About me Creating magicwith React Native in Debitoor/Ciklum :)
  • 3.
  • 4.
    Goals before developmentstarted + Fast to develop with existing JavaScript team + Firstly for iOS, then for Android + Offline first + Mostly native look and feel - animations, visual effects, etc...
  • 5.
  • 6.
    It’s possible toshare 70-80-90% of code between platforms Depending on: + Architecture + Functionality + Third-party plugins usage Sharing code between platforms
  • 7.
    Can be shared +Business logic + Container components Sharing code between platforms Can’t be shared - Platform specific stuff - Different interface patterns
  • 8.
    Can be shared +Business logic + Container components Sharing code between platforms Can’t be shared - Platform specific stuff - Different interface patterns It depends... ● Code that works with plugins ● Presentational Components (depending on design)
  • 9.
    Let’s make apparchitecture
  • 10.
    * That’s howReact/React Native ecosystem looks like :)
  • 11.
  • 12.
    View and BusinessLogic separation with Redux tap tap... User Container Component Thanks @andrestaltz for inspiration State Reducer Middleware Store Presentational Components View Actions Reducer API Render data
  • 13.
  • 15.
    But not allapps are working offline...
  • 16.
    And even don’tforget your maps route screenshot
  • 17.
    ...we are offline taptap... User Container Component State Reducer Middleware Store Presentational Components View Actions Reducer API Render ? ?
  • 18.
    We can solvea lot of problems it in few lines of code + Persist store state on each change to AsyncStorage + Rehydrate store on App Start const store = createStore(reducer, compose( applyMiddleware(/* ... some middleware */), autoRehydrate() )); Using redux-persist enhancer persistStore(store, {storage: AsyncStorage});
  • 19.
    Redux-persist Persist store stateon each change to AsyncStorage Container Component State Reducer Middleware Store Presentational Components View Reducer Device AsyncStorage Render Actions
  • 20.
    Redux-persist Auto rehydrate storeon Application start Container Component State Reducer Middleware Store Presentational Components View Reducer Device AsyncStorage Initial Render
  • 21.
    Actually we received offline-firstfor viewing data Let’s improve :)
  • 22.
    Viewing data offline taptap... User Container Component Store Presentational Components View Actions API Render existing data Adding “offline” label Redux-persist State Reducer Middleware Reducer Marking data as outdated
  • 23.
    Optimistically render new data Creating dataoffline (optimistic ui) tap tap... User Container Component Store Presentational Components View Actions API Adding “not sync” label Redux-persist State Reducer Middleware Reducer Marking data as not synchronised
  • 24.
  • 25.
    Hints If you havesome data dependencies invoice depending on client and product make simple dependency tree for synchronisation order
  • 26.
    Hints Use storage versionto not break anything while updating app
  • 27.
    Hints Offline-first editing ofexisting data is much more complicated: + Deal with conflicts + Sync challenges + Multiple users challenges * Do it only if it’s one of the main selling points of your app
  • 28.
  • 29.
  • 30.
    Navigation approaches JS-based Navigation ●Navigator ● NavigationExperimental ● react-native-router-flux ● ex-navigation ● ... Native Navigation ● NavigatorIOS ● react-native-navigation (wix) ● airbnb solution (not yet opensourced)
  • 31.
    Pros: + Works withall latest RN versions + Declarative approach: <Router createReducer={reducerCreate}> <Scene key="loginPage" component={LoginPage} title="Login"/> ... </Router> + Can be easily debugged (it’s just JS code) + Has good custom extensions support React-native-router-flux
  • 32.
    Cons: - Bad documentation -Non native JS animations, non native styles - Unable to smoothly integrate with existing IOS/Android apps React-native-router-flux
  • 33.
    Pros: + Native userexperience for Android and IOS platforms + Developed and supported by Wix Cons: - You need to know Objective C/Java to debug it/fix issue - Hard to implement something really custom - Limited React Native version support (stable version supports 0.25.1 and experimental 0.37.0) React-native-navigation
  • 34.
  • 35.
    React Native ThreadsCommunication JS Thread (JavaScriptCore) Native Thread UI (Main) Thread Async Bridge Process Serialize Deserialize Process Render Long process Becomes not responsive Process
  • 36.
    How to improve +Use shouldComponentUpdate - avoid not needed rendering (and bridging from JS to UI thread) + Run calculations after animations finish with InteractionManager.runAfterInteractions + Offload animations to UI Thread with Layout Animation + If everything is completely wrong - write Native code (we haven’t experienced such situations)
  • 37.
    Going to production- expected path Write some JS code
  • 38.
    Going to production- actually Write some JS code Tests and CI setup example at: https://git.io/rnstk
  • 39.
    What we needbesides JS code ● Javascript tests: Unit and components
  • 40.
    Unit tests -example describe('auth.reducer', () => { it('should set token on login success', () => { const action = { type: types.AUTH_LOGIN_SUCCESS, payload: {token: '111'} }; expect(auth(INITIAL_STATE, action)).to.be.deep.equal({ ...INITIAL_STATE, token: '111', loading: false }); }); });
  • 41.
    Components testing + enzyme +react-native-mock + chai-enzyme
  • 42.
    Components tests withEnzyme - example describe('Button.spec.js', () => { let wrapper, pressHandler; beforeEach(() => { pressHandler = sinon.stub(); wrapper = shallow(<Button onPress={pressHandler}>text</Button>); }); it('should render text in button', () => { const text = wrapper.find(Text).first(); expect(text).to.have.prop('children', 'text'); }); it('should handle press', () => { wrapper.simulate('Press'); expect(pressHandler).to.have.been.calledOnce; }); });
  • 43.
    Integrational (end toend) tests ● Javascript tests: Unit and components ● Integrational (end to end) tests: ○ Write in JavaScript - yeah, we are JS developers! :) ○ Run app on simulator ○ Login (check API work) ○ Went through main functionality and check that it works
  • 44.
    Appium - architecture ●Uses Selenium Webdriver ● Appium is an HTTP server that creates and handles WebDriver sessions ● Appium starts “test case” on device that spawns a server and listen for proxied commands Appium test
  • 45.
    The same testscrossplatform
  • 46.
    The same testscrossplatform React Native uses different props to access elements on iOS and Android. Hope will be fixed soon: https://github.com/facebook/react- native/pull/9942
  • 47.
    Test Example it('should login',() => getDriver() .waitAndClickElement('SignIn') .waitForElement('Login') .setElementValue('Email', EMAIL) .setElementValue('Password', PASSWORD) .clickElement('Login') .waitForElement('Dashboard') ) * With using of custom methods created with wd.addPromiseChainMethod
  • 48.
    What about automatingcustom actions? Like: + Signin + Signout + Redirect to page example at: https://git.io/rnstk
  • 49.
    Automate commands Appium test Mobile TestRunner Action Server (servesactions) Push action Press execute button fetch action get action dispatch action example at: https://git.io/rnstk
  • 50.
  • 51.
    Continuous delivery withFastlane + Manage certificates and provision profiles (for iOS) + Make app builds (for iOS) + Submits Beta builds to TestFlight / PlayStore alpha track + Submits Release builds to AppStore / PlayStore
  • 52.
    but... - We needto wait before Apple approves our app - Users do not update their apps for a long time - If you update app through appstore it resets user reviews :( - It’s harder to support different versions of app - especially for supporters :) We need to be able to update javascript immediately (like on web)
  • 53.
    CodePush Cloud service withReact Native support
  • 54.
    CodePush Deploy Code updatesdirectly to users: code-push release-react MyApp ios ● JS Bundle ● Image assets
  • 55.
    CodePush features ● SeparateStaging and Production versions ● Promoting updates ○ Partially promoting (like 20%) ○ A/B testing ● Rollback updates ○ Auto-rollback if app crashes
  • 56.
    Links React Native StarterKit: https://github.com/philipshurpik/react-native-starter-kit Performance tuning: https://medium.com/@talkol/performance-limitations-of-react-native-an d-how-to-overcome-them-947630d7f440#.2dsdud7yr https://facebook.github.io/react-native/docs/android-ui-performance.ht ml
  • 57.