Управление состоянием флаттера с помощью блока/локтя

#flutter #bloc #state-management #cubit

Вопрос:

для многих из вас это очевидный / глупый вопрос, но я дошел до того, что больше не имею ни малейшего понятия. У меня есть реальные трудности с пониманием управления государством с помощью Блока / Локтя.

Ожидание: У меня есть страница со списком (recipe_list) всех рецептов и кнопкой «добавить». Всякий раз, когда я нажимаю на элемент списка или кнопку «добавить», я перехожу на следующую страницу (recipe_detail). На этой странице я могу создать новый рецепт (если ранее нажал кнопку «Добавить»), обновить или удалить существующий рецепт (если ранее нажал на ListItem). Когда я нажимаю кнопку «сохранить» или «удалить», появляется навигатор, и я возвращаюсь на предыдущую страницу (список рецептов). Я использовал Cubit для управления состоянием списка рецептов. Я ожидаю, что ListView автоматически обновится после того, как я нажму «сохранить» или «удалить». Но мне нужно обновить приложение, чтобы отобразить изменения.

главная.дротик

 void main() {  runApp(const MyApp()); }  class MyApp extends StatelessWidget {  const MyApp({Key? key}) : super(key: key);   // This widget is the root of your application.  @override  Widget build(BuildContext context) {  return MaterialApp(  debugShowCheckedModeBanner: false,  title: 'Recipe Demo',  home: BlocProviderlt;RecipeCubitgt;(  create: (context) =gt; RecipeCubit(RecipeRepository())..getAllRecipes(),  child: const RecipeList(),  )  );  } }  

список рецептов.дротик

 class RecipeList extends StatefulWidget {  const RecipeList({Key? key}) : super(key: key);   @override  _RecipeListState createState() =gt; _RecipeListState(); }  class _RecipeListState extends Statelt;RecipeListgt; {  @override  Widget build(BuildContext context) {  return Scaffold(  body: SafeArea(  child: Container(  padding: const EdgeInsets.symmetric(  horizontal: 24.0  ),  color: const Color(0xFFF6F6F6),  child: Stack(  children: [  Column(  children: [  Container(  margin: const EdgeInsets.only(  top: 32.0,  bottom: 32.0  ),  child: const Center(  child: Text('Recipes'),  ),  ),  Expanded(  child: BlocBuilderlt;RecipeCubit, RecipeStategt;(  builder: (context, state) {  if (state is RecipeLoading) {  return const Center(  child: CircularProgressIndicator(),  );  } else if (state is RecipeError) {  return const Center(  child: Icon(Icons.close),  );  } else if (state is RecipeLoaded) {  final recipes = state.recipes;  return ListView.builder(  itemCount: recipes.length,  itemBuilder: (context, index) {  return GestureDetector(  onTap: () {  Navigator.push(context, MaterialPageRoute(  builder: (context) {  return BlocProviderlt;RecipeCubitgt;(  create: (context) =gt; RecipeCubit(RecipeRepository()),  child: RecipeDetail(recipe: recipes[index]),  );  }  ));  },  child: RecipeCardWidget(  title: recipes[index].title,  description: recipes[index].description,  ),  );  },  );  } else {  return const Text('Loading recipes error');  }  }  ),  ),  ],  ),  Positioned(  bottom: 24.0,  right: 0.0,  child: FloatingActionButton(  heroTag: 'addBtn',  onPressed: () {  Navigator.push(context, MaterialPageRoute(  builder: (context) {  return BlocProviderlt;RecipeCubitgt;(  create: (context) =gt; RecipeCubit(RecipeRepository()),  child: const RecipeDetail(recipe: null),  );  }  ));  },  child: const Icon(Icons.add_rounded),  backgroundColor: Colors.teal,  ),  )  ],  ),  ),  ),  );  } }  

recipe_detail.dart

 class RecipeDetail extends StatefulWidget {   final Recipe? recipe;   const RecipeDetail({Key? key, required this.recipe}) : super(key: key);   @override  _RecipeDetailState createState() =gt; _RecipeDetailState(); }  class _RecipeDetailState extends Statelt;RecipeDetailgt; {   final RecipeRepository recipeRepository = RecipeRepository();   final int _recipeId = 0;  late String _recipeTitle = '';  late String _recipeDescription = '';   final recipeTitleController = TextEditingController();  final recipeDescriptionController = TextEditingController();   late FocusNode _titleFocus;  late FocusNode _descriptionFocus;   bool _buttonVisible = false;   @override  void initState() {  if (widget.recipe != null) {  _recipeTitle = widget.recipe!.title;  _recipeDescription = widget.recipe!.description;  _buttonVisible = true;  }   _titleFocus = FocusNode();  _descriptionFocus = FocusNode();  super.initState();  }   @override  void dispose() {  recipeTitleController.dispose();  recipeDescriptionController.dispose();   _titleFocus.dispose();  _descriptionFocus.dispose();  super.dispose();  }   @override  Widget build(BuildContext context) {  return Scaffold(  body: SafeArea(  child: Container(  padding: const EdgeInsets.symmetric(  horizontal: 24.0  ),  color: const Color(0xFFF6F6F6),  child: Stack(  children: [  Column(  children: [  Align(  alignment: Alignment.topLeft,  child: InkWell(  child: IconButton(  highlightColor: Colors.transparent,  color: Colors.black54,  onPressed: () {  Navigator.pop(context);  },  icon: const Icon(Icons.arrow_back_ios_new_rounded),  ),  ),  ),  TextField(  focusNode: _titleFocus,  controller: recipeTitleController..text = _recipeTitle,  decoration: const InputDecoration(  hintText: 'Enter recipe title',  border: InputBorder.none  ),  style: const TextStyle(  fontSize: 26.0,  fontWeight: FontWeight.bold  ),  onSubmitted: (value) =gt; _descriptionFocus.requestFocus(),  ),  TextField(  focusNode: _descriptionFocus,  controller: recipeDescriptionController..text = _recipeDescription,  decoration: const InputDecoration(  hintText: 'Enter recipe description',  border: InputBorder.none  ),  ),  ],  ),  Positioned(  bottom: 24.0,  left: 0.0,  child: FloatingActionButton(  heroTag: 'saveBtn',  onPressed: () {  if (widget.recipe == null) {  Recipe _newRecipe = Recipe(  _recipeId,  recipeTitleController.text,  recipeDescriptionController.text  );  context.readlt;RecipeCubitgt;().createRecipe(_newRecipe);  //recipeRepository.createRecipe(_newRecipe);  Navigator.pop(context);  } else {  Recipe _newRecipe = Recipe(  widget.recipe!.id,  recipeTitleController.text,  recipeDescriptionController.text  );  context.readlt;RecipeCubitgt;().updateRecipe(_newRecipe);  //recipeRepository.updateRecipe(_newRecipe);  Navigator.pop(context);  }  },  child: const Icon(Icons.save_outlined),  backgroundColor: Colors.amberAccent,  ),  ),  Positioned(  bottom: 24.0,  right: 0.0,  child: Visibility(  visible: _buttonVisible,  child: FloatingActionButton(  heroTag: 'deleteBtn',  onPressed: () {  context.readlt;RecipeCubitgt;().deleteRecipe(widget.recipe!.id!);  //recipeRepository.deleteRecipe(widget.recipe!.id!);  Navigator.pop(context);  },  child: const Icon(Icons.delete_outline_rounded),  backgroundColor: Colors.redAccent,  ),  ),  ),  ],  ),  ),  ),  );  } }  

recipe_state.dart

 part of 'recipe_cubit.dart';  abstract class RecipeState extends Equatable {  const RecipeState(); }  class RecipeInitial extends RecipeState {  @override  Listlt;Objectgt; get props =gt; []; }  class RecipeLoading extends RecipeState {  @override  Listlt;Objectgt; get props =gt; []; }  class RecipeLoaded extends RecipeState {  final Listlt;Recipegt; recipes;  const RecipeLoaded(this.recipes);   @override  Listlt;Objectgt; get props =gt; [recipes]; }  class RecipeError extends RecipeState {  final String message;  const RecipeError(this.message);   @override  Listlt;Objectgt; get props =gt; [message]; }  

recipe_cubit.dart

 part 'recipe_state.dart';  class RecipeCubit extends Cubitlt;RecipeStategt; {   final RecipeRepository recipeRepository;   RecipeCubit(this.recipeRepository) : super(RecipeInitial()) {  getAllRecipes();  }   void getAllRecipes() async {  try {  emit(RecipeLoading());  final recipes = await recipeRepository.getAllRecipes();  emit(RecipeLoaded(recipes));  } catch (e) {  emit(const RecipeError('Error'));  }  }   void createRecipe(Recipe recipe) async {  await recipeRepository.createRecipe(recipe);  final newRecipes = await recipeRepository.getAllRecipes();  emit(RecipeLoaded(newRecipes));  }   void updateRecipe(Recipe recipe) async {  await recipeRepository.updateRecipe(recipe);  final newRecipes = await recipeRepository.getAllRecipes();  emit(RecipeLoaded(newRecipes));   }   void deleteRecipe(int id) async {  await recipeRepository.deleteRecipe(id);  final newRecipes = await recipeRepository.getAllRecipes();  emit(RecipeLoaded(newRecipes));  } }  

Ответ №1:

Похоже, что вы создаете еще BlocProvider одну, когда переходите на RecipeDetail страницу. Когда вы нажимаете MaterialPageRoute «Создать», эта новая страница дополнительно упаковывается в «новое RecipeCubit «. Затем, когда вы звоните context.readlt;RecipeCubitgt;() , вы ссылаетесь на этого поставщика (так как он находится ближе BlocProvider всего в дереве виджетов). Вы RecipeList не можете реагировать на эти изменения, потому что он BlocBuilder ищет BlocProvider объявление над ним в дереве виджетов (то, в MyApp котором он находится ). Кроме того, вновь созданный поставщик в любом случае удаляется из дерева виджетов, когда вы закрываете RecipeDetail страницу, как это объявлено в MaterialPageRoute только что удаленной с экрана.

Попробуйте удалить дополнительный BlocProvider (тот , в RecipeList котором, в OnTap функции RecipeCardWidget ):

 onTap: () {  Navigator.push(context, MaterialPageRoute(  builder: (context) {  return BlocProviderlt;RecipeCubitgt;( // remove this BlocProvider  create: (context) =gt; RecipeCubit(RecipeRepository()),  child: RecipeDetail(recipe: recipes[index]),  );  }  )); },  

Комментарии:

1. Спасибо вам за быстрый ответ на этот вопрос. Я удалил упомянутый поставщик блоков. Теперь, нажав кнопку «добавить», вы все равно создадите новый рецепт после отправки текстовых полей (на странице recipe_detail). Но по-прежнему нет обновленного списка без обновления. И когда я теперь нажимаю «сохранить» или «удалить» на странице списка рецептов, я получаю эту ошибку: Исключение, пойманное жестом, было вызвано следующее исключение ProviderNotFoundException при обработке жеста: Ошибка: Не удалось найти правильного поставщикаlt;RecipeCubitgt; над этим виджетом RecipeDetail ….. Получил эту ошибку раньше, и вот почему я добавил BlocProvider

2. О, и не могли бы вы переместить свой первый поставщик блоков выше MaterialApp в дереве виджетов? Таким образом, у вас будет глобальный доступ к нему в вашем приложении. Я забыл упомянуть об этом.

3. Хорошо, ошибка исчезла. Теперь: Когда я нажимаю на элемент списка и обновляю/удаляю рецепт, он автоматически обновляет представление списка, как и должно быть. Но когда я нажимаю кнопку «добавить», чтобы создать новый рецепт, он по-прежнему обновляет представление списка только после обновления. Но пока что большое вам спасибо, вы мне очень помогли. ПРАВКА: просто удалил БлокПровидер и с кнопки «добавить». Сейчас все работает нормально. Спасибо!