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

The things we’ve learned from iOS×React Native hybrid development

@hotchemi
August 31, 2018

The things we’ve learned from iOS×React Native hybrid development

@hotchemi

August 31, 2018
Tweet

More Decks by @hotchemi

Other Decks in Programming

Transcript

  1. iOSDC 2018
    The things we’ve learned from
    iOS×React Native hybrid development
    @hotchemi

    View Slide

  2. Goal: walk through hybrid style
    development in 15 mins!

    View Slide

  3. 01
    02
    03
    04
    05
    Agenda | Introduction
    Motivation
    How we integrated
    Tips & Tricks
    Conclusion

    View Slide

  4. 01 Introduction

    View Slide

  5. Introduction
    ● Shintaro Katafuchi
    ○ @hotchemi
    ● Engineering [email protected]
    ○ StudySapuri
    ● Co-host of dex.fm(Android, React Native, Flutter)
    ○ Check ep55, 56

    View Slide

  6. 02 Motivation

    View Slide

  7. React Native
    ● Build native mobile apps using JavaScript and React
    ○ “Learn Once, Write Anywhere”
    ● User: Facebook, Instagram, Skype, Microsoft, Discord
    ○ Quipper started investigation from Autumn 2017

    View Slide

  8. View Slide

  9. Why we chose?
    ● To secure enough amount of mobile developers
    ○ Almost all web developer in Quipper can write React
    ● Share some logics/UI components among Web and App
    ● Easy to integrate with existing app

    View Slide

  10. 03 How we integrated

    View Slide

  11. How we integrated
    ● Investigated by developing new small app
    ● Read “Integration with Existing Apps” section in official doc
    ● Migrated to “monorepo”
    ● Proceed a migration incrementally from new feature
    ● The magic API: RCTRootView
    ○ We wrap with UIViewController(ReactViewController)

    View Slide

  12. View Slide

  13. ● Surrounded: RCTRootView×TypeScript
    ● Others: (Pure)UIKit×Swift

    View Slide

  14. public typealias ReactProps = [String : AnyObject]
    class ReactViewController: UIViewController {
    var screenName: String?
    var props: ReactProps?
    convenience init(screenName: String, props: ReactProps? = nil) {
    self.init(nibName: nil, bundle: nil)
    self.props = props
    }
    override func viewDidLoad() {
    super.viewDidLoad()
    populateReactView()
    }
    private func populateReactView() {
    guard let reactView = RCTRootView(bridge: ReactNative.shared.bridge, moduleName: screenName, initialProperties: nil) else { return }
    view.addSubview(reactView)
    reactView.translatesAutoresizingMaskIntoConstraints = false
    if #available(iOS 11.0, *) {
    let guide = self.view.safeAreaLayoutGuide
    reactView.trailingAnchor.constraint(equalTo: guide.trailingAnchor).isActive = true
    reactView.leadingAnchor.constraint(equalTo: guide.leadingAnchor).isActive = true
    reactView.topAnchor.constraint(equalTo: guide.topAnchor).isActive = true
    reactView.bottomAnchor.constraint(equalTo: guide.bottomAnchor).isActive = true
    }
    }

    View Slide

  15. class ProfileContainer extends Component {
    render() {
    return (
    renderItem={({ item }) => {
    return (
    item={item}
    onPress={onSwitchAccountPress}
    />
    }}
    renderSectionHeader={({ section }) => {
    return ;
    }}
    sections={profileSections(this.props.user)}
    keyExtractor={item => item.name}
    stickySectionHeadersEnabled={true}
    />
    );
    }
    }

    View Slide

  16. export default function registerComponents(store: Store) {
    AppRegistry.registerComponent(Screens.profile, () =>
    withReduxStore(ProfileContainer, store),
    );
    }

    View Slide

  17. View Slide

  18. ● TypeScript: 25%
    ● Swift: 75%

    View Slide

  19. 04 Tips&Tricks

    View Slide

  20. Tips 01: Sunset Android support
    ● Just dropped 2 months ago
    ○ NDK, slow in debug mode, lots of workarounds are needed
    ○ Especially on “brown-field” app
    ● Cross platform is apparently difficult
    ○ Who knows both platform philosophies well?
    ○ Few developers can write a bridge

    View Slide

  21. View Slide

  22. Tips 02: Data Management
    ● We basically use Redux but it can’t be genuine “Single Store”
    ○ Because existing code already has its own infrastructure
    ● Don’t sync any data as much as you can
    ○ Follow single-way sync direction if you really need to do
    ○ We only sync essential user data(like userID)
    ○ The magic API: RCTBridge, RCTDeviceEventEmitter

    View Slide

  23. View Slide

  24. final class ReactNative: NSObject {
    enum EventType: String {
    case sendUserData
    }
    static let shared = ReactNative()
    var bridge: RCTBridge?
    private override init() {
    super.init()
    }
    func enqueueEvent(name: EventType, with: Any = []) {
    let args: [Any] = [name.rawValue as Any, with]
    bridge?.enqueueJSCall("RCTDeviceEventEmitter.emit", args: args)
    }
    }

    View Slide

  25. func activate() -> Promise {
    return API.Qlearn.Bootstrap
    .bootstrap()
    .then { bootstrap -> Promise in
    ReactNative.shared.enqueueEvent(name: .sendUserData, with: bootstrap.toJSONString())
    let initializer = Initializer()
    initializer.execute(user: user, context: userSessionContext)
    return Promise(resolved: bootstrap)
    }
    }

    View Slide

  26. import { DeviceEventEmitter } from 'react-native';
    import { Store } from 'redux';
    import { BootStrap, updateBootStrap } from '../actions/bootstrapAction';
    import { clearStore } from '../auth/authAction';
    import { EventType } from '../constants/eventType';
    export const observeSendUserDataEvent = (store: Store) => {
    DeviceEventEmitter.addListener(EventType.sendUserData, (payload: string) => {
    const bootstrap: BootStrap = JSON.parse(payload);
    store.dispatch(updateBootStrap(bootstrap));
    });
    };

    View Slide

  27. Tips 03: UI Consistency
    ● Recognize React Native as “A customView written in JavaScript”
    ○ Use UIViewController for container/screen transition
    ○ Easy to integrate with existing screens
    ○ Easy to deal with new platform change(like SafeArea)
    ● Still important to follow HIG/iOS standard way

    View Slide

  28. View Slide

  29. final class ProfileEditViewController: ReactViewController {
    var resolver: Resolver!
    override func viewDidLoad() {
    super.viewDidLoad()
    }
    @objc func onSave(_ sender: UIBarButtonItem) {
    ReactNative.shared.enqueueEvent(name: .onSaveProfileEdit)
    }
    @objc func onCancel(_ sender: UIBarButtonItem) {
    ReactNative.shared.enqueueEvent(name: .onCancelPress)
    }
    }

    View Slide

  30. class ProfileEditContainer extends Component {
    private emitterSubscriptions: EmitterSubscription[] = [];
    constructor(props: Props) {
    super(props);
    this.setupEventEmitterListeners();
    }
    setupEventEmitterListeners = () => {
    this.emitterSubscriptions.push(
    DeviceEventEmitter.addListener(EventType.onSaveProfileEdit, this.updateUserEvent);
    );
    this.emitterSubscriptions.push(
    DeviceEventEmitter.addListener(EventType.onCancelPress, () =>
    this.props.resetDraftUser(),
    ),
    );
    };
    }

    View Slide

  31. 05 Conclusion

    View Slide

  32. Retrospective -Good-
    ● We could scale up iOS team
    ○ 2 Web devs, 1 iOS dev
    ● React component and redux are loose coupling and testable
    ● Performance is better than expected and no memory issue for now
    ● We can share some logics/interfaces(not UI)

    View Slide

  33. Retrospective -Bad-
    ● Damn on Android!
    ● Not low learning curve for iOS dev
    ● Two style architectures are sort of of mixed
    ○ Need to know both Swift(ObjC) and JavaScript worlds

    View Slide

  34. Hybrid is the way to go?
    ● First of all React Native is not a silver bullet
    ● you can scale up a team by mixing web practice and iOS knowledge
    ● If you develop “green field” app, Go!
    ● If you develop “brown field” app, be careful for aforementioned points

    View Slide

  35. Thank you!
    @hotchemi

    View Slide