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

Living in an async world of React Native

Living in an async world of React Native

React Native’s asynchronous model of bridging between UI code written in JS and the UI thread that runs most of the native code has many advantages to the app users. But in same cases it may become a developer’s nightmare to deal with, especially when it comes to complex touch interactions, animations or "that damned AsyncStorage". In my talk I’ll be giving some tips on how to not get lost in the asynchronous world of React Native app development. We will discuss some common mistakes and important app and library design principles that are imposed by the asynchronous nature of the bridge. At the end of the presentation we give a short into to react-native-gesture-handler library.

Krzysztof Magiera

October 27, 2016
Tweet

More Decks by Krzysztof Magiera

Other Decks in Programming

Transcript

  1. Living in an async world of React Native Krzysztof Magiera

    (@kzzzf) krzys@swmansion.com
  2. REACT NATIVE JS Thread Native Thread UI Thread time

  3. ASYNC STORAGE onboardingCompleted? render(MainApp) render(OnboardingComponent) YES N O

  4. ASYNC STORAGE JS Thread Native Thread getItem('onboardingCompleted'); AsyncStorage if (YES)

    { return <MainApp/> } else { return <Onboarding/> } UI Thread
  5. ASYNC STORAGE JS Thread Native Thread getItem('onboardingCompleted'); AsyncStorage if (YES)

    { return <MainApp/> } else { return <Onboarding/> } UI Thread layout render render rendering !
  6. CREATING “SYNC” STORAGE - (NSDictionary<NSString *, id> *)constantsToExport RCTBridgeModule iOS

    public @Nullable Map<String, Object> getConstants() BaseJavaModule Android const MySyncStorageModule = require('NativeModules').MySyncStorageModule; const _data = MySyncStorageModule.data; function getItem(key) { return _data[key]; } function setItem(key, value) { _data[key] = value; MySyncStorageModule.asyncSet(key, value); } JS
  7. SCREEN ORIENTATION https://www.npmjs.com/package/react-native-orientation Orientation.getInitialOrientation(); // PORTRAIT, LANDSCAPE, etc... Orientation.addOrientationListener((orientation) =>

    { // orientation = PORTRAIT, LANDSCAPE, etc... }); DeviceEventEmitter.addListener('namedOrientationDidChange', (orientation) => { // orientation.rotationDegrees // orientation.isLandscape }); React Native Core (android only)
  8. SCREEN ORIENTATION

  9. SCREEN ORIENTATION JS Thread Native Thread UI Thread orientation changes

    if (isLandscape) { return <LandscapeView/> } else { return <PortraitView/> } event Layout rendering
  10. SCREEN ORIENTATION JS Thread Native Thread UI Thread orientation changes

    if (isLandscape) { return <LandscapeView/> } else { return <PortraitView/> } event Layout rendering render Layout rendering !
  11. SCREEN ORIENTATION ! ! ! ! ! ! ! !

    !
  12. CONTROL ORIENTATION CHANGES IN JS onOrientationChange = (orientation) => {

    lockToOrientation(orientation); this.setState({ orientation }) }; lockToOrientation(initialOrientation); render() { if (this.state.orientation === 'LANDSCAPE') { return <LandscapeView />; } else { return <PortraitView /> } }
  13. ANIMATED var someValue = new Animated.Value(0); // can be kept

    in the component state return ( <Animated.View style={{opacity: someValue}}> <Text>Hello!</Text> </Animated.View> ); Animated.spring( someValue, { toValue: 1, friction: 0.5, } ).start();
  14. ANIMATED JS Thread Native Thread Animated.spring(...).start() UI Thread event animationLoop()

    rendering update some views UIManager setTimeout TimerModule
  15. ANIMATED JS Thread Native Thread Animated.spring(...).start() UI Thread event animationLoop()

    rendering update some views UIManager setTimeout TimerModule event TimerModule animationLoop() rendering update some views UIManager event TimerModule animationLoop() update some views UIManager
  16. ANIMATED ! ! ! ! ! ! ! ! !

  17. ANIMATED - OFFLOADED Animated.spring( someValue, { toValue: 1, friction: 0.5,

    useNativeDriver: true, } ).start();
  18. TOUCH EVENTS JS Thread Native Thread UI Thread rendering update

    some views UIManager handleTouch() event
  19. handleTouch() f1 f2 f3 … sorry man, have to compose

    the frame input compose VSYNC input compose input rendering UI Thread JS Thread TOUCH EVENTS
  20. TOUCH EVENTS ! ! ! ! ! ! ! !

    !
  21. <ScrollView onScroll={Animated.event( [{ nativeEvent: { contentOffset: { y: this._scrollY }}}],

    { useNativeDriver: true } )} ... /> TOUCH EVENTS
  22. GESTURE HANDLING JS Thread Native Thread UI Thread handleTouch() send

    touch down event Touch DOWN Scroll recognizer Touchable
  23. GESTURE HANDLING JS Thread Native Thread UI Thread handleTouch() send

    touch down event Touch DOWN Touch MOVE handleTouch() handleTouch() Touch MOVE Touch MOVE send touch move event send touch move event Scroll recognizer Touchable
  24. GESTURE HANDLING JS Thread Native Thread UI Thread handleTouch() send

    touch down event Touch DOWN Touch MOVE handleTouch() handleTouch() Touch MOVE Touch MOVE send touch move event send touch move event send scroll event handleScroll() Scroll recognizer " Touchable #
  25. GESTURE HANDLING JS Thread Native Thread UI Thread Touch DOWN

    Scroll recognizer handleTouch() send touch down event Touchable
  26. GESTURE HANDLING JS Thread Native Thread UI Thread Touch DOWN

    handleTouch() handleTouch() Touch MOVE Touch MOVE Touch UP send touch move event send touch move event Scroll recognizer handleTouch() send touch down event Touchable
  27. GESTURE HANDLING JS Thread Native Thread UI Thread Touch DOWN

    handleTouch() handleTouch() Touch MOVE Touch MOVE Touch UP send touch move event send touch move event Scroll recognizer handleTouch() send touch up event # handleTouch() send touch down event Touchable "
  28. GESTURE HANDLING

  29. JS Thread Native Thread UI Thread Touch DOWN handleTouch() send

    touch down event PanResponder GESTURE HANDLING Scroll recognizer
  30. JS Thread Native Thread UI Thread Touch DOWN Touch MOVE

    handleTouch() handleTouch() Touch MOVE Touch MOVE send touch move event send touch move event handleTouch() send touch down event PanResponder GESTURE HANDLING Scroll recognizer
  31. JS Thread Native Thread UI Thread Touch DOWN Touch MOVE

    handleTouch() handleTouch() Touch MOVE Touch MOVE send touch move event send touch move event handleTouch() send touch down event PanResponder " GESTURE HANDLING Scroll recognizer "
  32. GESTURE HANDLING ! ! ! ! ! ! ! !

    ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! !
  33. class Button extends React.Component { render() { return ( <TapGestureHandler>

    <View style={[this.props.style, this.state.pressed && styles.pressed]}> <Text>Hello</Text> </View> </TapGestureHandler> ); } } GESTURE HANDLING
  34. class Button extends React.Component { render() { return ( <TapGestureHandler

    onHandlerStateChange={this._onStateChange}> <View style={[this.props.style, this.state.pressed && styles.pressed]}> <Text>Hello</Text> </View> </TapGestureHandler> ); } _onStateChange = (event) => { } } GESTURE HANDLING
  35. class Button extends React.Component { render() { return ( <TapGestureHandler

    onHandlerStateChange={this._onStateChange}> <View style={[this.props.style, this.state.pressed && styles.pressed]}> <Text>Hello</Text> </View> </TapGestureHandler> ); } _onStateChange = (event) => { this.setState({ pressed: event.state === State.BEGAN }); if (event.state === State.ACTIVE) { this.props.onClick(); } } } GESTURE HANDLING
  36. class Draggable extends React.Component { constructor(props) { super(props); this._translateX =

    new Animated.Value(0); this._translateY = new Animated.Value(0); } render() { return ( <Animated.View style={[styles.box, { transform: [ {translateX: this._translateX}, {translateY: this._translateY} ]}]}/> ); } } GESTURE HANDLING
  37. class Draggable extends React.Component { constructor(props) { super(props); this._translateX =

    new Animated.Value(0); this._translateY = new Animated.Value(0); } render() { return ( <PanGestureHandler> <Animated.View style={[styles.box, { transform: [ {translateX: this._translateX}, {translateY: this._translateY} ]}]}/> </PanGestureHandler> ); } } GESTURE HANDLING
  38. GESTURE HANDLING class Draggable extends React.Component { constructor(props) { super(props);

    this._translateX = new Animated.Value(0); this._translateY = new Animated.Value(0); } render() { return ( <PanGestureHandler onGestureEvent={this._onGestureEvent} onHandlerStateChange={this._onHandlerStateChange}> <Animated.View style={[styles.box, { transform: [ {translateX: this._translateX}, {translateY: this._translateY} ]}]}/> </PanGestureHandler> ); } }
  39. _onGestureEvent = (event) => { this._translateX.setValue(event.nativeEvent.translationX); this._translateY.setValue(event.nativeEvent.translationY); } _onHandlerStateChange =

    (event) => { if (event.nativeEvent.oldState === State.ACTIVE) { this._lastOffset.x += event.nativeEvent.lastTranslationX; this._lastOffset.y += event.nativeEvent.lastTranslationY; this._translateX.setOffset(this._lastOffset.x); this._translateX.setValue(0); this._translateY.setOffset(this._lastOffset.y); this._translateY.setValue(0); } } GESTURE HANDLING
  40. _onHandlerStateChange = (event) => { if (event.nativeEvent.oldState === State.ACTIVE) {

    this._lastOffset.x += event.nativeEvent.lastTranslationX; this._lastOffset.y += event.nativeEvent.lastTranslationY; this._translateX.setOffset(this._lastOffset.x); this._translateX.setValue(0); this._translateY.setOffset(this._lastOffset.y); this._translateY.setValue(0); } } GESTURE HANDLING _onGestureEvent = Animated.event( [{ nativeEvent: { translationX: this._translateX, translationY: this._translateY }}], { useNativeDriver: true } );
  41. GESTURE HANDLING render() { return ( <LongPressGestureHandler minDurationMs={2000}> <TapGestureHandler shouldCancelWhenOutside={true}>

    <PanGestureHandler minDeltaX={50}> <Animated.View style={...}/> </PanGestureHandler> </TapGestureHandler> </LongPressGestureHandler> ); } https://github.com/kmagiera/react-native-gesture-handler
  42. Thank you! Krzysztof Magiera krzys@swmansion.com @kzzzf