React Native становится все более зрелым фреймворком для создания кросс платформенных мобильных приложений. Доклад основан на нашем опыте создания production приложения - от архитектуры до тестирования и CI.
Рассмотрим вопрос переиспользования кода при разработке для разных платформ- что и сколько процентов можно переиспользовать и как этого достичь.
Поговорим о том, как можно очень просто сделать offline-first приложение для чтения и создания данных. И чем нам в этом могут помочь Redux и Redux persist.
Разберем, как максимально просто сделать навигацию в приложении.
И, конечно же, какое production приложение без тестирования и continuous integration? Рассмотрим компонентное тестирование с Enzyme и интеграционное с Appium. А также, как максимально приблизить процесс deploy к тому, чему мы привыкли в web, с помощью CodePush.
4. 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...
6. It’s possible to share 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)
12. 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
17. ...we are offline
tap tap...
User
Container
Component
State
Reducer
Middleware
Store
Presentational
Components
View
Actions
Reducer
API
Render
?
?
18. 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});
19. Redux-persist
Persist store state on each change to AsyncStorage
Container
Component
State
Reducer
Middleware
Store
Presentational
Components
View
Reducer
Device
AsyncStorage
Render
Actions
20. Redux-persist
Auto rehydrate store on Application start
Container
Component
State
Reducer
Middleware
Store
Presentational
Components
View
Reducer
Device
AsyncStorage
Initial
Render
22. 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
23. 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
27. 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
31. 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
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 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
35. 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
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)
42. 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;
});
});
43. 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
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
46. 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
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 automating custom actions?
Like:
+ Signin
+ Signout
+ Redirect to page
example at: https://git.io/rnstk
51. 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
52. 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)