Bringing onResume/viewDidAppear onPause/viewDidDisappear to Flutter
If I ask you to develop a page that fetches its information as soon as we open it, you’d probably be done in a couple of minutes. You have a variety of hooks to choose for doing the job. Maybe initState
, didChangeDependencies
with a control flag, your BLoC’s constructor or anything similar, and they would all work just fine.
But now, what if I ask you to re-fetch the same info whenever the page appears on screen instead of only when it is first opened?
Every Time the Page Appears on Screen?
In other words: every time the page has the focus.
That encompasses three scenarios:
- When we first open it;
- When we come back to it after opening another page;
- When we put the app in the background while the page was on screen, and then bring the app back to the foreground;
Native mobile developers acknowledge this as using the onResume
or viewDidAppear
callback, but in Flutter, there wasn’t a dead-simple way to accomplish the same.
Your first impulse will tell you to use the build
method, but that’s a terrible idea because it runs anytime your widget needs to rebuild, and that happens quite a lot.
Then, after spending some time at Google or StackOverflow, RouteObserver
along with RouteAware
widgets will pop at you. It’s a way to register your widget to be notified whenever we push and pop back to its Route
. Problems? It won’t effortlessly work if you use nested Navigator
s. Think about it: if you use, for example, a bottom navigation structure, there are two ways in which your page can appear on screen:
- If we push or pop back to its
Route
; - If we push or pop back to its “parent widget’s
Route
” (the page holding the bottom navigation menu);
To cover the second case, we would have to do all the RouteAware
set up on the parent widget as well and find a way to propagate the event down the tree. If it sounded like a lot of work, that’s because it is! Also, it wouldn’t cover the scenario in which we put the app in the background and then bring it to the foreground again, as the pushed route’s remained the same.
Thankfully, some guys at Google (apparently not from the core Flutter team) provided us a package that gets us closer to what we want.
The VisibilityDetector Package
A VisibilityDetector widget wraps an existing Flutter widget and fires a callback when the widget’s visibility changes.
It looks that now we have everything figured out, right?!
Not yet!
The VisibilityDetector
won’t detect the case in which we bring the app back to the foreground while the widget was visible.
Fortunately, Flutter does have an easy way for covering that: WidgetsBindingObserver
. We just need to combine it with the VisibilityDetector
.
The FocusDetector Package Was Born
https://pub.dev/packages/focus_detector
You can depend on it or just copy-paste the below gist into your project. I strongly recommend the former, so that you get any bug fixes.
Usage
If you’ve followed my previous posts, you know that I have this Breaking Bapp project I like using for showcasing. It uses a simple master-detail pattern for displaying Breaking Bad’s info.
From a product perspective, it isn’t the best use case for the FocusDetector
. Re-fetching information every time the page gains the focus isn’t useful at all for an app like this. It only consumes unnecessary bandwidth and makes the app slower.
On the other hand, as far as the code goes, its simplicity is ideal for you to see the FocusDetector
in action.
That’s all! It couldn’t be simpler!
Previously, the above code was calling the _fetchCharacterSummaryList
method on the initState
callback. You can see the initial and final versions by checking out the focus-detector/set-state-initial
and focus-detector/set-state-bloc-detector
branches, respectively, of the GitHub Repository.
Bonus
For an example using BLoCs, compare the focus-detector/bloc-initial
and focus-detector/bloc-focus_detector
branches.