One code Web, iOS, Android Artem Marchenko, 09 Feb 2017 https://twitter.com/AgileArtem
Artem https://twitter.com/AgileArtem • Buzzwords: – Interactive images, Qt/QML, Jolla SailfishOS, Agile- shmagile, TDD, product management, JavaScript, Java, whatever works, prototyping, startups, paragliding, salsa dancing, ReactJS, React Native – http://www.thinglink.com • Twitter: @AgileArtem
Подорожник https://twitter.com/AgileArtem
Подорожник – калькулятор https://twitter.com/AgileArtem
Подорожник – калькулятор https://twitter.com/AgileArtem
Actually usable https://twitter.com/AgileArtem
WHAT’S UNDER COVER https://twitter.com/AgileArtem
Under the cover https://twitter.com/AgileArtem
App structure https://twitter.com/AgileArtem index.js index.ios.js index.androi d.js native/ AppContainer AppContainer Other web UI components Other native UI components web reducers common reducer native reducers native- specific reducer (e.g. orientation change)
Project structure - src - native - components - styles - util - web - components - test - web - components https://twitter.com/AgileArtem
WHAT WORKED https://twitter.com/AgileArtem
Google Analytics: Mobile react-native-google-analytics-bridge • Worked as simple as const GA = require('react-native-google- analytics-bridge'); const GA_TRACKER_ID = Platform.OS === 'ios' ? 'UA-76217125-6' : 'UA-76217125-5'; GA.setTrackerId( GA_TRACKER_ID ); GA.trackEvent('general', 'app: activated'); https://twitter.com/AgileArtem
Mobile Ads: Google AdMob react-native-admob Converting default React Native iOS project to pods/workspace can be challenging, but then the usage is super-simple import { AdMobBanner } from 'react-native-admob’; <AdMobBanner style={appStyle.bottomBanner} bannerSize={"smartBannerPortrait"} adUnitID={"ca-app-pub- 6248714847105943/1045980532"} didFailToReceiveAdWithError={this.bannerError} /> https://twitter.com/AgileArtem
Redux and Redux Dev Tools • Redux (or Flux if you like) could be the best part of React practice actually https://twitter.com/AgileArtem
WHAT WORKED OKAYISH https://twitter.com/AgileArtem
Redux. Good parts • Redux is awesome. • Debugging dumb structures, tracing changes message by message and time traveling is simple and efficient • Definitely use Redux Dev Tools (e.g. as a Chrome extension) https://twitter.com/AgileArtem
Redux. Complexities • Making Redux, routing and Local Storage like each other was pain in the bottom and rain dances – There is logic certainly, but you’ll either need to learn a lot of it or dance around and hope – Or clone my solution, but be aware it’s coding by accident • Modifying several files in the different parts of code base (action creator, reducer, handler) for just passing same stuff around is a lot of error prone typing. – Consider ducks approach – all the code about one bit of functionality together https://twitter.com/AgileArtem
Redux: more mistakes • Do not store UI state (screen size) or computable data (final price) in the model • Use memoizable redux selectors for it (e.g. reselect). – Looks the same, feels the same, but you do not pollute the model with the data to keep in sync https://twitter.com/AgileArtem
Immutable.JS const calcedPaymentState = preUpdatedState.setIn(['paymentOptions', 'eTicket', 'totalCost'], newETicketCost) • State that’s guaranteed to be immutable is way easier to debug • But not all the components are ready for it out of the box and want to see plain JS objects (I had issues with browserHistory I think) • And on a small research-like app you might not see benefits of immutability yet while you might hit the integration obstacles https://twitter.com/AgileArtem
Routing and browse and sharable urls import { Router, Route, IndexRoute, browserHistory, useRouterHistory } from 'react- router'; import { routerMiddleware, syncHistoryWithStore, routerReducer, push } from 'react- router-redux'; import createBrowserHistory from 'history/lib/createBrowserHistory'; const queryString = require('query-string'); const history = syncHistoryWithStore(browserHistory, store, {selectLocationState: (state) => { const r = state.getIn(['metadata', 'routing']); return r || '/'; }}); const routedState = state.setIn(['metadata', 'routing'], { locationBeforeTransitions: { pathname: '/', search: searchString, query: {}, hash: '' } }) https://twitter.com/AgileArtem
Routing and browse and sharable urls • React Router is okay, browserHistory is okay, storing routing in a storage is okay, but making it work together is tough – Especially if some more middleware is involved: Redux Dev Tools • I used query string as the initial boss that commanded the state that was setting up the routing – Then state updates are changing the browserHistory-specific keys. browserHistory was updating the address bar • And there are WebKit bugs features. You cannot update URL too often • Use my solution if you just want things to work – Yet it’s programming by accident https://twitter.com/AgileArtem
Structuring controls for testing • Simple, isolates core part for testability, but I didn’t use much testing in the end – Started from awesome tutorial by a Finn @teropahttp://teropa.info/blog/2015/09/10/full-stack- redux-tutorial.html export class InputBlock extends React.Component { constructor(props) { … export const InputBlockContainer = connect( mapStateToProps, actionCreators )(InputBlock); https://twitter.com/AgileArtem
Using template for injecting stuff into the web root var HtmlWebpackPlugin = require('html-webpack-plugin'); … new HtmlWebpackPlugin({ inject: false, template: 'src/web/index.ejs', googleAnalytics: { trackingId: 'UA-76217125-4', pageViewOnLoad: true }, … <% if (htmlWebpackPlugin.options.googleAnalytics.trackingId) { %> ga('create', '<%= htmlWebpackPlugin.options.googleAnalytics.trackingId%>', 'auto'); https://twitter.com/AgileArtem
Title/Nav bar react-native-navbar • Surprisingly difficult to do in crosplatform way • react-native-navbar works, but don’t expect to fool a maniacal designer https://twitter.com/AgileArtem
Portrait-Landscape layouting and *cascading* styles react-native-media-queries • Works, but you may need to track rotation yourself • Not exactly full. E.g. no difference between min-width and min-device-width const baseStyle = { podorozhnikAppView: { flexDirection: 'column', … export const appStyle = createStyles( baseStyle, maxHeight(400, { optionsBlock: { marginTop: 0, … https://twitter.com/AgileArtem
Portrait-Landscape layouting and *cascading* styles <View style={appStyle.podorozhnikAppView} onLayout={(e) => { if(this.props.onAppLayout) { this.props.onAppLayout({ width: e.nativeEvent.layout.width, height: e.nativeEvent.layout.height }); https://twitter.com/AgileArtem
Autostoring redux data to localStorage import * as storage from 'redux-storage'; import createEngine from 'redux-storage-engine- reactnativeasyncstorage'; import merger from 'redux-storage-merger- immutablejs'; • Just works • Mobile app only implementation • Immutable JS was an issue here. As restoring data was trying to overwrite the model https://twitter.com/AgileArtem
WHAT NOT TO FOLLOW https://twitter.com/AgileArtem
WebViews on the app side for the web services • I tried one for disqus • Bad idea. Slow, error prone, hard to debug and fix https://twitter.com/AgileArtem
A note on testing • I love automated structured testing so much that for Jolla Sailfish OS I baked HelloWorld wizard that includes testing of engine, UI, C++, JavaScript, whatnot. • Started in full testing more in ReactJS/Native and.. • Nearly completely dropped in the end • For the relatively simple UI-intensive project Redux with its DevTools lets you identify and fix issues faster than tests would have prevented them • In a bigger project with collaborators and less core research I’d use auto testing though https://twitter.com/AgileArtem
iPad layout • Didn’t work • Seems to be possible, but solution I used results in the iPhone mode https://twitter.com/AgileArtem
Same code for React app on the web, iOS, Android • Yes • No • Maybe • You are only going to really benefit from the logic part only • iOS and Android are close enough for sharing almost everything https://twitter.com/AgileArtem
WHAT NEXT https://twitter.com/AgileArtem
Next project: compare medicine or alcohol prices around you. Anybody in? https://twitter.com/AgileArtem
Contacts • https://podorozhnik.firebaseapp.com • https://itunes.apple.com/ru/app/podoroznik-kal- kulator/id1107432204 • https://play.google.com/store/apps/details?id=com.art emmarchenko.podorozhnik • https://twitter.com/AgileArtem • http://github.com/amarchen • http://www.codingsubmarine.com • http://www.agilesoftwaredevelopment.com https://twitter.com/AgileArtem

One code Web, iOS, Android

  • 1.
    One code Web,iOS, Android Artem Marchenko, 09 Feb 2017 https://twitter.com/AgileArtem
  • 2.
    Artem https://twitter.com/AgileArtem • Buzzwords: – Interactiveimages, Qt/QML, Jolla SailfishOS, Agile- shmagile, TDD, product management, JavaScript, Java, whatever works, prototyping, startups, paragliding, salsa dancing, ReactJS, React Native – http://www.thinglink.com • Twitter: @AgileArtem
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
    App structure https://twitter.com/AgileArtem index.js index.ios.js index.androi d.js native/ AppContainer AppContainer Other webUI components Other native UI components web reducers common reducer native reducers native- specific reducer (e.g. orientation change)
  • 10.
    Project structure - src -native - components - styles - util - web - components - test - web - components https://twitter.com/AgileArtem
  • 11.
  • 12.
    Google Analytics: Mobile react-native-google-analytics-bridge •Worked as simple as const GA = require('react-native-google- analytics-bridge'); const GA_TRACKER_ID = Platform.OS === 'ios' ? 'UA-76217125-6' : 'UA-76217125-5'; GA.setTrackerId( GA_TRACKER_ID ); GA.trackEvent('general', 'app: activated'); https://twitter.com/AgileArtem
  • 13.
    Mobile Ads: GoogleAdMob react-native-admob Converting default React Native iOS project to pods/workspace can be challenging, but then the usage is super-simple import { AdMobBanner } from 'react-native-admob’; <AdMobBanner style={appStyle.bottomBanner} bannerSize={"smartBannerPortrait"} adUnitID={"ca-app-pub- 6248714847105943/1045980532"} didFailToReceiveAdWithError={this.bannerError} /> https://twitter.com/AgileArtem
  • 14.
    Redux and ReduxDev Tools • Redux (or Flux if you like) could be the best part of React practice actually https://twitter.com/AgileArtem
  • 15.
  • 16.
    Redux. Good parts •Redux is awesome. • Debugging dumb structures, tracing changes message by message and time traveling is simple and efficient • Definitely use Redux Dev Tools (e.g. as a Chrome extension) https://twitter.com/AgileArtem
  • 17.
    Redux. Complexities • MakingRedux, routing and Local Storage like each other was pain in the bottom and rain dances – There is logic certainly, but you’ll either need to learn a lot of it or dance around and hope – Or clone my solution, but be aware it’s coding by accident • Modifying several files in the different parts of code base (action creator, reducer, handler) for just passing same stuff around is a lot of error prone typing. – Consider ducks approach – all the code about one bit of functionality together https://twitter.com/AgileArtem
  • 18.
    Redux: more mistakes •Do not store UI state (screen size) or computable data (final price) in the model • Use memoizable redux selectors for it (e.g. reselect). – Looks the same, feels the same, but you do not pollute the model with the data to keep in sync https://twitter.com/AgileArtem
  • 19.
    Immutable.JS const calcedPaymentState = preUpdatedState.setIn(['paymentOptions','eTicket', 'totalCost'], newETicketCost) • State that’s guaranteed to be immutable is way easier to debug • But not all the components are ready for it out of the box and want to see plain JS objects (I had issues with browserHistory I think) • And on a small research-like app you might not see benefits of immutability yet while you might hit the integration obstacles https://twitter.com/AgileArtem
  • 20.
    Routing and browseand sharable urls import { Router, Route, IndexRoute, browserHistory, useRouterHistory } from 'react- router'; import { routerMiddleware, syncHistoryWithStore, routerReducer, push } from 'react- router-redux'; import createBrowserHistory from 'history/lib/createBrowserHistory'; const queryString = require('query-string'); const history = syncHistoryWithStore(browserHistory, store, {selectLocationState: (state) => { const r = state.getIn(['metadata', 'routing']); return r || '/'; }}); const routedState = state.setIn(['metadata', 'routing'], { locationBeforeTransitions: { pathname: '/', search: searchString, query: {}, hash: '' } }) https://twitter.com/AgileArtem
  • 21.
    Routing and browseand sharable urls • React Router is okay, browserHistory is okay, storing routing in a storage is okay, but making it work together is tough – Especially if some more middleware is involved: Redux Dev Tools • I used query string as the initial boss that commanded the state that was setting up the routing – Then state updates are changing the browserHistory-specific keys. browserHistory was updating the address bar • And there are WebKit bugs features. You cannot update URL too often • Use my solution if you just want things to work – Yet it’s programming by accident https://twitter.com/AgileArtem
  • 22.
    Structuring controls fortesting • Simple, isolates core part for testability, but I didn’t use much testing in the end – Started from awesome tutorial by a Finn @teropahttp://teropa.info/blog/2015/09/10/full-stack- redux-tutorial.html export class InputBlock extends React.Component { constructor(props) { … export const InputBlockContainer = connect( mapStateToProps, actionCreators )(InputBlock); https://twitter.com/AgileArtem
  • 23.
    Using template forinjecting stuff into the web root var HtmlWebpackPlugin = require('html-webpack-plugin'); … new HtmlWebpackPlugin({ inject: false, template: 'src/web/index.ejs', googleAnalytics: { trackingId: 'UA-76217125-4', pageViewOnLoad: true }, … <% if (htmlWebpackPlugin.options.googleAnalytics.trackingId) { %> ga('create', '<%= htmlWebpackPlugin.options.googleAnalytics.trackingId%>', 'auto'); https://twitter.com/AgileArtem
  • 24.
    Title/Nav bar react-native-navbar • Surprisinglydifficult to do in crosplatform way • react-native-navbar works, but don’t expect to fool a maniacal designer https://twitter.com/AgileArtem
  • 25.
    Portrait-Landscape layouting and *cascading*styles react-native-media-queries • Works, but you may need to track rotation yourself • Not exactly full. E.g. no difference between min-width and min-device-width const baseStyle = { podorozhnikAppView: { flexDirection: 'column', … export const appStyle = createStyles( baseStyle, maxHeight(400, { optionsBlock: { marginTop: 0, … https://twitter.com/AgileArtem
  • 26.
    Portrait-Landscape layouting and *cascading*styles <View style={appStyle.podorozhnikAppView} onLayout={(e) => { if(this.props.onAppLayout) { this.props.onAppLayout({ width: e.nativeEvent.layout.width, height: e.nativeEvent.layout.height }); https://twitter.com/AgileArtem
  • 27.
    Autostoring redux datato localStorage import * as storage from 'redux-storage'; import createEngine from 'redux-storage-engine- reactnativeasyncstorage'; import merger from 'redux-storage-merger- immutablejs'; • Just works • Mobile app only implementation • Immutable JS was an issue here. As restoring data was trying to overwrite the model https://twitter.com/AgileArtem
  • 28.
    WHAT NOT TOFOLLOW https://twitter.com/AgileArtem
  • 29.
    WebViews on theapp side for the web services • I tried one for disqus • Bad idea. Slow, error prone, hard to debug and fix https://twitter.com/AgileArtem
  • 30.
    A note ontesting • I love automated structured testing so much that for Jolla Sailfish OS I baked HelloWorld wizard that includes testing of engine, UI, C++, JavaScript, whatnot. • Started in full testing more in ReactJS/Native and.. • Nearly completely dropped in the end • For the relatively simple UI-intensive project Redux with its DevTools lets you identify and fix issues faster than tests would have prevented them • In a bigger project with collaborators and less core research I’d use auto testing though https://twitter.com/AgileArtem
  • 31.
    iPad layout • Didn’twork • Seems to be possible, but solution I used results in the iPhone mode https://twitter.com/AgileArtem
  • 32.
    Same code forReact app on the web, iOS, Android • Yes • No • Maybe • You are only going to really benefit from the logic part only • iOS and Android are close enough for sharing almost everything https://twitter.com/AgileArtem
  • 33.
  • 34.
    Next project: comparemedicine or alcohol prices around you. Anybody in? https://twitter.com/AgileArtem
  • 35.
    Contacts • https://podorozhnik.firebaseapp.com • https://itunes.apple.com/ru/app/podoroznik-kal- kulator/id1107432204 •https://play.google.com/store/apps/details?id=com.art emmarchenko.podorozhnik • https://twitter.com/AgileArtem • http://github.com/amarchen • http://www.codingsubmarine.com • http://www.agilesoftwaredevelopment.com https://twitter.com/AgileArtem