KEMBAR78
One code Web, iOS, Android | PPTX
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