0

I am trying to add a widget to the screen when the future has data in Screen 1.

class UserChoiceBooks extends StatelessWidget {
  final String title;
  UserChoiceBooks({Key key, this.title}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
        future: Provider.of<Books>(context, listen: false)
            .getRecommendedBooks("test"),
        builder: (ctx, snapshot) {
          // Checking if future is resolved
          if (snapshot.connectionState == ConnectionState.done) {
            // If we got an error
            if (snapshot.hasError) {
              return Center(
                child: Text(
                  '${snapshot.error} occured',
                  style: TextStyle(fontSize: 18),
                ),
              );
              // if we got our data
            } else if (snapshot.hasData) {
              // Extracting data from snapshot object
              final List<Book> recommendedBooksML = snapshot.data;
              return BookList(title, recommendedBooksML);
            } else {
              return SizedBox.shrink();
            }
          } else {
            return Container();
          }
        }
        //  ... some code here
        );
  }
}

I navigate to a different screen Screen 2. And then back to Screen 1. I loose the future data and it starts Building again.

PS:I found a temporary workaround by storing the Future data in a Global Variable List<Book> placeholder=[];

And then setting the value from future...placeholder=snapshot.data;

When Switching between Screen 1 and 2. I am checking placeholder==[]?UserChoiceBooks (title):BookList(title,placeholder)

Is there a better way to keep data from snapshot so that switching between screen future value is not Lost?

wick3d
  • 1,164
  • 14
  • 41

1 Answers1

1

You are making a call during your build:

Provider.of<Books>(context, listen: false).getRecommendedBooks("test")

Thus when the screen is rebuilt, it is invoked again. You are only too lucky that no other rebuilds are happening between those two navigations, because in theory they are possible.

A recommended mindset is that a rebuild may happen at each frame, and non-rebuilding frames should be treated as a framework optimization.

So you should make a call one time and save it. There are a few options:

  1. Create Books somewhere up the tree and pass it to the widget. Then make your widget stateful and call widget.books.getRecommendedBooks in its initState().

  2. If Books is a singleton, you may use a service locator like https://pub.dev/packages/get_it to instantiate it once and then use GetIt.instance.get to fetch this instance anywhere in your widget. A service locator pattern has some advantages over the provider pattern.

  3. A nasty way is to call the future in a stateful widget's state and call the method on first build if _future == null.

For stateful widgets see this tutorial: https://flutter.dev/docs/development/ui/interactive#stateful-and-stateless-widgets

Also note that you should use a key for such a widget that would be different for different titles that you pass to the constructor. Otherwise, when you replace your widget with one with another title, the framework would not know that all that follows is for another book and will not create a new state, thus initState() will not be called.

Alexey Inkin
  • 1,853
  • 1
  • 12
  • 32
  • Init state doesn't take a context. So I cannot call ```Provider.of(context, listen: false).getRecommendedBooks("test")```. I tried using didChangeDependencies. Adding future to Init state would block the page from laoding until it has finished? – wick3d Mar 28 '21 at 14:49
  • Thank you, that escaped my head. I have added 3 other solutions. – Alexey Inkin Mar 28 '21 at 18:37