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

The dark side of Background tasks in React Native - React Alicante 2018

ferrannp
September 15, 2018

The dark side of Background tasks in React Native - React Alicante 2018

In this talk we are going to explore how to manage background tasks within your React Native application. Background tasks are essentially tasks that run when the app is not in the foreground (visible).

ferrannp

September 15, 2018
Tweet

Other Decks in Programming

Transcript

  1. View Slide

  2. View Slide

  3. Hi, I am Ferran!
    Software Engineer at Callstack
    @ferrannp

    View Slide

  4. View Slide

  5. View Slide

  6. ~60% of the code is not JavaScript

    View Slide

  7. Why this talk?

    View Slide

  8. Location
    Audio
    Gyroscope
    Accelerometer
    Heart rate
    etc

    View Slide

  9. View Slide

  10. App elements

    View Slide

  11. React UI

    View Slide

  12. … almost all npm
    packages you love

    View Slide

  13. React Native
    A wrapper for native code
    …with a bridge!

    View Slide

  14. Android
    Services, Broadcast receivers, Content providers,
    Widgets…

    View Slide

  15. iOS
    Background modes, Touch ID, Face ID…

    View Slide

  16. Your app in the
    background

    View Slide

  17. Need to understand it

    View Slide

  18. Android vs iOS
    It’s different!

    View Slide

  19. React Native: The virus

    View Slide

  20. The Android way
    Services

    View Slide

  21. Activity
    Service
    BroadcastReceiver,
    JobScheduler, etc
    Bridge

    View Slide

  22. Activity
    Service
    BroadcastReceiver,
    JobScheduler, etc
    Bridge

    View Slide

  23. The iOS way
    iOS background modes

    View Slide

  24. App instance Bridge
    Background
    Modes

    View Slide

  25. App instance Bridge
    Background
    Modes

    View Slide

  26. Audio, AirPlay and Picture in Picture
    Location updates
    Newsstand downloads
    External accessory communication
    Use Bluetooth LE accessories
    Act as Bluetooth LE accessory
    Background fetch
    Remote notifications

    View Slide

  27. Playing music

    View Slide


  28. View Slide

  29. react-native-track-player
    https://github.com/react-native-kit/react-native-track-player

    View Slide

  30. iOS

    View Slide

  31. Android


    View Slide

  32. JS
    initPlayer = async () => {
    try {
    await TrackPlayer.setupPlayer();
    } catch (e) {
    // Error
    }
    };

    View Slide

  33. iOS
    RCT_EXTERN_METHOD(setupPlayer:(NSDictionary *)data
    resolver:(RCTPromiseResolveBlock)resolve
    rejecter:(RCTPromiseRejectBlock)reject);

    View Slide

  34. iOS
    @objc(setupPlayer:resolver:rejecter:)
    func setupPlayer(
    config: [String: Any],
    resolve: RCTPromiseResolveBlock,
    reject: RCTPromiseRejectBlock) {
    do {
    try AVAudioSession.sharedInstance()
    .setCategory(AVAudioSessionCategoryPlayback)
    try AVAudioSession.sharedInstance()
    .setActive(true)
    } catch {
    reject(error)
    }
    resolve(NSNull())
    }

    View Slide

  35. setupPlayer()
    AVAudioSession
    .setActive(true);
    App instance
    Background
    Modes

    View Slide

  36. Android
    @ReactMethod
    public void setupPlayer(ReadableMap data, final Promise promise) {
    final Bundle options = Arguments.toBundle(data);
    waitForConnection(new Runnable() {
    @Override
    public void run() {
    binder.setupPlayer(options, promise);
    }
    });
    }

    View Slide

  37. Android
    Intent intent =
    new Intent(context, PlayerService.class);
    context.startService(intent);
    intent.setAction(PlayerService.ACTION_CONNECT);

    View Slide

  38. Activity
    Service
    setupPlayer()
    player.setDataSource()
    startService()

    View Slide

  39. JS
    playNewTrack = async () => {
    const { track } = this.props;
    await TrackPlayer.add(track);
    await TrackPlayer.play();
    };

    View Slide

  40. iOS

    View Slide

  41. Android
    private void waitForConnection(Runnable r) {
    ...
    Intent intent =
    new Intent(context, PlayerService.class);
    context.startService(intent);
    intent.setAction(PlayerService.ACTION_CONNECT);
    ...
    }

    View Slide

  42. Android
    public void destroyPlayer() {
    playback.destroy();
    if(!Utils.isStopped(playback.getState())) {
    onStop();
    }
    playback = null;
    if(serviceStarted) {
    service.stopSelf();
    serviceStarted = false;
    }
    }

    View Slide

  43. Service
    destroy()
    stopSelf()
    Activity

    View Slide

  44. Service
    destroy()
    stopSelf()
    Activity

    View Slide

  45. View Slide

  46. react-native-spotify-streamer
    https://github.com/ferrannp/react-native-spotify-streamer

    View Slide

  47. Sync data

    View Slide

  48. Pulling
    Native side
    External API
    Background
    Fetch
    Every X time
    fetch()

    View Slide

  49. Android Headless JS
    https://facebook.github.io/react-native/docs/headless-js-android.html

    View Slide

  50. Activity
    Headless JS
    Service
    BroadcastReceiver,
    JobScheduler, etc
    Starts a JS task
    + inits the bridge

    View Slide

  51. Headless JS
    public abstract class HeadlessJsTaskService
    extends Service
    implements HeadlessJsTaskEventListener

    View Slide

  52. Headless JS
    protected void startTask(
    final HeadlessJsTaskConfig taskConfig
    ) {
    ...
    reactInstanceManager
    .createReactContextInBackground();
    ...
    invokeStartTask(reactContext, taskConfig);
    ...
    }

    View Slide

  53. Setup Headless JS - Native
    public class SyncShowsService extends HeadlessJsTaskService {
    @Override
    protected HeadlessJsTaskConfig getTaskConfig(Intent intent) {
    return new HeadlessJsTaskConfig(
    "SyncShowsTask",
    null, // We could pass arguments
    120000, // Timeout in ms
    false // Task is allowed in foreground
    );
    }
    }

    …also on your AndroidManifest.xml

    View Slide

  54. Setup Headless JS - JS
    module.exports = async (taskData) => {
    // Sync code
    };
    AppRegistry
    .registerHeadlessTask(‘SyncShowsTask',
    () => require(‘./SyncShowsTask’));

    View Slide

  55. When to start it?

    View Slide

  56. When to start it?
    “ Now, whenever you start your service, e.g. as
    a periodic task or in response to some system
    event / broadcast, JS will spin up, run your
    task, then spin down. “

    View Slide

  57. Android SyncAdapter
    https://developer.android.com/training/sync-
    adapters/creating-sync-adapter.html

    View Slide

  58. Android SyncAdapter
    Schedule periodic syncs
    Network connectivity
    Battery performance
    ACTION_BOOT_COMPLETED

    View Slide

  59. react-native-sync-adapter
    https://github.com/ferrannp/react-native-sync-adapter

    View Slide

  60. react-native-sync-adapter
    Android
    SyncAdapter
    Android
    HeadlessJsTask
    Service
    JS code
    Starts Executes

    View Slide

  61. react-native-sync-adapter
    $ yarn add react-native-sync-adapter
    $ react-native link react-native-sync-adapter

    View Slide

  62. JS
    import SyncAdapter from 'react-native-sync-adapter';
    class MyRoot extends Component {
    componentDidMount() {
    SyncAdapter.init({
    syncInterval,
    syncFlexTime,
    });
    }
    }
    AppRegistry
    .registerHeadlessTask('TASK_SYNC_ADAPTER',
    () => SyncShowsTask);

    View Slide

  63. Android
    @ReactMethod
    public void init(int syncInterval, int syncFlexTime) {
    // All Java code described in Android docs
    }
    @Override
    public void onPerformSync(...) {
    Intent service = new Intent(
    getContext(),
    HeadlessService.class
    );
    getContext().startService(service);
    }

    View Slide

  64. View Slide

  65. https://blog.callstack.io/react-native-and-native-modules-the-android-syncadapter-517ddf851bf4

    View Slide

  66. iOS Headless JS

    View Slide

  67. View Slide

  68. https://react-native.canny.io/feature-requests/p/headless-js-for-ios

    View Slide

  69. https://react-native.canny.io/feature-requests/p/headless-js-for-ios

    View Slide

  70. iOS Headless JS
    No such concept

    View Slide

  71. iOS Background Fetch Mode
    https://developer.apple.com/library/content/documentation/iPhone/Conceptual/
    iPhoneOSProgrammingGuide/BackgroundExecution/BackgroundExecution.html

    View Slide

  72. “The app regularly downloads and processes
    small amounts of content from the network.”

    View Slide

  73. iOS Background Fetch Mode
    Network availability
    Device is awake
    Data & time previous attempt
    30 seconds every 15 minutes

    View Slide

  74. react-native-background-fetch
    https://github.com/transistorsoft/react-native-background-fetch

    View Slide

  75. react-native-background-fetch
    $ yarn add react-native-background-fetch
    $ react-native link react-native-background-fetch

    View Slide

  76. iOS

    View Slide

  77. JS
    import BackgroundFetch from 'react-native-background-fetch';
    BackgroundFetch.configure(
    { minimumFetchInterval: 240 },
    () => {
    setTimeout(() => {
    console.log(‘[JS] Finish');
    BackgroundFetch.finish();
    }, 30000);
    // Sync code
    },
    error => {
    console.log('[JS] Error', error);
    }
    );

    View Slide

  78. iOS
    RCT_EXPORT_METHOD(configure:(NSDictionary*)config
    failure:(RCTResponseSenderBlock)failure)
    {
    ...
    [fetchManager configure:config callback:^(UIBackgroundRefreshStatus status) {
    ...
    [fetchManager addListener:RN_BACKGROUND_FETCH_TAG callback:handler];
    [fetchManager start];
    }];
    }

    View Slide

  79. iOS
    - (void) performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))
    handler applicationState:(UIApplicationState)state
    {
    NSLog(@"[%@ performFetchWithCompletionHandler]", TAG);
    if (!hasReceivedEvent) {
    hasReceivedEvent = YES;
    launchedInBackground = (state == UIApplicationStateBackground);
    }
    ...
    for (NSString* componentName in listeners) {
    void (^callback)() = [listeners objectForKey:componentName];
    callback();
    }
    ...
    }

    View Slide

  80. View Slide

  81. Broadcast Receivers
    On Android

    View Slide

  82. HeadlessTaskService
    Receiver
    wifi.STATE_CHANGE
    startService()
    startTask()

    View Slide

  83. Android - Be aware
    New background limitations: https://
    developer.android.com/about/versions/
    oreo/background (API 26)
    JobScheduler (API 21), SyncAdapter
    startForegroundService (API 26)

    View Slide

  84. Wake up recap!

    View Slide

  85. The bridge
    App instance
    React Native

    View Slide

  86. The bridge
    App instance
    Background modes
    React Native

    View Slide

  87. The bridge
    App instance
    Background modes (+ waking
    up mechanisms)
    React Native

    View Slide

  88. The bridge
    Activity Service
    App instance
    Background modes (+ waking
    up mechanisms)
    React Native

    View Slide

  89. React Native
    The bridge
    Activity
    Service
    (Headless)
    Waking up mechanisms
    (receivers, SyncAdapter, etc)
    App instance
    Background modes (+ waking
    up mechanisms)
    JS task

    (same Android & iOS)

    View Slide

  90. There is no magic

    View Slide

  91. We are mobile developers

    View Slide

  92. Thank you!
    @ferrannp

    View Slide