Flutter BLoC:如何在 Navigator.push 之后添加新事件而不用 BlocProvider 包装 MaterialApp

Flutter BLoC: How to add a new event after Navigator.push without wrapping MaterialApp with BlocProvider

所以我有启动我的 BLoC FilteredRecipesBloc 的屏幕 ExploreScreen。 在此屏幕内,有一个导航到新屏幕的按钮 FilterScreen。 从 FilterScreen,我想添加一个影响两个屏幕的新事件。现在的问题是我收到此错误消息(onError Bad state: Cannot add new events after calling close)。如果不使用 BlocProvider 包装 MaterialApp 是否可行?我只想让本地集团访问两个屏幕。

探索屏幕:

class ExploreScreen extends StatefulWidget {
  @override
  _ExploreScreenState createState() => _ExploreScreenState();
}

class _ExploreScreenState extends State<ExploreScreen> {
  FilteredRecipesBloc _filteredRecipesBloc;

  @override
  void initState() {
    _filteredRecipesBloc = FilteredRecipesBloc(
        recipeList:
            (BlocProvider.of<RecipesBloc>(context).state as RecipesLoaded)
                .recipeList);
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Explore"),
      ),
      body: BlocProvider(
        create: (context) => _filteredRecipesBloc,
        child: BlocBuilder<FilteredRecipesBloc, FilteredRecipesState>(
            builder: (context, state) {
          if (state is FilteredRecipeEmpty) {
            return CategoriesScreen();
          }
          if (state is FilteredRecipesLoading) {
            return Column(
              children: <Widget>[
                CircularProgressIndicator(),
                IconButton(
                  icon: Icon(Icons.add),
                  onPressed: () {
                    _filteredRecipesBloc.add(UpdateRecipesFilter(
                        ingredients: ["Curry"], maxCookTime: 30));
                  },
                ),
              ],
            );
          }
          if (state is FilteredRecipeLoaded) {
            return ListView.builder(
                itemCount: state.recipeList.length,
                itemBuilder: (_, int index) {
                  return ImageRecipeContainer(recipe: state.recipeList[index]);
                });
          }
          return Container();
        }),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _navigateToFilterScreen,
        child: Icon(EvaIcons.funnelOutline),
        heroTag: "fafdsf",
      ),
    );
  }

  void _navigateToFilterScreen() {
    Navigator.of(context)
        .push(MaterialPageRoute<FilterScreen>(builder: (context) {
      return BlocProvider.value(
        value: _filteredRecipesBloc,
        child: FilterScreen(_filteredRecipesBloc),
      );
    }));
  }

  @override
  void dispose() {
    _filteredRecipesBloc.close();
    super.dispose();
  }
}

过滤屏幕:

class FilterScreen extends StatefulWidget {
  final FilteredRecipesBloc filteredRecipesBloc;

  FilterScreen(this.filteredRecipesBloc);
  @override
  _FilterScreenState createState() => _FilterScreenState();
}

class _FilterScreenState extends State<FilterScreen> {
  Map<String, bool> _selectedCategories = {};
  Map<String, bool> _selectedIngredients = {};

  @override
  void initState() {
    _initIngredientList();
    super.initState();
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Filter"),),
      body: SingleChildScrollView(
        child: Padding(
          padding: const EdgeInsets.all(12.0),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: <Widget>[
              Text("Category",style: TextStyle(fontSize: 20,fontWeight: FontWeight.bold),),
              ChoiceChip(
                  label: Text("Vegan"),
                selected: _selectedCategories["vegan"] == true,
                onSelected: (isActive){
                    setState(() {
                      _selectedCategories["vegan"] = isActive;
                    });
                },
              ),
              Text("Ingredients"),
              ShowMoreChoiceChips(children: _buildIngredientChoiceChips()),
              RaisedButton(onPressed: _updateFilter),


            ],
          ),
        ),
      ),
    );
  }

  void _initIngredientList(){
    List<Recipe> recipeList =
        (BlocProvider.of<RecipesBloc>(context).state as RecipesLoaded).recipeList ?? [];

    for(int i = 0; i < recipeList.length;i++){
      for(int y = 0; y < recipeList[i].ingredientsFlat.length;y++){
        _selectedIngredients[recipeList[i].ingredientsFlat[y].name] = false;
      }
    }
  }

  List<Widget> _buildIngredientChoiceChips(){
    List<Widget> widgetList = [];
    _selectedIngredients.forEach((key, value){
      widgetList.add(ChoiceChip(label: Text(key), selected: value,onSelected: (isActive){
        setState(() {
          _selectedIngredients[key] = isActive;
        });
      },));
    });
    return widgetList;
  }

  void _updateFilter(){
    List<String> ingredients = [];
    _selectedIngredients.forEach((k,v){
      if(v) ingredients.add(k);
    });

    widget.filteredRecipesBloc.add(
        UpdateRecipesFilter(ingredients: ingredients.isNotEmpty ? ingredients : null));
    //BlocProvider.of<FilteredRecipesBloc>(context).add(
      //  UpdateRecipesFilter(ingredients: ingredients.isNotEmpty ? ingredients : null),);
  }
}

您不希望 StatefulWidget 控制您的 Bloc。您可以在 initState 方法中实例化您的 Bloc,但您不需要通过 dispose 方法关闭它,因为它会自动为您完成。

如果您已经在 initState 中创建了一个 Bloc 实例,您不想通过 BlocProvider 创建另一个实例。但是您应该使用命名构造函数 .value.

两者都

  FilteredRecipesBloc _filteredRecipesBloc;

  @override
  void initState() {
    _filteredRecipesBloc = FilteredRecipesBloc(
        recipeList:
            (BlocProvider.of<RecipesBloc>(context).state as RecipesLoaded)
                .recipeList);
    super.initState();
  }

  BlocProvider.value(
    value: _filteredRecipesBloc,
    child: ...
  )

  // Preferable at least for me, because I don't need to bother with the instance of the Bloc.
  BlocProvider(
    create: (context) => FilteredRecipesBloc(
        recipeList:
            (BlocProvider.of<RecipesBloc>(context).state as RecipesLoaded)
                .recipeList),
    child: ...
  )
class ExploreScreen extends StatefulWidget {
  @override
  _ExploreScreenState createState() => _ExploreScreenState();
}

class _ExploreScreenState extends State<ExploreScreen> {
  FilteredRecipesBloc _filteredRecipesBloc;

  @override
  void initState() {
    _filteredRecipesBloc = FilteredRecipesBloc(
        recipeList:
            (BlocProvider.of<RecipesBloc>(context).state as RecipesLoaded)
                .recipeList);
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Explore"),
      ),
      body: BlocProvider.value(
        value: _filteredRecipesBloc,
        child: BlocBuilder<FilteredRecipesBloc, FilteredRecipesState>(
            builder: (context, state) {
          if (state is FilteredRecipeEmpty) {
            return CategoriesScreen();
          }
          if (state is FilteredRecipesLoading) {
            return Column(
              children: <Widget>[
                CircularProgressIndicator(),
                IconButton(
                  icon: Icon(Icons.add),
                  onPressed: () {
                    _filteredRecipesBloc.add(UpdateRecipesFilter(
                        ingredients: ["Curry"], maxCookTime: 30));
                  },
                ),
              ],
            );
          }
          if (state is FilteredRecipeLoaded) {
            return ListView.builder(
                itemCount: state.recipeList.length,
                itemBuilder: (_, int index) {
                  return ImageRecipeContainer(recipe: state.recipeList[index]);
                });
          }
          return Container();
        }),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _navigateToFilterScreen,
        child: Icon(EvaIcons.funnelOutline),
        heroTag: "fafdsf",
      ),
    );
  }

  void _navigateToFilterScreen() {
    Navigator.of(context)
        .push(MaterialPageRoute<FilterScreen>(builder: (context) {
      return BlocProvider.value(
        value: _filteredRecipesBloc,
        child: FilterScreen(_filteredRecipesBloc),
      );
    }));
  }
}

在 flutter 主文档中查看此文档

Resource here

    import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
    title: 'Returning Data',
    home: HomeScreen(),
  ));
}

class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Returning Data Demo'),
      ),
      body: Center(child: SelectionButton()),
    );
  }
}

class SelectionButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () {
        _navigateAndDisplaySelection(context);
      },
      child: Text('Pick an option, any option!'),
    );
  }

  // A method that launches the SelectionScreen and awaits the result from
  // Navigator.pop.
  _navigateAndDisplaySelection(BuildContext context) async {
    // Navigator.push returns a Future that completes after calling
    // Navigator.pop on the Selection Screen.
    final result = await Navigator.push(
      context,
      MaterialPageRoute(builder: (context) => SelectionScreen()),
    );

    // After the Selection Screen returns a result, hide any previous snackbars
    // and show the new result.
    Scaffold.of(context)
      ..removeCurrentSnackBar()
      ..showSnackBar(SnackBar(content: Text("$result")));
  }
}

class SelectionScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Pick an option'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: ElevatedButton(
                onPressed: () {
                  // Close the screen and return "Yep!" as the result.
                  Navigator.pop(context, 'Yep!');
                },
                child: Text('Yep!'),
              ),
            ),
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: ElevatedButton(
                onPressed: () {
                  // Close the screen and return "Nope!" as the result.
                  Navigator.pop(context, 'Nope.');
                },
                child: Text('Nope.'),
              ),
            )
          ],
        ),
      ),
    );
  }
}