Reuse Widget Across Two Pages With Keys - flutter

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.

Related

How to not change the state in widget that shared in two differents Tabs

I'm creating an app and I'm using AutoTabs so I can have different widget tree and be able to move between them without losing state from one tree to another.
I have a small problem and it is that when I share the same widget in two trees and change the state of one, using a StateNotifier, it is logically changed also in the other.
For example, I'm on Tab1 on a page that is an article searched and I move to Tab2 and navigate to the page to find an article that is shared,
How can I do so that when changing the state of the searched in Tab1, the state is not modified on the same page but in Tab2? being able to share the max code
if you don't want to lose your state. you can use IndexedStack widget
It sounds like you're using Riverpod to represent local widget state.
Riverpod is about shared state. So the state being shared between widgets is logical
If you don't want that, you can use a normal StatefulWidget, or flutter_hook's HookWidget

Preserving the State of a Drawer in Flutter

I have a stateful Scaffold widget, where the body contains a list of entities with various properties (e.g. shoes or cars) and Drawer widget, which is empty at first, except for two buttons at the bottom ("Cancel" and "Filter") and a FAB. The user can add various filters with the FAB (e.g. the user can add a shoesize-filter, or a color-filter).
The problem I have is the following: Let's say the user selects various filters and works with them (i.e. ticks checkboxes, enters a shoesize, changes sliders, etc.). When the user is ready to apply the filters, he can click the "Filter" button, which closes the Drawer (onTap -> callback -> Navigator.of(context).pop()). But when the user wants to go back, reopen the Drawer, and adjust one of the filters, it obviously doesn't work, since the widget is being rebuilt from scratch.
Currently, the way the filtering works is, once the "Filter" Button is pressed, all the various values from the added filters are collected into an object FilterPackage, which is then passed via callback to the Scaffold widget, which then applies the values from the FilterPackage to the list of entities.
One solution I came up with, would be feeding this FilterPackage object to the Drawer widget in its constructor, which would provide all the information necessary to rebuild the widget just how it has to be.
Is that a reasonable solution? I have already made some research, but struggled finding a solution on what would clearly be the best and recommended way to preserve the drawer's state. Using PageStorage / PageStorageBucket seems overly complicated for this case. Or am I wrong?
Thanks in advance for any guidance.

How to prevent Stateful Widget stored in List to be disposed?

Let me start by explaining the problem. I have several buttons which are created based on data I'm getting from a server. On each button click I need to create and display a widget which will present me some data (this widget is also built dynamically). The state of this widget has to be preserved during the lifetime of the app. (for example if I click another button and show a different widget, I need to be able to click on the first button and show the first widget in its preserved state). Number of buttons can also be changed during the app lifetime.
I tried using IndexedStack to achieve this, but when the number of buttons is changed I need to add "pages" to IndexedStack, therefore I need to recreate a new IndexedStack which will have some of my old widgets, so I pull widgets from a List or create new ones if needed. This works great, except Flutter calls dispose() method on my widgets which are stored in the list. I tried using the AutomaticKeepAliveClientMixIn but it didn't help.
I'm guessing this has something to do that the widgets get detached from the parent, but when I reattach them to new parent (new Indexed stack) Flutter doesn't figure out this properly.
Any ideas?
Try to store data to local storage or sqlite for persistence data.

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.

Tracking screen views in Flutter with Firebase Analytics

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).