Bringing onResume/viewDidAppear onPause/viewDidDisappear to Flutter
2020, Mar 19
Did you miss those guys as much as I did? That ends now! And it couldn’t be simpler!
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 Navigators. 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 “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?!
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.
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.
For an example using BLoCs, compare the focus-detector/bloc-initial and focus-detector/bloc-focus_detector branches.