Philip Shurpik "Architecting React Native app"

Preview:

Citation preview

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 platformsCan’t be shared

- Platform specific stuff- Different interface

patterns

Can be shared

+ Business logic+ Container components

Sharing code between platformsCan’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

ContainerComponent

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

ContainerComponent

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 Startconst 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

ContainerComponent State

Reducer

Middleware

Store

Presentational Components

View

Reducer

DeviceAsyncStorageRender

Actions

Redux-persist

Auto rehydrate store on Application start

ContainerComponent State

Reducer

Middleware

Store

Presentational Components

View

Reducer

DeviceAsyncStorage

InitialRender

Actually we received offline-first for viewing data

Let’s improve :)

Viewing data offline

tap tap...

User

ContainerComponent

Store

Presentational Components

View

Actions

API

Renderexisting 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

ContainerComponent

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

NavigationExperimentalreact-native-router-flux

react-native-navigation

ex-navigation

ex-navigatorreact-native-router

react-router-nativereact-native-simple-router

react-native-controllers

Navigation approachesJS-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 CommunicationJS Thread

(JavaScriptCore) Native Thread UI (Main) Thread

AsyncBridge

Process Serialize Deserialize

Process

RenderLong

processBecomes 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 - exampledescribe('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 - exampledescribe('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 crossplatformReact 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 Exampleit('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

MobileTestRunner

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)

CodePushCloud service with React Native support

CodePushDeploy 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

Questions

skype: philipshurpiktwitter: philipshurpik

https://github.com/philipshurpik

Recommended