Search code examples
androidiosflutterlifecycle

Flutter exact lifecycle equivalents to onResume / onPause on Android and viewWillAppear / viewDidDisappear on iOS


I've seen this asked before, but the questions are always subsets of functionality. I'm looking for exact equivalent life cycle events. And I can be more specific.

The ones I'm aware that exist in Flutter widgets are

  • onCreate/viewDidLoad = initState
  • onDestroy/viewDidUnload = dispose

These are the life cycle events I'd like to know about (for the currently visible widget/route, not the entire app):

onResume / viewWillAppear getting fired on the currently visible widget when
  1. Internal nav (navigating around within the app)

    a. You push the route/widget onto the stack

    b. You navigate back to the route/widget (that was already on the back of nav stack)

  2. External nav (backgrounding and foregrounding the app)

    a. You open the app from the background (and the route/widget is the one being displayed to the user)

    b. You turn the screen on (power button) and the app was already open

onPause / viewDidDisappear getting fired on the currently visible widget when
  1. Internal nav (navigating around within the app)

    a. You navigate away from a widget/route going forward

    b. You navigate away from the widget by dismissing it / going backward in the stack

  2. External nav (backgrounding and foregrounding the app)

    a. You background the app (and the route/widget is the one being displayed to the user) b. You turn the screen off (power button) with the app open


Solution

  • I've found a few solutions, each with its own pros and cons. The one that answers this question the best is FocusDetector.

    Best Pick

    FocusDetector (Edson Bueno)

    FocusDetector handles all the cases covered in the original question. Instead of overrides (like initState() and dispose()), you supply callback functions to a wrapping widget called FocusDetector. The two relevant callbacks are:

    • onFocusGained = onResume

    • onFocusLost = onPause

    • Cons

      • Isn’t maintained by Flutter or Google (but it uses VisibilityDetector and WigetBindingObserver under the hood)
      • The default VisibilityDetectorController.updateInterval is 500ms which means the events get fired somewhat late.

    Borrowing the style from one of my favorite posts:

    iOS, Android, and Flutter Life Cycle

    Example Widget

    class PageState extends State<Page> {
    
      @override
      void initState() {
        super.initState();
        log("onCreate / viewDidLoad / initState");
        WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
          layoutComplete();
        });
      }
    
      // Bonus one I've found helpful, once layout is finished
      void layoutComplete() {
        log("onActivityCreated / viewDidLoad / layoutComplete");
      }
    
      void viewWillAppear() {
        log("onResume / viewWillAppear / onFocusGained");
      }
    
      void viewWillDisappear() {
        log("onPause / viewWillDisappear / onFocusLost");
      }
    
      @override
      void dispose() {
        log("onDestroy / viewDidUnload / dispose");
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return FocusDetector(
          onFocusGained: viewWillAppear,
          onFocusLost: viewWillDisappear,
          child: Text('Rest of my widget'),
          );
      }
    }
    

    Other options

    RouteObserver (Flutter)

    • didPush = onResume current screen is pushed on

    • didPopNext = onResume current screen is being navigated back to

    • didPop = onPause dismissing current page / going back

    • didPushNext = onPause navigating forward to a new page

    • Cons:

      • Doesn’t cover use-case (2), backgrounding then foregrounding the app

    WidgetsBindingObserver (Flutter)

    • AppLifecycleState.resumed = The app is visible and responding to user input

    • AppLifecycleState.paused = The app is not visible and not responding to user input

    • Cons:

      • Isn’t widget/route specific (for external nav (2))
      • Doesn’t cover use-case (1), navigating between pages

    VisibilityDetector (Google)

    • onVisibilityChanged (visibility == 1) = onResume

    • onVisibilityChanged (visibility == 0) = onPause

    • Cons:

      • Doesn’t cover use-case (2), backgrounding then foregrounding the app