Bloc/Cubit的Flutter状态管理



对你们中的许多人来说,这是一个显而易见/愚蠢的问题,但我已经到了一个毫无头绪的地步。我很难理解Bloc/Cubit的状态管理。

期望:我有一个页面,上面有所有食谱的ListView(recipe_list(和一个"添加"按钮。每当我单击ListItem或"添加"按钮时,我都会转到下一页(recipe_detail(。在这个页面上,我可以创建一个新配方(如果之前单击"添加"按钮(,更新或删除现有配方(如果以前单击ListItem(。当我点击"保存"或"删除"按钮时,导航器弹出,我返回上一页(recipe_list(。我使用Cubit来管理食谱列表的状态。我的期望是ListView在我单击"保存"或"删除"后自动更新。但我必须刷新应用程序才能显示更改。

main.dart

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: BlocProvider<RecipeCubit>(
create: (context) => RecipeCubit(RecipeRepository())..getAllRecipes(),
child: const RecipeList(),
)
);
}
}

recipe_list.dart

class RecipeList extends StatefulWidget {
const RecipeList({Key? key}) : super(key: key);
@override
_RecipeListState createState() => _RecipeListState();
}
class _RecipeListState extends State<RecipeList> {
@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: BlocBuilder<RecipeCubit, RecipeState>(
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 BlocProvider<RecipeCubit>(
create: (context) => 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 BlocProvider<RecipeCubit>(
create: (context) => 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() => _RecipeDetailState();
}
class _RecipeDetailState extends State<RecipeDetail> {
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) => _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.read<RecipeCubit>().createRecipe(_newRecipe);
//recipeRepository.createRecipe(_newRecipe);
Navigator.pop(context);
} else {
Recipe _newRecipe = Recipe(
widget.recipe!.id,
recipeTitleController.text,
recipeDescriptionController.text
);
context.read<RecipeCubit>().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.read<RecipeCubit>().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
List<Object> get props => [];
}
class RecipeLoading extends RecipeState {
@override
List<Object> get props => [];
}
class RecipeLoaded extends RecipeState {
final List<Recipe> recipes;
const RecipeLoaded(this.recipes);
@override
List<Object> get props => [recipes];
}
class RecipeError extends RecipeState {
final String message;
const RecipeError(this.message);
@override
List<Object> get props => [message];
}

recipe_cubit.dart

part 'recipe_state.dart';
class RecipeCubit extends Cubit<RecipeState> {
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));
}
}

在导航到RecipeDetail页面时,您似乎正在创建另一个BlocProvider。当您推送新的MaterialPageRoute时,这个新页面会被新的RecipeCubit额外包装。然后,当您调用context.read<RecipeCubit>()时,您正在引用该提供程序(因为这是小部件树中最接近的BlocProvider(。您的RecipeList无法对这些更改做出反应,因为它的BlocBuilder正在小部件树(MyApp中的那个(中查找在其上方声明的BlocProvider。除此之外,当您关闭RecipeDetail页面时,新创建的提供程序无论如何都会从小部件树中删除,因为它是在刚刚从屏幕上推下的MaterialPageRoute中声明的。

尝试删除附加的BlocProvider(RecipeList中的RecipeCardWidgetOnTap函数中的一个(:

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

最新更新