Flutter Bloc 从网格中删除项目

Flutter Bloc Remove Item From Grid

我有一个由集团状态控制的小部件。首先,将 URL 列表传递给小部件,该小部件用于创建初始网格。然后,一旦必须添加或删除 URL,就会调用 bloc 事件来更新列表并产生新状态。然而,我的问题是,当一个项目被删除时,打印到屏幕上的列表是正确的(项目被删除),但网格视图似乎只删除最后一个图像而不是删除的图像。我在这里做错了什么?

Related question(我确实从Gridview.count改成了Gridview.builder),但是没有用。

小部件:

class ItemImageGrid extends StatelessWidget {


  final List<String> urls;

  const ItemImageGrid({Key key, this.urls }) : super(key: key);


  @override
  Widget build(BuildContext context) {
    return Container(
      child: BlocBuilder<ItemGridBloc, ItemGridState>(
        builder: (context, state) {
          if(state is ItemGridInitialState){
            print("state is ItemGridInitialState");
            return _showCarouselAndStartLoading(context);

          }else  if(state is ImagesUpdatedState){
            print("state is ImagesUpdatedState");
            return _showGrid(context, state.urls);

          }
          return Center(child: CircularProgressIndicator());
        },
      ),
    );
  }

  Widget _showCarouselAndStartLoading(BuildContext context){
    BlocProvider.of<ItemGridBloc>(context).add( // add or dispatch??? try both to see difference - seems dispatch cannot be used here...
      LoadImageEvent(urls),
    );
    return Center(child: CircularProgressIndicator());
  }



  Widget _showGrid(BuildContext context, List<String> urls) {

    if(urls.isEmpty){
      return Center(
          child: Text("No photos yet")
      );
    }

  return GridView.builder(
    itemCount: urls.length,
      physics: NeverScrollableScrollPhysics(),
      shrinkWrap: true,
      // You must use the GridDelegate to specify row item count
      // and spacing between items
      gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 2,
       // childAspectRatio: 1.0,
        crossAxisSpacing: 5.0,
        mainAxisSpacing: 5.0,
      ),
      itemBuilder: (BuildContext context, int index) {
        return  BlocProvider(
          create: (context) => CachedImageBloc( ),
          child: GestureDetector(
            onTap: () => {
              Navigator.of(context).push(
                  MaterialPageRoute(
                      builder: (context2) {
                        //   List<String> clonedList;
                        return BlocProvider(
                          create: (context) => new CachedImageBloc( ),
                          child: new BasicImagePreview(
                            imageFilePath: urls[index],
                            //  bloc: BlocProvider.of<InspectionItemAddEditBloc>(context),
                            //   image_paths: imagePaths,
                            onDelete: () =>{
             
                              BlocProvider.of<ItemGridBloc>(context).add(ImageRemovedEvent( urls[index])),

                            },
                          ),
                        );
                      }))
            },
            child: LocalImageViewer(
                url:  urls[index],
                errorAssetPath: 'assets/error_loading.png' ),
          ),

        );
      },
  );


  }
  
}

集团:

class ItemGridBloc extends Bloc<ItemGridEvent, ItemGridState> {

  List<String> urls;

  ItemGridBloc();

  @override
  Stream<ItemGridState> mapEventToState(
      ItemGridEvent event,
      ) async* {
    print('event = $event');

    if(event is ImageAddedEvent){
      yield* _mapImageAddedEventToState(event);
    }
    else if(event is ImageRemovedEvent){
      yield* _mapImageRemovedEventToState(event);
    } else if(event is LoadImageEvent){
      yield* _mapLoadImageEventToState(event);
    }
  }

  @override
  ItemGridState get initialState => ItemGridInitialState();

  Stream<ItemGridState> _mapImageAddedEventToState(ImageAddedEvent event) async*{

    urls.add(event.url);
    yield ImagesUpdatedState(List.of(urls));
  }

  Stream<ItemGridState>_mapImageRemovedEventToState(ImageRemovedEvent event) async*{

    print('urls b4 remove:');
    urls.forEach((element) {print('$element');});

    print('removing url: ${event.url}');
    urls.remove(event.url);
    print('urls after remove:');

    urls.forEach((element) {print('$element');});

    yield ImagesUpdatedState(urls);

  }

  Stream<ItemGridState> _mapLoadImageEventToState(LoadImageEvent event) async*{
    urls = List.of(event.urls);
    yield ImagesUpdatedState(event.urls);
  }
}

事件class:

abstract class ItemGridEvent extends Equatable {
  const ItemGridEvent();
}

class ImageAddedEvent extends ItemGridEvent{

  final String url;

  ImageAddedEvent(this.url);

  @override
  List<Object> get props => [url];

}

class LoadImageEvent extends ItemGridEvent{

  final List<String> urls;

  LoadImageEvent(this.urls);

  @override
  List<Object> get props => [urls];

}

class ImageRemovedEvent extends ItemGridEvent{

  final String url;

  ImageRemovedEvent(this.url);

  @override
  List<Object> get props => [url];

}

状态class:

abstract class ItemGridState extends Equatable {
  const ItemGridState();
}

class ItemGridInitialState extends ItemGridState {
  @override
  List<Object> get props => [];
}

class ImagesUpdatedState extends ItemGridState {

  final List<String> urls;

  ImagesUpdatedState(this.urls);

  @override
  List<Object> get props => [urls];
}

在 bloc 中,当您删除了一个项目时,您应该将列表的一个新实例传递给 ImagesUpdatedState

为什么? 因为您正在将对象的引用传递给您的状态,而 bloc 会将新状态与前一个状态进行比较,并且它会发现前一个状态 url 属性 和新状态之间没有区别,因此它不会重建小部件 传递对象的引用是 dengares 因为当你改变对象时 属性 它会反映你使用的所有引用

经过几个小时的搜索,我发现答案与 Flutter 中的 KEYS 有关。

简而言之,Flutter 仅通过类型而不是状态来比较小部件。因此,当 GridView 中表示的列表的状态发生变化时,Flutter 不知道应该删除哪些子项,因为它们的类型仍然相同并会检出。 Flutter 拾取的唯一问题是项目的数量,这就是为什么它总是删除网格中的最后一个小部件。

因此,如果您想在 Flutter 中操作包含有状态子项的列表,请为每个子项的顶级小部件分配一个 Key。 this article.

中提供了更详细的解释

我对代码所做的唯一更改是生成项目时的以下内容:

itemBuilder: (BuildContext context, int index) {
        return  BlocProvider(
          key: UniqueKey(), // assign the key
          create: (context) => CachedImageBloc( ),
          child: GestureDetector(
            onTap: () => {
              Navigator.of(context).push(
                  MaterialPageRoute(
                      builder: (context2) {
                        //   List<String> clonedList;
                        return BlocProvider(
                          create: (context) => new CachedImageBloc( ),
                          child: new BasicImagePreview(
                            imageFilePath: urls[index],
                            //  bloc: BlocProvider.of<InspectionItemAddEditBloc>(context),
                            //   image_paths: imagePaths,
                            onDelete: () =>{
             
                              BlocProvider.of<ItemGridBloc>(context).add(ImageRemovedEvent( urls[index])),

                            },
                          ),
                        );
                      }))
            },
            child: LocalImageViewer(
                url:  urls[index],
                errorAssetPath: 'assets/error_loading.png' ),
          ),

        );
      },

非常感谢您TM00的回答。

我的 ListView.builder 在删除时没有正确更新,感谢你,我让它工作了。

对于遇到列表视图或网格视图构建器未使用 BLoC 删除正确项目的问题的任何其他人:

只需将您建筑物中的物品包装在容器中并添加一个 UniqueKey

ListView.builder(
                      itemBuilder: (context, index) => 
                          Container(
                            key: UniqueKey(), 
                            child: Widget(),
                      itemCount: array.length,
                    );