Tracking screen views in Flutter with Firebase Analytics - flutter

What is the best location for manually logging a screen view in Flutter with an analytics package (in my case I am using Firebase Analytics, eg. track screens)?
Looking at the Flutter lifecycle, it's not clear where it makes sense to track a screen view.
For a StatelessWidget, I think build() might make sense as I guess it's only called one time per screen view.
What about for a StatefulWidget though? In this case build() would not be useful as it could be called many times. Other candidates are initState(), createState() or the constructor which all appear to only be called once although I'm guessing they may all be called more than once per screen view as widgets up the hierarchy are re-built.

The answer is: it depends. For a StatelessWidget, it might be suitable to have an Analytics event in build(), but only if the parent widgets are not re-built frequently. For a StatefulWidget the same applies but you also have to factor in re-builds due to state change (which are, more than likely often).
Really, the safest path is not to call Analytics events in any parts of the widget lifecycle, but instead on the UI event that might trigger a screen, for example, an edit button that opens up an edit screen. However, the problem with that approach is that the screen might be opened from a variety of locations within the app (meaning you have to duplicate Analytics calls across all those locations).
This means the best option is probably to tie Analytics to PageRoute transitions so that it is called consistently whenever a page route is executed. This is demonstrated in the docs. This will miss tracking screens within a tab bar and other types of UI navigation but as the docs also say, one way to handle this is to implement RouteAware and subscribing it to FirebaseAnalyticsObserver (example tabs implementation).

Related

Usage of didChangeAppLifecycleState

I have one didChangeAppLifecycleState method at the top hierarchy of the navigator. It's located at MyApp widget of main.dart which is extended for the whole app and it works as expected.
There comes another page i'd like to add didChangeAppLifecycleState again for the purpose of when resume and user is happened to be at that particular page, then do something. But unfortunately, the second one is never invoked.
My questions are:
can I have more than one didChangeAppLifecycleState which located in different pages?
what should I do to achieve what I want

Reuse Widget Across Two Pages With Keys

Long-winded context, scroll to bottom for specific question:
I have a page with a custom widget (EntrySearch) containing a ListView and a search bar. When the user clicks on an item in the list view, I push a new page with the same list view and a panel displaying item details. The list view's items are lazily loaded from a Stream backed by an asynchronous query (the stream is paused until the user scrolls to the end of elements fetched so far). The stream is replaced whenever the user types in a new string in the search bar.
I want the widget to have the same list view (elements, scroll position, etc.) and search bar state across both pages.
I could achieve this with any of the many state management solutions suggested on similar questions about shared widget state across pages (eg this one). However, because my ListView is backed by an asynchronous stream along with a ton of other state, this becomes a total pain of caching results fetched so far, creating a new Stream that starts where the old one left off (can't have two StreamBuilders backed by the same non-broadcast stream), etc. etc. I'd really like to avoid such a messy solution.
Giving the top level Widget whose state I want to share a global key is one line (instead of ~50) and results in shared state across the pages. However, when I push the new page, the old page stays in the Widget tree on standby, so I get an error for having two widgets with the same global key in the widget tree at the same time. I can set maintainState to false on the first page, but then when I pop the second the first page shows up as just a black screen.
Is there:
a way to use a global key widget across two pages without them both being in the tree at the same time
a specialized key specifically for reusing a widget across two different pages
some other approach I'm not thinking of?
Here's my widget tree with the two pages. EntrySearch is the widget I want to reuse across the two pages.
If none of the above works, I'll have to use a traditional state management strategy which is going to be at least an order of magnitude more verbose and bug-prone.
Here's the solution I went with, if anyone else is approaching a similar problem:
I couldn't find a way to use keys for this problem. What I'm doing now is I have a single class that contains all the state my custom widget needs to build itself. Whenever I push a route to the navigator, I copy the current page's state object with the desired changes and then add the new state to the RouteSettings.argument field. I can then access the page's state object anywhere within the page using ModalRoute.of(context).settings.arguments.
As expected, this solution was massively complex and painful with a lot of subtle bugs I had to work through. It works and at least has slightly more desirable behavior than simply maintaining identical state across both pages: when I pop a page, the state pops too. This means a user can go back to their previous scroll position in the ListView with the back gesture.

In Flutter does avoiding setState() achieve anything?

In Flutter does avoiding setState() achieve anything?
I have a StatefulWidget (screen) where I use Widgets that handle their own state completely with the exception of the FAB button. The Fab button is disabled or enabled when data has changed in order to allow the update of the DB when data has changed. I posted a question on SO as to how to use Provider to handle that and thus prevent the need to setState(), and implemented that with Provider.
When looking at another screen to implement something similar, I wondered if another solution may be better. This screen also contains Widgets that all handle their own state - TextFields with TextEditControllers, so the only place I was calling setState() was to enable or disable the FAB. I had previously created a Stateful Widget to create a CheckBox with its own state because the standard CheckBox doesn't have state. It's fairly simple and only 50 lines of code. So, I did the same with the FAB, I created a Stateful Widget (CustomFab) that encapsulates a FAB. When it needs to be enabled or disabled, it is called to set its own state, and returns a FAB that is either enabled or disabled with the appropriate color. Because I have other similar screens, I can use that same component with them.
Does it achieve anything by avoiding setState() in this situation (for mobile and Web), and if so, which is the best way to handle that (Custom-Widget or Provider or another)?
setState is merely avoided in Flutter development for two main reasons:
It rebuilds the whole widget, which may hinder performance on consecutive calls if not used correctly.
using setState only as a state management couples the business logic to the UI, which may reduce code re-usability and maintainability.
In simple apps, there are no significant issues in using setState. However, if you need more control over your code, I suggest going for more complex state management patterns.

Determining if widget is in the foreground/background

Say I have a Flutter app with two screens - screen A and screen B. Screen A is just a ListView that displays a list of items but needs to perform a network call whenever the list changes and then update the list. Screen B can modify the list of items but I want screen A to only perform the network call when screen A is in the foreground and being displayed. As far as I can tell, I cannot do this easily. Screen A is not disposed and reinitialized when navigating to and from screen B. It would very helpful if StatefulWidget had onForeground and onBackground methods to override, but they do not. This problem is not exclusive to navigation either, but this same problem presents itself when using PageView with full-screen pages. Is there some proper/standard way of implementing this?
My current setup is as follows: I let the parent widget to both screen A and B hold the list in a ValueNotifier and pass it to both screens when constructing them. Then, screen A listens for changes on the ValueNotifier and performs a network call whenever it does. So, in order to determine whether screen A is in the foreground/background, I will have to start/stop listening for changes before/after navigating. But I haven't started implementing this, as I think it will get complicated when widgets far down the widget tree trigger the navigation or other widgets need to know whether they're in the foreground/background.
Another option I've thought of is instead of observing for changes in the list, I could rather just return a result from screen B saying whether or not the list changed and react then. But I can think of many ways this can complicate my code as well since my real app involves more than just one dependency. I would have to create a custom result class for each screen containing a record of all the data that changed then it would be tedious if I want to add more data in the future. And how would I handle navigation to screen B then screen C? The result would have to be retained and passed down so screen A can react to changes made by screen C. I would also have to ensure all calls to Navigator.pop contained the result, and override back button presses to pop with the result. And I'd also have to ensure that the result makes it to the proper widgets that need to react to changes.
Am I just missing something simple to accomplish this? I am not the most experienced with Flutter and I wouldn't be surprised if there's some easy solution I haven't learned yet.
Edit: After some more testing, it appears AnimationController does something similar to what I need with the vsync parameter, in that it does not update when the State is in the background or when it is not being drawn. So, I could use SingleTickerProviderStateMixin on screen A's state, then use createTicker to create a Ticker, and then check if the Ticker.isTicking whenever the list changes. If it is ticking, screen A is in the foreground, otherwise it is in the background. Although I'm not sure if this is a good approach, since it appears Ticker's are really only used for Animations and there's nothing documented for a use case like mine.

Inter Widget communication

Is it possible to do inter widget communication via something like a notification/event bus?
I need to be able to tell one widget to react to something that happened in another and didn't want to create a hard link.
The notification listener will only fire is if it is higher in the widget tree than both of the widgets so that isn't probably a viable solution.
There are lots of ways to do this depending on your use case.
You could have them be AnimatedWidgets that are passed a ValueNotifier or ChangeNotifier as the listenable. You can see this pattern in the Gallery's animation example.
You could use StreamBuilder to have your widgets rebuild automatically when new events come in on a Stream. There aren't a lot of examples of this in the main Flutter repo, but it's something that you're likely to need once you start using plugins or doing network I/O.
You could use a GlobalKey to get currentState and have one State call methods on the other. This is how snackbars work (example).
You can also extend InheritedWidget to provide widgets with information that wasn't passed as a constructor argument, and they'll automatically be marked for rebuild when that information changes. This is how Themes work, for example.
If you can provide more details on what your widgets do / what their relationship is, or ideally a code snippet I can help you decide which approach would make the most sense for your situation.