0

My FutureBuilder rebuilds unnecessarily when navigate between screens. Each time I have to get the download URL from the Firebase Storage and this results in extremely flickering. How can I prevent that the FutureBuilder rebuilds everytime I navigate between screens?

I already tried the following solutions:

https://medium.com/saugo360/flutter-my-futurebuilder-keeps-firing-6e774830bc2

Flutter Switching to Tab Reloads Widgets and runs FutureBuilder

But without success. With both the FutureBuilder get always rebuild.

This is my code (Attempt 1):

class _GuestbookCardImageState extends State<GuestbookCardImage> {
  final AsyncMemoizer _memoizer = AsyncMemoizer();

  _getFeaturedImages() {
    return this._memoizer.runOnce(() async {
      return await MyStorage().getDownloadUrl(widget.guestbook.imagePath);
    });
  }

  @override
  Widget build(BuildContext context) {
    return Card(
      semanticContainer: true,
      clipBehavior: Clip.antiAliasWithSaveLayer,
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(10.0),
      ),
      elevation: 2,
      margin: EdgeInsets.all(5),
      child: FutureBuilder(
          future: _getFeaturedImages(),
          builder: (context, snapshot) {
            if (snapshot.connectionState == ConnectionState.waiting) {
              return GuestbookPlaceholder();
            } else {
              if (snapshot.data != null) {
                return Hero(
                  tag: "to_single_guestbook_" + widget.guestbook.entryCode,
                  child: Container(
                    width: Get.width / 1.7,
                    height: Get.height / 3.5,
                    child: Image.network(
                      snapshot.data,
                      fit: BoxFit.cover,
                    ),
                  ),
                );
              } else {
                return Hero(
                    tag: "to_single_guestbook_" + widget.guestbook.entryCode,
                    child: GuestbookPlaceholder());
              }
            }
          }),
    );
  }
}

Attempt 2

class _GuestbookCardImageState extends State<GuestbookCardImage> {
  Future<String> _future;

  @override
  void initState() {
    _future = MyStorage().getDownloadUrl(widget.guestbook.imagePath);
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Card(
      semanticContainer: true,
      clipBehavior: Clip.antiAliasWithSaveLayer,
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(10.0),
      ),
      elevation: 2,
      margin: EdgeInsets.all(5),
      child: FutureBuilder(
          future: _future,
          builder: (context, snapshot) {
            if (snapshot.connectionState == ConnectionState.waiting) {
              return GuestbookPlaceholder();
            } else {
              if (snapshot.data != null) {
                return Hero(
                  tag: "to_single_guestbook_" + widget.guestbook.entryCode,
                  child: Container(
                    width: Get.width / 1.7,
                    height: Get.height / 3.5,
                    child: CachedNetworkImage(
                        imageUrl: snapshot.data, fit: BoxFit.cover),
                  ),
                );
              } else {
                return Hero(
                    tag: "to_single_guestbook_" + widget.guestbook.entryCode,
                    child: GuestbookPlaceholder());
              }
            }
          }),
    );
  }
}

What I expect

I expect that after changing the screen and return to the initial screen my images still appearing without the need of reload. I am using Hero animation for the images. This loading behavior destroys my animation because while loading the image urls it shows a placeholder.

A small video how this looks: (And this happens also if I push to the detail screen and pop back to the initial screen)

enter image description here

What could I do to solve this issue?

Vueer
  • 1,432
  • 3
  • 21
  • 57

2 Answers2

2

I think you need to go higher in the widget tree and preserve the state of your page. you can try this solution. The solution is to use PageView widget to display your screen through the navigation bar, and the PageView allowing you to save the page state easily.

Omer Gamliel
  • 456
  • 3
  • 6
  • But the Futurebuilder also rebuilds if I push to another page and go back to the initial. It is not only the bottom navigation.. – Vueer Oct 14 '20 at 12:13
  • your page state should be preserved because of the 'PageView'. You assign a page controller to the pageview widget and set the 'keep page' argument to true inside the page controller instance. I think this is the best approach , try this solution – Omer Gamliel Oct 14 '20 at 12:31
  • Just an additional question... what if for example I change some values on the object for which I am using the FutureBuilder? Then I want manually trigger a rebuild.... with the pageView solution I have forever the initial state. Is there a workaround for this? Thank you very much in advance! – Vueer Oct 15 '20 at 15:55
  • you can setState the future itself – Omer Gamliel Oct 15 '20 at 17:20
  • Thanks, but how can I add a condition? By default I don’t want that the future builder rebuilds, but only if there is an update in the database... so if the value changes... is there a best way to to this? – Vueer Oct 15 '20 at 19:15
  • you need in some way to listen to changes in the db, and setState the future every time there is a change. one easy solution is to use stream or behavior subject. assign a stream controller in your db file. every time there is a change in the db, add new event to the stream (it can be even 'null'). where you want to listen and rebuild, just listen to the db stream and setState when an event occurs – Omer Gamliel Oct 16 '20 at 07:55
0

What helped me was setting the memCacheWidth property to be screen width x devicePixelRatio, so the image is cached in the highest resolution that will ever be necessary.

Note: if you want to do zooming, consider doing width x 10 (or some other number which works for your case).

CachedNetworkImage(
      imageUrl: url,
      memCacheWidth: mediaQuery.size.width.ceil() * mediaQuery.devicePixelRatio.ceil(),
      // ... other properties ...
),
Aleksandar
  • 3,558
  • 1
  • 39
  • 42