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

Flutter Provider添加交易后需手动刷新分类信息问题求助

记账应用分类详情自动更新问题

我正在开发一款记账应用,希望实现添加交易后,分类详情能根据交易所属分类自动更新。目前用随机数做测试,逻辑后续再完善,但现在的问题是添加记录后必须手动刷新才能看到分类详情变化。

现有代码

SqfliteProvider类代码

class SqfliteProvider with ChangeNotifier {
  Database? _database;

  Future<Database> get getdatabase async {
    _database = await openDatabase(
        join(await getDatabasesPath(), 'expensedb34.db'),
        version: 1, onCreate: (db, version) {
      db.execute(
          "CREATE TABLE categorytb(title TEXT NOT NULL,entries INTEGER NOT NULL,totalamount STRING NOT NULL)");

      db.execute(
          "CREATE TABLE expensetb(id TEXT NOT NULL,title TEXT NOT NULL,amount STRING NOT NULL,category STRING NOT NULL,date STRING NOT NULL)");

      //add some default categories
      for (int x = 0; x < icons.length; x++) {
        db.insert('categorytb', {
          'title': icons.keys.toList()[x],
          'entries': 0,
          'totalamount': (0.0).toString()
        });
      }
    });
    return _database!;
  }

  Future<List<CategoryModel>?> fetchcategory() async {
    final db = await getdatabase;

    List<CategoryModel> templist = [];
    List<Map<String, dynamic>> maplist = await db.query('categorytb');

    if (maplist.isEmpty) return null;
    templist = maplist.map((e) => CategoryModel.frommap(e)).toList();
    return templist;

  }

  Future<void> addTransaction(TransactionModel transactionModel) async {
    final db = await getdatabase;

    //insert in database
    await db.insert('expensetb', transactionModel.tomap(),
        conflictAlgorithm: ConflictAlgorithm.replace);

    notifyListeners();
    await db.update('categorytb', {
      'entries':Random().nextInt(500)
    },where: 'title==?',whereArgs:['Travel']).then((value) => print('Updated'));
    notifyListeners();

    //update category information
  }
}

分类详情展示Widget代码

class CategoryFetcher extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
   final provider=Provider.of<SqfliteProvider>(context,listen: false);
    return  FutureBuilder(
      future: provider.fetchcategory(),
      builder: (context,AsyncSnapshot<List<CategoryModel>?>snapshot){
        if (snapshot.connectionState == ConnectionState.waiting) {
          return Center(child: WaitWidget());
        } else if (snapshot.hasError) {
          return Center(
            child: Text(snapshot.error.toString()),
          );
        } else if (snapshot.hasData) {
          if (snapshot.data != null) {
            return   ListView.builder(
                  itemCount: snapshot.data!.length,
                  itemBuilder: (context, index) {
                    CategoryModel t = snapshot.data![index];
                    return CategoryCard(categorymodel: t);
                  });
          } else {
            return Text('Something wrong');
          }
        } else {
          return Text('Last if');
        }
      },
    );
  }
}

问题原因

  • CategoryFetcher中设置Provider.of(..., listen: false),关闭了状态监听,导致notifyListeners()触发时Widget不会重建。
  • FutureBuilder的future参数仅在Widget首次构建时执行一次fetchcategory(),后续Provider状态变化不会重新触发数据库查询,无法获取最新分类数据。
  • addTransaction中先调用notifyListeners()再更新数据库,会导致Widget提前用旧数据重建,逻辑顺序错误。

修复方案

方案一:维护分类数据缓存(推荐)

修改SqfliteProvider,添加缓存变量存储分类数据,更新数据库后清空缓存并通知状态变化:

class SqfliteProvider with ChangeNotifier {
  Database? _database;
  List<CategoryModel>? _categories; // 新增缓存变量

  Future<Database> get getdatabase async {
    // 原代码保持不变
    _database = await openDatabase(
        join(await getDatabasesPath(), 'expensedb34.db'),
        version: 1, onCreate: (db, version) {
      db.execute(
          "CREATE TABLE categorytb(title TEXT NOT NULL,entries INTEGER NOT NULL,totalamount STRING NOT NULL)");

      db.execute(
          "CREATE TABLE expensetb(id TEXT NOT NULL,title TEXT NOT NULL,amount STRING NOT NULL,category STRING NOT NULL,date STRING NOT NULL)");

      for (int x = 0; x < icons.length; x++) {
        db.insert('categorytb', {
          'title': icons.keys.toList()[x],
          'entries': 0,
          'totalamount': (0.0).toString()
        });
      }
    });
    return _database!;
  }

  // 新增:优先返回缓存数据,无缓存则从数据库加载
  Future<List<CategoryModel>?> get categories async {
    if (_categories == null) {
      _categories = await fetchcategory();
    }
    return _categories;
  }

  Future<List<CategoryModel>?> fetchcategory() async {
    final db = await getdatabase;
    List<Map<String, dynamic>> maplist = await db.query('categorytb');
    if (maplist.isEmpty) return null;
    return maplist.map((e) => CategoryModel.frommap(e)).toList();
  }

  Future<void> addTransaction(TransactionModel transactionModel) async {
    final db = await getdatabase;

    // 1. 插入交易记录
    await db.insert('expensetb', transactionModel.tomap(),
        conflictAlgorithm: ConflictAlgorithm.replace);

    // 2. 更新分类数据
    await db.update('categorytb', {
      'entries': Random().nextInt(500)
    }, where: 'title==?', whereArgs: ['Travel']);

    // 3. 清空缓存,下次获取时加载最新数据
    _categories = null;
    // 4. 通知状态变化,触发Widget重建
    notifyListeners();
  }
}

修改CategoryFetcher,监听Provider状态并使用缓存的分类数据:

class CategoryFetcher extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: Provider.of<SqfliteProvider>(context).categories,
      builder: (context, AsyncSnapshot<List<CategoryModel>?> snapshot) {
        if (snapshot.connectionState == ConnectionState.waiting) {
          return const Center(child: WaitWidget());
        } else if (snapshot.hasError) {
          return Center(child: Text(snapshot.error.toString()));
        } else if (snapshot.hasData && snapshot.data != null) {
          return ListView.builder(
            itemCount: snapshot.data!.length,
            itemBuilder: (context, index) {
              CategoryModel t = snapshot.data![index];
              return CategoryCard(categorymodel: t);
            },
          );
        } else {
          return const Text('暂无分类数据');
        }
      },
    );
  }
}

方案二:使用Consumer触发重新查询

如果不想维护缓存,可以用Consumer包裹FutureBuilder,每次Provider状态变化时重新执行数据库查询:

class CategoryFetcher extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Consumer<SqfliteProvider>(
      builder: (context, provider, child) {
        return FutureBuilder(
          future: provider.fetchcategory(),
          builder: (context, AsyncSnapshot<List<CategoryModel>?> snapshot) {
            if (snapshot.connectionState == ConnectionState.waiting) {
              return const Center(child: WaitWidget());
            } else if (snapshot.hasError) {
              return Center(child: Text(snapshot.error.toString()));
            } else if (snapshot.hasData && snapshot.data != null) {
              return ListView.builder(
                itemCount: snapshot.data!.length,
                itemBuilder: (context, index) {
                  CategoryModel t = snapshot.data![index];
                  return CategoryCard(categorymodel: t);
                },
              );
            } else {
              return const Text('暂无分类数据');
            }
          },
        );
      },
    );
  }
}

同时修改addTransaction中的notifyListeners()位置,确保数据库更新完成后再通知:

Future<void> addTransaction(TransactionModel transactionModel) async {
  final db = await getdatabase;

  await db.insert('expensetb', transactionModel.tomap(),
      conflictAlgorithm: ConflictAlgorithm.replace);

  await db.update('categorytb', {
    'entries': Random().nextInt(500)
  }, where: 'title==?', whereArgs: ['Travel']);

  // 只在所有数据库操作完成后通知一次
  notifyListeners();
}

内容的提问来源于stack exchange,提问作者Irfan Ganatra

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 18:25:24