Slide 1

Slide 1 text

React-Native in a Content-Driven World Patrick Stapfer @ryyppy JavaScript-Engineer / Runtastic December 16, 2015

Slide 2

Slide 2 text

http://dialecticmusic.com/2015/08/13/hot-off-the-presses-the-even-ground-d Content-Driven?

Slide 3

Slide 3 text

content kənˈtɛnt/ Goals Achievements User Interactions => Available => Addressable => Browsable => Sharable => Extendable Goals Achievements User Interactions Activities Body-Measurements Leaderboards Achievements User Interactions Goals ... User generated content (content) => UX

Slide 4

Slide 4 text

+ Same web-codebase + Easy to integrate + Fast iteration - No native experience - No specific UI for each platform ~ (Currently only available for iOS) U can’t touch this (properly) We want to improve this! Embedded webframe

Slide 5

Slide 5 text

Our Technical Goals... Support multiple platforms Iterate fast without breaking stuff Content Share knowledge & code-style Runtastic Apps Content

Slide 6

Slide 6 text

Apps runtastic.com Content Module Macro Services Backend JSON API RTCore.js Data Use-Cases LIB Architecture ... Components Native Code RN-Stuff get newest bundle

Slide 7

Slide 7 text

Talking about React-Native

Slide 8

Slide 8 text

Project structure looks kinda familiar?

Slide 9

Slide 9 text

import React from 'react-native'; import NewsFeedApp from './src/containers/apps/NewsFeedApp'; React.AppRegistry.registerComponent('NewsFeedApp', () => NewsFeedApp); The entry-point (index.android.js) looks kinda different…?

Slide 10

Slide 10 text

import React from 'react-native'; import { Provider } from 'react-redux/native'; import { fromJS } from 'immutable'; import configureStore from '../../store/configureStore'; import * as userActionCreators from '../../actions/userActionCreators'; import NewsFeedNavigator from '../NewsFeedNavigator'; const store = configureStore(fromJS({})); class NewsFeedApp extends React.Component { componentWillMount() { store.dispatch(userActionCreators.setCurrentUser(0, 'first', 'last', null)); } render() { return ( {() => }); } } export default NewsFeedApp; No difference to common react.js … but wait! It is not rendering DOM! React can render to different platforms AppRegistry is taking care of that. Neat. But how do I run it? It is using react-native components

Slide 11

Slide 11 text

react-native packager.sh cli.js native components + js-bindings bridge / magic content-module App-Code init view and bridge bundle (android) bundle (ios) first deploy / install app => {app, react-native, bundle.js} update bundle.js (production) Newsfeed,... Native-Code bundle (dev) hot-changes (dev) JS-Libs (RTCore) GET /index.bundle?platform=ios

Slide 12

Slide 12 text

No content

Slide 13

Slide 13 text

Good times for -USERS

Slide 14

Slide 14 text

But how to get native? RTMapViewManager

Slide 15

Slide 15 text

Native (Java/C++) [1] Under the hood of react-native (mkonicek) JavaScriptCore (JS-VM) RTMapViewManager RTMapView Bridge-Communication [[ ‘RTMapView’], {...}]

Slide 16

Slide 16 text

//src/native_components/RTMapView.android.js import { requireNativeComponent, PropTypes } from 'react-native'; const mapViewComponent = { name: 'RTMapView', propTypes: { encodedTrace: PropTypes.string, }, }; const RTMapView = requireNativeComponent('RTMapView', mapViewComponent); export default RTMapView; Defines the React-Component in JS-Land Maps the bridge-details to the component

Slide 17

Slide 17 text

public class RuntasticMapViewManager extends SimpleViewManager implements OnMapReadyCallback { public static final String REACT_CLASS = "RTMapView"; private ReactContext reactContext; private MapView mapView; private GoogleMap googleMap; private String encodedTrace; @Override public String getName() { return REACT_CLASS; } @ReactProp(name = "encodedTrace") public void setEncodedTrace(MapView view, @Nullable String encodedTrace) { this.encodedTrace = encodedTrace; updateTrace(); } //Do the gmaps render logic } Needs to match the name Needs to match the propType!

Slide 18

Slide 18 text

RuntasticReactPackage extends ReactPackage @Override public List createViewManagers(ReactApplicationContext reactContext) { List modules = new ArrayList<>(); modules.add(new RuntasticToolbarManager()); modules.add(new RuntasticMapViewManager()); return modules; } RuntasticReactManager extends ActivityProvider ReactInstanceManager.Builder builder = ReactInstanceManager.builder(); builder.setApplication(application) .setBundleAssetName("") .setJSMainModuleName("index.android") .addPackage(new MainReactPackage()) .addPackage(new RuntasticReactPackage(this)) .setUseDeveloperSupport(devModeEnabled) .setInitialLifecycleState(createInBackground ? LifecycleState.BEFORE_RESUME : LifecycleState.RESUMED);

Slide 19

Slide 19 text

Some App Details

Slide 20

Slide 20 text

React-Native + Redux = ❤ Action-Creator Store Reducer RTCore.js state dispatch(action) async-code Components new state (state, action) action interaction thunk(dispatch, getState) emit changes action

Slide 21

Slide 21 text

(Async) action-creators import newsFeedRepo from '../RTCoreRepository'; export function fetchSocialFeed() { return (dispatch, getState) => { dispatch({ type: actions.FETCH_SOCIAL_FEED }); // TODO use getSocialFeed const promises = newsFeedRepo.getProfileFeed('1'); promises.forEach(p => p.then( feed => dispatch(fetchSocialFeedSuccess(feed.posts)), error => dispatch(fetchSocialFeedFailure(error)) )); }; } function fetchSocialFeedSuccess(posts) { return { type: actions.FETCH_SOCIAL_FEED_SUCCESS, posts, lastUpdate: Date.now() }; } function fetchSocialFeedFailure(error) { return { type: actions.FETCH_SOCIAL_FEED_FAILURE, error }; }

Slide 22

Slide 22 text

Redux connect //./src/containers/NewsFeedSocialScreen.js class NewsFeedSocialScreen extends React.Component {} function mapStateToProps(state) { return { isFetching: state.getIn(['newsFeed', 'socialFeed', 'isFetching']), error: state.getIn([‘newsFeed’, 'socialFeed', 'error']), lastUpdate: state.getIn(['newsFeed', 'socialFeed', 'lastUpdate']), posts: state.getIn(['newsFeed', 'socialFeed', 'posts']).toJS() }; } export default connect(mapStateToProps)(NewsFeedSocialScreen);

Slide 23

Slide 23 text

Platform specific render-methods _renderList() { return ( {this._renderToolbarAndroid()} ); } const IS_ANDROID = Platform.OS === 'android'; _renderToolbarAndroid() { if (IS_ANDROID && this.props.needsToolbar) { return ( ); } } We actually prefer separate files, whenever possible

Slide 24

Slide 24 text

Bundle Update Process On App start BundleHelper Content-Module-Version: 1.0 Backend New Bundle? (Re)load Bridge (Re)load App-View Locale Storage GET /bundle?filter[latest.version]=~1. 0 Yes Stored bundle available? Show Fallback Error View http-failure... CodePush bundle-file

Slide 25

Slide 25 text

3 - 7 days {Iterate, ship} fast without breaking stuff 14 days (Feature-) Team Sprint Release new bundle.js App-Store Review 0 days

Slide 26

Slide 26 text

We are hiring! https://www.runtastic.com/jobs

Slide 27

Slide 27 text

Questions? @ryyppy [email protected]