Flutter Bloc结合Sqflite实现CRUD时UI不更新问题及优化咨询
食谱APP Bloc+Sqflite问题解决方案:UI不更新与架构优化
一、解决UI不更新的核心问题
你的推测没错,UI不更新的根源在于状态判断未触发变化,结合代码细节,修复点如下:
1. 修正RecipeState的Equatable判断
父类RecipeState的props未包含recipes,导致Equatable无法识别列表变化。修改代码:
abstract class RecipeState extends Equatable { final List<Recipe> recipes; const RecipeState(this.recipes); @override List<Object> get props => [recipes]; // 新增recipes到props列表 }
2. 重构RecipeRepository,避免列表引用复用
当前Repository用成员变量维护recipes,导致每次返回的是同一个列表引用,Equatable会认为状态无变化。修改为每次操作返回新列表:
class RecipeRepository { // 移除成员变量List<Recipe> recipes = []; Future<List<Recipe>> getRecipe(Database database) async { final List<Recipe> recipes = []; // 每次创建新列表 await database.transaction((txn) async { final rawData = await txn.rawQuery('SELECT * FROM Recipes'); for (var item in rawData) { recipes.add(Recipe( id: item['id'].toString(), name: item['name'] as String, isVegan: item['isvegan'] == 1 ? true : false, content: item['content'] as String, calories: item['calories'].toString(), )); } }); return recipes; } Future<List<Recipe>> addRecipe(Database database, Recipe newRecipe) async { // 插入数据库并获取自动生成的ID final int newId = await database.rawInsert( 'INSERT INTO Recipes(name, calories, isvegan, content) VALUES(?, ? ,? ,?)', [newRecipe.name, newRecipe.calories, newRecipe.isVegan ? 1 : 0, newRecipe.content], ); // 重新查询数据库,确保返回最新全量数据 return getRecipe(database); } Future<List<Recipe>> removeRecipe(Database database, int id) async { await database.rawDelete('DELETE FROM Recipes WHERE id = ?', [id]); // 重新查询数据库获取最新列表 return getRecipe(database); } // 新增编辑操作的数据库方法 Future<List<Recipe>> editRecipe(Database database, Recipe updatedRecipe) async { await database.rawUpdate( 'UPDATE Recipes SET name = ?, calories = ?, isvegan = ?, content = ? WHERE id = ?', [updatedRecipe.name, updatedRecipe.calories, updatedRecipe.isVegan ? 1 : 0, updatedRecipe.content, int.parse(updatedRecipe.id)], ); return getRecipe(database); } }
3. 修复EditRecipeEvent的数据库调用
原代码仅修改本地列表未同步数据库,现在更新Bloc逻辑:
on<EditRecipeEvent>((event, emit) async { if (state is RecipeLoaded) { try { final updatedRecipes = await _recipeRepo.editRecipe(recipeDatabase, event.recipe); emit(RecipeLoaded(updatedRecipes)); } catch (e) { print(e); } } });
二、更优的数据库集成方式
单独的DatabaseCubit没必要,可简化为两种方案:
1. 启动时初始化数据库,直接传入RecipeBloc
修改main.dart,用FutureBuilder等待数据库初始化完成:
class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return FutureBuilder<Database>( future: _initDatabase(), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.done) { if (snapshot.hasData) { return BlocProvider( create: (context) => RecipeBloc(recipeDatabase: snapshot.data!)..add(LoadRecipeEvent()), child: const MaterialApp( debugShowCheckedModeBanner: false, title: "The Recipe Book", home: HomeScreen(), ), ); } else { return const Center(child: Text("数据库初始化失败")); } } return const Center(child: CircularProgressIndicator(color: Colors.orange)); }, ); } Future<Database> _initDatabase() async { final databasesPath = await getDatabasesPath(); final path = join(databasesPath, 'recipes.db'); return openDatabase( path, version: 1, onCreate: (Database db, int version) async { await db.execute('CREATE TABLE Recipes (id INTEGER PRIMARY KEY, name TEXT, calories INTEGER, isvegan INTEGER, content TEXT)'); }, ); } }
2. 用单例类封装数据库服务
创建全局可复用的数据库实例,避免重复初始化:
class DatabaseService { static final DatabaseService _instance = DatabaseService._internal(); factory DatabaseService() => _instance; DatabaseService._internal(); Database? _database; Future<Database> get database async { if (_database != null) return _database!; _database = await _initDatabase(); return _database!; } Future<Database> _initDatabase() async { final databasesPath = await getDatabasesPath(); final path = join(databasesPath, 'recipes.db'); return openDatabase( path, version: 1, onCreate: (db, version) { return db.execute('CREATE TABLE Recipes (id INTEGER PRIMARY KEY, name TEXT, calories INTEGER, isvegan INTEGER, content TEXT)'); }, ); } }
在RecipeBloc中使用:
class RecipeBloc extends Bloc<RecipeEvent, RecipeState> { final Database _database; final RecipeRepository _recipeRepo; RecipeBloc() : _database = await DatabaseService().database, _recipeRepo = RecipeRepository(), super(const RecipeInitial([])) { // 事件处理逻辑 } }
三、状态设计优化建议
增加加载、错误状态,提升用户体验,同时遵循Bloc无状态原则:
1. 扩展RecipeState类型
abstract class RecipeState extends Equatable { const RecipeState(); @override List<Object> get props => []; } class RecipeInitial extends RecipeState {} class RecipeLoading extends RecipeState {} 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]; }
2. 移除Bloc中的私有列表,依赖State存储数据
Bloc不再维护_recipes变量,所有数据从State获取,避免状态不一致:
class RecipeBloc extends Bloc<RecipeEvent, RecipeState> { final Database _database; final RecipeRepository _recipeRepo; RecipeBloc({required Database database}) : _database = database, _recipeRepo = RecipeRepository(), super(RecipeInitial()) { on<LoadRecipeEvent>(_onLoadRecipes); on<AddRecipeEvent>(_onAddRecipe); } void _onLoadRecipes(LoadRecipeEvent event, Emitter<RecipeState> emit) async { emit(RecipeLoading()); try { final recipes = await _recipeRepo.getRecipe(_database); emit(RecipeLoaded(recipes)); } catch (e) { emit(RecipeError(e.toString())); } } void _onAddRecipe(AddRecipeEvent event, Emitter<RecipeState> emit) async { emit(RecipeLoading()); try { final recipes = await _recipeRepo.addRecipe(_database, event.recipe); emit(RecipeLoaded(recipes)); } catch (e) { emit(RecipeError(e.toString())); // 恢复之前的状态 if (state is RecipeLoaded) { emit(RecipeLoaded((state as RecipeLoaded).recipes)); } } } }
内容的提问来源于stack exchange,提问作者David Black
相关产品推荐
相关产品推荐

