flutter_streamer 1.0.0 flutter_streamer: ^1.0.0 copied to clipboard
A pure dart state management class based on standard Dart's Stream.
streamer_example #
The classic counter example to show how it's easy to use the streamer package.
Here we implemented the EASE pattern* with the streamer package.
*We defined Events, Actions, States & Errors classes to handle communication between our Logic class and our View through a stream.
Note: we used flutter_inject to inject our Logic class and stream_listener_widget to handle view's logic (showing dialogs).
main.dart #
import 'package:flutter/material.dart';
import 'package:flutter_inject/flutter_inject.dart';
import 'package:stream_listener_widget/stream_listener_widget.dart';
import 'package:streamer/streamer.dart';
void main() => runApp(
const MaterialApp(
home: CounterView(initialValue: 5),
),
);
class CounterLogic extends Streamer<CounterIO> {
int _value;
CounterLogic(this._value) : super(CounterState(_value)) {
on<Increment>(onIncrement);
on<Decrement>(onDecrement);
}
set value(int newValue) {
if (newValue >= 10) {
emit(MaxReached());
} else if (newValue < 0) {
emit(UnauthorizedValue('A counter under zero is not allowed'));
}
_value = newValue;
}
int get value => _value;
void onIncrement(Increment event) => emit(CounterState(++value));
void onDecrement(Decrement event) => emit(CounterState(--value));
}
class CounterView extends StatelessWidget {
final int initialValue;
const CounterView({super.key, required this.initialValue});
void _showDialog(BuildContext context, String message) => showDialog(
context: context,
barrierDismissible: true,
builder: (context) {
return Dialog(
child: Padding(
padding: const EdgeInsets.all(20),
child: Text(message),
),
);
},
);
void _onMaxReached(BuildContext context) => _showDialog(context, 'Your counter has reached max value');
void _onError(BuildContext context, CounterError event) => _showDialog(context, event.message);
@override
Widget build(BuildContext context) {
// Here we use flutter_inject to simply inject view's dependencies (our logic class here)
// Note that flutter_inject will automatically dispose injected dependencies :D
return Inject<Streamer<CounterIO>>(
factory: (context) => CounterLogic(initialValue),
builder: (context) {
final streamer = Dependency.get<Streamer<CounterIO>>(context);
// Here we use stream_listener_widget package to listen stream without rebuilding child widget
return StreamListener(
listeners: [
(context) => streamer.on<MaxReached>((e) => _onMaxReached(context)),
(context) => streamer.on<CounterError>((e) => _onError(context, e)),
],
child: Scaffold(
appBar: AppBar(title: const Text('Counter demo')),
body: Center(
child: StreamBuilder<CounterState>(
stream: streamer.select<CounterState>(),
builder: (context, snapshot) {
if (snapshot.hasError) return Text(snapshot.error.toString());
if (!snapshot.hasData) return const CircularProgressIndicator();
final state = snapshot.data!;
return Text('Counter: ${state.value}');
},
),
),
floatingActionButton: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
onPressed: () => streamer.emit(Increment()),
child: const Icon(Icons.add),
),
FloatingActionButton(
onPressed: () => streamer.emit(Decrement()),
child: const Icon(Icons.remove),
),
],
),
),
);
});
}
}
/// Here is an example of a simple implementation of the EASE pattern
///
/// Events, States and Errors are always emitted from the Logic class
/// Actions are always emitted from the View
///
/// Note: to easily test Events equality we could use the freezed package
///
/// Events
sealed class CounterEvent extends CounterIO {}
class MaxReached extends CounterEvent {}
/// Actions
sealed class CounterAction extends CounterIO {}
class Increment extends CounterAction {}
class Decrement extends CounterAction {}
/// States
class CounterState extends CounterIO {
final int value;
CounterState(this.value);
}
class Loading extends CounterState {
Loading(super.value);
}
/// Errors
sealed class CounterError extends CounterIO {
final String message;
CounterError(this.message);
}
class UnauthorizedValue extends CounterError {
UnauthorizedValue(super.message);
}
class UnknownError extends CounterError {
UnknownError(super.message);
}