sagar suri
sagar suri

Reputation: 4731

Better way to load images from network flutter

I am trying to load images from network and show them in a GridView. I am using a StatefulWidget and loading the images inside the build method. But according to my understanding its not good to make a network call inside the build method. How can I download images from the network inside my BLoC file and later pass the list of downloaded images to the widget? Below is my current implementation.

class MovieList extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return MovieListState();
  }
}

class MovieListState extends State<MovieList> {
  @override
  void initState() {
    super.initState();
    bloc.fetchAllMovies();
  }

  @override
  void dispose() {
    bloc.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Popular Movies'),
      ),
      body: StreamBuilder(
        stream: bloc.allMovies,
        builder: (context, AsyncSnapshot<ItemModel> snapshot) {
          if (snapshot.hasData) {
            return buildList(snapshot);
          } else if (snapshot.hasError) {
            return Text(snapshot.error.toString());
          }
          return Center(child: CircularProgressIndicator());
        },
      ),
    );
  }

  Widget buildList(AsyncSnapshot<ItemModel> snapshot) {
    return GridView.builder(
        itemCount: snapshot.data.results.length,
        gridDelegate:
        new SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
        itemBuilder: (BuildContext context, int index) {
          return GridTile(
            child: InkResponse(
              enableFeedback: true,
              child: Image.network(
                'https://image.tmdb.org/t/p/w185${snapshot.data
                    .results[index].poster_path}',
                fit: BoxFit.cover,
              ),
              onTap: () => openDetailPage(snapshot.data, index),
            ),
          );
        });
  }

  openDetailPage(ItemModel data, int index) {
    Navigator.push(
      context,
      MaterialPageRoute(builder: (context) {
        return MovieDetailBlocProvider(
          child: MovieDetail(
            title: data.results[index].title,
            posterUrl: data.results[index].backdrop_path,
            description: data.results[index].overview,
            releaseDate: data.results[index].release_date,
            voteAverage: data.results[index].vote_average.toString(),
            movieId: data.results[index].id,
          ),
        );
      }),
    );
  }
}

Upvotes: 74

Views: 172959

Answers (6)

Abdur Rehman
Abdur Rehman

Reputation: 535

Best way to Handle Network Images

The best way to load image from the network in flutter is to use flutter's built-in network function and customize it according to your requirements, I do not recommend/prefer to use any package like CachedNetworkImage because it sometimes works on mobile phone and give unexpected error on the web.

You can manage the Network image like this:

  Image.network(
        netWorkImageURL,
        fit: BoxFit.fill,
        // When image is loading from the server it takes some time
        // So we will show progress indicator while loading
        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,
            ),
          );
        },
        // When dealing with networks it completes with two states, 
        // complete with a value or completed with an error, 
        // So handling errors is very important otherwise it will crash the app screen. 
        // I showed dummy images from assets when there is an error, you can show some texts or anything you want.
        errorBuilder: (context, exception, stackTrace) {
          return Image.asset(
            AppAssets.dummyPostImg,
            fit: BoxFit.cover,
            height: (widget.hideBelowImage == null ||
                    widget.hideBelowImage == false)
                ? 170.h
                : 130.h,
            width: double.infinity,
          );
        },
      ),

Upvotes: 6

Rudresh Narwal
Rudresh Narwal

Reputation: 3100

You can use loadingBuilder which is inbuilt feature from flutter for Image.Network

       Image.network(
                  widget.networkUrl,
                  fit: BoxFit.fill,
                  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,
                      ),
                    );
                  },
                ),

Upvotes: 194

Paweł Zawiślak
Paweł Zawiślak

Reputation: 157

It is a good practice to handle errors related to, for example, lack of Internet, when trying to load an image from the Internet. ErrorBuilder is really good if you use Image.network()

Image.network(
      'https://example.does.not.exist/image.jpg',
      errorBuilder: (BuildContext context, Object exception, StackTrace stackTrace) {
        // Appropriate logging or analytics, e.g.
        // myAnalytics.recordError(
        //   'An error occurred loading "https://example.does.not.exist/image.jpg"',
        //   exception,
        //   stackTrace,
        // );
        return Text('😢');
      },
    ),

Upvotes: 3

Pratham Sarankar
Pratham Sarankar

Reputation: 599

If you want a circular shape for your image. You can use Circle Avatar in such a way that it will act as loader and displayer both....

Parent circle avatar will be having loader and If we put transparent color to child circle avatar it will show loading until it is loaded...

Plus point with this way is you can simply give border also by setting background color of parent circle avatar and increasing it's radius slightly.

CircleAvatar(
                backgroundColor: Colors.red,
                radius: 65,
                backgroundImage: AssetImage('assets/bottombar/loading.gif'),
                child: CircleAvatar(
                  radius: 65,
                  backgroundColor: Colors.transparent,
                  backgroundImage: NetworkImage(_url),
                ),
              ),

Upvotes: 7

Amr
Amr

Reputation: 73

You can also use FadeInImage https://flutter.dev/docs/cookbook/images/fading-in-images

FadeInImage.assetNetwork(
        placeholder: 'assets/loading.gif',
        image: 'https://picsum.photos/250?image=9',
      ),

Upvotes: 4

Valentin Schukin
Valentin Schukin

Reputation: 2174

I would recommend you to use https://pub.dartlang.org/packages/cached_network_image

It's really works good for my cases.

Simple code example from their r

CachedNetworkImage(
   imageUrl: "http://via.placeholder.com/350x150",
   placeholder: (context, url) => new CircularProgressIndicator(),
   errorWidget: (context, url, error) => new Icon(Icons.error),
 ),

or

Image(image: CachedNetworkImageProvider(url))

You should add to the pubspec file

cached_network_image: <actual version here>

into the dependencies section

Upvotes: 93

Related Questions