Flutter BLo C: Как добавить новое событие после Navigator.pu sh без переноса MaterialApp с BlocProvider - PullRequest
1 голос
/ 13 марта 2020

Итак, у меня есть экран ExploreScreen, который запускает мой BLo C FilteredRecipesBlo c. Внутри этого экрана есть кнопка для перехода на новый экран FilterScreen. Из FilterScreen я хочу добавить новое событие, которое влияет на оба экрана. Проблема в том, что я получаю это сообщение об ошибке ( onError Bad state: невозможно добавить новые события после вызова close ). Возможно ли это без упаковки MaterialApp с BlocProvider? Я просто хочу, чтобы локальный блок c имел доступ к двум экранам.

ExploreScreen:

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),);
  }
}

1 Ответ

1 голос
/ 13 марта 2020

Вы не хотите, чтобы StatefulWidget управлял вашим Blo c. Вы можете создать экземпляр своего Blo c в методе initState, но вам не нужно закрывать его с помощью метода dispose, поскольку он автоматически сделает это за вас.

Если вы создали экземпляр Blo c в initState, вы не хотите делать еще один через 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),
      );
    }));
  }
}
...