stream_listener_widget

Creator: coderz1093

Last updated:

0 purchases

TODO
Add to Cart

Description:

stream listener widget

stream_listener_widget #
A simple widget that listen streams without rebuilding its child (ex. to show a dialog)
Keep it simple #

A logic class (ViewModel, Controller, etc) should not make use of BuildContext
Triggering a popup or navigation belongs to View's responsibility

The StreamListener widget is as simple as Flutter's StreamBuilder widget.

It allows you to react to streams' events without rebuilding any widget
And it will clean up memory for you by cancelling all the given streams' subscriptions

Here is an example where our logic just trigger some events handled by the View in order to navigate or to to display an error dialog.
class MyView extends StatelessWidget {
final MyController logic;

const MyView({super.key, required this.logic});

void _onLoginSuccess(LoginSuccess e) {
Navigator.of(context).pushNamed('/homePage');
}

void _onError(LoginError e) {
showDialog(
context: context,
barrierDismissible: true,
builder: (context) => Dialog(child: Text('An unknown error occured')),
);
}

@override
Widget build(BuildContext context) {
return StreamListener(
listeners: [
(context) => logic.controller.stream.whereType<LoginSuccess>().listen(_onLoginSuccess),
(context) => logic.controller.stream.whereType<LoginError>().listen(_onError),
],
child: Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () => logic.submitForm('my_email', 'my_password'),
child: const Text('Submit'),
),
),
);
}
}

/// Here's some Type classes that defines possible domain/logic's events
sealed class MyEvent {}
class LoginSuccess extends MyEvent {}
class LoginError extends MyEvent {}
copied to clipboard

Our view handle View's logic
Our logic class handle Business logic
Responsibilities & dependencies are well separated
Our controller has been Domain Driven Designed and can be used elsewhere :)
By defining a Type to logic's events the Business logic is clear
The code is stable and testable

Here's what you should avoid to do (handling navigation, popup, etc in your logic class):
class MyController {
BuildContext context;

Future<void> submitForm(String email, String password) {
try {
await authenticationApi.login(email, password);
Navigator.of(context).pushNamed('/homePage');
} catch(e) {
showDialog(
context: context,
barrierDismissible: true,
builder: (context) => Dialog(child: Text('An unknown error occured')),
);
}
}
}


class MyView extends StatefulWidget {
final logic = MyController();

const MyView({super.key});

@override
Widget build(BuildContext context) {
logic.context = context; // That's really ugly
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () => logic.submitForm('my_email', 'my_password'),
child: const Text('Submit'),
),
);
}
}
copied to clipboard
But wait ?! The example above is shorter !

Yes ! But we haven't separated View logic & Business logic
Our controller is doing everything while the View is doing nothing
Responsibilities and dependencies are mixed up
Your controller has been designed for this specific View
BuildContext is used after a Future/async gap which is forbidden because it can be unstable

License

For personal and professional use. You cannot resell or redistribute these repositories in their original state.

Files In This Product:

Customer Reviews

There are no reviews.