DB Listeners

DeltaTraceDB provides a very lightweight listener mechanism that allows applications (such as UIs) to react when a specific collection is modified. The listener simply calls the registered callback whenever the collection’s content changes. There is no network synchronization or remote event propagation involved.

When to Use

Use listeners when the UI or logic needs to update immediately after DB modification.

Typical use:

  • Frontend UI auto-update

  • Observer-style reactive state refresh

This is not intended for:

  • Remote synchronization

  • Multi-device real-time updates

(These are outside the scope of this DB package.)

API Summary

void addListener(String target, void Function() cb, {String? name})
void removeListener(String target, void Function() cb, {String? name})

Parameters

Parameter

Description

target

Name of the collection to listen to

cb

Function to call when the collection changes

name

(Optional) Identifier for precise registration/removal

Basic Usage (UI)

import 'package:delta_trace_db/delta_trace_db.dart';
import 'package:flutter/material.dart';

final db = DeltaTraceDatabase();

void main() {
  // Add a background color entry as the initial state
  db.executeQuery(
    RawQueryBuilder.clearAdd(
      target: 'appPreferences',
      rawAddData: [
        {'bgColor': '#FFFFFF'},
      ],
      resetSerial: true,
      mustAffectAtLeastOne:
          false, // Should be false if you might be adding to an empty DB.
    ).build(),
  );

  runApp(const MaterialApp(home: SamplePage()));
}

class SamplePage extends StatefulWidget {
  const SamplePage({super.key});

  @override
  State<SamplePage> createState() => _SamplePageState();
}

class _SamplePageState extends State<SamplePage> {
  @override
  void initState() {
    super.initState();
    db.addListener("users", _onDbChanged);
    db.addListener("appPreferences", _onDbChanged);
  }

  void _onDbChanged() {
    if(mounted) {
      setState(() {}); // refresh UI
    }
  }

  @override
  void dispose() {
    db.removeListener("users", _onDbChanged);
    db.removeListener("appPreferences", _onDbChanged);
    super.dispose();
  }

  // Get the current background color from the DB
  Color _getBackgroundColor() {
    final result = db
        .executeQuery(RawQueryBuilder.getAll(target: "appPreferences").build())
        .result;
    if (result.isEmpty) return Colors.white;
    final hex = result.first['bgColor'] as String;
    return Color(int.parse(hex.replaceFirst('#', '0xff')));
  }

  // Get the current users
  // You can change the limit and add paging options as needed.
  // Read the getAll chapter for more details.
  List<Map<String, dynamic>> _getUsers() {
    final result = db
        .executeQuery(RawQueryBuilder.getAll(target: "users").build())
        .result;
    return result;
  }

  // Add a user
  void _addUser() {
    final id = _getUsers().length + 1;

    final addQuery = RawQueryBuilder.add(
      target: 'users',
      rawAddData: [
        {'id': -1, 'name': 'User $id'},
      ],
      serialKey: "id",
      returnData: true,
    ).build();

    db.executeQuery(addQuery);
  }

  // Switch background color
  void _toggleBackgroundColor() {
    final currentColor = _getBackgroundColor();
    final nextColor = currentColor == Colors.white ? '#FFDDDD' : '#FFFFFF';

    final clearAddQuery = RawQueryBuilder.clearAdd(
      target: 'appPreferences',
      rawAddData: [
        {'bgColor': nextColor},
      ],
      resetSerial: true,
      mustAffectAtLeastOne: false,
    ).build();

    db.executeQuery(clearAddQuery);
  }

  @override
  Widget build(BuildContext context) {
    final users = _getUsers();

    return Scaffold(
      backgroundColor: _getBackgroundColor(),
      appBar: AppBar(title: const Text("DB Listener Sample")),
      body: Column(
        children: [
          Container(
            color: Colors.blue[50],
            height: 64,
            child: Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                TextButton.icon(
                  onPressed: _addUser,
                  icon: const Icon(Icons.add),
                  label: const Text("Add User"),
                ),
                const SizedBox(width: 16),
                TextButton.icon(
                  onPressed: _toggleBackgroundColor,
                  icon: const Icon(Icons.color_lens),
                  label: const Text("Change Background"),
                ),
              ],
            ),
          ),
          Expanded(
            child: ListView(
              children: users.map((user) {
                return ListTile(
                  title: Text(user['name']),
                  subtitle: Text("id: ${user['id']}"),
                );
              }).toList(),
            ),
          ),
        ],
      ),
    );
  }
}

Using Named Listeners

Useful when managing multiple listeners cleanly.

db.addListener("logs", _refreshLogs, name: "logView");

// later
db.removeListener("logs", _refreshLogs, name: "logView");

Important Notes

  • Listeners do not persist across deserialization. You must call addListener() again after loading the DB.

  • Only local changes trigger callbacks. No network messaging or distributed event tracking is involved.

  • Callbacks should be fast. Long operations should be moved to separate tasks.

  • When you execute a TransactionQuery, a callback occurs only once for each collection that is operated on.

Summary

  • Listeners provide a simple callback on local collection changes.

  • Ideal for UI auto-refresh or lightweight event hooks.

  • Must be manually registered and removed.

  • No remote or real-time synchronization is included.

API