diff --git a/CHANGELOG.md b/CHANGELOG.md index bf7b753..1e8b448 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +## 0.2.0 +Builder, Consumer, and Listener Fixed +- Fixed Bug +- Fixed Library Name +- Fixed Repo URL +- Fixed Example Package Name +- Migrate ViewModelListener to StateFlowListener and SharedFlowListener +- Migrate ViewModelBuilder to StateFlowBuilder +- Migrate ViewModelConsumer to StateFlowConsumer + ## 0.1.0 Initial version of the library diff --git a/README.md b/README.md index cff80d2..210e4e1 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,27 @@ # View Model X -A ViewModel and Flow based state management package (inspired by Android ViewModel) make it easy to implement the MVVM pattern. +An Android similar state management package which helps to implement MVVM pattern easily. + +> **Note:** In Android, ViewModel have an special functionality of keeping the state on configuration change. +> The ViewModel of this package is not for that as Flutter Project doesn't need it. It is only for separating the View Logic from UI. ## Features - Simplified ☺️ State Management +- Similar code pattern with an Android project 🟰 +- Easy for developers to migrate πŸ›©οΈ from Android to Flutter +- Allows developer to work with both Android to Flutter projects with ease πŸ₯ - Easy to implement MVVM pattern πŸ’ͺ -- Android πŸ’š like Environment -- StateFlow (equivalent to LiveData) β›΅ -- SharedFlow 🌊 - -## Getting started -```console -flutter pub add view_model_x -``` +## Package Components +- StateFlow, MutableStateFlow (equivalent to LiveData) β›΅ +- SharedFlow, MutableSharedFlow 🌊 +- ViewModel (to separate the view logic from UI like Cubit) +- ViewModelProvider +- StateFlowBuilder +- StateFlowConsumer +- StateFlowListener +- SharedFlowListener ## Usage @@ -69,8 +76,8 @@ class CounterPageContent extends StatelessWidget { return Scaffold( appBar: AppBar(title: const Text('ViewModel Counter Example')), body: Center( - // implement ViewModelBuilder to rebuild Text on StateFlow value changed/updated - child: ViewModelBuilder( + // implement StateFlowBuilder to rebuild Text on StateFlow value changed/updated + child: StateFlowBuilder( // pass your StateFlow stateFlow: context.vm().counterStateFlow, builder: (context, value) { @@ -90,10 +97,10 @@ class CounterPageContent extends StatelessWidget { } ``` -## ViewModel Elements +## Package Components -### Custom ViewModel class -Create a your custom View-Model which must extends `ViewModel`. Declare all your Flows and View related logic inside of it. +### ViewModel (Create custom ViewModel class) +Create your custom View-Model which must extends `ViewModel`. Declare all your Flows and View related logic inside of it. Don't forget to dispose all flows inside `dispose` method of `ViewModel`. ```dart @@ -177,13 +184,13 @@ OR context.vm() ``` -### ViewModelBuilder +### StateFlowBuilder -`ViewModelBuilder` is used to rebuild the widgets inside of it. +`StateFlowBuilder` is used to rebuild the widgets inside of it. This requires `stateFlow` to listen on and `builder` to which rebuilds when the `stateFlow`'s value changed/updated. ```dart -ViewModelBuilder( +StateFlowBuilder( stateFlow: context.vm().myStateFlow, // pass StateFlow builder: (context, value) { return ChildWidget(value); // rebuild the widget with updated/changed value. @@ -191,14 +198,14 @@ ViewModelBuilder( ) ``` -### ViewModelConsumer +### StateFlowConsumer -`ViewModelConsumer` is used to rebuild the widgets inside of it and call the listener. +`StateFlowConsumer` is used to rebuild the widgets inside of it and call the listener. This requires `stateFlow` to listen on, `builder` and `listener`. Whenever `stateFlow`'s value changed/updated, `builder` will rebuild the widgets inside of it and `listener` will called. ```dart -ViewModelConsumer( +StateFlowConsumer( stateFlow: ViewModelProvider.of(context).myStateFlow, // pass SharedFlow listener: (context, value) { // do stuff here based on value @@ -209,15 +216,15 @@ ViewModelConsumer( ) ``` -### ViewModelListener +### StateFlowListener -`ViewModelListener` is used to rebuild the widgets inside of it and call the listener. -This requires `flow` (which can be `SharedFlow` or `StateFlow`), `listener` and `child`. -Whenever `flow`'s value changed/updated (if it is StateFlow) or emit value (it it is StateFlow), `listener` will called. +`StateFlowListener` is used to catch the change/update value event of a `stateFlow`. +This requires `stateFlow`, `listener` and `child`. +Whenever `stateFlow`'s value changed/updated, `listener` will called. ```dart -ViewModelListener( - flow: ViewModelProvider.of(context).anyStateFlowOrSharedFlow, // pass StateFlow or SharedFlow +StateFlowListener( + stateFlow: ViewModelProvider.of(context).myStateFlow, // pass StateFlow listener: (context, value) { // do stuff here based on value }, @@ -225,6 +232,27 @@ ViewModelListener( ) ``` + +### SharedFlowListener + +`SharedFlowListener` is used to catch the emitted value from `sharedFlow`. +This requires `sharedFlow`, `listener` and `child`. +Whenever `sharedFlow` emits a value, `listener` will called. + +```dart +SharedFlowListener( + sharedFlow: ViewModelProvider.of(context).mySharedFlow, // pass SharedFlow + listener: (context, value) { + // do stuff here based on value + }, + child: ChildWidget(), +) +``` + +## Coming soon +- MultiViewModelProvider +- MultiFlowListener + ## Contributing Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index fab6aad..71dee70 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -44,7 +44,7 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.example.viewmodelexample" + applicationId "com.example.viewmodelxexample" // You can update the following values to match your application needs. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. minSdkVersion flutter.minSdkVersion diff --git a/example/lib/main.dart b/example/lib/main.dart index be663fd..e2183a6 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:viewmodel/viewmodel.dart'; +import 'package:view_model_x/view_model_x.dart'; void main() { runApp(const MyApp()); @@ -85,18 +85,18 @@ class HomePageContent extends StatelessWidget { icon: const Icon(Icons.mail_outline)) ], ), - // implement ViewModelListener anywhere in code to listen any flow - body: ViewModelListener( - // pass your flow (StateFlow or SharedFlow) - flow: context.vm()._messageSharedFlow, + // implement SharedFlowListener anywhere in code to listen for emits from sharedFlow + body: SharedFlowListener( + // pass your SharedFlow + sharedFlow: context.vm().messageSharedFlow, listener: (context, value) { - // get the emitted value. in this case "Hello from ViewModel!" + // get the emitted value. in this case "Hello from ViewModel!" ScaffoldMessenger.of(context) - .showSnackBar(SnackBar(content: Text(value ?? "null message"))); + .showSnackBar(SnackBar(content: Text(value))); }, child: Center( // implement ViewModelBuilder to rebuild Text on StateFlow value changed/updated - child: ViewModelBuilder( + child: StateFlowBuilder( // pass your StateFlow stateFlow: context.vm().counterStateFlow, builder: (context, value) { diff --git a/example/pubspec.lock b/example/pubspec.lock index e62d115..59cd3d9 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -156,13 +156,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.2" - viewmodel: + view_model_x: dependency: "direct main" description: path: ".." relative: true source: path - version: "0.0.1" + version: "0.1.0" sdks: dart: ">=2.18.6 <3.0.0" flutter: ">=1.17.0" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 6235f6c..e8295d2 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -33,7 +33,7 @@ dependencies: sdk: flutter - viewmodel: + view_model_x: path: ../ cupertino_icons: ^1.0.2 diff --git a/lib/src/flow.dart b/lib/src/flow.dart index b8476be..c1313fd 100644 --- a/lib/src/flow.dart +++ b/lib/src/flow.dart @@ -1,15 +1,8 @@ import 'package:flutter/widgets.dart'; -/// Abstract generic class which extends [ChangeNotifier] and stores value -abstract class AnyFlow extends ChangeNotifier { - T _value; - - AnyFlow(this._value); -} - /// [SharedFlow] is used to send data to the listeners. -class SharedFlow extends AnyFlow { - SharedFlow() : super(null); +class SharedFlow extends ChangeNotifier { + T? _value; /// get the last emitted value T? get lastEmitValue => _value; @@ -25,11 +18,13 @@ class MutableSharedFlow extends SharedFlow { } /// [StateFlow] stores value and notify listeners whenever it changes. -class StateFlow extends AnyFlow { +class StateFlow extends ChangeNotifier { + T _value; + /// get the current value. T get value => _value; - StateFlow(T value) : super(value); + StateFlow(this._value); } /// [MutableStateFlow] is inherited from [StateFlow]. It can change/update the value. diff --git a/lib/src/shared_flow_listener.dart b/lib/src/shared_flow_listener.dart new file mode 100644 index 0000000..1396ca7 --- /dev/null +++ b/lib/src/shared_flow_listener.dart @@ -0,0 +1,61 @@ +import 'package:flutter/widgets.dart'; +import 'flow.dart'; + +/// [SharedFlowListener] is used to catch the emitted value from [sharedFlow]. +/// This requires [sharedFlow], [listener] and [child]. +/// Whenever [sharedFlow] emits a value, [listener] will called. +class SharedFlowListener extends StatefulWidget { + final SharedFlow sharedFlow; + final void Function(BuildContext context, T value) listener; + final Widget child; + + const SharedFlowListener( + {super.key, + required this.sharedFlow, + required this.listener, + required this.child}); + + @override + State> createState() => _SharedFlowListenerState(); +} + +class _SharedFlowListenerState extends State> { + @override + void initState() { + super.initState(); + widget.sharedFlow.addListener(_stateListener); + } + + @override + void dispose() { + widget.sharedFlow.removeListener(_stateListener); + super.dispose(); + } + + void _stateListener() { + if (mounted) { + final value = widget.sharedFlow.lastEmitValue; + if (value != null) { + widget.listener(context, value); + } + } + } + + @override + void didUpdateWidget(covariant SharedFlowListener oldWidget) { + if (widget.sharedFlow != oldWidget.sharedFlow) { + _migrate(widget.sharedFlow, oldWidget.sharedFlow, _stateListener); + } + super.didUpdateWidget(oldWidget); + } + + void _migrate(Listenable a, Listenable b, void Function() listener) { + b.removeListener(listener); + a.addListener(listener); + } + + @override + Widget build(BuildContext context) { + return widget.child; + } +} diff --git a/lib/src/view_model_builder.dart b/lib/src/state_flow_builder.dart similarity index 77% rename from lib/src/view_model_builder.dart rename to lib/src/state_flow_builder.dart index fe2d943..81b98a0 100644 --- a/lib/src/view_model_builder.dart +++ b/lib/src/state_flow_builder.dart @@ -2,20 +2,20 @@ import 'package:flutter/widgets.dart'; import 'flow.dart'; -/// [ViewModelBuilder] is used to rebuild the widgets inside of it. +/// [StateFlowBuilder] is used to rebuild the widgets inside of it. /// This requires [stateFlow] to listen on and [builder] to which rebuilds when the [stateFlow]'s value changed/updated. -class ViewModelBuilder extends StatefulWidget { +class StateFlowBuilder extends StatefulWidget { final StateFlow stateFlow; final Widget Function(BuildContext context, T? value) builder; - const ViewModelBuilder( + const StateFlowBuilder( {super.key, required this.stateFlow, required this.builder}); @override - State> createState() => _ViewModelBuilderState(); + State> createState() => _StateFlowBuilderState(); } -class _ViewModelBuilderState extends State> { +class _StateFlowBuilderState extends State> { @override void initState() { super.initState(); @@ -35,7 +35,7 @@ class _ViewModelBuilderState extends State> { } @override - void didUpdateWidget(covariant ViewModelBuilder oldWidget) { + void didUpdateWidget(covariant StateFlowBuilder oldWidget) { if (widget.stateFlow != oldWidget.stateFlow) { _migrate(widget.stateFlow, oldWidget.stateFlow, _stateListener); } diff --git a/lib/src/view_model_consumer.dart b/lib/src/state_flow_consumer.dart similarity index 78% rename from lib/src/view_model_consumer.dart rename to lib/src/state_flow_consumer.dart index b17cd61..6679c1e 100644 --- a/lib/src/view_model_consumer.dart +++ b/lib/src/state_flow_consumer.dart @@ -2,25 +2,25 @@ import 'package:flutter/widgets.dart'; import 'flow.dart'; -/// [ViewModelConsumer] is used to rebuild the widgets inside of it and call the listener. +/// [StateFlowConsumer] is used to rebuild the widgets inside of it and catch the event in [listener]. /// This requires [stateFlow] to listen on, [builder] and [listener]. /// Whenever [stateFlow]'s value changed/updated, [builder] will rebuild the widgets inside of it and [listener] will called. -class ViewModelConsumer extends StatefulWidget { +class StateFlowConsumer extends StatefulWidget { final StateFlow stateFlow; final void Function(BuildContext context, T? value) listener; final Widget Function(BuildContext context, T? value) builder; - const ViewModelConsumer( + const StateFlowConsumer( {super.key, required this.stateFlow, required this.listener, required this.builder}); @override - State> createState() => _ViewModelConsumerState(); + State> createState() => _StateFlowConsumerState(); } -class _ViewModelConsumerState extends State> { +class _StateFlowConsumerState extends State> { @override void initState() { super.initState(); @@ -41,7 +41,7 @@ class _ViewModelConsumerState extends State> { } @override - void didUpdateWidget(covariant ViewModelConsumer oldWidget) { + void didUpdateWidget(covariant StateFlowConsumer oldWidget) { if (widget.stateFlow != oldWidget.stateFlow) { _migrate(widget.stateFlow, oldWidget.stateFlow, _stateListener); } diff --git a/lib/src/state_flow_listener.dart b/lib/src/state_flow_listener.dart new file mode 100644 index 0000000..349de50 --- /dev/null +++ b/lib/src/state_flow_listener.dart @@ -0,0 +1,58 @@ +import 'package:flutter/widgets.dart'; +import 'flow.dart'; + +/// [StateFlowListener] is used to catch the change/update value event of a [stateFlow]. +/// This requires [stateFlow], [listener] and [child]. +/// Whenever [stateFlow]'s value changed/updated , [listener] will called. +class StateFlowListener extends StatefulWidget { + final StateFlow stateFlow; + final void Function(BuildContext context, T value) listener; + final Widget child; + + const StateFlowListener( + {super.key, + required this.stateFlow, + required this.listener, + required this.child}); + + @override + State> createState() => _StateFlowListenerState(); +} + +class _StateFlowListenerState extends State> { + @override + void initState() { + super.initState(); + widget.stateFlow.addListener(_stateListener); + } + + @override + void dispose() { + widget.stateFlow.removeListener(_stateListener); + super.dispose(); + } + + void _stateListener() { + if (mounted) { + widget.listener(context, widget.stateFlow.value); + } + } + + @override + void didUpdateWidget(covariant StateFlowListener oldWidget) { + if (widget.stateFlow != oldWidget.stateFlow) { + _migrate(widget.stateFlow, oldWidget.stateFlow, _stateListener); + } + super.didUpdateWidget(oldWidget); + } + + void _migrate(Listenable a, Listenable b, void Function() listener) { + b.removeListener(listener); + a.addListener(listener); + } + + @override + Widget build(BuildContext context) { + return widget.child; + } +} diff --git a/lib/src/view_model_listener.dart b/lib/src/view_model_listener.dart deleted file mode 100644 index da118a4..0000000 --- a/lib/src/view_model_listener.dart +++ /dev/null @@ -1,62 +0,0 @@ -import 'package:flutter/widgets.dart'; -import 'flow.dart'; - -/// [ViewModelListener] is used to rebuild the widgets inside of it and call the listener. -/// This requires [flow] (which can be [SharedFlow] or [StateFlow]), [listener] and [child]. -/// Whenever [flow]'s value changed/updated (if it is StateFlow) or emit value (it it is StateFlow), [listener] will called. -class ViewModelListener extends StatefulWidget { - final AnyFlow flow; - final void Function(BuildContext context, T? value) listener; - final Widget child; - - const ViewModelListener( - {super.key, - required this.flow, - required this.listener, - required this.child}); - - @override - State> createState() => _ViewModelListenerState(); -} - -class _ViewModelListenerState extends State> { - @override - void initState() { - super.initState(); - widget.flow.addListener(_stateListener); - } - - @override - void dispose() { - widget.flow.removeListener(_stateListener); - super.dispose(); - } - - void _stateListener() { - if (mounted) { - if (widget.flow is SharedFlow) { - widget.listener(context, (widget.flow as SharedFlow).lastEmitValue); - } else if (widget.flow is StateFlow) { - widget.listener(context, (widget.flow as StateFlow).value); - } - } - } - - @override - void didUpdateWidget(covariant ViewModelListener oldWidget) { - if (widget.flow != oldWidget.flow) { - _migrate(widget.flow, oldWidget.flow, _stateListener); - } - super.didUpdateWidget(oldWidget); - } - - void _migrate(Listenable a, Listenable b, void Function() listener) { - b.removeListener(listener); - a.addListener(listener); - } - - @override - Widget build(BuildContext context) { - return widget.child; - } -} diff --git a/lib/view_model_x.dart b/lib/view_model_x.dart new file mode 100644 index 0000000..d9028fa --- /dev/null +++ b/lib/view_model_x.dart @@ -0,0 +1,9 @@ +library view_model_x; + +export './src/flow.dart'; +export './src/view_model.dart'; +export './src/state_flow_builder.dart'; +export './src/state_flow_consumer.dart'; +export './src/state_flow_listener.dart'; +export './src/shared_flow_listener.dart'; +export './src/view_model_provider.dart'; diff --git a/lib/viewmodel.dart b/lib/viewmodel.dart deleted file mode 100644 index d4b5f16..0000000 --- a/lib/viewmodel.dart +++ /dev/null @@ -1,8 +0,0 @@ -library viewmodel; - -export './src/flow.dart'; -export './src/view_model.dart'; -export './src/view_model_builder.dart'; -export './src/view_model_consumer.dart'; -export './src/view_model_listener.dart'; -export './src/view_model_provider.dart'; diff --git a/pubspec.yaml b/pubspec.yaml index 36f3d23..258ac9d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,9 +1,9 @@ name: view_model_x -description: A ViewModel and Flow based state management package (inspired by Android ViewModel) make it easy to implement the MVVM pattern. -version: 0.1.0 -homepage: https://github.com/shubham-gupta-16/viewmodel -repository: https://github.com/shubham-gupta-16/viewmodel -issue_tracker: https://github.com/shubham-gupta-16/viewmodel/issues +description: An Android similar state management package (StateFlow and SharedFlow with ViewModel) which helps to implement MVVM pattern easily. +version: 0.2.0 +homepage: https://github.com/shubham-gupta-16/view_model_x +repository: https://github.com/shubham-gupta-16/view_model_x +issue_tracker: https://github.com/shubham-gupta-16/view_model_x/issues environment: sdk: '>=2.18.6 <3.0.0' @@ -18,39 +18,4 @@ dev_dependencies: sdk: flutter flutter_lints: ^2.0.0 -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter packages. -flutter: - - # To add assets to your package, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - # - # For details regarding assets in packages, see - # https://flutter.dev/assets-and-images/#from-packages - # - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware - - # To add custom fonts to your package, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts in packages, see - # https://flutter.dev/custom-fonts/#from-packages +flutter: \ No newline at end of file