{"id":21748384,"url":"https://github.com/surfstudio/flutter-mwwm","last_synced_at":"2025-07-19T00:32:01.196Z","repository":{"id":46652659,"uuid":"384333128","full_name":"surfstudio/flutter-mwwm","owner":"surfstudio","description":null,"archived":false,"fork":false,"pushed_at":"2023-04-24T11:20:17.000Z","size":2776,"stargazers_count":8,"open_issues_count":8,"forks_count":1,"subscribers_count":6,"default_branch":"main","last_synced_at":"2023-08-01T12:24:27.806Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Dart","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/surfstudio.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-07-09T05:39:43.000Z","updated_at":"2023-08-01T12:24:27.807Z","dependencies_parsed_at":"2022-09-02T01:20:55.863Z","dependency_job_id":null,"html_url":"https://github.com/surfstudio/flutter-mwwm","commit_stats":null,"previous_names":[],"tags_count":0,"template":null,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/surfstudio%2Fflutter-mwwm","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/surfstudio%2Fflutter-mwwm/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/surfstudio%2Fflutter-mwwm/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/surfstudio%2Fflutter-mwwm/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/surfstudio","download_url":"https://codeload.github.com/surfstudio/flutter-mwwm/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":226462286,"owners_count":17629049,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-26T08:13:19.268Z","updated_at":"2024-11-26T08:13:20.091Z","avatar_url":"https://github.com/surfstudio.png","language":"Dart","funding_links":[],"categories":[],"sub_categories":[],"readme":"# MWWM\n\n[![Build Status](https://shields.io/github/workflow/status/surfstudio/SurfGear/build?logo=github\u0026logoColor=white)](https://github.com/surfstudio/SurfGear/tree/main/packages/mwwm)\n[![Coverage Status](https://img.shields.io/codecov/c/github/surfstudio/SurfGear?flag=mwwm\u0026logo=codecov\u0026logoColor=white)](https://codecov.io/gh/surfstudio/SurfGear)\n[![Pub Version](https://img.shields.io/pub/v/mwwm?logo=dart\u0026logoColor=white)](https://pub.dev/packages/mwwm)\n[![Pub Likes](https://badgen.net/pub/likes/mwwm)](https://pub.dev/packages/mwwm)\n[![Pub popularity](https://badgen.net/pub/popularity/mwwm)](https://pub.dev/packages/mwwm/score)\n![Flutter Platform](https://badgen.net/pub/flutter-platform/mwwm)\n\nThis package is part of the [SurfGear](https://github.com/surfstudio/SurfGear) toolkit made by [Surf](https://surf.ru/).\n\n![MWWM Cover](https://i.ibb.co/9qBTf3S/Bunner-LOGO.png)\n\n## Description\n\nMVVM-inspired lightweight architectural framework for Flutter apps made with respect to Clean Architecture.\n\n## Currently supported features\n\n- Complete separation of the application's codebase into independent layers: *UI*, *presentation* and *business logic*;\n- Keeps widget tree clear: the main building block is just an extended version of StatefulWidget;\n- Built-in mechanisms for handling asynchronous operations;\n- The ability to easily implement the default error handling strategy;\n- An event-like mechanism that helps keep the business logic well structured and testable.\n\n## Overview\n\nMWWM is the perfect mix of the [Flutter framework architectural concept](https://flutter.dev/docs/resources/architectural-overview), the simple [MVVM](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel) pattern, and the [Clean Architecture principles](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html).\n\n### MWWM's superpowers\n\n- **Simple** - no crazy complex abstractions that change the structure of your project beyond recognition;\n\n- **Flexible** - you can apply MWWM components to a certain project area (screen or widget). You still can use `StatefulWidget` and `StatelessWidget` wherever you would like to keep things simple;\n\n- **Scalable** - the scale of your project is not a problem. You can use additional MWWM features as your project grows more complex (such as `Model`);\n\n- **No limitations** - this architecture package doesn't dictate what DI, navigation, or any other approaches you should use in your project. You can even implement communication between layers the way you want. Or take our advice and do so with the [Relation package](https://pub.dev/packages/relation). Everything is up to you!\n\n### Key components\n\n![MWWM Architecture Scheme](https://i.ibb.co/Y778rtM/Key-components.png)\n\n#### Widget\n\nThis is where your declarative layouts live. Components of this layer contain UI-related code only and act like typical `StatefulWidget`. The Widget contains the link to its widget model.\n\nPresented by `CoreMwwmWidget` class.\n\n#### WidgetModel\n\n`WidgetModel` is like the mechanism behind your widget. Its concept is very similar to `State` but there is one big difference. `State` knows nothing about the business logic of your app and can't refer to any business logic components, while `WidgetModel` can.\n\n- It tells the current state of the widget, which can be meaningful for business logic scenarios;\n- It knows which user input (or any other UI event) triggers a certain business logic scenario and acts as a dispatcher between them.\n\nPresented by the `WidgetModel` class.\n\n#### Model\n\nUnlike other components, the `Model` is optional.\n\n`Model` is a great way to simplify the business logic layer of your app by breaking it into two sets of abstractions:\n\n- `Change` is an intent to do something without any concrete implementation details, only input parameters;\n- `Performer` is the reaction to the `Change` that is associated with it. The Performer contains the implementation of a certain operation that is triggered by the Change.\n\nPresented by the `Model`, `Change`, and `Performer` classes.\n\n## Example\n\n### Basic use case (without Model)\n\n![MWWM Architecture Mini Scheme](https://i.ibb.co/qN4z81w/arch-scheme-min.png)\n\n#### Create WidgetModel\n\nCreate a WidgetModel for the widget by extending the `WidgetModel` class.\n\nIf you want certain part of your code to run as soon as the widget is initialized, you can override the `onLoad()` function and place this code right after the super-function invocation.\n\n```dart\nclass LoginWm extends WidgetModel {\n\n  LoginWm(\n    WidgetModelDependencies baseDependencies,\n  ) : super(baseDependencies);\n\n  @override\n  void onLoad() {\n    super.onLoad();\n    ...\n  }\n}\n```\n\n#### Implement WidgetModel builder\n\nYou need to create an instance of the `WidgetModel`. We recommend using top-level functions for this.\n\nDon't forget to pass `WidgetModelDependencies` as the first argument. Use it as a bundle that contains a set of dependencies required for all WidgetModels in your app (e.g. error handlers, loggers).\n\n```dart\nWidgetModel buildLoginWM(BuildContext context) =\u003e \n  LoginWm(\n    WidgetModelDependencies(),\n  );\n}\n```\n\n#### Create Widget\n\nCreate a Widget by extending the `CoreMwwmWidget` class.\n\n```dart\nclass LoginScreen extends CoreMwwmWidget {\n\n  LoginScreen({\n    WidgetModelBuilder widgetModelBuilder,\n  })  : assert(widgetModelBuilder != null),\n        super(widgetModelBuilder: widgetModelBuilder);\n\n  @override\n  State\u003cStatefulWidget\u003e createState() =\u003e _LoginScreenState();\n}\n```\n\n#### Create WidgetState\n\n`CoreMwwmWidget` is an extended version of `StatefulWidget`. We need to attach a State for it by extending `WidgetState` class.\n\nCollect the widget tree and return it from a `build()` function the way you usually do it with regular *StatefulWidget*.\n\nEvery `WidgetState` refers to its `WidgetModel`. That's why you need to specify the concrete `WidgetModel` type as a generic type of the `WidgetState`.\n\n```dart\nclass _LoginScreenState extends WidgetState\u003cLoginWm\u003e {\n\n  @override\n  Widget build(BuildContext context) =\u003e \n    Scaffold(\n      body: Container(), \n    );\n}\n```\n\n#### Create entry point\n\nYou will most likely start your screen with a *Route*. In this case, the *Route* becomes the place where you put everything together.\n\n```dart\n  class LoginScreenRoute extends MaterialPageRoute {\n    LoginScreenRoute()\n        : super(\n            builder: (context) =\u003e LoginScreen(\n              widgetModelBuilder: buildLoginWM(),\n            ),\n          );\n  }\n```\n\nThat's it! Now you can implement your first MWWM-powered screen.\n\n### Advanced use case (with Model)\n\nIn case your app is too complicated, you can simplify it by breaking down the business logic layer into a set of smaller components (*Performers*), each responsible for performing a single business logic operation.\n\n#### Create Change\n\n**Change** is the intention to perform an action. Each operation is associated with a *Change*.\n\nCreate a *Change* by extending `FutureChange` or `StreamChange` classes.\n\nThe difference between them is the type of the returned operation result. `FutureChange` will wrap the result into a `Future`, and `StreamChange` into a `Stream`.\n\nA *Change* can also serve to contain passing input parameters if they are needed to perform an operation. If not, leave the class body empty.\n\n```dart\nclass LoginUser extends FutureChange\u003cUserProfile\u003e {\n  final String login;\n  final String password;\n}\n```\n\n#### Create Performer\n\n**Performer** - is a functional part of the contract. *Performer* should be responsible for no more than one operation. Each *Performer* is associated with *Change* one-to-one.\n\nWhile declaring *Performer* you should specify two types of parameters: the first is the operation result type (the same as you already set up in *Change*), the second is the associated *Change* type.\n\nYou can insert any object into the *Performer* to launch any necessary operations.\n\nCreate a Performer by extending the `FuturePerformer` or `StreamPerformer` classes.\n\n```dart\nclass LoginUserPerformer extends FuturePerformer\u003cUserProfile, LoginUser\u003e {\n\n  final AuthService authService;\n\n  @override\n  Future\u003cUserProfile\u003e perform(LoginUser change) {\n    // api call retrieving user profile instance\n    return userProfile;\n  }\n}\n```\n\n#### Pass configured Model to WidgetModel\n\nYou probably already know the basic MWWM use case. If not, see [this section](#basic-use-case-without-model).\n\nDeclare `Model` instance as an argument for your *WidgetModel* constructor and pass it to its super-constructor.\n\n```dart\nclass LoginWm extends WidgetModel {\n\n  LoginWm(\n    WidgetModelDependencies baseDependencies,\n    Model model,\n    ) : super(baseDependencies, model: model);\n}\n```\n\nGo back to the top-level function that creates *WidgetModel*. Pass the newly created *Model* instance to the *WidgetModel's* constructor.\n\nRemember to pass an array containing all the *Performers* that can be accessed through this *WidgetModel* to the *Model's* constructor.\n\n```dart\nWidgetModel buildLoginWM(BuildContext context) =\u003e \n  LoginWm(\n    WidgetModelDependencies(),\n    Model([\n      LoginUserPerformer(),\n    ]),\n  );\n}\n```\n\n#### Perform the operation\n\nFinally, you can request your *Model* to perform any operation by passing the right *Change* through the `Model.perform()` function. You just have to correctly process the result of the operation.\n\n```dart\nclass LoginWm extends WidgetModel {\n\n  void performUserLogin() async {\n    final userProfile = await model.perform(LoginUser(login, password);,\n    ...\n  }\n}\n```\n\nThat's all folks! You are now familiar with the advanced technique of using MWWM-architecture.\n\n### Asynchronous operations handling\n\nMWWM package provides built-in capabilities for asynchronous operations. In addition, you can take advantage of the centralized error handling function.\n\n| Function name        | Supported data type | Is default error handling mechanism enabled |\n|----------------------|---------------------|---------------------------------------------|\n| subscribe()            | Stream              | -                                           |\n| subscribeHandleError() | Stream              | +                                           |\n| doFuture()             | Future              | -                                           |\n| doFutureHandleError()  | Future              | +                                           |\n\nWhen using `doFuture()` and `subscribe()`, select `Future` or `Stream` as the first parameter. The `onValue` function handles the result of the asynchronous operation as the second.\n\nThe `onError` parameter is optional and can be used to handle errors manually.\n\n```dart\ndoFuture\u003cbool\u003e(\n  isAuthenticated(),\n  (isAuth) {\n    if (isAuth)\n      navigator.push(MainScreenRoute());\n  },\n  onError: (error) {\n    print(error);\n  },\n);\n```\n\nWhat about `doFutureHandleError()` and `subscribeHandleError()`? You can use them the same exact way as their \"no handle error\" companions, with all the benefits of the default error handling mechanism.\n\nYou can still use the `onError` function to pass but it will now act as an addition to default error handling.\n\n```dart\ndoFutureHandleError\u003cbool\u003e(\n  login(),\n  (isLogin) {\n    if (isLogin)\n      navigator.push(MainScreenRoute());\n  },\n  onError: (error) {\n    print(error);\n  },\n);\n```\n\n#### Default error handling customization\n\nError handling is easily customizable.\n\nFirst, extend the `ErrorHandler` class and override the `handleError()` function. This function will be used whenever an error occurs. You can implement your error handling right there.\n\n```dart\nclass CustomErrorHandler extends ErrorHandler {\n  @override\n  void handleError(Object e) {\n    debugPrint('Custom error handler regretfully informs that $e has occurred.');\n  }\n}\n```\n\nFollow the top-level function that creates WidgetModel. Pass your custom error handler instance as errorHandler argument value. That's it.\n\n```dart\nWidgetModel buildLoginWM(BuildContext context) =\u003e \n  LoginWm(\n    WidgetModelDependencies(\n      errorHandler: LoginScreenErrorHandler(),\n    ),    \n  );\n}\n```\n\nNow all errors that occur during asynchronous operations launched by the `doFutureHandleError()` and `subscribeHandleError()` functions will fall into a custom handler.\n\n## FAQ\n\n### Where should my UI layout be placed?\n\nDirectly in the `WidgetState.build()` function. For widgets that are simple use `StatelessWidget` or `StatefulWidget` from SDK.\n\n### How can I access the widget model?\n\nEvery `WidgetState` reference its `WidgetModel` through `wm` getter. You can access it immediately after running `initState()`.\n\n### Where should I place the navigation?\n\nWe don't limit you in the choice of navigation approaches, but we strongly recommend implementing navigation in the **WidgetModel**.\n\n## Recommended project structure\n\nYou decide how your project will be arranged. However, we recommend you organize your code this way:\n\n- /lib\n  - model/\n    - feature1/\n      - services\n      - changes.dart\n      - performer.dart\n    - feature2/\n      - services\n      - changes.dart\n      - performer.dart\n  - ui/\n    - screen1/\n      - wm.dart\n      - widget.dart  \n      - route.dart\n    - screen2/\n      - wm.dart\n      - widget.dart  \n      - route.dart\n\n## Installation\n\nAdd mwwm to your `pubspec.yaml` file:\n\n```yaml\ndependencies:\n  mwwm: ^1.0.0\n```\n\nYou can use both `stable` and `dev` versions of the package listed above in the badges bar.\n\n## Changelog\n\nAll notable changes to this project will be documented in [this file](./CHANGELOG.md).\n\n## Issues\n\nFor issues, file directly in the Issues section.\n\n## Contribute\n\nIf you would like to contribute to the package (e.g. by improving the documentation, solving a bug or adding a cool new feature), please review our [contribution guide](../../CONTRIBUTING.md) first and send us your pull request.\n\nYour PRs are always welcome.\n\n## How to reach us\n\nPlease feel free to ask any questions about this package. Join our community chat on Telegram. We speak English and Russian.\n\n[![Telegram](https://img.shields.io/badge/chat-on%20Telegram-blue.svg)](https://t.me/SurfGear)\n\n## License\n\n[Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsurfstudio%2Fflutter-mwwm","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsurfstudio%2Fflutter-mwwm","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsurfstudio%2Fflutter-mwwm/lists"}