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