From 5d2d7e3f13252ab3085055d4277a688b5c44b330 Mon Sep 17 00:00:00 2001 From: Shubham Gupta Date: Sun, 3 Nov 2024 13:58:39 +0530 Subject: [PATCH] Docs updated. --- README.md | 368 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 219 insertions(+), 149 deletions(-) diff --git a/README.md b/README.md index b96b0ea..520c91d 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,27 @@ 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. +## Why I made this package? -**🚀 LIVE DEMO OF EXAMPLE PROJECT:** https://shubham-gupta-16.github.io/view_model_x/ +In Flutter, there are several state management options, each with its own advantages and +limitations: + +- **GetX:** GetX provides a powerful, unified approach with `GetXController`, enabling multiple + observables in a single file, which can simplify state management. However, GetX introduces its + own patterns for navigation, dependencies, and more, which can deviate from standard Flutter + conventions, leading to an ecosystem that doesn’t fully align with Flutter's core principles. + +- **Bloc/Provider (ChangeNotifier):** These are popular solutions that align well with Flutter’s + design philosophy. However, they often require creating multiple files to manage different states, + which can lead to boilerplate and reduced cohesiveness when managing related states. +Inspired by the **Android ViewModel**, I created a package that allows for multiple `StateFlows` and +`SharedFlows` within a single `ViewModel`. This design combines the simplicity and cohesion of +GetX’s GetXController with the modularity and adherence to Flutter’s patterns similar to `Provider` +and `Bloc`. It allows for organized state management while following Flutter's native way of +handling widgets and reactivity. + +**🚀 LIVE DEMO OF EXAMPLE PROJECT:** https://shubham-gupta-16.github.io/view_model_x/ ## Features @@ -19,16 +35,16 @@ An Android similar state management package which helps to implement MVVM patter - Easy to implement MVVM pattern 💪 ## Package Components + - StateFlow (equivalent to LiveData) ⛵ - SharedFlow 🌊 -- ViewModel (to separate the view logic from UI like Cubit) +- ViewModel (to separate the view logic from UI like Cubit/GetXController) - ViewModelProvider - StateFlowBuilder - StateFlowConsumer - StateFlowListener - SharedFlowListener - MultiFlowListener -- ChangeNotifierProvider - MultiProvider ## Usage @@ -86,11 +102,13 @@ class CounterPage extends StatelessWidget { body: Center( // implement StateFlowBuilder to rebuild Text on StateFlow value changed/updated child: StateFlowBuilder( - // pass your StateFlow - stateFlow: context.vm().counterStateFlow, - builder: (context, value) { - return Text("$value", style: const TextStyle(fontSize: 30)); - }, + // pass your StateFlow + stateFlow: context + .vm() + .counterStateFlow, + builder: (context, value) { + return Text("$value", style: const TextStyle(fontSize: 30)); + }, ), ), floatingActionButton: FloatingActionButton( @@ -108,7 +126,9 @@ class CounterPage extends StatelessWidget { ## Package Components ### 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. + +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 @@ -127,12 +147,14 @@ class CustomViewModel extends ViewModel { ``` ### PostFrameCallback with ViewModel -Using `PostFrameCallback` with `ViewModel` helps to get `onPostFrameCallback` event inside `ViewModel` easily. + +Using `PostFrameCallback` with `ViewModel` helps to get `onPostFrameCallback` event +inside `ViewModel` easily. ```dart class CustomViewModel extends ViewModel with PostFrameCallback { //... - + @override void onPostFrameCallback(Duration timestamp) { // do stuffs here @@ -141,13 +163,23 @@ class CustomViewModel extends ViewModel with PostFrameCallback { ``` ### StateFlow + It stores value and notify listeners whenever it changes. It can change/update the value. ```dart + final myStateFlow = StateFlow(1, notifyOnSameValue: true); ``` -Here, notifyOnSameValue is optional. If `notifyOnSameValue` is set to false, whenever you call `stateFlow.value = newValue` +OR + +```dart + +final myStateFlow = 1.stf(); +``` + +Here, notifyOnSameValue is optional. If `notifyOnSameValue` is set to false, whenever you +call `stateFlow.value = newValue` where newValue is same as current value, it will not notify listeners. by default it is set to true. **To change the value** @@ -157,24 +189,40 @@ myStateFlow.value = 5; // listeners were automatically notified ``` **To update the value** +For something like list or map, you may have to update the existing object instead on resigning a +value. ```dart -listStateFlow.update((value) { - value.add(obj); +listStateFlow.update +( +(value) { +value.add(obj); }); // listeners were automatically notified ``` ### SharedFlow + It is used to send data to the listeners. It can emit the value. ```dart + final mySharedFlow = SharedFlow(); ``` +OR + +```dart + +final mySharedFlow = stf(); +``` + **To emit the value** ```dart -myStateFlow.emit("Hello from ViewModel!"); // listeners were automatically notified +myStateFlow.emit +("Hello from ViewModel! +" +); // listeners were automatically notified ``` ## Integrate ViewModel Into Flutter Widget @@ -185,61 +233,79 @@ myStateFlow.emit("Hello from ViewModel!"); // listeners were automatically notif This requires `create` which accepts custom `ViewModel` and `child` Widget. ```dart -ViewModelProvider( - create: (context) => counterViewModel, // provide your custom viewModel - child: ChildWidget(), +ViewModelProvider +( +create: (context) => counterViewModel, // provide your custom viewModel +child: ChildWidget( +) +, ); ``` ### Get ViewModel instance inside Widget Tree ```dart -ViewModelProvider.of(context) +ViewModelProvider.of +( +context +) ``` + OR + ```dart -context.vm() +context.vm +() ``` -## Builder, Listener, and Consumer Flutter Widgets +## Builder, Listener, and Consumer Flutter Widgets ### StateFlowBuilder `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. +This requires `stateFlow` to listen on and `builder` to which rebuilds when the `stateFlow`'s value +changed/updated. ```dart -StateFlowBuilder( - stateFlow: context.vm().myStateFlow, // pass StateFlow - builder: (context, value) { - return ChildWidget(value); // rebuild the widget with updated/changed value. - }, +StateFlowBuilder +( +stateFlow: context.vm().myStateFlow, // pass StateFlow +builder: (context, value) { +return ChildWidget(value); // rebuild the widget with updated/changed value. +}, ) ``` +### Binding with context `stateFlowObject.bind(BuildContext)` + +We can use `bind` with any `StateFlow` which observe the value change and update the ui. +This is similar to Provider's `context.watch()`. -### Experimental `StateFlow.watch(BuildContext)` -We can use `watch` with any `StateFlow` which observe the value change and update the ui. -This is similar to Provider's `context.watch()`. ```dart -final value = context.vm().myStateFlow.watch(context); + +final value = context + .vm() + .myStateFlow + .bind(context); ``` ### StateFlowConsumer `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. +Whenever `stateFlow`'s value changed/updated, `builder` will rebuild the widgets inside of it +and `listener` will called. ```dart -StateFlowConsumer( - stateFlow: ViewModelProvider.of(context).myStateFlow, // pass SharedFlow - listener: (context, value) { - // do stuff here based on value - }, - builder: (context, value) { - return ChildWidget(value); // rebuild the widget with updated/changed value. - }, +StateFlowConsumer +( +stateFlow: ViewModelProvider.of(context).myStateFlow, // pass SharedFlow +listener: (context, value) { +// do stuff here based on value +}, +builder: (context, value) { +return ChildWidget(value); // rebuild the widget with updated/changed value. +}, ) ``` @@ -250,16 +316,21 @@ This requires `stateFlow`, `listener` and `child`. Whenever `stateFlow`'s value changed/updated, `listener` will called. ```dart -StateFlowListener( - stateFlow: ViewModelProvider.of(context).myStateFlow, // pass StateFlow - listener: (context, value) { - // do stuff here based on value - }, - child: ChildWidget(), +StateFlowListener +( +stateFlow: ViewModelProvider.of(context).myStateFlow, // pass StateFlow +listener: (context, value) { +// do stuff here based on value +}, +child +: +ChildWidget +( +) +, ) ``` - ### SharedFlowListener `SharedFlowListener` is used to catch the emitted value from `sharedFlow`. @@ -267,135 +338,134 @@ 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(), +SharedFlowListener +( +sharedFlow: ViewModelProvider.of(context).mySharedFlow, // pass SharedFlow +listener: (context, value) { +// do stuff here based on value +}, +child +: +ChildWidget +( +) +, ) ``` - ### MultiFlowListener -`MultiFlowListener` is a Flutter widget that merges multiple `SharedFlowListener` and `StateFlowListener` widgets into one. +`MultiFlowListener` is a Flutter widget that merges multiple `SharedFlowListener` +and `StateFlowListener` widgets into one. `MultiFlowListener` improves the readability and eliminates the need to nest multiple listeners. By using `MultiFlowListener` we can go from: ```dart -SharedFlowListener( - sharedFlow: context.vm().mySharedFlow, - listener: (context, value) { - // do stuff here based on value - }, - child: StateFlowListener( - stateFlow: context.vm().myStateFlow, - listener: (context, value) { - // do stuff here based on value - }, - child: SharedFlowListener( - sharedFlow: context.vm().anySharedFlow, - listener: (context, value) { - // do stuff here based on value - }, - child: ChildA(), - ) - ) +SharedFlowListener +( +sharedFlow: context.vm().mySharedFlow, +listener: (context, value) { +// do stuff here based on value +}, +child: StateFlowListener( +stateFlow: context.vm().myStateFlow, +listener: (context, value) { +// do stuff here based on value +}, +child: SharedFlowListener( +sharedFlow: context.vm().anySharedFlow, +listener: (context, value) { +// do stuff here based on value +}, +child: ChildA(), +) ) -``` -to -```dart -MultiFlowListener( - providers: [ - SharedFlowListener( - sharedFlow: context.vm().mySharedFlow, - listener: (context, value) { - // do stuff here based on value - }, - ), - StateFlowListener( - stateFlow: context.vm().myStateFlow, - listener: (context, value) { - // do stuff here based on value - }, - ), - SharedFlowListener( - sharedFlow: context.vm().anySharedFlow, - listener: (context, value) { - // do stuff here based on value - }, - ), - ], - child: ChildA(), ) ``` -## Extra Provider -This package also includes some of Provider components with some modification. These are: - -### ChangeNotifierProvider -This will allows to wrap a widget around ChangeNotifier. +to ```dart -ChangeNotifierProvider( - create: (context) => CustomChangeNotifier(), - child: WidgetA(), +MultiFlowListener +( +providers: [ +SharedFlowListener( +sharedFlow: context.vm().mySharedFlow, +listener: (context, value) { +// do stuff here based on value +}, +), +StateFlowListener( +stateFlow: context.vm().myStateFlow, +listener: (context, value) { +// do stuff here based on value +}, +), +SharedFlowListener( +sharedFlow: context.vm().anySharedFlow, +listener: (context, value) { +// do stuff here based on value +}, +), +], +child: ChildA(), ) ``` -To get the instance of `ChangeNotifier` or listen for `notifyListeners()`: -```dart -ChangeNotifierProvider.of(context, listen: true) -``` -If `listen` is `true`, the Widget will rebuild on `notifyListeners()`. This can also be written in simplified way. -If want to listen for `notifyListeners()`, use: -```dart -context.watch() -``` -Or if want only instance, use: -```dart -context.read() -``` - -> **Note:** Here `context.watch` and `context.read` is modified from provider library. Here, type is restricted to ChangeNotifier. - ### MultiProvider -`MultiProvider` is a Widget that merges multiple `ViewModelProvider` and `ChangeNotifierProvider` widgets into one. + +`MutliProvider` is `Provider` package's widget. +It merges multiple `ViewModelProvider`, `ChangeNotifierProvider`, and/or `Provider` widgets into +one. `MultiProvider` improves the readability and eliminates the need to nest multiple widgets. By using `MultiProvider` we can go from: ```dart -ViewModelProvider( - create: (context) => ViewModelA(), - child: ViewModelProvider( - create: (context) => ViewModelB(), - child: ChangeNotifierProvider( - create: (context) => ChageNotifierA(), - child: ChildA(), - ) - ) +ViewModelProvider +( +create: (context) => ViewModelA(), +child: ViewModelProvider( +create: (context) => ViewModelB(), +child: ChangeNotifierProvider( +create: (context) => ChageNotifierA(), +child: ChildA( +) +, +) +) ) ``` + to + ```dart -MultiProvider( - providers: [ - ViewModelProvider( - create: (context) => ViewModelA(), - ), - ViewModelProvider( - create: (context) => ViewModelB(), - ), - ChageNotifierProvider( - create: (context) => ChageNotifierA(), - ), - ], - child: ChildA(), +MultiProvider +( +providers: [ +ViewModelProvider( +create: (context) => ViewModelA(), +), +ViewModelProvider( +create: (context) => ViewModelB(), +), +ChageNotifierProvider( +create: (context) => ChageNotifierA(), +), +], +child: ChildA() +, ) ``` -> **Note:** This MultiProvider is different from one in Provider package. This will only accepts `ViewModelProvider` and `ChangeNotifierProvider`. + +> **Note:** This MultiProvider is different from one in Provider package. This will only +> accepts `ViewModelProvider` and `ChangeNotifierProvider`. + ## Contributing -Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. +Pull requests are welcome. For major changes, please open an issue first to discuss what you would +like to change. +> **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.