Dart Streams and SQLite Data Management
1. Single vs. Broadcast Streams
Single Subscription Streams
- Allow only one listener at a time.
- Ideal for sequential data processing (e.g., file reading, API calls).
- Example: File I/O, HTTP requests.
Broadcast Streams
- Allow multiple listeners simultaneously.
- Best for real-time data/event broadcasting (e.g., WebSocket updates, UI events).
- Example: Button clicks, live data updates.
Code Example
import 'dart:async';
void main() {
// Single subscription stream
StreamController<String> singleController = StreamController();
singleController.stream.listen((data) => print('Single subscription: $data'));
singleController.add('Data 1');
singleController.close();
// Broadcast stream
StreamController<String> broadcastController = StreamController.broadcast();
broadcastController.stream.listen((data) => print('Listener 1: $data'));
broadcastController.stream.listen((data) => print('Listener 2: $data'));
broadcastController.add('Data 2');
broadcastController.close();
}
2. StreamController, Stream, and StreamSink
StreamController: Manages a stream.
Stream: Source of asynchronous data events.
StreamSink: Adds data to the stream.
Code Example
import 'dart:async';
void main() {
// Create a StreamController
final StreamController<int> controller = StreamController<int>();
// Access the stream and sink
Stream<int> stream = controller.stream;
StreamSink<int> sink = controller.sink;
// Listen to the stream
stream.listen((data) => print('Stream data: $data'));
// Add data to the sink
sink.add(1);
sink.add(2);
// Close the controller
controller.close();
}
3. StreamBuilder in Flutter
StreamBuilder
is a Flutter widget that rebuilds itself when its stream’s data changes.
Example Usage
import 'dart:async';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('StreamBuilder Example')),
body: CounterStreamBuilder(),
),
);
}
}
class CounterStreamBuilder extends StatelessWidget {
final Stream<int> counterStream = (() {
StreamController<int> controller = StreamController<int>();
int counter = 0;
Timer.periodic(Duration(seconds: 1), (timer) {
if (counter == 5) {
timer.cancel();
controller.close();
} else {
controller.add(counter++);
}
});
return controller.stream;
})();
@override
Widget build(BuildContext context) {
return StreamBuilder<int>(
stream: counterStream,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
} else if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}'));
} else if (!snapshot.hasData) {
return Center(child: Text('No data'));
} else {
return Center(child: Text('Counter: ${snapshot.data}'));
}
},
);
}
}
4. Storing Data in SQLite with sqflite
Steps:
- Add
sqflite
andpath
dependencies. - Create a database and table.
- Use
insert
to store data.
Code Example
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
void main() async {
final database = openDatabase(
join(await getDatabasesPath(), 'example.db'),
onCreate: (db, version) {
return db.execute(
'CREATE TABLE users(id INTEGER PRIMARY KEY, name TEXT)',
);
},
version: 1,
);
Future<void> insertUser(String name) async {
final db = await database;
await db.insert(
'users',
{'name': name},
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
await insertUser('John Doe');
print('Data inserted!');
}
5. Reading Data from SQLite with sqflite
Steps:
- Open the database.
- Use
query
to fetch data.
Code Example
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
void main() async {
final database = openDatabase(
join(await getDatabasesPath(), 'example.db'),
);
Future<List<Map<String, dynamic>>> fetchUsers() async {
final db = await database;
return await db.query('users');
}
final users = await fetchUsers();
print('Fetched Users: $users');
}
6. Moor: Reactive Persistence Library
Moor simplifies database management in Flutter/Dart. Built on SQLite.
Advantages:
- Type safety: Reduces runtime errors.
- Reactive updates: Automatic UI rebuilds on data changes.
- Query flexibility: Raw SQL and Dart-based queries.
- Cross-platform support: Works on Android, iOS, macOS, and web.
Example
// Include `moor` and `moor_flutter` in pubspec.yaml
import 'package:moor_flutter/moor_flutter.dart';
part 'database.g.dart';
@DataClassName('User')
class Users extends Table {
IntColumn get id => integer().autoIncrement()();
TextColumn get name => text()();
}
@UseMoor(tables: [Users])
class AppDatabase extends _$AppDatabase {
AppDatabase() : super(FlutterQueryExecutor.inDatabaseFolder(path: 'db.sqlite'));
@override
int get schemaVersion => 1;
Future<List<User>> getAllUsers() => select(users).get();
Future insertUser(User user) => into(users).insert(user);
}