Drift stream not updating on insert, update and delete

2.5k Views Asked by At

I'm having problem when trying to display data on the UI where it should rebuild the widget prior to changes on menus table. I'm using GetX for the state management and Drift, a.k.a Moor as the database.

My presentation logic looks like this

categories_panel.dart

class CategoriesPanel extends StatelessWidget {
  const CategoriesPanel({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Expanded(
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: const [
          // unrelated codes ommitted...

          _GridView(),
        ],
      ),
    );
  }
}

class _GridView extends GetView<HomeController> {
  const _GridView({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Expanded(
      child: Obx(
        () => GridView.count(
          crossAxisCount: 3,
          shrinkWrap: true,
          mainAxisSpacing: 20,
          crossAxisSpacing: 20,
          physics: const BouncingScrollPhysics(),
          childAspectRatio: 0.8,
          padding: const EdgeInsets.all(30),
          children: controller.categories.map((e) {
            return CategoryCard(
              color: e.category.labelColor,
              name: e.category.name,
              itemCount: e.menus.length, // This should be updated prior to changes on menus table
              onTap: () => controller.selectedCategory(e),
              onLongPress: () => controller.showCategoryActionDialog(e),
            );
          }).toList(),
        ),
      ),
    );
  }
}

In HomeController, I declared a variable named categories and binded the stream in onInit lifecycle.

home_controller.dart

class HomeController extends GetxController {
  HomeController({
    required CategoryRepository categoryRepository,
    required MenuRepository menuRepository,
  })  : _categoryRepository = categoryRepository,
        _menuRepository = menuRepository;

  final CategoryRepository _categoryRepository;
  final MenuRepository _menuRepository;
  final categories = <CategoryWithMenus>[].obs; // HERE

  // unrelated codes ommitted...

  @override
  void onInit() {
    categories.bindStream(_categoryRepository.stream()); // BINDED HERE
    super.onInit();
  }

  // another unrelated codes ommitted...
}

The stream itself, looks like this... I've tried to listen to the menusStream to print each changes to make sure it was triggerred, but it doesn't.

category_repository.dart

class CategoryRepository extends Database {
  Stream<List<CategoryWithMenus>> stream() {
    final categoriesQuery = select(categories);

    return categoriesQuery.watch().switchMap((categories) {
      final idToCategory = {for (var c in categories) c.id: c};
      final ids = idToCategory.keys;

      final menusStream =
          (select(menus)..where((tbl) => tbl.categoryId.isIn(ids))).watch();

      menusStream.listen(print); // This does not print anything on create, update or delete

      return menusStream.map((menus) {
        final idToMenus = <int, List<Menu>>{};

        for (final menu in menus) {
          idToMenus.putIfAbsent(menu.categoryId, () => []).add(menu);
        }

        return [
          for (var id in ids)
            CategoryWithMenus(
              category: idToCategory[id]!,
              menus: idToMenus[id] ?? [],
            ),
        ];
      });
    });
  }

  // unrelated codes ommitted...
}

Finally, my model, migrations and database config looks like this

models/category_with_menus.dart

class CategoryWithMenus {
  const CategoryWithMenus({
    required this.category,
    this.menus = const <Menu>[],
  });

  final Category category;
  final List<Menu> menus;
}

database/migrations/categories.dart

@DataClassName('Category')
class Categories extends Table {
  IntColumn get id => integer().autoIncrement()();
  TextColumn get name => text().withLength(max: 100)();
  IntColumn get labelColor => integer().nullable()();
}

database/migrations/menus.dart

class Menus extends Table {
  IntColumn get id => integer().autoIncrement()();
  TextColumn get name => text().withLength(max: 255)();
  RealColumn get price => real().withDefault(const Constant(0.0))();
  IntColumn get categoryId =>
      integer().references(Categories, #id, onDelete: KeyAction.cascade)();
}

database/database.dart

@DriftDatabase(tables: [
  Categories,
  Menus,
  // unrelated codes ommitted...
])
class Database extends _$Database {
  Database() : super(_openConnection());

  @override
  int get schemaVersion => 1;

  @override
  MigrationStrategy get migration {
    return MigrationStrategy(
      beforeOpen: (OpeningDetails details) async {
        await customStatement('PRAGMA foreign_keys = ON');
      },
    );
  }
}

LazyDatabase _openConnection() {
  return LazyDatabase(() async {
    final databaseFolder = await getApplicationDocumentsDirectory();
    final file = File(p.join(databaseFolder.path, 'myapp.sqlite'));
    return NativeDatabase(file);
  });
}

I've tried to change my stream logic too but it still not working.

Stream<List<CategoryWithMenus>> stream() {
  final categoriesStream = select(categories).watch();
  final menusStream = select(menus).watch();

  return Rx.combineLatest2(
    categoriesStream,
    menusStream,
    (List<Category> a, List<Menu> b) {
      return a.map((category) {
        return CategoryWithMenus(
          category: category,
          menus: b.where((m) => m.categoryId == category.id).toList(),
        );
      }).toList();
    },
  );
}

Please help!

1

There are 1 best solutions below

0
Yura On

It turns out that this behavior occurs after I do an extraction of the database logic into the repositories, the problem is not on the stream logic.

When I did that, I put each repositories into the bindings of HomeBinding

home_binding.dart

class HomeBinding extends Bindings {
  @override
  void dependencies() {
    Get.put(CategoryRepository());
    Get.put(MenuRepository());
    Get.put(AdditionRepository());

    Get.put<AdditionFormController>(
      AdditionFormController(
        additionRepository: Get.find(),
      ),
    );

    Get.put<MenuFormController>(
      MenuFormController(
        menuRepository: Get.find(),
        additionRepository: Get.find(),
      ),
    );

    Get.put<AddMenuDialogController>(
      AddMenuDialogController(
        menuRepository: Get.find(),
      ),
    );

    Get.put<CategoryFormController>(
      CategoryFormController(
        categoryRepository: Get.find(),
      ),
    );

    Get.lazyPut<HomeController>(
      () => HomeController(
        categoryRepository: Get.find(),
        menuRepository: Get.find(),
      ),
    );
  }
}

Those put statements I think, creates new database instances for each repository so the watchers are unable to figure out that some data has been changed.

I fixed this by reverting my refactor commit and re-implementing my stream logic the exact same way as I did on my question. But now, I put all my database logic inside of database/database.dart instead of making a repository.

It works perfectly.