简体   繁体   中英

How to use future inside a list in Flutter?

I'm trying to load images from network and than passing it to ExtendedRawImage as finished future Image to crop it.

The problem is that every new image that comes in uses (I think) the same instance of future to check if image has been loaded and that's why it shows CircularProgressIndicator on every new image.

The things I have tried and didn't solve my problem:

  • Adding key to the future,
  • Move functionality to completely new StatefulWidget widget.

The code and gif I'm working with right now:

AnimatedList(
  key: _sListKey,
  physics: BouncingScrollPhysics(),
  controller: _sScrollCtrl,
  padding: EdgeInsets.all(5),
  initialItemCount: sData.length,
  itemBuilder: (context, index, animation) {
    final vehicle = sData[index];
    final color = defaultRegions.contains(vehicle.region)
        ? Colors.grey
        : Colors.blue;
    return _dataContainer(
        vehicle, animation);
  },
)
class CropImage extends StatefulWidget {
  final Vehicle vehicle;

  CropImage({Key key, this.vehicle}) : super(key: key);

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

class _CropImageState extends State<CropImage> {
  Future<ImageInfo> _imageInfo;

  @override
  void initState() {
    super.initState();

    NetworkImage image = NetworkImage(widget.vehicle.image);
    Completer<ImageInfo> completer = Completer();
    image
        .resolve(new ImageConfiguration())
        .addListener(ImageStreamListener((ImageInfo info, bool _) {
      completer.complete(info);
    }));

    _imageInfo = completer.future;
  }

  @override
  Widget build(BuildContext context) {
    return AspectRatio(
      aspectRatio: widget.vehicle.vehicleRegion.width /
          widget.vehicle.vehicleRegion.height,
      child: FutureBuilder(
        // key: UniqueKey(),
        future: _imageInfo,
        builder: (context, AsyncSnapshot<ImageInfo> snapshot) {
          if (snapshot.hasData) {
            return ExtendedRawImage(
              image: snapshot.data.image,
              fit: BoxFit.cover,
              soucreRect: Rect.fromLTWH(
                widget.vehicle.vehicleRegion.x,
                widget.vehicle.vehicleRegion.y,
                widget.vehicle.vehicleRegion.width,
                widget.vehicle.vehicleRegion.height,
              ),
            );
          } else {
            return Center(
              child: CircularProgressIndicator(),
            );
          }
        },
      ),
    );
  }
}

在此处输入图片说明

UPDATE_1 : _dataContainer() contains CropImage widget that I have wrapped with regular Container. Also as a side note, I'm using websocket to pass data to the AnimatedList by inserting it in the top of the list using _sListKey.currentState.insertItem(0) .

UPDATE_2 I think I have narrowed down a problem. If I use any kind of FutureBuilder inside AnimatedList (as showed below), it creates the same problem (adding a 'Key' or moving to an other Statefull widget doesn't solve the problem).

FutureBuilder(
  future: http.get(vehicle.image),
  builder: (context, AsyncSnapshot<http.Response> snapshot) {
    if (snapshot.connectionState == ConnectionState.done) {
      return Image.memory(snapshot.data.bodyBytes);
    } else {
      return CircularProgressIndicator();
    }
  },
),

UPDATE_3 : It seams that the core problem comes from setState() call. Because I'm using weboskcet to pass data to view, every time new data comes in, setState() is being called to notify the AnimatedList or Listview to reload the view, which forces the FutureBuilder to refresh. But knowing this doesn't solve my problem.

@override
void initState() {
  super.initState();

  ws.dataCallback = (vehicle, status) {
    switch (status) {
      case 0:
        setState(() {});
        break;
    }
  };
}

NOTE : As a request, created sample repository of my problem ( link to repo ).

Okay, I don't know how to create unique instance of the Future but I probably know how to solve your problem.

You can use FadeInImage to sort this out. FadeInImage puts a place-holder image and it replaces with the actual network image once it is loaded. The place-holder image can be gif loading indicator.

Widget _imageView(BuildContext context, String imageUrl) {
    return FadeInImage.assetNetwork(
        placeholder:'assets/giphy.gif' ,
        image: imageUrl,
    );

}

More information is available here and here .

See the documentation of FutureBuilder: https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html

The future must have been obtained earlier, eg during State.initState, State.didUpdateConfig, or State.didChangeDependencies. It must not be created during the State.build or StatelessWidget.build method call when constructing the FutureBuilder. If the future is created at the same time as the FutureBuilder, then every time the FutureBuilder's parent is rebuilt, the asynchronous task will be restarted.

You can solve this problem by wrapping the FutureBuilder in your own StatefulWidget and calling fetchImage in eg initState and assigning it to a field.

EDIT: If all images still reload even though the loading is done inside the initState method, this must mean the State objects are being reinitialized. Given that you mention you add the item to the beginning, your problem might be related to the following issue https://github.com/flutter/flutter/issues/21023#issuecomment-510950338 . It's mentioned that ListView.builder does not support changes in the list as it uses the index for tracking. Although the issue is about ListView.builder, the AnimatedList doc mentions:

This widget is similar to one created by [ListView.builder].

If your only requirement is to insert items to the top, you may consider adding items to the end and using a reverse shrinkWrap AnimatedList. A minimal example:

import 'dart:math';

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Example(),
    );
  }
}

class Example extends StatefulWidget {
  @override
  _ExampleState createState() => _ExampleState();
}

class _ExampleState extends State<Example> {
  List<Vehicle> vehicles = new List();
  GlobalKey<AnimatedListState> listKey = GlobalKey<AnimatedListState>();
  Random rng = new Random();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: AnimatedList(
        shrinkWrap: true,
        reverse: true,
        key: listKey,
        initialItemCount: vehicles.length,
        itemBuilder: (context, index, animation) => SizeTransition(
          axis: Axis.vertical,
          sizeFactor: animation,
          child: ListTile(
            title: WidgetWithFutureBuilder('${vehicles[index].name}'),
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            int id = rng.nextInt(5000);
            vehicles.add(new Vehicle(name: "Vehicle $id"));
            listKey.currentState.insertItem(vehicles.length - 1);
          });
        },
      ),
    );
  }
}

class WidgetWithFutureBuilder extends StatefulWidget {
  final String name;

  WidgetWithFutureBuilder(this.name);

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

class _WidgetWithFutureBuilderState extends State<WidgetWithFutureBuilder> {
  Future<String> future;

  @override
  void initState() {
    super.initState();
    this.future = Future.delayed(Duration(seconds: 1), () => widget.name);
  }

  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: future,
      builder: (context, state) {
        if (state.hasData) {
          return ListTile(
            title: Text(state.data),
          );
        }
        return Center(child: CircularProgressIndicator());
      },
    );
  }
}

class Vehicle {
  Vehicle({this.name});

  String name;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM