【问题标题】:Bloc State is yielded but UI is not updatingBloc State 已产生,但 UI 未更新
【发布时间】:2021-07-04 17:49:22
【问题描述】:

我在使用 bloc 时遇到问题。集团状态正在更新,但似乎对 UI 没有影响。我已经使用示例应用程序重现了该问题。问题是从列表页面中删除一个项目不会改变颜色回到取消选择状态。实际上会发生什么,当您在主页上选择一个项目时,购物车按钮会出现,而在没有选择项目的状态下它会消失。因此,当您在主页上选择一个项目时,会出现购物车按钮。当您单击按钮时,您将导航到另一个页面,即 ListPage ,您可以在其中看到所有选定项目的列表以及与每个项目对应的删除按钮。当您删除一个项目时,它会从 ListPage 中删除,但它的颜色仍然是红色,即在 HomePage 上被选中。我必须再次单击以更改颜色,尽管它已被取消选择,即(item.active = false)从 ListPage 中删除它。只是颜色保持不变,它没有得到更新。代码文件如下:

item_bloc.dart

import 'dart:async';

import 'package:bloc/bloc.dart';
import 'package:demo/bloc/cart/cart_bloc.dart';
import 'package:demo/item_model.dart';
import 'package:demo/repository.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.dart';

part 'items_event.dart';
part 'items_state.dart';

class ItemsBloc extends Bloc<ItemsEvent, ItemsState> {
  final ItemRepository itemRepository;
  final CartBloc cartBloc;

  ItemsBloc({
    @required this.itemRepository,
    @required this.cartBloc,
  }) : super(ItemsLoading());

  @override
  Stream<ItemsState> mapEventToState(
    ItemsEvent event,
  ) async* {
    if (event is GetItems) {
      yield* _mapGetItemsToState(event);
    } else if (event is UpdateItem) {
      yield* _mapUpdateItemToState(event);
    }
  }

  Stream<ItemsState> _mapGetItemsToState(GetItems event) async* {
    yield ItemsLoading();
    try {
      final _items = await itemRepository.loadItems();
      yield ItemsLoaded(items: _items);
      print('Yielded');
    } catch (_) {
      yield ItemsFailure();
    }
  }

  Stream<ItemsState> _mapUpdateItemToState(UpdateItem event) async* {
    try {
      _updateRepositoryItems(event.item);
      if (event.item.active) {
        cartBloc.add(ShowItems());
      } else {
        cartBloc.add(RemoveItem(itemId: event.item.id));
      }
      yield* _mapGetItemsToState(GetItems());
    } catch (_) {
      yield ItemsFailure();
    }
  }

  Item _updateRepositoryItems(Item item) => itemRepository.setItem = item;
}

cart_bloc.dart

import 'dart:async';

import 'package:bloc/bloc.dart';
import 'package:demo/item_data_model.dart';
import 'package:demo/repository.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.dart';

part 'cart_event.dart';
part 'cart_state.dart';

class CartBloc extends Bloc<CartEvent, CartState> {
  final ItemRepository itemRepository;

  CartBloc({@required this.itemRepository}) : super(CartLoading());

  @override
  Stream<CartState> mapEventToState(
    CartEvent event,
  ) async* {
    if (event is ShowItems) {
      yield* _mapShowItemsToState(event);
    } else if (event is RemoveItem) {
      yield* _mapRemoveItemToState(event);
    }
  }

  Stream<CartState> _mapShowItemsToState(ShowItems event) async* {
    try {
      final _selectedItems = await itemRepository.getSelectedItems;

      if (_selectedItems.isNotEmpty) {
        final data = ItemData(items: _selectedItems);

        yield CartLoaded(itemData: data);
      } else {
        yield CartLoading();
      }
    } catch (_) {
      yield CartFailure();
    }
  }

  Stream<CartState> _mapRemoveItemToState(RemoveItem event) async* {
    try {
      final _selectedSounds = await itemRepository.getSelectedItems;

      if (_selectedSounds.isEmpty) {
        yield CartLoading();
      } else {
        yield* _mapShowItemsToState(ShowItems());
      }
    } catch (_) {
      yield CartFailure();
    }
  }
}

主页.dart

import 'package:demo/bloc/cart/cart_bloc.dart';
import 'package:demo/bloc/items/items_bloc.dart';
import 'package:demo/item_tile.dart';
import 'package:demo/list_page.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Scaffold(
        appBar: AppBar(
          title: Text("Home Page"),
          centerTitle: true,
        ),
        body: Column(
          children: [
            Expanded(
              child: Padding(
                padding: const EdgeInsets.all(8.0),
                child: BlocBuilder<ItemsBloc, ItemsState>(
                  // ignore: missing_return
                  builder: (context, state) {
                    if (state is ItemsLoading) {
                      return Center(
                        child: CircularProgressIndicator(),
                      );
                    } else if (state is ItemsLoaded) {
                      print('Items Loaded');
                      return GridView.count(
                        crossAxisCount: 3,
                        crossAxisSpacing: 20.0,
                        mainAxisSpacing: 20.0,
                        childAspectRatio: 0.6,
                        children: [
                          for (var item in state.items) ItemTile(item: item),
                        ],
                      );
                    } else if (state is ItemsFailure) {
                      return Center(
                        child: Text(
                          'Unexpected Failure, Cannot Load Items',
                          style: Theme.of(context).textTheme.headline6,
                        ),
                      );
                    }
                  },
                ),
              ),
            ),
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: CartButton(),
            ),
          ],
        ),
      ),
    );
  }
}

class CartButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocBuilder<CartBloc, CartState>(
      // ignore: missing_return
      builder: (context, state) {
        if (state is CartLoading) {
          return SizedBox.shrink();
        } else if (state is CartLoaded) {
          print('Cart Loaded');
          return TextButton(
            onPressed: () {
              Navigator.push(
                context,
                MaterialPageRoute(builder: (context) {
                  return ListPage(
                    items: state.itemData.items,
                  );
                }),
              );
            },
            child: Text('Go To Cart'),
          );
        } else if (state is CartFailure) {
          return Center(
            child: Text(
              'Unexpected Failure, Cannot Load Cart',
              style: Theme.of(context).textTheme.headline6,
            ),
          );
        }
      },
    );
  }
}

item_tile.dart

import 'package:demo/bloc/items/items_bloc.dart';
import 'package:demo/get_it.dart';
import 'package:demo/item_model.dart';
import 'package:flutter/material.dart';

class ItemTile extends StatefulWidget {
  final Item item;

  const ItemTile({Key key, this.item}) : super(key: key);
  @override
  _ItemTileState createState() => _ItemTileState();
}

class _ItemTileState extends State<ItemTile> {
  bool active;

  @override
  void initState() {
    super.initState();
    active = widget.item.active;
  }

  void _onItemTap() {
    setState(() {
      active = !active;
    });

    getIt<ItemsBloc>()
        .add(UpdateItem(item: widget.item.copyWith(active: active)));
  }

  @override
  Widget build(BuildContext context) {
    return ListTile(
      title: Text(widget.item.title),
      tileColor: active ? Colors.redAccent : Colors.blueAccent,
      onTap: () {
        _onItemTap();
      },
    );
  }
}

item_page.dart

import 'package:demo/bloc/items/items_bloc.dart';
import 'package:demo/get_it.dart';
import 'package:flutter/material.dart';

import 'item_model.dart';

class ListPage extends StatefulWidget {
  final List<Item> items;

  const ListPage({Key key, this.items}) : super(key: key);
  @override
  _PlayerScreenState createState() => _PlayerScreenState();
}

class _PlayerScreenState extends State<ListPage> {
  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Scaffold(
        appBar: AppBar(
          title: Text(
            'Cart List Page',
            style: Theme.of(context).textTheme.headline6,
          ),
          centerTitle: true,
        ),
        body: Padding(
          padding: const EdgeInsets.all(12.0),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Expanded(
                child: ListView.separated(
                  itemCount: widget.items.length,
                  separatorBuilder: (BuildContext context, int index) =>
                      Divider(),
                  itemBuilder: (BuildContext context, int index) {
                    return ListTile(
                      trailing: IconButton(
                        icon: Icon(
                          Icons.close_rounded,
                          color: Colors.black,
                        ),
                        onPressed: () {
                          getIt<ItemsBloc>().add(UpdateItem(
                            item: widget.items.elementAt(index).copyWith(
                                active: !widget.items.elementAt(index).active),
                          ));

                          setState(() {
                            widget.items.removeAt(index);
                          });
                          if (widget.items.isEmpty) {
                            Navigator.pop(context);
                          }
                        },
                      ),
                      title: Text(
                        widget.items.elementAt(index).title,
                      ),
                    );
                  },
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

get_it.dart

import 'package:demo/bloc/cart/cart_bloc.dart';
import 'package:demo/bloc/items/items_bloc.dart';
import 'package:demo/repository.dart';
import 'package:get_it/get_it.dart';

final getIt = GetIt.instance;

Future<void> init() async {
  // Bloc

  getIt.registerLazySingleton<CartBloc>(() => CartBloc(itemRepository: getIt()));

  getIt.registerFactory(() => ItemsBloc(
        itemRepository: getIt(),
        cartBloc: getIt(),
      ));

  // Repository
  getIt.registerSingleton<ItemRepository>(ItemRepository());
}

main.dart

import 'package:demo/bloc/cart/cart_bloc.dart';
import 'package:demo/bloc/items/items_bloc.dart';
import 'package:demo/get_it.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

import 'get_it.dart' as di;
import 'package:flutter/material.dart';

import 'home_page.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  di.init();
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MultiBlocProvider(
      providers: [
        BlocProvider(
          create: (context) => getIt<ItemsBloc>()..add(GetItems()),
        ),
        BlocProvider(
          create: (context) => getIt<CartBloc>(),
        ),
      ],
      child: MaterialApp(
        title: 'Flutter Demo',
        debugShowCheckedModeBanner: false,
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: HomePage(),
      ),
    );
  }
}

下面是问题的图片:

【问题讨论】:

  • 当您期望颜色发生变化时,您是否在控制台中看到您的print('Items Loaded'); 语句?如果没有,那么BlocBuilder 不会在您想要的时候重建。一旦你的ItemsLoaded 状态产生,它应该用更新的颜色重建整个瓷砖列表。
  • 当应用程序启动时,它在控制台中显示为 (I/flutter (4923): Items Loaded),当您选择一个项目时它显示 (I/flutter (4923): Cart Loaded)。是的,正如您所说,当我们从 ItemPage 中删除项目时,它没有显示 Items Loaded。这就是问题所在。
  • 我在 Items_bloc 文件中的 _mapGetItemsToState 作为 print('Yielded') 中的 yield ItemsLoaded 之后添加了一行。它在移除项目时打印该行。所以状态已经产生,但不知何故,我认为 bloc 将带有更新项目的新状态视为前一个状态,这就是为什么不再调用 BlocBuilder 函数的原因。我不知道如何解决这个问题。请帮忙。

标签: flutter user-interface dart bloc flutter-bloc


【解决方案1】:

好的,根据 cmets,这表明您在移除项目时已经处于 ItemsLoaded 状态。由于它不是新状态,因此不会触发重建。所以它的行为符合预期。看起来您需要重新考虑如何做到这一点,也许是先产生 ItemsUpdated 状态,然后再产生 ItemsLoaded 状态。

【讨论】:

  • 我认为我应该创建一个新状态,即 ItemsUpdated,但它也会产生与更新项目列表相同的问题。但是正如您所说,在 ItemsLoaded 之前产生 ItemsUpdated 状态会起作用,那么为什么在 ItemsLoaded 之前产生 ItemsLoading 不起作用。不过这两种情况都是一样的。
猜你喜欢
  • 2020-07-20
  • 2014-01-02
  • 1970-01-01
  • 1970-01-01
  • 2018-01-04
  • 2021-01-19
  • 2020-09-03
  • 2019-12-02
  • 2021-03-19
相关资源
最近更新 更多