diff --git a/README.md b/README.md index 1d6058f..aa24178 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,9 @@ final subscription = eventBus .respond(responderA) // Subscribe to EventA .respond(responderB) // Subscribe to EventB .respond(responderC) // Subscribe to EventC - .respond(genericResponder); // Subscribe to EventA EventB EventC and any other event on event bus + .respond(responderNullableA) // Subscribe to EventA and any null + .respond(responderNullableB) // Subscribe to EventB and any null + .respond(genericResponder); // Subscribe to EventA EventB EventC null and any other event on event bus // Generic Responder could be useful for monitoring, logging or diagnosis purpose, probably will be hardly used to take action to event diff --git a/example/example.dart b/example/example.dart index 7dbd49a..70718fb 100644 --- a/example/example.dart +++ b/example/example.dart @@ -7,7 +7,7 @@ class MyApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) => EventBusWidget( - child: MaterialApp( + child: MaterialApp( title: 'Flutter Demo', theme: ThemeData( // This is the theme of your application. @@ -26,7 +26,7 @@ class MyApp extends StatelessWidget { } class MyHomePage extends StatefulWidget { - MyHomePage({Key key, this.title}) : super(key: key); + MyHomePage({Key? key, required this.title}) : super(key: key); // This widget is the home page of your application. It is stateful, meaning // that it has a State object (defined below) that contains fields that affect @@ -43,23 +43,23 @@ class MyHomePage extends StatefulWidget { _MyHomePageState createState() => _MyHomePageState(); } -class InreaseCounterEvent {} +class IncreaseCounterEvent {} class IncreaseCounterButton extends StatelessWidget { @override Widget build(BuildContext context) => FloatingActionButton( - onPressed: () { - EventBus.publishTo(context, InreaseCounterEvent()); - }, - tooltip: 'Increment', - child: Icon(Icons.add), - ); + onPressed: () { + EventBus.publishTo(context, IncreaseCounterEvent()); + }, + tooltip: 'Increment', + child: Icon(Icons.add), + ); } class _MyHomePageState extends Interactor { int _counter = 0; - void _incrementCounter(InreaseCounterEvent event) { + void _incrementCounter(IncreaseCounterEvent event) { setState(() { // This call to setState tells the Flutter framework that something has // changed in this State, which causes it to rerun the build method below @@ -109,17 +109,17 @@ class _MyHomePageState extends Interactor { ), Text( '$_counter', - style: Theme.of(context).textTheme.display1, + style: Theme.of(context).textTheme.headline4, ), ], ), ), floatingActionButton: - IncreaseCounterButton(), // This trailing comma makes auto-formatting nicer for build methods. + IncreaseCounterButton(), // This trailing comma makes auto-formatting nicer for build methods. ); } @override Subscription subscribeEvents(EventBus eventBus) => - eventBus.respond(_incrementCounter); + eventBus.respond(_incrementCounter); } diff --git a/lib/BuilderInteractor.dart b/lib/BuilderInteractor.dart index c9c0e2c..feee180 100644 --- a/lib/BuilderInteractor.dart +++ b/lib/BuilderInteractor.dart @@ -4,7 +4,7 @@ import 'Interactor.dart'; abstract class BuilderInteractorWidget extends StatefulWidget { final WidgetBuilder childBuilder; - const BuilderInteractorWidget({Key key, @required this.childBuilder}) + const BuilderInteractorWidget({Key? key, required this.childBuilder}) : super(key: key); } diff --git a/lib/EventBus.dart b/lib/EventBus.dart index 7283ef1..4a6de21 100644 --- a/lib/EventBus.dart +++ b/lib/EventBus.dart @@ -18,15 +18,15 @@ class EventBus { EventBus.customController(this._streamController); /// Create an event bus with default dart StreamController. - /// You can use [sync] to indentify whether the event bus should be sync or async, by default it is async. + /// You can use [sync] to identify whether the event bus should be sync or async, by default it is async. /// For more detail, check [Stream] document. EventBus({bool sync = false}) - : _streamController = StreamController.broadcast(sync: sync); + : _streamController = StreamController.broadcast(sync: sync); Stream get _stream => _streamController.stream; /// Publish an event to all corresponding responders via EventBus - void publish(event) { + void publish(dynamic event) { _streamController.add(event); } @@ -58,7 +58,7 @@ class EventBus { /// ``` /// EventBus.of(context).publish(event); /// ``` - static void publishTo(BuildContext context, event) => + static void publishTo(BuildContext context, dynamic event) => of(context).publish(event); } @@ -88,10 +88,10 @@ class EventBusWidget extends InheritedWidget { /// is `synchronized` or `asynchronized`, default to `asynchronized`. /// If [eventBus] is given, [sync] is ignored /// - /// The [eventBus] param chould be useful if you want to access eventBus from widget who hold [EventBusWidget], + /// The [eventBus] param should be useful if you want to access eventBus from widget who hold [EventBusWidget], /// or you are using custom [StreamController] in Event Bus. EventBusWidget( - {Key key, @required Widget child, EventBus eventBus, bool sync = false}) + {Key? key, required Widget child, EventBus? eventBus, bool sync = false}) : eventBus = eventBus ?? EventBus(sync: sync), super(key: key, child: child); @@ -99,7 +99,7 @@ class EventBusWidget extends InheritedWidget { bool updateShouldNotify(EventBusWidget oldWidget) => eventBus != oldWidget.eventBus; - /// Find the closeset [EventBusWidget] from ancester tree. + /// Find the closest [EventBusWidget] from ancestor tree. static EventBusWidget of(BuildContext context) => - context.dependOnInheritedWidgetOfExactType(); + context.dependOnInheritedWidgetOfExactType()!; } diff --git a/lib/Interactor.dart b/lib/Interactor.dart index 2f83594..0b2d91d 100644 --- a/lib/Interactor.dart +++ b/lib/Interactor.dart @@ -24,7 +24,7 @@ import 'EventBus.dart'; /// } /// ``` abstract class Interactor extends State { - Subscription _subscription; + Subscription? _subscription; /// [EventBus] provided by ancestor [EventBusWidget] /// Cannot not be accessed in [initState] or [dispose], as state has not yet or had been removed from element tree. @@ -42,7 +42,7 @@ abstract class Interactor extends State { @override void dispose() { - _subscription.dispose(); + _subscription?.dispose(); super.dispose(); } diff --git a/lib/ProxyInteractor.dart b/lib/ProxyInteractor.dart index 0297cf1..ef3720f 100644 --- a/lib/ProxyInteractor.dart +++ b/lib/ProxyInteractor.dart @@ -4,7 +4,7 @@ import 'Interactor.dart'; abstract class ProxyInteractorWidget extends StatefulWidget { final Widget child; - const ProxyInteractorWidget({Key key, @required this.child}) + const ProxyInteractorWidget({Key? key, required this.child}) : super(key: key); } diff --git a/lib/Subscription.dart b/lib/Subscription.dart index c101d98..f87ddbb 100644 --- a/lib/Subscription.dart +++ b/lib/Subscription.dart @@ -8,7 +8,7 @@ class Subscription { final Stream _stream; /// Subscriptions that registered to event bus - final List subscriptions = List(); + final subscriptions = []; /// Create the subscription /// @@ -18,8 +18,13 @@ class Subscription { /// Returns an instance that indicates there is no subscription factory Subscription.empty() => const _EmptySubscription(); - Stream _cast() => - (T == dynamic) ? _stream : _stream.where((event) => event is T).cast(); + Stream _cast() { + if((T == dynamic)) { + return _stream.cast(); + } + + return _stream.where((event) => event is T).cast(); + } /// Register a [responder] to event bus for the event type [T]. /// If [T] is omitted or given as `dyanmic`, it listens to all events that publised on [EventBus]. @@ -51,7 +56,7 @@ class Subscription { class _EmptySubscription implements Subscription { static final List emptyList = - List.unmodifiable([]); + List.unmodifiable([]); const _EmptySubscription(); diff --git a/pubspec.yaml b/pubspec.yaml index 5be04ba..c175434 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -7,7 +7,7 @@ homepage: https://github.com/timnew/flutter_event_bus/ repository: https://github.com/timnew/flutter_event_bus/ environment: - sdk: '>=2.1.0 <3.0.0' + sdk: '>=2.12.0 <3.0.0' dependencies: flutter: @@ -17,6 +17,6 @@ dev_dependencies: flutter_test: sdk: flutter - mockito: ^4.1.1 + mockito: ^5.0.10 flutter: diff --git a/test/event_bus_test.dart b/test/event_bus_test.dart index 94d6434..995a241 100644 --- a/test/event_bus_test.dart +++ b/test/event_bus_test.dart @@ -7,7 +7,7 @@ void main() { test("should publish and respond to event", () { final eventBus = EventBus(sync: true); - String capturedEvent; + late String capturedEvent; eventBus.respond((String event) { capturedEvent = event; @@ -18,11 +18,25 @@ void main() { expect(capturedEvent, "event"); }); + test("should publish and respond to nullable event", () { + final eventBus = EventBus(sync: true); + + String? capturedEvent = 'some-random-value'; + + eventBus.respond((String? event) { + capturedEvent = event; + }); + + eventBus.publish(null); + + expect(capturedEvent, null); + }); + test("should respond to event by type", () { final eventBus = EventBus(sync: true); - List capturedStrings = List(); - List capturedNumbers = List(); + final capturedStrings = []; + final capturedNumbers = []; eventBus.respond((String event) { capturedStrings.add(event); @@ -42,22 +56,116 @@ void main() { expect(capturedNumbers, [2, 4]); }); - test("should not recieve event after disposed subscription", () { + test("should respond to dynamic events", () { final eventBus = EventBus(sync: true); - List capturedEvents = List(); + final capturedDynamics = []; + final capturedStrings = []; + + eventBus.respond((dynamic event) { + capturedDynamics.add(event); + }); + + eventBus.respond((String event) { + capturedStrings.add(event); + }); + + eventBus.publish("1"); + eventBus.publish(2); + eventBus.publish(null); + eventBus.publish(true); + eventBus.publish("3"); + + expect(capturedDynamics, ["1", 2, null, true, "3"]); + expect(capturedStrings, ["1", "3"]); + }); + + test("should respond to nullable events by type", () { + final eventBus = EventBus(sync: true); + + final capturedStrings = []; + final capturedNumbers = []; + + eventBus.respond((String? event) { + capturedStrings.add(event); + }); + + eventBus.respond((int? event) { + capturedNumbers.add(event); + }); + + eventBus.publish("1"); + eventBus.publish(2); + eventBus.publish(null); + + eventBus.publish("3"); + eventBus.publish(4); + eventBus.publish(null); - final subscription = eventBus.respond((String event) { + expect(capturedStrings, ["1", null, "3", null]); + expect(capturedNumbers, [2, null, 4, null]); + }); + + test("should respond to some nullable events and some not-nullable events by type", () { + final eventBus = EventBus(sync: true); + + final capturedStrings = []; + final capturedNumbers = []; + + eventBus.respond((String? event) { + capturedStrings.add(event); + }); + + eventBus.respond((int event) { + capturedNumbers.add(event); + }); + + eventBus.publish("1"); + eventBus.publish(2); + eventBus.publish(null); + + eventBus.publish("3"); + eventBus.publish(4); + eventBus.publish(null); + + expect(capturedStrings, ["1", null, "3", null]); + expect(capturedNumbers, [2, 4]); + }); + + test("should not receive event after disposed subscription", () { + final eventBus = EventBus(sync: true); + + final capturedEvents = []; + + final subscription = eventBus.respond((String? event) { capturedEvents.add(event); }); eventBus.publish("1"); eventBus.publish("2"); + eventBus.publish(null); subscription.dispose(); eventBus.publish("3"); eventBus.publish("4"); + eventBus.publish(null); + + expect(capturedEvents, ["1", "2", null]); + }); + + test("should respond to event by type ignoring nulls", () { + final eventBus = EventBus(sync: true); + + final capturedEvents = []; + + eventBus.respond((String event) { + capturedEvents.add(event); + }); + + eventBus.publish("1"); + eventBus.publish("2"); + eventBus.publish(null); expect(capturedEvents, ["1", "2"]); }); diff --git a/test/event_bus_widget_test.dart b/test/event_bus_widget_test.dart index ab16759..9129809 100644 --- a/test/event_bus_widget_test.dart +++ b/test/event_bus_widget_test.dart @@ -39,7 +39,7 @@ class TestInteractor extends Interactor { class TestButton extends StatelessWidget { @override - Widget build(BuildContext context) => RaisedButton( + Widget build(BuildContext context) => ElevatedButton( child: const Text("Button"), onPressed: () { EventBus.publishTo(context, ButtonPressedEvent("Pressed")); @@ -49,10 +49,10 @@ class TestButton extends StatelessWidget { class TestWidget extends StatelessWidget { @override Widget build(BuildContext context) => MaterialApp( - home: Scaffold( - body: Row( - children: [InteractorWidget(), TestButton()], - ))); + home: Scaffold( + body: Row( + children: [InteractorWidget(), TestButton()], + ))); } void main() { @@ -61,7 +61,7 @@ void main() { expect(find.text("None"), findsOneWidget); - await tester.tap(find.byType(RaisedButton)); + await tester.tap(find.byType(ElevatedButton)); await tester.pump();