didChangeAppLifecycleState method

  1. @override
void didChangeAppLifecycleState(
  1. AppLifecycleState state
)
override

Called when the system puts the app in the background or returns the app to the foreground.

An example of implementing this method is provided in the class-level documentation for the WidgetsBindingObserver class.

This method exposes notifications from SystemChannels.lifecycle.

See also:

Implementation

@override
void didChangeAppLifecycleState(AppLifecycleState state) {
  // The session observes the lifecycle itself, but its observer runs after
  // this one. Rotate here first, so a session that timed out ends the waits
  // before this return's `app_foregrounded` starts its own.
  if (state == AppLifecycleState.resumed) {
    _services?.sessionManager.onForeground();
  }
  if (state == AppLifecycleState.resumed) {
    // The trampoline relaunches the host's Activity, so a warm debug-settings
    // deeplink arrives here rather than as a route push.
    unawaited(_consumeRelayedDebugDeepLink());
  }
  // Waits keep counting in the background but end only on screen: on the
  // return, read the sleep clock, then fire every wait that ended meanwhile.
  if (state == AppLifecycleState.resumed) {
    unawaited(_sleepClock.sync().then((_) {
      if (!_inBackground) _engine.resume();
    }));
    _dwellTracker.resume();
  } else if (state == AppLifecycleState.hidden ||
      state == AppLifecycleState.paused ||
      state == AppLifecycleState.detached) {
    _engine.pause();
    _dwellTracker.pause();
  }
  // DMC §6.2: a return from the background, not a dialog over the app.
  if (state == AppLifecycleState.resumed) {
    if (_inBackground) {
      _inBackground = false;
      // DMC §3.7: a return after the context expired, or after the session
      // went idle, fetches it again. A context still valid costs no call.
      // Not awaited: this return's signal is checked against the copy held
      // now, and the next signal sees the new one.
      if (_initialized) unawaited(_refreshUserContext());
      if (_appOpenedDue) {
        _appOpenedDue = false;
        _sendAppOpened();
      } else {
        _engine.send(const AppForegroundedSignal());
      }
    }
  } else if (state == AppLifecycleState.hidden ||
      state == AppLifecycleState.paused ||
      state == AppLifecycleState.detached) {
    _inBackground = true;
    // Held signals are of the app that was on screen (R58).
    if (!_engine.isReady) _engine.dropHeld();
  }
  // Pause the PiP's player and close its open expanded span while the app is
  // away, so `engaged_ms` counts only time the window was actually watchable.
  // Without this the span stays open across the whole background stretch.
  //
  // `inactive` is deliberately excluded: it fires for transient interruptions
  // the user is still looking through — the app switcher, an incoming call,
  // Control Centre — and pausing playback for those would stutter the window
  // on every glance. `hidden` and `paused` are the real "no longer on screen"
  // signals, which is where Android's ON_STOP draws the same line.
  switch (state) {
    case AppLifecycleState.resumed:
      _pipOrchestrator.setAppForegrounded(true);
      _floaterStoryOrchestrator.setAppForegrounded(true);
    case AppLifecycleState.hidden:
    case AppLifecycleState.paused:
    case AppLifecycleState.detached:
    case AppLifecycleState.inactive:
      _coordinator.noteAppLeftForeground();
      _pipOrchestrator.setAppForegrounded(false);
      _floaterStoryOrchestrator.setAppForegrounded(false);

      break;
  }
  // detached = permanent process destruction. Not paused (every background).
  // The owner flushes both queues; neither knows about the other.
  _services?.appLifecycleChanged(state);
  if (state == AppLifecycleState.detached) {
    final plugin = _activePlugin;
    if (plugin != null) {
      // Same ordering as register(): settle what the plugin owns while its
      // bridge is alive, then tear it down.
      _coordinator.detach(plugin.id);
      plugin.detach();
    }
    _activePlugin = null;
  }
  // Closes/reopens the live-test stream on background/resume so a stale
  // presence lease isn't held between heartbeats.
  _services?.liveTestService.onAppLifecycleChanged(state);
}