Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get frameBuilder and loadingBuilder to work togerther in Image.network?

How to get frameBuilder and loadingBuilder to work togerther ?

"loadingBuilder" works all the time (with or without frameBuilder) but "frameBuilder" works only if it is alone (i.e. commenting loadingBuilder out).

The documentation talks about "chaining" frameBuilder with loadingBuilder, it's not obvious that is something happens internally or it is something we have to consider.

Image.network(_imageUrl,
    height: 400,
    width: MediaQuery.of(context).size.width,
    fit: BoxFit.fitWidth,

    frameBuilder: (BuildContext context, Widget child, int frame, bool wasSynchronouslyLoaded) {
        if (wasSynchronouslyLoaded) return child;
        return AnimatedOpacity(
                opacity: frame == null ? 0 : 1,
                duration: Duration(seconds: 5),
                curve: Curves.easeOut,
                child: child,
            );
    },

    loadingBuilder: (BuildContext context, Widget child, ImageChunkEvent loadingProgress) {
        if (loadingProgress == null) return child;
        return Center(
            child: CircularProgressIndicator(
                        value: loadingProgress.expectedTotalBytes != null ?
                        loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes :
                        null,
                    ),
                );
    },

    errorBuilder: (BuildContext context, Object exception, StackTrace stackTrace) {
        return Text('Failed to load image');
    },
),

Edit:

What Happens Exactly:

Case 1: When loadingBuilder is ALONE the CircularProgressIndicator is showing and the loading process is completed then the indicator disappears and the loaded image is showing without fading. (Perfect)

Case 2: When frameBuilder is ALONE the image takes its time to load (of cource there is the indicator), then the loaded image is showing gradually as expected. (Perfect)

Case 3: When the frameBuilder and loadingBuilder BOTH are enabled, what happened is CASE 1 exactly without any fading of the image. (Wrong)

Expected Behavior:

The indicator is shown while the loading process is going, then disappear and the image is shown with fading effect.

I need to fix this code and figure out where is the problem.

like image 290
EmadFathy Avatar asked Sep 14 '25 15:09

EmadFathy


1 Answers

It seems the "chaining" is happening automatically but you will still need to consider the results of them chaining together.

The problem that they won't work together is because when frame == null, the CircularProgressIndicator is shown; the AnimatedOpacity is only shown when frame is not null, thus the opacity of it is always 1, which means we lost the fade out effect.

The solution would be to set the opticity to 0 when the loading, and set it to 1 when loaded.

The following code should work:

/// Flutter code sample for Image.frameBuilder

// The following sample demonstrates how to use this builder to implement an
// image that fades in once it's been loaded.
//
// This sample contains a limited subset of the functionality that the
// [FadeInImage] widget provides out of the box.

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

/// This is the main application widget.
class MyApp extends StatelessWidget {
  static const String _title = 'Flutter Code Sample';

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: _title,
      home: MyStatefulWidget(),
    );
  }
}

bool loading = false;

/// This is the stateless widget that the main application instantiates.
class MyStatefulWidget extends StatefulWidget {
  MyStatefulWidget({Key key}) : super(key: key);

  @override
  MyStatefulWigdetState createState() => MyStatefulWigdetState();
}

class MyStatefulWigdetState extends State<MyStatefulWidget> {
  @override
  Widget build(BuildContext context) {
    return DecoratedBox(
      decoration: BoxDecoration(
        color: Colors.white,
        border: Border.all(),
        borderRadius: BorderRadius.circular(20),
      ),
      child: Image.network(
        'https://flutter.github.io/assets-for-api-docs/assets/widgets/puffin.jpg',
        frameBuilder: (BuildContext context, Widget child, int frame,
            bool wasSynchronouslyLoaded) {
          if (wasSynchronouslyLoaded) {
            return child;
          }
          return AnimatedOpacity(
            child: child,
            opacity: loading ? 0 : 1,
            duration: const Duration(seconds: 3),
            curve: Curves.easeOut,
          );
        },
        loadingBuilder: (BuildContext context, Widget child,
            ImageChunkEvent loadingProgress) {
          if (loadingProgress == null) {
            // The child (AnimatedOpacity) is build with loading == true, and then the setState will change loading to false, which trigger the animation
            WidgetsBinding.instance.addPostFrameCallback((_) {
              setState(() => loading = false);
            });

            return child;
          }
          loading = true;
          return Center(
            child: CircularProgressIndicator(
              value: loadingProgress.expectedTotalBytes != null
                  ? loadingProgress.cumulativeBytesLoaded /
                      loadingProgress.expectedTotalBytes
                  : null,
            ),
          );
        },
      ),
    );
  }
}

like image 56
Luj Avatar answered Sep 17 '25 07:09

Luj