You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 16:25:55