All Projects → NourEldinShobier → pmvvm

NourEldinShobier / pmvvm

Licence: MIT License
A clean & simple MVVM solution for state management using Provider package.

Programming Languages

dart
5743 projects
C++
36643 projects - #6 most used programming language
CMake
9771 projects
HTML
75241 projects
c
50402 projects - #5 most used programming language
swift
15916 projects

Projects that are alternatives of or similar to pmvvm

reactive state
An easy to understand reactive state management solution for Flutter.
Stars: ✭ 19 (-42.42%)
Mutual labels:  flutter-package
glider
Server-side rendering for mobile apps, powered by Flutter
Stars: ✭ 20 (-39.39%)
Mutual labels:  flutter-package
flutter-bloc-patterns
A set of most common BLoC use cases built on top of flutter_bloc library
Stars: ✭ 58 (+75.76%)
Mutual labels:  flutter-package
lang table
lang_table is a dart plugin to generate string files from a source. Use a table to manage all multi-language resources. Inspired by fetch-mobile-localization-from-airtable
Stars: ✭ 17 (-48.48%)
Mutual labels:  flutter-package
time chart
A scrollable time chart in Flutter.
Stars: ✭ 21 (-36.36%)
Mutual labels:  flutter-package
injector io
InjectorIO - Dependency Injection for Flutter
Stars: ✭ 73 (+121.21%)
Mutual labels:  flutter-package
anim search bar
A flutter package that has an animated search bar with loads of customization
Stars: ✭ 28 (-15.15%)
Mutual labels:  flutter-package
credit card validator
A Dart package that validates credit card numbers, expiration dates, and security codes (CVV/CVC) based on the type of credit card
Stars: ✭ 19 (-42.42%)
Mutual labels:  flutter-package
Flutter-firestore-auth
Flutter mobile app with firestore authentication including Email and Social auth.
Stars: ✭ 95 (+187.88%)
Mutual labels:  flutter-package
flutter country picker
A flutter package to select a country from a list of countries.
Stars: ✭ 43 (+30.3%)
Mutual labels:  flutter-package
water drop nav bar
flutter navigation bar with water drop effect.
Stars: ✭ 29 (-12.12%)
Mutual labels:  flutter-package
eva icons flutter
Flutter package for Eva Icons. Eva Icons is a pack of more than 480 beautifully crafted Open Source icons for common actions and items. https://pub.dartlang.org/packages/eva_icons_flutter
Stars: ✭ 80 (+142.42%)
Mutual labels:  flutter-package
emoji picker
A Flutter package that provides an Emoji Keyboard widget
Stars: ✭ 82 (+148.48%)
Mutual labels:  flutter-package
flutter feather icons
Flutter package for Feather Icons
Stars: ✭ 33 (+0%)
Mutual labels:  flutter-package
custom timer
A Flutter package to create a customizable timer.
Stars: ✭ 25 (-24.24%)
Mutual labels:  flutter-package
nepali date picker
Material Style Date Picker with Bikram Sambat(Nepali) Calendar Support. Supports both Android and ios.
Stars: ✭ 30 (-9.09%)
Mutual labels:  flutter-package
sliding clipped nav bar
Bottom navigation bar with sliding clip effect.
Stars: ✭ 46 (+39.39%)
Mutual labels:  flutter-package
dart-package-analyzer
GitHub Action that uses the Dart Package Analyzer to compute the Pub score of Dart/Flutter packages
Stars: ✭ 44 (+33.33%)
Mutual labels:  flutter-package
nativeweb
Build your Own Plugin using (PlatformViews) Demo for Flutter Live 2018 Extended Event - Hyderabad
Stars: ✭ 26 (-21.21%)
Mutual labels:  flutter-package
getwidget-web-kit
Get Widget Web app Demo
Stars: ✭ 40 (+21.21%)
Mutual labels:  flutter-package

PMVVM is a Flutter package for simple and scalable state management based on the MVVM pattern, it uses Provider & Hooks under the hood. PMVVM serves the same purpose BloC, but unlike BloC it doesn’t require too much boilerplate.

It's worth mentioning that the package adopts some concepts from the Stacked package, but with a much simpler and cleaner approach.

How does it work ⚙️

Three major pieces are needed, everything else is up to you. These pieces are:

View

It represents the UI of the application devoid of any application logic. The view model sends notifications to the view to update the UI whenever the state changes.

ViewModel

Which holds the state and the events of the view. Additionally, It acts as a bridge between the model and the view.

The view model is platform-independent and doesn't know its view. Therefore, it can be easily bound to a web, mobile, or desktop view.

Model

Holds app data and the business logic. It consists of the business logic (e.g. local and remote data sources, model classes, and repositories). They’re usually simple classes.

When should you use PMVVM? 👌

To keep it simple, use it whenever your widget has its own events that can mutate the state directly e.g: pages, posts, ...etc.

Usage 👨‍💻

The best way to get to know PMVVM is to get your hands dirty and try it out. Let's look at the code:

  • Build your ViewModel.
class MyViewModel extends ViewModel {
  int counter = 0;

  // Optional
  @override
  void init() {
    // It's called after the ViewModel is constructed
  }

  // Optional
  @override
  void onBuild() {
    // It's called everytime the view is rebuilt
  }

  void increase() {
    counter++;
    notifyListeners();
  }
}
  • You can also access the context inside the ViewModel directly.
class MyViewModel extends ViewModel {
  @override
  void init() {
    var height = MediaQuery.of(context).size.height;
  }
}
  • Declare MVVM inside your builder.
class MyWidget extends StatelessWidget {
  const MyWidget({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MVVM<MyViewModel>(
      view: () => _MyView(),
      viewModel: MyViewModel(),
    );
  }
}
  • Build your View.
// StatelessView

class _MyView extends StatelessView<MyViewModel> {
  /// Set [reactive] to [false] if you don't want the view to listen to the ViewModel.
  /// It's [true] by default.
  const _MyView({Key key}) : super(key: key, reactive: true);

  @override
  Widget render(context, vmodel) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      crossAxisAlignment: CrossAxisAlignment.center,
      children: <Widget>[
        Text(vmodel.counter.toString()),
        SizedBox(height: 24),
        RaisedButton(onPressed: vmodel.increase, child: Text('Increase')),
      ],
  );
}

// HookView

class _MyView extends HookView<MyViewModel> {
  /// Set [reactive] to [false] if you don't want the view to listen to the ViewModel.
  /// It's [true] by default.
  const _MyView({Key key}) : super(key: key, reactive: true);

  @override
  Widget render(context, vmodel) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      crossAxisAlignment: CrossAxisAlignment.center,
      children: <Widget>[
        Text(vmodel.counter.toString()),
        SizedBox(height: 24),
        RaisedButton(onPressed: vmodel.increase, child: Text('Increase')),
      ],
    );
  }
}

If your view is simple, you can use the MVVVM.builder instead:

class MyWidget extends StatelessWidget {
  const MyWidget({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MVVM<MyViewModel>.builder(
      viewModel: viewModel,
      viewBuilder: (_, vm) {
        return Text(
          vm.counter.toString(),
          textDirection: TextDirection.ltr,
        );
      },
    );
  }
}

Advanced 🚀

Immutability & Observability 🧐

PMVVM supports immutability as well, it also allows you to observe the changes/actions applied to the variables through the Observable class.

  • First wrap your variable with the Observable:
/// 'MyCounter' is an alias for the variable, this comes handy in logging
final counter = Observable<int>('MyCounter');

// Or

final counter = Observable.initialized(0, 'MyCounter');
final counter = 0.observable('MyCounter');
  • Then apply actions to it:
counter.setValue(counter.value + 1, action: 'INCREASE');
  • Lastly, start consuming this observable:
/// NOTE: make sure that it's initialized before using the [value] getter
/// by checking [counter.hasValue] or just use [counter.valueOrNull] or [counter.valueOrDefault]

counter.value;

counter.stream.listen((value) { });   

You can also make the view model observe, unobserve to any list of observables, or clear clearAllObservers

class CounterPageVM extends ViewModel {
  final counter = 0.reactive('MyCounter');

  @override
  void init() {
    observe([counter]);
  }

  void increase() {
    counter.setValue(counter.value + 1, action: 'INCREASE');
  }
}

Note 📌

The observe method has an optional boolean parameter reset that clears all the previous observers before listening to the new observables.

Observable API

Getter / Method Description
value Returns the current value of the observable
valueOrNull Returns the current value or null if it's not initialized
prevValue Returns the previous value from history if it exists
hasValue Checks whether the observable is initialized or not
history Returns a history of all actions applied to the observable
stream Returns a stream for the observable values
valueOrDefault A method that returns the current value or default if it's not initialized
setValue A method to change the current value of the observable
log A method that allows changing the shape of the logs
clearHistory Remove all stored actions
resetLogCallback To clear the custom log functions.

Patterns 🧩

In this section Let's discuss some patterns that can help your view model to access your widget properties:

  • Naive approach: Using cascade notation to initialize your view model with the widget properties. The problem with this approach is that your view model becomes non-reactive when the widget's dependencies (properties) are updated.
class MyWidget extends StatelessWidget {
  const MyWidget({Key key, this.varName}) : super(key: key);

  final String varName;

  @override
  Widget build(BuildContext context) {
    return MVVM<MyViewModel>(
      view: () => _MyView(),
      viewModel: MyViewModel()..varName = varName,
    );
  }
}
  • Clean approach: similar to ReactJS, you should create a properties class, in which all your widget properties are kept.

my_widget.props.dart

class MyWidgetProps {
  MyWidgetProps({required this.name});

  final String name;
}

my_widget.vm.dart

class MyWidgetVM extends ViewModel {
  late MyWidgetProps props;

  @override
  void init() {
    props = context.fetch<MyWidgetProps>();
  }
}

my_widget.view.dart

class MyWidget extends StatelessWidget {
  MyWidget({
    Key? key,
    required String name,
  })  : props = MyWidgetProps(name: name),
        super(key: key);

  final MyWidgetProps props;

  Widget build(context) {
    return Provider.value(
      value: props,
      child: MVVM<MyWidgetVM>(
        view: () => _MyWidgetView(),
        viewModel: MyWidgetVM(),
      ),
    );
  }
}

More about PMVVM🎯

  • The init lifecycle method is called by default every time the view model dependencies are updated. To init the ViewModel only once and ignore dependencies updates, set initOnce of the MVVM widget to true.
  • You can use context.fetch<T>(listen: true/false) which is equivalent to Provider.of<T>(context)
  • You can use the PMVVMConfig to control enableLogging and trackObservablesHistory. If trackObservablesHistory is false, the observables won't store the history of the actions applied to them.
  • To make the view ignore the state notifications from the ViewModel , set reactive to false when you are constructing the StatelessView or HookView :
class _MyView extends StatelessView<MyViewModel> {
  const _MyView({Key key}) : super(key: key, reactive: false);
  ....
}
  • ViewModel Lifecycle methods (All of them are optional)
  /// - Event callback after [ViewModel] is constructed.
  /// - The event is called by default every time the [ViewModel] view dependencies are updated.
  /// - Set [initOnce] of the [MVVM] as [true] to ignore dependencies updates.
  void init() {}

  /// Event callback when the [build] method is called.
  void onBuild() {}

  /// Event callback when the view disposed.
  void onDispose() {}

  /// Event callback when the application is visible and responding to user input.
  void onResume() {}

  /// Event callback when the application is not currently visible to the user, not responding to
  /// user input, and running in the background.
  void onPause() {}

  /// - Event callback when the application is in an inactive state and is not receiving user input.
  /// - For [IOS] only.
  void onInactive() {}

  /// - Event callback when the application is still hosted on a flutter engine but
  ///   is detached from any host views.
  /// - For [Android] only.
  void onDetach() {}

FAQ 🤔

  • Can I use it in production?
    • Yep! It's stable and ready to rock
  • What is the difference between Stacked & PMVVM since both adopt the same principles?
Stacked PMVVM
You can't access the BuildContext from the ViewModel. BuildContext can be accessed inside the ViewModel using:
- Provider.of<T>(context)
- context.watch<T>()
- context.read<T>()
- context.select<T, R>(R cb(T value))
You should implement the Initialisable interface to call initialise. init event is called by default, all you need to do is to override it (optional).
There is no build method in the ViewModel. onBuild method is called by default every time the View is rebuilt, and you can override it to implement yours (optional).
It over-wraps provider with many ViewModels like FutureViewModelStreamViewModel, …etc. Which provider & flutter_hooks are built to do without any wrapping. It doesn’t over-wrap provider package with such classes. Instead, you can use StreamProvider/FutureProvider or Hooks which gives you the flexibility to make the most out of provider & flutter_hooks.

In summary, PMVVM is simpler & cleaner, there is no over-wrapping, and idioms are more clear.

Dependencies 📦

  • provider
  • flutter_hooks
  • tint

Made with ❤️

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].