Upgrade to Pro — share decks privately, control downloads, hide ads and more …

React-Native in a Content-Driven World

React-Native in a Content-Driven World

We want to be more agile, we want to continuously deliver a better native experience - and React-Native enables us to do so! I will talk about our on-going process of implementing a react-native powered, content-driven feature in our existing iOS / Android Apps at Runtastic. I will cover some topics like our Redux workflow, native navigation, app-updates without Apphub and more!

Patrick Stapfer

December 16, 2015
Tweet

More Decks by Patrick Stapfer

Other Decks in Programming

Transcript

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

    View full-size slide

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

    View full-size slide

  3. 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

    View full-size slide

  4. + 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

    View full-size slide

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

    View full-size slide

  6. 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

    View full-size slide

  7. Talking about React-Native

    View full-size slide

  8. Project structure looks kinda familiar?

    View full-size slide

  9. 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…?

    View full-size slide

  10. 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

    View full-size slide

  11. 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

    View full-size slide

  12. Good times for -USERS

    View full-size slide

  13. But how to get native?
    RTMapViewManager

    View full-size slide

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

    View full-size slide

  15. //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

    View full-size slide

  16. 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!

    View full-size slide

  17. 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);

    View full-size slide

  18. Some App Details

    View full-size slide

  19. 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

    View full-size slide

  20. (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
    };
    }

    View full-size slide

  21. 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);

    View full-size slide

  22. Platform specific render-methods
    _renderList() {
    return (

    {this._renderToolbarAndroid()}
    posts={this.props.posts}
    onPostPressed={this._onPostPressed}
    onProfilePressed={this._onProfilePressed}
    onLikePostPressed={this._onLikePostPressed}
    />

    );
    }
    const IS_ANDROID = Platform.OS === 'android';
    _renderToolbarAndroid() {
    if (IS_ANDROID && this.props.needsToolbar) {
    return (

    );
    }
    }
    We actually prefer separate files,
    whenever possible

    View full-size slide

  23. 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

    View full-size slide

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

    View full-size slide

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

    View full-size slide