├── .fvmrc ├── example ├── linux │ ├── .gitignore │ ├── runner │ │ ├── main.cc │ │ ├── my_application.h │ │ ├── CMakeLists.txt │ │ └── my_application.cc │ ├── flutter │ │ ├── generated_plugin_registrant.cc │ │ ├── generated_plugin_registrant.h │ │ ├── generated_plugins.cmake │ │ └── CMakeLists.txt │ └── CMakeLists.txt ├── ios │ ├── Flutter │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ └── AppFrameworkInfo.plist │ ├── Runner │ │ ├── Runner-Bridging-Header.h │ │ ├── Assets.xcassets │ │ │ ├── LaunchImage.imageset │ │ │ │ ├── LaunchImage.png │ │ │ │ ├── LaunchImage@2x.png │ │ │ │ ├── LaunchImage@3x.png │ │ │ │ ├── README.md │ │ │ │ └── Contents.json │ │ │ └── AppIcon.appiconset │ │ │ │ ├── Icon-App-20x20@1x.png │ │ │ │ ├── Icon-App-20x20@2x.png │ │ │ │ ├── Icon-App-20x20@3x.png │ │ │ │ ├── Icon-App-29x29@1x.png │ │ │ │ ├── Icon-App-29x29@2x.png │ │ │ │ ├── Icon-App-29x29@3x.png │ │ │ │ ├── Icon-App-40x40@1x.png │ │ │ │ ├── Icon-App-40x40@2x.png │ │ │ │ ├── Icon-App-40x40@3x.png │ │ │ │ ├── Icon-App-60x60@2x.png │ │ │ │ ├── Icon-App-60x60@3x.png │ │ │ │ ├── Icon-App-76x76@1x.png │ │ │ │ ├── Icon-App-76x76@2x.png │ │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ │ ├── Icon-App-83.5x83.5@2x.png │ │ │ │ └── Contents.json │ │ ├── AppDelegate.swift │ │ ├── Base.lproj │ │ │ ├── Main.storyboard │ │ │ └── LaunchScreen.storyboard │ │ └── Info.plist │ ├── Runner.xcodeproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── WorkspaceSettings.xcsettings │ │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── WorkspaceSettings.xcsettings │ │ │ └── IDEWorkspaceChecks.plist │ ├── RunnerTests │ │ └── RunnerTests.swift │ └── .gitignore ├── macos │ ├── Flutter │ │ ├── Flutter-Debug.xcconfig │ │ ├── Flutter-Release.xcconfig │ │ └── GeneratedPluginRegistrant.swift │ ├── Runner │ │ ├── Configs │ │ │ ├── Debug.xcconfig │ │ │ ├── Release.xcconfig │ │ │ ├── Warnings.xcconfig │ │ │ └── AppInfo.xcconfig │ │ ├── Assets.xcassets │ │ │ └── AppIcon.appiconset │ │ │ │ ├── app_icon_16.png │ │ │ │ ├── app_icon_32.png │ │ │ │ ├── app_icon_64.png │ │ │ │ ├── app_icon_1024.png │ │ │ │ ├── app_icon_128.png │ │ │ │ ├── app_icon_256.png │ │ │ │ ├── app_icon_512.png │ │ │ │ └── Contents.json │ │ ├── Release.entitlements │ │ ├── AppDelegate.swift │ │ ├── DebugProfile.entitlements │ │ ├── MainFlutterWindow.swift │ │ └── Info.plist │ ├── .gitignore │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ ├── Runner.xcodeproj │ │ ├── project.xcworkspace │ │ │ └── xcshareddata │ │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ └── RunnerTests │ │ └── RunnerTests.swift ├── web │ ├── favicon.png │ ├── icons │ │ ├── Icon-192.png │ │ ├── Icon-512.png │ │ ├── Icon-maskable-192.png │ │ └── Icon-maskable-512.png │ ├── manifest.json │ └── index.html ├── windows │ ├── runner │ │ ├── resources │ │ │ └── app_icon.ico │ │ ├── resource.h │ │ ├── runner.exe.manifest │ │ ├── utils.h │ │ ├── flutter_window.h │ │ ├── main.cpp │ │ ├── CMakeLists.txt │ │ ├── utils.cpp │ │ ├── flutter_window.cpp │ │ ├── Runner.rc │ │ └── win32_window.h │ ├── flutter │ │ ├── generated_plugin_registrant.cc │ │ ├── generated_plugin_registrant.h │ │ ├── generated_plugins.cmake │ │ └── CMakeLists.txt │ ├── .gitignore │ └── CMakeLists.txt ├── lib │ ├── main.dart │ └── src │ │ ├── todos │ │ ├── views │ │ │ ├── ui_states │ │ │ │ ├── tasks_ui_state.dart │ │ │ │ └── tasks_ui_state.mapper.dart │ │ │ ├── widgets │ │ │ │ ├── done_panel.dart │ │ │ │ ├── progress_panel.dart │ │ │ │ └── tasks_panel.dart │ │ │ └── pages │ │ │ │ └── todos_page.dart │ │ ├── models │ │ │ ├── data │ │ │ │ ├── task.dart │ │ │ │ └── task.mapper.dart │ │ │ └── repositories │ │ │ │ └── tasks_repository.dart │ │ └── notifiers │ │ │ ├── done_notifier.dart │ │ │ └── progress_notifier.dart │ │ ├── counter │ │ ├── notifiers │ │ │ └── counter_notifier.dart │ │ └── views │ │ │ └── pages │ │ │ └── counter_page.dart │ │ ├── version │ │ ├── notifiers │ │ │ └── version_notifier.dart │ │ ├── utils │ │ │ └── version.dart │ │ └── views │ │ │ └── widgets │ │ │ └── version_panel.dart │ │ ├── chroma_counter │ │ ├── views │ │ │ ├── ui_states │ │ │ │ ├── chroma_counter_ui_state.dart │ │ │ │ └── chroma_counter_ui_state.mapper.dart │ │ │ ├── widgets │ │ │ │ └── chroma_counter.dart │ │ │ └── pages │ │ │ │ └── chroma_counter_page.dart │ │ └── notifiers │ │ │ └── chroma_counter_notifier.dart │ │ └── minimal_app.dart ├── android │ ├── app │ │ ├── src │ │ │ ├── main │ │ │ │ ├── res │ │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── drawable │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ ├── drawable-v21 │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ ├── values │ │ │ │ │ │ └── styles.xml │ │ │ │ │ └── values-night │ │ │ │ │ │ └── styles.xml │ │ │ │ ├── kotlin │ │ │ │ │ └── com │ │ │ │ │ │ └── example │ │ │ │ │ │ └── minimal_example │ │ │ │ │ │ └── MainActivity.kt │ │ │ │ └── AndroidManifest.xml │ │ │ ├── debug │ │ │ │ └── AndroidManifest.xml │ │ │ └── profile │ │ │ │ └── AndroidManifest.xml │ │ └── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ ├── .gitignore │ ├── build.gradle │ └── settings.gradle ├── .cursor │ └── rules │ │ ├── error-handling.mdc │ │ ├── state-management.mdc │ │ ├── git-workflow.mdc │ │ ├── documentation.mdc │ │ ├── expertise.mdc │ │ ├── testing.mdc │ │ ├── performance.mdc │ │ ├── code-generation.mdc │ │ ├── ui-design.mdc │ │ ├── dart-coding.mdc │ │ ├── flutter-architecture.mdc │ │ └── data-models.mdc ├── README.md ├── pubspec.yaml ├── .gitignore ├── assets │ └── minimal.svg ├── .metadata ├── test │ └── src │ │ └── chroma_counter │ │ └── views │ │ └── widgets │ │ └── chroma_counter_test.dart └── analysis_options.yaml ├── scripts ├── check_dependencies.sh └── generate_models.sh ├── test ├── minimal_test.dart └── src │ ├── mm_locator_test.dart │ ├── utils │ └── test_common.dart │ ├── mm_manager_test.dart │ └── mm_notifier_test.dart ├── .metadata ├── .vscode ├── settings.json ├── extensions.json └── launch.json ├── pubspec.yaml ├── lib ├── minimal_mvn.dart └── src │ ├── mm_locator.dart │ ├── mm_manager.dart │ └── mm_notifier.dart ├── .gitignore ├── .github └── workflows │ └── deploy-example.yml ├── LICENSE ├── CHANGELOG.md ├── analysis_options.yaml ├── README.md └── all_lint_rules.yaml /.fvmrc: -------------------------------------------------------------------------------- 1 | { 2 | "flutter": "3.32.0" 3 | } -------------------------------------------------------------------------------- /example/linux/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral 2 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /example/macos/Flutter/Flutter-Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "ephemeral/Flutter-Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/macos/Flutter/Flutter-Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "ephemeral/Flutter-Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alesalv/minimal/HEAD/example/web/favicon.png -------------------------------------------------------------------------------- /example/web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alesalv/minimal/HEAD/example/web/icons/Icon-192.png -------------------------------------------------------------------------------- /example/web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alesalv/minimal/HEAD/example/web/icons/Icon-512.png -------------------------------------------------------------------------------- /scripts/check_dependencies.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | fvm dart pub outdated --no-prereleases --no-transitive 4 | -------------------------------------------------------------------------------- /scripts/generate_models.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | fvm dart run build_runner build --delete-conflicting-outputs 4 | -------------------------------------------------------------------------------- /example/macos/Runner/Configs/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Debug.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /example/macos/Runner/Configs/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Release.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /example/web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alesalv/minimal/HEAD/example/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /example/web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alesalv/minimal/HEAD/example/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /example/macos/.gitignore: -------------------------------------------------------------------------------- 1 | # Flutter-related 2 | **/Flutter/ephemeral/ 3 | **/Pods/ 4 | 5 | # Xcode-related 6 | **/dgph 7 | **/xcuserdata/ 8 | -------------------------------------------------------------------------------- /example/windows/runner/resources/app_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alesalv/minimal/HEAD/example/windows/runner/resources/app_icon.ico -------------------------------------------------------------------------------- /test/minimal_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | 3 | void main() { 4 | test('adds one to input values', () {}); 5 | } 6 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'src/minimal_app.dart'; 4 | 5 | void main() { 6 | runApp(const MinimalApp()); 7 | } 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alesalv/minimal/HEAD/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alesalv/minimal/HEAD/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alesalv/minimal/HEAD/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alesalv/minimal/HEAD/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alesalv/minimal/HEAD/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alesalv/minimal/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alesalv/minimal/HEAD/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alesalv/minimal/HEAD/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alesalv/minimal/HEAD/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alesalv/minimal/HEAD/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alesalv/minimal/HEAD/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alesalv/minimal/HEAD/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alesalv/minimal/HEAD/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alesalv/minimal/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alesalv/minimal/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alesalv/minimal/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alesalv/minimal/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alesalv/minimal/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alesalv/minimal/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alesalv/minimal/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alesalv/minimal/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alesalv/minimal/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alesalv/minimal/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alesalv/minimal/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alesalv/minimal/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alesalv/minimal/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alesalv/minimal/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alesalv/minimal/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alesalv/minimal/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alesalv/minimal/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/com/example/minimal_example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.minimal_example 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() 6 | -------------------------------------------------------------------------------- /example/linux/runner/main.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | int main(int argc, char** argv) { 4 | g_autoptr(MyApplication) app = my_application_new(); 5 | return g_application_run(G_APPLICATION(app), argc, argv); 6 | } 7 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/macos/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/macos/Flutter/GeneratedPluginRegistrant.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | import FlutterMacOS 6 | import Foundation 7 | 8 | 9 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { 10 | } 11 | -------------------------------------------------------------------------------- /example/linux/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | 10 | void fl_register_plugins(FlPluginRegistry* registry) { 11 | } 12 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip 6 | -------------------------------------------------------------------------------- /example/windows/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | 10 | void RegisterPlugins(flutter::PluginRegistry* registry) { 11 | } 12 | -------------------------------------------------------------------------------- /example/macos/Runner/Release.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/to/reference-keystore 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: "17025dd88227cd9532c33fa78f5250d548d87e9a" 8 | channel: "stable" 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /example/ios/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /example/lib/src/todos/views/ui_states/tasks_ui_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:dart_mappable/dart_mappable.dart'; 2 | 3 | import '../../models/data/task.dart'; 4 | 5 | part 'tasks_ui_state.mapper.dart'; 6 | 7 | @MappableClass() 8 | class TasksUIState with TasksUIStateMappable { 9 | const TasksUIState({this.tasks = const []}); 10 | 11 | final List tasks; 12 | } 13 | -------------------------------------------------------------------------------- /example/macos/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /example/windows/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral/ 2 | 3 | # Visual Studio user-specific files. 4 | *.suo 5 | *.user 6 | *.userosscache 7 | *.sln.docstates 8 | 9 | # Visual Studio build-related files. 10 | x64/ 11 | x86/ 12 | 13 | # Visual Studio cache files 14 | # files ending in .cache can be ignored 15 | *.[Cc]ache 16 | # but keep track of directories ending in .cache 17 | !*.[Cc]ache/ 18 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /example/macos/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | @main 5 | class AppDelegate: FlutterAppDelegate { 6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 7 | return true 8 | } 9 | 10 | override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { 11 | return true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/linux/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void fl_register_plugins(FlPluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /example/windows/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void RegisterPlugins(flutter::PluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | allprojects { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | } 6 | } 7 | 8 | rootProject.buildDir = "../build" 9 | subprojects { 10 | project.buildDir = "${rootProject.buildDir}/${project.name}" 11 | } 12 | subprojects { 13 | project.evaluationDependsOn(":app") 14 | } 15 | 16 | tasks.register("clean", Delete) { 17 | delete rootProject.buildDir 18 | } 19 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "dart.flutterSdkPath": ".fvm/versions/3.32.0", 3 | "search.exclude": { 4 | "**/.fvm": true, 5 | "**/*.freezed.dart": true, 6 | "**/*.g.dart": true 7 | }, 8 | "files.watcherExclude": { 9 | "**/.fvm": true, 10 | "**/*.freezed.dart": true, 11 | "**/*.g.dart": true 12 | }, 13 | "files.exclude": { 14 | "**/*.freezed.dart": true, 15 | "**/*.g.dart": true 16 | } 17 | } -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/macos/Runner/DebugProfile.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.cs.allow-jit 8 | 9 | com.apple.security.network.server 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /example/lib/src/counter/notifiers/counter_notifier.dart: -------------------------------------------------------------------------------- 1 | import 'package:minimal_mvn/minimal_mvn.dart'; 2 | 3 | class CounterNotifier extends MMNotifier { 4 | CounterNotifier() : super(0); 5 | 6 | void increment() => notify(state + 1); 7 | } 8 | 9 | final MMManager counterManager = MMManager(CounterNotifier.new); 10 | 11 | final MMManager disposableCounterManager = 12 | MMManager(CounterNotifier.new, autodispose: true); 13 | -------------------------------------------------------------------------------- /example/lib/src/version/notifiers/version_notifier.dart: -------------------------------------------------------------------------------- 1 | import 'package:minimal_mvn/minimal_mvn.dart'; 2 | 3 | import '../utils/version.dart'; 4 | 5 | class VersionNotifier extends MMNotifier { 6 | VersionNotifier() : super(''); 7 | 8 | Future loadVersion() async { 9 | final version = await getPackageVersion(); 10 | notify(version); 11 | } 12 | } 13 | 14 | final versionNotifierManager = MMManager(VersionNotifier.new); 15 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | 4 | @main 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/macos/Runner/MainFlutterWindow.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | class MainFlutterWindow: NSWindow { 5 | override func awakeFromNib() { 6 | let flutterViewController = FlutterViewController() 7 | let windowFrame = self.frame 8 | self.contentViewController = flutterViewController 9 | self.setFrame(windowFrame, display: true) 10 | 11 | RegisterGeneratedPlugins(registry: flutterViewController) 12 | 13 | super.awakeFromNib() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /example/linux/runner/my_application.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_MY_APPLICATION_H_ 2 | #define FLUTTER_MY_APPLICATION_H_ 3 | 4 | #include 5 | 6 | G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, 7 | GtkApplication) 8 | 9 | /** 10 | * my_application_new: 11 | * 12 | * Creates a new Flutter-based application. 13 | * 14 | * Returns: a new #MyApplication. 15 | */ 16 | MyApplication* my_application_new(); 17 | 18 | #endif // FLUTTER_MY_APPLICATION_H_ 19 | -------------------------------------------------------------------------------- /example/windows/runner/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by Runner.rc 4 | // 5 | #define IDI_APP_ICON 101 6 | 7 | // Next default values for new objects 8 | // 9 | #ifdef APSTUDIO_INVOKED 10 | #ifndef APSTUDIO_READONLY_SYMBOLS 11 | #define _APS_NEXT_RESOURCE_VALUE 102 12 | #define _APS_NEXT_COMMAND_VALUE 40001 13 | #define _APS_NEXT_CONTROL_VALUE 1001 14 | #define _APS_NEXT_SYMED_VALUE 101 15 | #endif 16 | #endif 17 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/.cursor/rules/error-handling.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | globs: 4 | alwaysApply: false 5 | --- 6 | # Error Handling Guidelines 7 | 8 | 9 | files: lib/src/**/*.dart 10 | 11 | ## Description 12 | Standards for handling, logging, and displaying errors throughout the application 13 | 14 | ## Rules 15 | - Throw errors when needed, and catch them at appropriate boundaries 16 | - Log errors with context 17 | - Present user-friendly error messages in the UI 18 | - Avoid silent failures; always handle or propagate errors 19 | -------------------------------------------------------------------------------- /example/lib/src/todos/models/data/task.dart: -------------------------------------------------------------------------------- 1 | import 'package:dart_mappable/dart_mappable.dart'; 2 | 3 | part 'task.mapper.dart'; 4 | 5 | @MappableClass() 6 | class Task with TaskMappable { 7 | const Task({ 8 | required this.id, 9 | required this.label, 10 | this.isDone = false, 11 | }); 12 | 13 | final String id; 14 | final String label; 15 | final bool isDone; 16 | } 17 | 18 | extension TasksIterator on List { 19 | List get progress => where((final t) => !t.isDone).toList(); 20 | List get done => where((final t) => t.isDone).toList(); 21 | } 22 | -------------------------------------------------------------------------------- /example/.cursor/rules/state-management.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | globs: 4 | alwaysApply: false 5 | --- 6 | # State Management Guidelines 7 | 8 | 9 | files: lib/src/**/controllers/**/*.dart 10 | 11 | ## Description 12 | Guidelines for state management using Minimal 13 | 14 | ## Rules 15 | - Use Minimal as the primary state management and dependency injection solution 16 | - Use `MMManager` to provide a `MMNotifier` 17 | - Use `MMLocator` to provide repositories and services 18 | - Organize notifiers logically by feature 19 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: minimal_mvn 2 | description: A minimal state management solution for Flutter apps using the Model-View-Notifier (MVN) pattern 3 | version: 2.0.2 4 | repository: https://github.com/alesalv/minimal 5 | homepage: https://github.com/alesalv/minimal 6 | 7 | environment: 8 | sdk: ">=3.3.0 <4.0.0" 9 | flutter: ">=3.19.0" 10 | 11 | topics: 12 | - state-management 13 | - mvn 14 | - dependency-injection 15 | - architecture 16 | 17 | dependencies: 18 | flutter: 19 | sdk: flutter 20 | 21 | dev_dependencies: 22 | flutter_test: 23 | sdk: flutter 24 | 25 | flutter: 26 | uses-material-design: true 27 | -------------------------------------------------------------------------------- /example/.cursor/rules/git-workflow.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | globs: 4 | alwaysApply: false 5 | --- 6 | # Git Workflow Guidelines 7 | 8 | 9 | files: **/* 10 | 11 | ## Description 12 | Standards for Git operations, commits, branches, and pull requests 13 | 14 | ## Git Practices 15 | - Write atomic commits 16 | - Do not commit files that are not related to the current feature 17 | - When committing, write a short description of the changes you are making 18 | - When creating a new feature, create a new branch from the `main` branch 19 | - When merging a feature, merge the branch into the `main` branch 20 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # minimal_example 2 | 3 | A new Flutter project. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) 13 | 14 | For help getting started with Flutter development, view the 15 | [online documentation](https://docs.flutter.dev/), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /example/lib/src/todos/views/widgets/done_panel.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../notifiers/done_notifier.dart'; 4 | import 'tasks_panel.dart'; 5 | 6 | class DonePanel extends StatelessWidget { 7 | const DonePanel({super.key}); 8 | 9 | @override 10 | Widget build(final BuildContext context) { 11 | final notifier = doneNotifierManager.notifier; 12 | 13 | return ListenableBuilder( 14 | listenable: notifier, 15 | builder: (final _, final __) => TasksPanel( 16 | tasks: notifier.state.tasks, 17 | onTap: notifier.toggleTask, 18 | ), 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/minimal_mvn.dart: -------------------------------------------------------------------------------- 1 | import 'minimal_mvn.dart'; 2 | 3 | /// A minimal state management package for Flutter 4 | /// 5 | /// This package provides a simple and efficient way to manage state in Flutter 6 | /// applications using the MVN (Model-View-Notifier) pattern. 7 | /// 8 | /// The main components are: 9 | /// * [MMNotifier] - Minimal notifier for all notifiers that manage state 10 | /// * [MMManager] - Minimal manager for accessing notifiers 11 | /// * [MMLocator] - Minimal locator for accessing repositories and services 12 | 13 | export 'src/mm_locator.dart'; 14 | export 'src/mm_manager.dart'; 15 | export 'src/mm_notifier.dart'; 16 | -------------------------------------------------------------------------------- /example/lib/src/chroma_counter/views/ui_states/chroma_counter_ui_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:dart_mappable/dart_mappable.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | part 'chroma_counter_ui_state.mapper.dart'; 5 | 6 | @MappableClass() 7 | class ChromaCounterUIState with ChromaCounterUIStateMappable { 8 | const ChromaCounterUIState({ 9 | this.backgroundColor = Colors.blue, 10 | this.borderRadius = BorderRadius.zero, 11 | this.count = 0, 12 | }) : milestone = count ~/ 10; 13 | 14 | final Color backgroundColor; 15 | final BorderRadius borderRadius; 16 | final int count; 17 | final int milestone; 18 | } 19 | -------------------------------------------------------------------------------- /example/lib/src/version/utils/version.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/services.dart' show rootBundle; 2 | 3 | /// Utility to read the package version from pubspec.yaml at runtime. 4 | /// 5 | /// Example: 6 | /// ```dart 7 | /// final version = await getPackageVersion(); 8 | /// print('Current version: $version'); // prints "1.0.4" 9 | /// ``` 10 | Future getPackageVersion() async { 11 | final content = await rootBundle.loadString('../pubspec.yaml'); 12 | final versionLine = content.split('\n').firstWhere( 13 | (final line) => line.startsWith('version:'), 14 | ); 15 | return versionLine.split(':')[1].trim(); 16 | } 17 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "AakashP.adb-qr", // ADB QR 4 | "Dart-Code.dart-code", // Dart 5 | "Dart-Code.flutter", // Flutter 6 | "waderyan.gitblame", // Git blame 7 | "redhat.vscode-yaml", // Yaml 8 | "SimonSiefke.svg-preview" // SVG preview 9 | ], 10 | "unwantedRecommendations": [ 11 | "Nuxtr.nuxtr-vscode", // Nuxtr 12 | "ms-playwright.playwright", // Playwright tests 13 | "bradlc.vscode-tailwindcss", // Tailwind CSS 14 | "vitest.explorer", // Vitest 15 | "Vue.volar" // Vue 16 | ] 17 | } 18 | 19 | -------------------------------------------------------------------------------- /example/lib/src/todos/views/widgets/progress_panel.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../notifiers/progress_notifier.dart'; 4 | import 'tasks_panel.dart'; 5 | 6 | class ProgressPanel extends StatelessWidget { 7 | const ProgressPanel({super.key}); 8 | 9 | @override 10 | Widget build(final BuildContext context) { 11 | final notifier = progressNotifierManager.notifier; 12 | 13 | return ListenableBuilder( 14 | listenable: notifier, 15 | builder: (final _, final __) => TasksPanel( 16 | tasks: notifier.state.tasks, 17 | onTap: notifier.toggleTask, 18 | ), 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /example/macos/Runner/Configs/Warnings.xcconfig: -------------------------------------------------------------------------------- 1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings 2 | GCC_WARN_UNDECLARED_SELECTOR = YES 3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES 4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE 5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES 6 | CLANG_WARN_PRAGMA_PACK = YES 7 | CLANG_WARN_STRICT_PROTOTYPES = YES 8 | CLANG_WARN_COMMA = YES 9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES 10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES 11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES 12 | GCC_WARN_SHADOW = YES 13 | CLANG_WARN_UNREACHABLE_CODE = YES 14 | -------------------------------------------------------------------------------- /example/.cursor/rules/documentation.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | globs: 4 | alwaysApply: false 5 | --- 6 | # Documentation Guidelines 7 | 8 | 9 | files: lib/src/**/*.dart 10 | 11 | ## Description 12 | Standards for code documentation and commenting 13 | 14 | ## Documentation 15 | - Follow official documentation for best practices: 16 | - [Flutter Documentation](mdc:https:/docs.flutter.dev) 17 | - @Minimal Documentation 18 | - Document public classes and methods with clear descriptions, parameters, return values, and a code snippet taken from the codebase 19 | - Comment briefly only complex logic and non-obvious code decisions 20 | -------------------------------------------------------------------------------- /example/.cursor/rules/expertise.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | globs: 4 | alwaysApply: false 5 | --- 6 | # Project Expertise Guidelines 7 | 8 | 9 | files: **/*.dart 10 | 11 | ## Description 12 | Core expertise and technology stack guidelines for this project 13 | 14 | ## Technology Stack 15 | - Flutter for cross-platform development 16 | - Dart as the primary programming language 17 | - Minimal for state management and dependency injection 18 | - Dart Mappable for immutable data models 19 | - Firebase for backend services 20 | 21 | ## Architecture 22 | - MVN design pattern 23 | - Feature-first folder structure 24 | - Clean separation of concerns 25 | -------------------------------------------------------------------------------- /example/windows/runner/runner.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PerMonitorV2 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /example/macos/Runner/Configs/AppInfo.xcconfig: -------------------------------------------------------------------------------- 1 | // Application-level settings for the Runner target. 2 | // 3 | // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the 4 | // future. If not, the values below would default to using the project name when this becomes a 5 | // 'flutter create' template. 6 | 7 | // The application's name. By default this is also the title of the Flutter window. 8 | PRODUCT_NAME = minimal_example 9 | 10 | // The application's bundle identifier 11 | PRODUCT_BUNDLE_IDENTIFIER = com.example.minimalExample 12 | 13 | // The copyright displayed in application information 14 | PRODUCT_COPYRIGHT = Copyright © 2025 com.example. All rights reserved. 15 | -------------------------------------------------------------------------------- /example/.cursor/rules/testing.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | globs: 4 | alwaysApply: false 5 | --- 6 | # Testing Guidelines 7 | 8 | 9 | files: test/**/*.dart, integration_test/**/*.dart 10 | 11 | ## Description 12 | Standards for writing and organizing tests across different testing levels 13 | 14 | ## Unit Testing 15 | - Follow the Arrange-Act-Assert convention for clear and maintainable tests 16 | - Use mocks for dependencies except for lightweight third-party services 17 | - Test business logic in isolation from the UI 18 | 19 | ## Widget Testing 20 | - Write widget tests for all major UI components 21 | - Test user interactions and state changes 22 | -------------------------------------------------------------------------------- /example/.cursor/rules/performance.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | globs: 4 | alwaysApply: false 5 | --- 6 | # Performance Guidelines 7 | 8 | 9 | files: lib/src/**/*.dart 10 | 11 | ## Description 12 | Best practices for optimizing application performance 13 | 14 | ## Rules 15 | - Optimize list views with `ListView.builder` 16 | - Optimize image handling with `cached_network_image` 17 | - Use `const` widgets and flatten widget hierarchies for improved rendering efficiency 18 | - Handle asynchronous operations cleanly with proper cancellation during widget disposal 19 | - Minimize unnecessary rebuilds using memoization techniques 20 | - Implement pagination for large data sets 21 | -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. 26 | /pubspec.lock 27 | **/doc/api/ 28 | .dart_tool/ 29 | .flutter-plugins 30 | .flutter-plugins-dependencies 31 | build/ 32 | 33 | # FVM Version Cache 34 | .fvm/ -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: minimal_example 2 | description: Example app for minimal package 3 | publish_to: none 4 | 5 | environment: 6 | sdk: ">=3.3.0 <4.0.0" 7 | flutter: ">=3.19.0" 8 | 9 | dependencies: 10 | dart_mappable: ^4.5.0 11 | flutter: 12 | sdk: flutter 13 | flutter_svg: ^2.1.0 14 | intl: ^0.20.2 15 | minimal_mvn: 16 | path: ../ 17 | 18 | dev_dependencies: 19 | build_runner: ^2.4.15 20 | dart_mappable_builder: ^4.5.0 21 | flutter_test: 22 | sdk: flutter 23 | 24 | flutter: 25 | uses-material-design: true 26 | assets: 27 | - assets/minimal.svg 28 | # Needed to read package version at runtime 29 | - ../pubspec.yaml 30 | 31 | # Enable generation of localized Strings from arb files. 32 | generate: true 33 | -------------------------------------------------------------------------------- /example/windows/runner/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_UTILS_H_ 2 | #define RUNNER_UTILS_H_ 3 | 4 | #include 5 | #include 6 | 7 | // Creates a console for the process, and redirects stdout and stderr to 8 | // it for both the runner and the Flutter library. 9 | void CreateAndAttachConsole(); 10 | 11 | // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string 12 | // encoded in UTF-8. Returns an empty std::string on failure. 13 | std::string Utf8FromUtf16(const wchar_t* utf16_string); 14 | 15 | // Gets the command line arguments passed in as a std::vector, 16 | // encoded in UTF-8. Returns an empty std::vector on failure. 17 | std::vector GetCommandLineArguments(); 18 | 19 | #endif // RUNNER_UTILS_H_ 20 | -------------------------------------------------------------------------------- /test/src/mm_locator_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:minimal_mvn/minimal_mvn.dart'; 3 | 4 | void main() { 5 | group('MMLocator', () { 6 | test('creates instance lazily', () { 7 | var created = 0; 8 | final locator = MMLocator(() { 9 | created++; 10 | return 42; 11 | }); 12 | expect(created, 0); 13 | locator.instance; 14 | expect(created, 1); 15 | locator.instance; 16 | expect(created, 1); 17 | }); 18 | 19 | test('returns same instance', () { 20 | final locator = MMLocator(() => []); 21 | final first = locator.instance; 22 | final second = locator.instance; 23 | expect(second, same(first)); 24 | }); 25 | }); 26 | } 27 | -------------------------------------------------------------------------------- /example/.cursor/rules/code-generation.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | globs: 4 | alwaysApply: false 5 | --- 6 | # Code Generation Guidelines 7 | 8 | 9 | files: lib/src/**/*.dart 10 | 11 | ## Description 12 | Guidelines for code generation using build_runner, Dart Mappable, and other code generation tools 13 | 14 | ## Rules 15 | - Generate code for: 16 | - Mappable classes 17 | - JSON serialization 18 | - Other generated code 19 | - Use this command for code generation: 20 | ```bash 21 | fvm dart run build_runner build --delete-conflicting-outputs 22 | ``` 23 | - Run code generation after: 24 | - Adding new Mappable classes 25 | - Modifying existing Mappable classes 26 | 27 | [generate_models.sh](mdc:scripts/generate_models.sh) 28 | -------------------------------------------------------------------------------- /example/lib/src/todos/views/widgets/tasks_panel.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../models/data/task.dart'; 4 | 5 | class TasksPanel extends StatelessWidget { 6 | const TasksPanel({ 7 | required this.tasks, 8 | required this.onTap, 9 | super.key, 10 | }); 11 | 12 | final List tasks; 13 | final void Function(String) onTap; 14 | 15 | @override 16 | Widget build(final BuildContext context) { 17 | return ListView.builder( 18 | itemCount: tasks.length, 19 | itemBuilder: (final _, final index) { 20 | final task = tasks[index]; 21 | return ListTile( 22 | title: Text(task.label), 23 | trailing: Icon(task.isDone ? Icons.hourglass_empty : Icons.done), 24 | onTap: () => onTap(task.id), 25 | ); 26 | }, 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | def flutterSdkPath = { 3 | def properties = new Properties() 4 | file("local.properties").withInputStream { properties.load(it) } 5 | def flutterSdkPath = properties.getProperty("flutter.sdk") 6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 7 | return flutterSdkPath 8 | }() 9 | 10 | includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") 11 | 12 | repositories { 13 | google() 14 | mavenCentral() 15 | gradlePluginPortal() 16 | } 17 | } 18 | 19 | plugins { 20 | id "dev.flutter.flutter-plugin-loader" version "1.0.0" 21 | id "com.android.application" version "8.1.0" apply false 22 | id "org.jetbrains.kotlin.android" version "1.8.22" apply false 23 | } 24 | 25 | include ":app" 26 | -------------------------------------------------------------------------------- /example/linux/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | ) 7 | 8 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 9 | ) 10 | 11 | set(PLUGIN_BUNDLED_LIBRARIES) 12 | 13 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 14 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) 15 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 16 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 17 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 18 | endforeach(plugin) 19 | 20 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 21 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) 22 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 23 | endforeach(ffi_plugin) 24 | -------------------------------------------------------------------------------- /example/windows/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | ) 7 | 8 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 9 | ) 10 | 11 | set(PLUGIN_BUNDLED_LIBRARIES) 12 | 13 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 14 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) 15 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 16 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 17 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 18 | endforeach(plugin) 19 | 20 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 21 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) 22 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 23 | endforeach(ffi_plugin) 24 | -------------------------------------------------------------------------------- /example/lib/src/todos/views/pages/todos_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../widgets/done_panel.dart'; 4 | import '../widgets/progress_panel.dart'; 5 | 6 | class TodosPage extends StatelessWidget { 7 | const TodosPage({super.key}); 8 | 9 | @override 10 | Widget build(final BuildContext context) { 11 | return DefaultTabController( 12 | length: 2, 13 | child: Scaffold( 14 | appBar: AppBar( 15 | title: const Text('Todos'), 16 | bottom: const TabBar( 17 | tabs: [ 18 | Tab(icon: Icon(Icons.hourglass_empty)), 19 | Tab(icon: Icon(Icons.done)), 20 | ], 21 | ), 22 | ), 23 | body: const TabBarView( 24 | children: [ 25 | ProgressPanel(), 26 | DonePanel(), 27 | ], 28 | ), 29 | ), 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 12.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .build/ 9 | .buildlog/ 10 | .history 11 | .svn/ 12 | .swiftpm/ 13 | migrate_working_dir/ 14 | 15 | # IntelliJ related 16 | *.iml 17 | *.ipr 18 | *.iws 19 | .idea/ 20 | 21 | # The .vscode folder contains launch configuration and tasks you configure in 22 | # VS Code which you may wish to be included in version control, so this line 23 | # is commented out by default. 24 | #.vscode/ 25 | 26 | # Flutter/Dart/Pub related 27 | **/doc/api/ 28 | **/ios/Flutter/.last_build_id 29 | .dart_tool/ 30 | .flutter-plugins 31 | .flutter-plugins-dependencies 32 | .pub-cache/ 33 | .pub/ 34 | /build/ 35 | 36 | # Symbolication related 37 | app.*.symbols 38 | 39 | # Obfuscation related 40 | app.*.map.json 41 | 42 | # Android Studio will place build artifacts here 43 | /android/app/debug 44 | /android/app/profile 45 | /android/app/release 46 | -------------------------------------------------------------------------------- /example/assets/minimal.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /lib/src/mm_locator.dart: -------------------------------------------------------------------------------- 1 | /// Minimal locator 2 | /// 3 | /// A minimal locator is responsible for holding a singleton instance of any 4 | /// class, usually a repository or a service. It lazily creates the instance, 5 | /// and provides access to it 6 | /// 7 | /// Example: 8 | /// ```dart 9 | /// final settingsLocator = MMLocator( 10 | /// () => SettingsRepository(), 11 | /// ); 12 | /// ``` 13 | class MMLocator { 14 | /// Creates a minimal locator for the given class type 15 | MMLocator(this._create); 16 | 17 | T? _instance; 18 | final T Function() _create; 19 | 20 | /// Returns the instance managed by this locator 21 | /// 22 | /// If no instance exists, a new one will be created. The same instance will 23 | /// be returned on subsequent calls 24 | /// 25 | /// Example: 26 | /// ```dart 27 | /// final settings = settingsLocator.instance; 28 | /// await settings.load(); 29 | /// ``` 30 | T get instance { 31 | _instance ??= _create(); 32 | return _instance!; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /example/.cursor/rules/ui-design.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | globs: 4 | alwaysApply: false 5 | --- 6 | # UI Design Guidelines 7 | 8 | 9 | files: lib/src/**/views/**/*.dart 10 | 11 | ## Description 12 | Guidelines for UI components, widget structure, and responsive design patterns 13 | 14 | ## Widget Structure 15 | - Keep widgets focused and composable with clear responsibilities 16 | - Flatten widget hierarchies where reasonable for better rendering performance 17 | 18 | ## Responsive Design 19 | - Design mobile first 20 | - Use `LayoutBuilder` and `MediaQuery` for adaptive layouts 21 | - Centralize themes and styles in `ThemeData` for consistency 22 | - Design for different screen sizes and orientations using responsive breakpoints 23 | 24 | ## Loading on UI 25 | - Use a loading indicator while fetching data 26 | 27 | ## Errors on UI 28 | - Use an error indicator with appropriate messaging for error displays 29 | - Handle empty states gracefully in UI with clear messaging 30 | -------------------------------------------------------------------------------- /example/web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "minimal_example", 3 | "short_name": "minimal_example", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | }, 22 | { 23 | "src": "icons/Icon-maskable-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-maskable-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /example/windows/runner/flutter_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_FLUTTER_WINDOW_H_ 2 | #define RUNNER_FLUTTER_WINDOW_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "win32_window.h" 10 | 11 | // A window that does nothing but host a Flutter view. 12 | class FlutterWindow : public Win32Window { 13 | public: 14 | // Creates a new FlutterWindow hosting a Flutter view running |project|. 15 | explicit FlutterWindow(const flutter::DartProject& project); 16 | virtual ~FlutterWindow(); 17 | 18 | protected: 19 | // Win32Window: 20 | bool OnCreate() override; 21 | void OnDestroy() override; 22 | LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, 23 | LPARAM const lparam) noexcept override; 24 | 25 | private: 26 | // The project to run. 27 | flutter::DartProject project_; 28 | 29 | // The Flutter instance hosted by this window. 30 | std::unique_ptr flutter_controller_; 31 | }; 32 | 33 | #endif // RUNNER_FLUTTER_WINDOW_H_ 34 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/linux/runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | project(runner LANGUAGES CXX) 3 | 4 | # Define the application target. To change its name, change BINARY_NAME in the 5 | # top-level CMakeLists.txt, not the value here, or `flutter run` will no longer 6 | # work. 7 | # 8 | # Any new source files that you add to the application should be added here. 9 | add_executable(${BINARY_NAME} 10 | "main.cc" 11 | "my_application.cc" 12 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 13 | ) 14 | 15 | # Apply the standard set of build settings. This can be removed for applications 16 | # that need different build settings. 17 | apply_standard_settings(${BINARY_NAME}) 18 | 19 | # Add preprocessor definitions for the application ID. 20 | add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") 21 | 22 | # Add dependency libraries. Add any application-specific dependencies here. 23 | target_link_libraries(${BINARY_NAME} PRIVATE flutter) 24 | target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) 25 | 26 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") 27 | -------------------------------------------------------------------------------- /example/lib/src/todos/notifiers/done_notifier.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:minimal_mvn/minimal_mvn.dart'; 4 | 5 | import '../models/data/task.dart'; 6 | import '../models/repositories/tasks_repository.dart'; 7 | import '../views/ui_states/tasks_ui_state.dart'; 8 | 9 | class DoneNotifier extends MMNotifier { 10 | DoneNotifier(this.tasksRepository) : super(TasksUIState(tasks: tasksRepository.read().done)) { 11 | _tasksSubscription = tasksRepository.watch().listen(_onTasks); 12 | } 13 | 14 | final TasksRepository tasksRepository; 15 | late final StreamSubscription> _tasksSubscription; 16 | 17 | void toggleTask(final String id) { 18 | tasksRepository.update(id); 19 | } 20 | 21 | void _onTasks(final List tasks) { 22 | if (!disposed) { 23 | notify(state.copyWith(tasks: tasks.done)); 24 | } 25 | } 26 | 27 | @override 28 | void dispose() { 29 | unawaited(_tasksSubscription.cancel()); 30 | super.dispose(); 31 | } 32 | } 33 | 34 | final doneNotifierManager = MMManager(() => DoneNotifier(tasksRepositoryLocator.instance)); 35 | -------------------------------------------------------------------------------- /example/lib/src/todos/notifiers/progress_notifier.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:minimal_mvn/minimal_mvn.dart'; 4 | 5 | import '../models/data/task.dart'; 6 | import '../models/repositories/tasks_repository.dart'; 7 | import '../views/ui_states/tasks_ui_state.dart'; 8 | 9 | class ProgressNotifier extends MMNotifier { 10 | ProgressNotifier(this.tasksRepository) 11 | : super(TasksUIState(tasks: tasksRepository.read().progress)) { 12 | _tasksSubscription = tasksRepository.watch().listen(_onTasks); 13 | } 14 | 15 | final TasksRepository tasksRepository; 16 | late final StreamSubscription> _tasksSubscription; 17 | 18 | void toggleTask(final String id) { 19 | tasksRepository.update(id); 20 | } 21 | 22 | void _onTasks(final List tasks) { 23 | if (!disposed) { 24 | notify(state.copyWith(tasks: tasks.progress)); 25 | } 26 | } 27 | 28 | @override 29 | void dispose() { 30 | unawaited(_tasksSubscription.cancel()); 31 | super.dispose(); 32 | } 33 | } 34 | 35 | final progressNotifierManager = MMManager(() => ProgressNotifier(tasksRepositoryLocator.instance)); 36 | -------------------------------------------------------------------------------- /example/lib/src/todos/models/repositories/tasks_repository.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:minimal_mvn/minimal_mvn.dart'; 4 | 5 | import '../data/task.dart'; 6 | 7 | class TasksRepository { 8 | TasksRepository() { 9 | _streamController = StreamController>.broadcast(sync: true); 10 | _tasks = [ 11 | const Task(id: '1', label: 'Task 1'), 12 | const Task(id: '2', label: 'Task 2'), 13 | const Task(id: '3', label: 'Task 3'), 14 | const Task(id: '4', label: 'Task 4', isDone: true), 15 | const Task(id: '5', label: 'Task 5', isDone: true), 16 | ]; 17 | } 18 | 19 | late final StreamController> _streamController; 20 | late List _tasks; 21 | 22 | Stream> watch() { 23 | return _streamController.stream; 24 | } 25 | 26 | List read() => _tasks; 27 | 28 | void update(final String id) { 29 | _tasks = _tasks 30 | .map((final task) => task.id == id ? task.copyWith(isDone: !task.isDone) : task) 31 | .toList(); 32 | _streamController.add(_tasks); 33 | } 34 | } 35 | 36 | final tasksRepositoryLocator = MMLocator(TasksRepository.new); 37 | -------------------------------------------------------------------------------- /example/lib/src/version/views/widgets/version_panel.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | import '../../notifiers/version_notifier.dart'; 6 | 7 | class VersionPanel extends StatefulWidget { 8 | const VersionPanel({super.key}); 9 | 10 | @override 11 | State createState() => _VersionPanelState(); 12 | } 13 | 14 | class _VersionPanelState extends State { 15 | @override 16 | void initState() { 17 | super.initState(); 18 | WidgetsBinding.instance.addPostFrameCallback( 19 | (final timeStamp) => unawaited(versionNotifierManager.notifier.loadVersion()), 20 | ); 21 | } 22 | 23 | @override 24 | Widget build(final BuildContext context) { 25 | final notifier = versionNotifierManager.notifier; 26 | return ListenableBuilder( 27 | listenable: notifier, 28 | builder: (final _, final __) => Text( 29 | notifier.state, 30 | style: const TextStyle( 31 | fontSize: 12, 32 | color: Color(0x99666666), // Semi-transparent grey 33 | fontWeight: FontWeight.w300, 34 | ), 35 | ), 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /.github/workflows/deploy-example.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Example to Pages 2 | 3 | on: 4 | release: 5 | types: [published] 6 | # Keeping manual trigger for testing 7 | workflow_dispatch: 8 | 9 | jobs: 10 | build-and-deploy: 11 | runs-on: ubuntu-latest 12 | permissions: 13 | contents: write 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | 18 | - name: Setup Flutter 19 | uses: subosito/flutter-action@v2 20 | with: 21 | channel: "stable" 22 | 23 | - name: Install FVM 24 | run: | 25 | dart pub global activate fvm 26 | fvm install 27 | fvm flutter --version 28 | 29 | - name: Get dependencies 30 | run: fvm flutter pub get 31 | working-directory: example 32 | 33 | # web available at https://alesalv.github.io/minimal/ 34 | - name: Build Web 35 | run: fvm flutter build web --base-href "/minimal/" 36 | working-directory: example 37 | 38 | - name: Deploy to GitHub Pages 39 | uses: peaceiris/actions-gh-pages@v3 40 | with: 41 | github_token: ${{ secrets.GITHUB_TOKEN }} 42 | publish_dir: example/build/web 43 | -------------------------------------------------------------------------------- /test/src/utils/test_common.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:minimal_mvn/minimal_mvn.dart'; 3 | 4 | @immutable 5 | class TState { 6 | const TState({ 7 | required this.value, 8 | required this.text, 9 | }); 10 | 11 | final int value; 12 | final String text; 13 | 14 | TState copyWith({ 15 | final int? value, 16 | final String? text, 17 | }) => 18 | TState( 19 | value: value ?? this.value, 20 | text: text ?? this.text, 21 | ); 22 | 23 | @override 24 | bool operator ==(final Object other) => 25 | identical(this, other) || 26 | other is TState && 27 | runtimeType == other.runtimeType && 28 | value == other.value && 29 | text == other.text; 30 | 31 | @override 32 | int get hashCode => Object.hash(value, text); 33 | } 34 | 35 | class TNotifier extends MMNotifier { 36 | TNotifier() : super(const TState(value: 0, text: '')); 37 | 38 | void increment() => notify(state.copyWith(value: state.value + 1)); 39 | 40 | void append(final String suffix) => notify(state.copyWith(text: '${state.text}$suffix')); 41 | 42 | bool get isDisposed => disposed; 43 | } 44 | -------------------------------------------------------------------------------- /example/lib/src/chroma_counter/views/widgets/chroma_counter.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../notifiers/chroma_counter_notifier.dart'; 4 | 5 | class ChromaCounter extends StatelessWidget { 6 | const ChromaCounter({super.key}); 7 | 8 | @override 9 | Widget build(final BuildContext context) { 10 | final notifier = chromaCounterManager.notifier; 11 | 12 | return ListenableBuilder( 13 | listenable: notifier, 14 | builder: (final _, final __) { 15 | return AnimatedContainer( 16 | duration: const Duration(milliseconds: 500), 17 | curve: Curves.easeInOut, 18 | width: 200, 19 | height: 200, 20 | decoration: BoxDecoration( 21 | color: notifier.state.backgroundColor, 22 | borderRadius: notifier.state.borderRadius, 23 | ), 24 | child: Center( 25 | child: Text( 26 | '${notifier.state.count}', 27 | style: const TextStyle( 28 | fontSize: 48, 29 | color: Colors.white, 30 | ), 31 | ), 32 | ), 33 | ); 34 | }, 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /example/macos/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | $(PRODUCT_COPYRIGHT) 27 | NSMainNibFile 28 | MainMenu 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /example/lib/src/chroma_counter/notifiers/chroma_counter_notifier.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math' as math; 2 | import 'package:flutter/material.dart'; 3 | import 'package:minimal_mvn/minimal_mvn.dart'; 4 | import '../views/ui_states/chroma_counter_ui_state.dart'; 5 | 6 | class ChromaCounterNotifier extends MMNotifier { 7 | ChromaCounterNotifier() : super(const ChromaCounterUIState()); 8 | 9 | static final _random = math.Random(); 10 | 11 | void nextMetamorph() => notify( 12 | state.copyWith( 13 | count: state.count + 1, 14 | backgroundColor: _randomColor(), 15 | borderRadius: _randomRadius(), 16 | ), 17 | ); 18 | 19 | Color _randomColor() => Color.fromRGBO( 20 | _random.nextInt(256), 21 | _random.nextInt(256), 22 | _random.nextInt(256), 23 | 1, 24 | ); 25 | 26 | BorderRadius _randomRadius() => BorderRadius.only( 27 | topLeft: Radius.circular(_random.nextDouble() * 100), 28 | topRight: Radius.circular(_random.nextDouble() * 100), 29 | bottomLeft: Radius.circular(_random.nextDouble() * 100), 30 | bottomRight: Radius.circular(_random.nextDouble() * 100), 31 | ); 32 | } 33 | 34 | final MMManager chromaCounterManager = 35 | MMManager(ChromaCounterNotifier.new, autodispose: true); 36 | -------------------------------------------------------------------------------- /example/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | minimal_example 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "minimal", 9 | "request": "launch", 10 | "type": "dart" 11 | }, 12 | { 13 | "name": "minimal (profile mode)", 14 | "request": "launch", 15 | "type": "dart", 16 | "flutterMode": "profile" 17 | }, 18 | { 19 | "name": "minimal (release mode)", 20 | "request": "launch", 21 | "type": "dart", 22 | "flutterMode": "release" 23 | }, 24 | { 25 | "name": "example", 26 | "cwd": "example", 27 | "request": "launch", 28 | "type": "dart" 29 | }, 30 | { 31 | "name": "example (profile mode)", 32 | "cwd": "example", 33 | "request": "launch", 34 | "type": "dart", 35 | "flutterMode": "profile" 36 | }, 37 | { 38 | "name": "example (release mode)", 39 | "cwd": "example", 40 | "request": "launch", 41 | "type": "dart", 42 | "flutterMode": "release" 43 | } 44 | ] 45 | } -------------------------------------------------------------------------------- /example/windows/runner/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "flutter_window.h" 6 | #include "utils.h" 7 | 8 | int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, 9 | _In_ wchar_t *command_line, _In_ int show_command) { 10 | // Attach to console when present (e.g., 'flutter run') or create a 11 | // new console when running with a debugger. 12 | if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { 13 | CreateAndAttachConsole(); 14 | } 15 | 16 | // Initialize COM, so that it is available for use in the library and/or 17 | // plugins. 18 | ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); 19 | 20 | flutter::DartProject project(L"data"); 21 | 22 | std::vector command_line_arguments = 23 | GetCommandLineArguments(); 24 | 25 | project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); 26 | 27 | FlutterWindow window(project); 28 | Win32Window::Point origin(10, 10); 29 | Win32Window::Size size(1280, 720); 30 | if (!window.Create(L"minimal_example", origin, size)) { 31 | return EXIT_FAILURE; 32 | } 33 | window.SetQuitOnClose(true); 34 | 35 | ::MSG msg; 36 | while (::GetMessage(&msg, nullptr, 0, 0)) { 37 | ::TranslateMessage(&msg); 38 | ::DispatchMessage(&msg); 39 | } 40 | 41 | ::CoUninitialize(); 42 | return EXIT_SUCCESS; 43 | } 44 | -------------------------------------------------------------------------------- /example/.cursor/rules/dart-coding.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | globs: 4 | alwaysApply: false 5 | --- 6 | # Dart Coding Guidelines 7 | 8 | 9 | files: **/*.dart 10 | 11 | ## Description 12 | These guidelines apply to all Dart files in the project and outline basic coding principles, style conventions, and best practices 13 | 14 | ## Key Principles 15 | - Write concise, technical Dart code with accurate examples 16 | - Use functional and declarative programming patterns 17 | - Choose descriptive variable names with auxiliary verbs (e.g., `isLoading`, `hasError`) 18 | - Keep functions and classes short (< 200 lines, < 10 public methods) 19 | 20 | ## Code Style 21 | - Always use English for code and documentation 22 | - Use PascalCase for classes, camelCase for variables and functions, snake_case for files, UPPERCASE for constants 23 | 24 | ## Best Practices 25 | - Use arrow syntax for simple methods and expression bodies for one-line getters/setters 26 | - Avoid nesting blocks; prefer early checks and returns 27 | - Pass and return parameters using RO-RO (Receive Object, Return Object) 28 | - Avoid magic numbers; use well-named constants 29 | - Follow an 100-character line limit and use trailing commas for better formatting 30 | - Follow the lint rules set for this project 31 | - All lint rules are enabled in `all_lint_rules.yaml` 32 | - Only specific rules are selectively disabled in `analysis_options.yaml` 33 | 34 | ## Debugging 35 | - Prefer `debugPrint()` over `print()` for debugging 36 | 37 | @all_lint_rules.yaml 38 | @analysis_options.yaml 39 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "com.android.application" 3 | id "kotlin-android" 4 | // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. 5 | id "dev.flutter.flutter-gradle-plugin" 6 | } 7 | 8 | android { 9 | namespace = "com.example.minimal_example" 10 | compileSdk = flutter.compileSdkVersion 11 | ndkVersion = flutter.ndkVersion 12 | 13 | compileOptions { 14 | sourceCompatibility = JavaVersion.VERSION_1_8 15 | targetCompatibility = JavaVersion.VERSION_1_8 16 | } 17 | 18 | kotlinOptions { 19 | jvmTarget = JavaVersion.VERSION_1_8 20 | } 21 | 22 | defaultConfig { 23 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 24 | applicationId = "com.example.minimal_example" 25 | // You can update the following values to match your application needs. 26 | // For more information, see: https://flutter.dev/to/review-gradle-config. 27 | minSdk = flutter.minSdkVersion 28 | targetSdk = flutter.targetSdkVersion 29 | versionCode = flutter.versionCode 30 | versionName = flutter.versionName 31 | } 32 | 33 | buildTypes { 34 | release { 35 | // TODO: Add your own signing config for the release build. 36 | // Signing with the debug keys for now, so `flutter run --release` works. 37 | signingConfig = signingConfigs.debug 38 | } 39 | } 40 | } 41 | 42 | flutter { 43 | source = "../.." 44 | } 45 | -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "app_icon_16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "app_icon_32.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "app_icon_32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "app_icon_64.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "app_icon_128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "app_icon_256.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "app_icon_256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "app_icon_512.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "app_icon_512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "app_icon_1024.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Minimal Copyright (c) 2025 4 | Alessio Salvadorini (Twitter @ASalvadorini GitHub alesalv) 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 10 | 1. Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | 13 | 2. Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | 17 | 3. Neither the name of the copyright holder nor the names of its 18 | contributors may be used to endorse or promote products derived from 19 | this software without specific prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 2.0.2 2 | 3 | * Add Cursor rules 4 | * Update to Flutter 3.32.0 5 | * Update all lint rules 6 | * Update example dependencies 7 | 8 | ## 2.0.1 9 | 10 | * Update to Flutter 3.29.2 11 | * Update format to Dart 3.7.0 and onwards 12 | * Update all lint rules 13 | * Update example dependencies 14 | * More tests for select 15 | * TODOs page for the example app 16 | 17 | ## 2.0.0 18 | 19 | * Document minimal manager 20 | * Remove minimal state (breaking change), easing notifiers with primitives 21 | * Improve MMMNotifier notify() 22 | * Improve internal value notifier for selection 23 | * Make selection read-only (breaking change) 24 | 25 | ## 1.0.5 (retracted) 26 | 27 | * Document minimal manager 28 | * Remove minimal state (breaking change), easing notifiers with primitives 29 | * Improve MMMNotifier notify() 30 | * Improve internal value notifier for selection 31 | * Make selection read-only 32 | 33 | ## 1.0.4 34 | 35 | * Deploy web example to GitHub Pages upon publishing 36 | * Update README.md about web example 37 | * Add package version on example main page 38 | 39 | ## 1.0.3 40 | 41 | * Add pub and license badges to README.md 42 | * Rename morphing example to chroma counter 43 | * Add override() method to MMManager for testing purposes 44 | * Add test for chroma counter widget using mocked notifier 45 | 46 | ## 1.0.2 47 | 48 | * Expose a check for notifier been disposed 49 | * Test when notifier is accessed after disposal 50 | * Improve README.md 51 | * Improve pubspec.yaml 52 | 53 | ## 1.0.1 54 | 55 | * Fix logo URL in README.md 56 | 57 | ## 1.0.0 58 | 59 | * Initial release of Minimal state management package 60 | * Features: 61 | * MVN (Model-View-Notifier) pattern 62 | * Lazy initialization of notifiers 63 | * Optional autodispose for notifiers 64 | * State selection for optimized rebuilds 65 | * Dependency injection with locator 66 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | Minimal Example 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | minimal_example 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(FLUTTER_BUILD_NUMBER) 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | CADisableMinimumFrameDurationOnPhone 45 | 46 | UIApplicationSupportsIndirectInputEvents 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: "17025dd88227cd9532c33fa78f5250d548d87e9a" 8 | channel: "stable" 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 17 | base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 18 | - platform: android 19 | create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 20 | base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 21 | - platform: ios 22 | create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 23 | base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 24 | - platform: linux 25 | create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 26 | base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 27 | - platform: macos 28 | create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 29 | base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 30 | - platform: web 31 | create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 32 | base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 33 | - platform: windows 34 | create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 35 | base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 36 | 37 | # User provided section 38 | 39 | # List of Local paths (relative to this file) that should be 40 | # ignored by the migrate tool. 41 | # 42 | # Files that are not part of the templates will be ignored by default. 43 | unmanaged_files: 44 | - 'lib/main.dart' 45 | - 'ios/Runner.xcodeproj/project.pbxproj' 46 | -------------------------------------------------------------------------------- /example/.cursor/rules/flutter-architecture.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | globs: 4 | alwaysApply: false 5 | --- 6 | # Flutter Architecture Guidelines 7 | 8 | 9 | files: lib/src/**/*.dart 10 | 11 | ## Description 12 | These guidelines define the project's architectural approach using MVN (Model View Notifier) and folder structure conventions 13 | 14 | ## Structure 15 | - Organize code according to feature-based MVN pattern: 16 | - featureA/ 17 | - notifiers/ 18 | - models/ 19 | - data/ 20 | - data_sources/ 21 | - repositories/ 22 | - views/ 23 | - pages/ 24 | - ui_states/ 25 | - widgets/ 26 | - Place cross-feature components in the `core` directory 27 | - Group shared widgets in `core/views/widgets` by type (animations, buttons, cards, etc) 28 | 29 | ## Notifiers 30 | - Use MMNotifier for state management 31 | - Consider notifiers as state holders 32 | - For a notifier which holds a UI state, handle loading and error in the UI state explicitely 33 | - Usually use one notifier per page or per widget 34 | - Notifiers are focused on a single responsibility 35 | - Notifiers depend on repositories, and sometimes on other notifiers 36 | 37 | ## Models 38 | - Use Mappable classes for API models as come from server (ie ItemApiModel) 39 | - Use Mappable classes for internal models (ie Item) 40 | - Use Mappable classes for UI state models (ie ItemUiState) 41 | - Implement repository pattern for persistance 42 | - Repositories orchestate between data sources (return Item) 43 | - Repositories can have from zero to many data sources 44 | - Usually repositories have at least local and remote data sources 45 | - Data sources access raw data (return ItemApiModel) 46 | 47 | ## Views 48 | - Keep widgets small and focused 49 | - Extract reusable widgets 50 | - Each page widget must have a well defined corresponding UI state 51 | - Widget build() method only builds, doesn't contain any business logic 52 | -------------------------------------------------------------------------------- /example/windows/runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | project(runner LANGUAGES CXX) 3 | 4 | # Define the application target. To change its name, change BINARY_NAME in the 5 | # top-level CMakeLists.txt, not the value here, or `flutter run` will no longer 6 | # work. 7 | # 8 | # Any new source files that you add to the application should be added here. 9 | add_executable(${BINARY_NAME} WIN32 10 | "flutter_window.cpp" 11 | "main.cpp" 12 | "utils.cpp" 13 | "win32_window.cpp" 14 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 15 | "Runner.rc" 16 | "runner.exe.manifest" 17 | ) 18 | 19 | # Apply the standard set of build settings. This can be removed for applications 20 | # that need different build settings. 21 | apply_standard_settings(${BINARY_NAME}) 22 | 23 | # Add preprocessor definitions for the build version. 24 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") 25 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") 26 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") 27 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") 28 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") 29 | 30 | # Disable Windows macros that collide with C++ standard library functions. 31 | target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") 32 | 33 | # Add dependency libraries and include directories. Add any application-specific 34 | # dependencies here. 35 | target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) 36 | target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") 37 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") 38 | 39 | # Run the Flutter tool portions of the build. This must not be removed. 40 | add_dependencies(${BINARY_NAME} flutter_assemble) 41 | -------------------------------------------------------------------------------- /example/test/src/chroma_counter/views/widgets/chroma_counter_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | import 'package:minimal_example/src/chroma_counter/notifiers/chroma_counter_notifier.dart'; 4 | import 'package:minimal_example/src/chroma_counter/views/widgets/chroma_counter.dart'; 5 | 6 | void main() { 7 | group('ChromaCounter', () { 8 | testWidgets('should update UI when state changes', (final tester) async { 9 | // Use the minimal manager to override the notifier 10 | chromaCounterManager.override(MockChromaCounterNotifier.new); 11 | 12 | await tester.pumpWidget(const MaterialApp(home: ChromaCounter())); 13 | 14 | expect(find.text('0'), findsOneWidget); 15 | final initialColor = _getContainerColor(tester); 16 | expect(initialColor, equals(Colors.blue)); 17 | 18 | // Change state through the mock notifier 19 | chromaCounterManager.notifier.nextMetamorph(); 20 | await tester.pump(); 21 | 22 | expect(find.text('1'), findsOneWidget); 23 | final newColor = _getContainerColor(tester); 24 | expect(newColor, isNot(equals(initialColor))); 25 | // Test the exact color instead of a random one 26 | expect(newColor, equals(Colors.red)); 27 | }); 28 | }); 29 | } 30 | 31 | Color _getContainerColor(final WidgetTester tester) { 32 | final container = tester.widget( 33 | find.byType(AnimatedContainer), 34 | ); 35 | final decoration = container.decoration! as BoxDecoration; 36 | return decoration.color!; 37 | } 38 | 39 | /// A mock [ChromaCounterNotifier] that uses predictable colors for testing 40 | /// purposes 41 | class MockChromaCounterNotifier extends ChromaCounterNotifier { 42 | @override 43 | void nextMetamorph() { 44 | notify( 45 | state.copyWith( 46 | backgroundColor: Colors.red, // Predictable color 47 | count: state.count + 1, 48 | ), 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /example/windows/runner/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | void CreateAndAttachConsole() { 11 | if (::AllocConsole()) { 12 | FILE *unused; 13 | if (freopen_s(&unused, "CONOUT$", "w", stdout)) { 14 | _dup2(_fileno(stdout), 1); 15 | } 16 | if (freopen_s(&unused, "CONOUT$", "w", stderr)) { 17 | _dup2(_fileno(stdout), 2); 18 | } 19 | std::ios::sync_with_stdio(); 20 | FlutterDesktopResyncOutputStreams(); 21 | } 22 | } 23 | 24 | std::vector GetCommandLineArguments() { 25 | // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. 26 | int argc; 27 | wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); 28 | if (argv == nullptr) { 29 | return std::vector(); 30 | } 31 | 32 | std::vector command_line_arguments; 33 | 34 | // Skip the first argument as it's the binary name. 35 | for (int i = 1; i < argc; i++) { 36 | command_line_arguments.push_back(Utf8FromUtf16(argv[i])); 37 | } 38 | 39 | ::LocalFree(argv); 40 | 41 | return command_line_arguments; 42 | } 43 | 44 | std::string Utf8FromUtf16(const wchar_t* utf16_string) { 45 | if (utf16_string == nullptr) { 46 | return std::string(); 47 | } 48 | unsigned int target_length = ::WideCharToMultiByte( 49 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 50 | -1, nullptr, 0, nullptr, nullptr) 51 | -1; // remove the trailing null character 52 | int input_length = (int)wcslen(utf16_string); 53 | std::string utf8_string; 54 | if (target_length == 0 || target_length > utf8_string.max_size()) { 55 | return utf8_string; 56 | } 57 | utf8_string.resize(target_length); 58 | int converted_length = ::WideCharToMultiByte( 59 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 60 | input_length, utf8_string.data(), target_length, nullptr, nullptr); 61 | if (converted_length == 0) { 62 | return std::string(); 63 | } 64 | return utf8_string; 65 | } 66 | -------------------------------------------------------------------------------- /lib/src/mm_manager.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'mm_notifier.dart'; 4 | 5 | /// Minimal manager 6 | /// 7 | /// A minimal manager is responsible for handling the lifecycle of a 8 | /// [MMNotifier]. It lazily creates the notifier's instance, provides 9 | /// access to it, and it can optionally dispose the notifier when it has no 10 | /// more subscribers 11 | /// 12 | /// Example: 13 | /// ```dart 14 | /// final counterManager = MMManager( 15 | /// () => CounterNotifier(), 16 | /// autodispose: true, 17 | /// ); 18 | /// ``` 19 | class MMManager> { 20 | /// Creates a minimal manager for the given minimal notifier type 21 | /// 22 | /// The optional [autodispose] parameter dictates whether the notifier 23 | /// should be autodisposed or not (singleton case) 24 | MMManager(this._create, {this.autodispose = false}); 25 | 26 | T? _instance; 27 | final T Function() _create; 28 | T Function()? _override; 29 | 30 | /// Whether the notifier should be disposed when it has no more subscribers 31 | final bool autodispose; 32 | 33 | /// Returns the notifier instance managed by this manager 34 | /// 35 | /// If no instance exists, a new one will be created. The same instance will 36 | /// be returned on subsequent calls unless [autodispose] is true and the 37 | /// notifier was disposed 38 | /// 39 | /// Example: 40 | /// ```dart 41 | /// final counter = counterManager.notifier; 42 | /// counter.increment(); 43 | /// ``` 44 | T get notifier { 45 | final creator = _override ?? _create; 46 | _instance ??= creator()..onUnsubscribed = _onUnsubscribed; 47 | return _instance!; 48 | } 49 | 50 | /// Overrides the notifier factory for testing purposes 51 | /// 52 | /// This method is only available in tests and should not be used in 53 | /// production 54 | /// 55 | /// Example: 56 | /// ```dart 57 | /// chromaCounterManager.override(MockChromaCounterNotifier.new); 58 | /// ``` 59 | @visibleForTesting 60 | void override(final T Function() override) { 61 | _override = override; 62 | _instance = null; 63 | } 64 | 65 | void _onUnsubscribed() { 66 | if (autodispose) { 67 | _instance?.dispose(); 68 | _instance = null; 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /example/lib/src/counter/views/pages/counter_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../notifiers/counter_notifier.dart'; 4 | 5 | class CounterPage extends StatelessWidget { 6 | const CounterPage({super.key}); 7 | 8 | @override 9 | Widget build(final BuildContext context) { 10 | return Scaffold( 11 | appBar: AppBar( 12 | title: const Text('Counter'), 13 | ), 14 | body: const Center( 15 | child: Column( 16 | mainAxisAlignment: MainAxisAlignment.center, 17 | spacing: 16, 18 | children: [ 19 | _CounterPanel(), 20 | _DisposableCounterPanel(), 21 | ], 22 | ), 23 | ), 24 | floatingActionButton: FloatingActionButton( 25 | onPressed: () { 26 | counterManager.notifier.increment(); 27 | disposableCounterManager.notifier.increment(); 28 | }, 29 | tooltip: 'Increment', 30 | child: const Icon(Icons.add), 31 | ), 32 | ); 33 | } 34 | } 35 | 36 | class _CounterPanel extends StatelessWidget { 37 | const _CounterPanel(); 38 | 39 | @override 40 | Widget build(final BuildContext context) { 41 | final notifier = counterManager.notifier; 42 | return ListenableBuilder( 43 | listenable: notifier, 44 | builder: (final _, final __) { 45 | return Column( 46 | children: [ 47 | const Text('You have pushed the button this many times:'), 48 | Text( 49 | notifier.state.toString(), 50 | style: const TextStyle(fontSize: 48), 51 | ), 52 | ], 53 | ); 54 | }, 55 | ); 56 | } 57 | } 58 | 59 | class _DisposableCounterPanel extends StatelessWidget { 60 | const _DisposableCounterPanel(); 61 | 62 | @override 63 | Widget build(final BuildContext context) { 64 | final notifier = disposableCounterManager.notifier; 65 | return ListenableBuilder( 66 | listenable: notifier, 67 | builder: (final _, final __) { 68 | return Column( 69 | children: [ 70 | const Text('You have pushed the button this many times:'), 71 | Text( 72 | notifier.state.toString(), 73 | style: const TextStyle(fontSize: 48), 74 | ), 75 | ], 76 | ); 77 | }, 78 | ); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 15 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 30 | 33 | 34 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /example/windows/runner/flutter_window.cpp: -------------------------------------------------------------------------------- 1 | #include "flutter_window.h" 2 | 3 | #include 4 | 5 | #include "flutter/generated_plugin_registrant.h" 6 | 7 | FlutterWindow::FlutterWindow(const flutter::DartProject& project) 8 | : project_(project) {} 9 | 10 | FlutterWindow::~FlutterWindow() {} 11 | 12 | bool FlutterWindow::OnCreate() { 13 | if (!Win32Window::OnCreate()) { 14 | return false; 15 | } 16 | 17 | RECT frame = GetClientArea(); 18 | 19 | // The size here must match the window dimensions to avoid unnecessary surface 20 | // creation / destruction in the startup path. 21 | flutter_controller_ = std::make_unique( 22 | frame.right - frame.left, frame.bottom - frame.top, project_); 23 | // Ensure that basic setup of the controller was successful. 24 | if (!flutter_controller_->engine() || !flutter_controller_->view()) { 25 | return false; 26 | } 27 | RegisterPlugins(flutter_controller_->engine()); 28 | SetChildContent(flutter_controller_->view()->GetNativeWindow()); 29 | 30 | flutter_controller_->engine()->SetNextFrameCallback([&]() { 31 | this->Show(); 32 | }); 33 | 34 | // Flutter can complete the first frame before the "show window" callback is 35 | // registered. The following call ensures a frame is pending to ensure the 36 | // window is shown. It is a no-op if the first frame hasn't completed yet. 37 | flutter_controller_->ForceRedraw(); 38 | 39 | return true; 40 | } 41 | 42 | void FlutterWindow::OnDestroy() { 43 | if (flutter_controller_) { 44 | flutter_controller_ = nullptr; 45 | } 46 | 47 | Win32Window::OnDestroy(); 48 | } 49 | 50 | LRESULT 51 | FlutterWindow::MessageHandler(HWND hwnd, UINT const message, 52 | WPARAM const wparam, 53 | LPARAM const lparam) noexcept { 54 | // Give Flutter, including plugins, an opportunity to handle window messages. 55 | if (flutter_controller_) { 56 | std::optional result = 57 | flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, 58 | lparam); 59 | if (result) { 60 | return *result; 61 | } 62 | } 63 | 64 | switch (message) { 65 | case WM_FONTCHANGE: 66 | flutter_controller_->engine()->ReloadSystemFonts(); 67 | break; 68 | } 69 | 70 | return Win32Window::MessageHandler(hwnd, message, wparam, lparam); 71 | } 72 | -------------------------------------------------------------------------------- /test/src/mm_manager_test.dart: -------------------------------------------------------------------------------- 1 | // Let's make the tests more readable and explicit 2 | // ignore_for_file: cascade_invocations 3 | 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_test/flutter_test.dart'; 6 | import 'package:minimal_mvn/minimal_mvn.dart'; 7 | 8 | import 'utils/test_common.dart'; 9 | 10 | void main() { 11 | group('MMManager', () { 12 | test('creates notifier lazily', () { 13 | var created = 0; 14 | final manager = MMManager(() { 15 | created++; 16 | return TNotifier(); 17 | }); 18 | expect(created, 0); 19 | manager.notifier; 20 | expect(created, 1); 21 | manager.notifier; 22 | expect(created, 1); 23 | }); 24 | 25 | test('keeps instance when autodispose is false (singleton)', () { 26 | final manager = MMManager(TNotifier.new); 27 | final first = manager.notifier; 28 | first.increment(); 29 | 30 | // Adding and removing a listener triggers disposing the notifier 31 | void l() {} 32 | first 33 | ..addListener(l) 34 | ..removeListener(l); 35 | 36 | final second = manager.notifier; 37 | 38 | expect(second, same(first)); 39 | expect(second.state.value, 1); 40 | }); 41 | 42 | test('disposes instance when autodispose is true', () { 43 | final manager = MMManager( 44 | TNotifier.new, 45 | autodispose: true, 46 | ); 47 | 48 | final first = manager.notifier; 49 | first.increment(); 50 | 51 | // Adding and removing a listener triggers disposing the notifier 52 | void l() {} 53 | first 54 | ..addListener(l) 55 | ..removeListener(l); 56 | 57 | final second = manager.notifier; 58 | 59 | expect(second, isNot(same(first))); 60 | expect(second.state.value, 0); 61 | }); 62 | 63 | test('throws an error when accessing notifier after disposal', () { 64 | final manager = MMManager( 65 | TNotifier.new, 66 | autodispose: true, 67 | ); 68 | 69 | final notifier = manager.notifier; 70 | notifier.increment(); 71 | 72 | // Adding and removing a listener triggers disposing the notifier 73 | void l() {} 74 | notifier 75 | ..addListener(l) 76 | ..removeListener(l); 77 | 78 | expect( 79 | notifier.increment, 80 | throwsA(isA()), 81 | reason: 'Accessing notifier after disposal will throw an error', 82 | ); 83 | }); 84 | }); 85 | } 86 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/.cursor/rules/data-models.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | globs: 4 | alwaysApply: false 5 | --- 6 | # Data Models Guidelines 7 | 8 | 9 | files: lib/src/**/models/**/*.dart 10 | 11 | ## Description 12 | Guidelines for data models using Dart Mappable and JSON serialization. The application uses a three-layer data model pattern to handle data transformation and state management: 13 | 14 | 1. **API Layer (ItemApiModel)** 15 | - Represents the raw data structure as received from the server 16 | - Contains all fields provided by the API 17 | - Should match the API contract exactly 18 | - Example: `ItemApiModel` 19 | 20 | 2. **Domain Layer (Item)** 21 | - Represents the internal data model 22 | - Contains only the fields necessary for business logic 23 | - Strips out unnecessary API fields 24 | - Example: `Item` 25 | 26 | 3. **UI Layer (ItemUiState)** 27 | - Represents the data model optimized for UI rendering 28 | - Contains parsed and formatted data ready for display 29 | - Handles all UI-specific transformations 30 | - Example: `ItemUiState` 31 | 32 | ## Rules 33 | - Use Dart Mappable for defining immutable UI states 34 | - Each layer should have its own type definition 35 | - The UI layer should use the UI state data models, never directly the domain model or the API model 36 | - The UI state model should be derived from the domain model, not the API model 37 | - The domain model should be derived from the API model, not the UI state model 38 | - The repository should be the source of truth, and it returns the domain model (for example Item) 39 | - The repository use data sources, which return the API model (for example ItemApiModel) 40 | 41 | ## Example 42 | Here's an example of each data model (ItemApiModel, Item, ItemUiState): 43 | 44 | ```dart 45 | @MappableClass() 46 | class ItemApiModel with ItemApiModelMappable { 47 | const ItemApiModel({ 48 | this.id = '', 49 | this.images = const [], 50 | this.price = '', 51 | this.title = '', 52 | this.desc = '', 53 | }); 54 | 55 | factory ItemApiModel.fromJson(final JsonObj json) => ItemApiModelMapper.fromJson(json); 56 | 57 | final String id; 58 | final List images; 59 | final String price; 60 | final String title; 61 | final String desc; 62 | } 63 | 64 | @MappableClass() 65 | class Item with ItemMappable { 66 | const Item({ 67 | required this.id, 68 | required this.title, 69 | }); 70 | 71 | factory Item.fromApiModel(final ItemApiModel model) => Item(id: model.id, title: model.title); 72 | 73 | final String id; 74 | final String title; 75 | } 76 | 77 | @MappableClass() 78 | class ItemUiState with ItemUiStateMappable { 79 | const ItemUiState({ 80 | this.id = '', 81 | this.title = '', 82 | }); 83 | 84 | factory ItemUiState.fromModel(final Item model) => ItemUiState(id: model.id, title: model.title); 85 | 86 | final String id; 87 | final String title; 88 | } 89 | ``` 90 | -------------------------------------------------------------------------------- /example/linux/flutter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file controls Flutter-level build steps. It should not be edited. 2 | cmake_minimum_required(VERSION 3.10) 3 | 4 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") 5 | 6 | # Configuration provided via flutter tool. 7 | include(${EPHEMERAL_DIR}/generated_config.cmake) 8 | 9 | # TODO: Move the rest of this into files in ephemeral. See 10 | # https://github.com/flutter/flutter/issues/57146. 11 | 12 | # Serves the same purpose as list(TRANSFORM ... PREPEND ...), 13 | # which isn't available in 3.10. 14 | function(list_prepend LIST_NAME PREFIX) 15 | set(NEW_LIST "") 16 | foreach(element ${${LIST_NAME}}) 17 | list(APPEND NEW_LIST "${PREFIX}${element}") 18 | endforeach(element) 19 | set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) 20 | endfunction() 21 | 22 | # === Flutter Library === 23 | # System-level dependencies. 24 | find_package(PkgConfig REQUIRED) 25 | pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) 26 | pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) 27 | pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) 28 | 29 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") 30 | 31 | # Published to parent scope for install step. 32 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) 33 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) 34 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) 35 | set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) 36 | 37 | list(APPEND FLUTTER_LIBRARY_HEADERS 38 | "fl_basic_message_channel.h" 39 | "fl_binary_codec.h" 40 | "fl_binary_messenger.h" 41 | "fl_dart_project.h" 42 | "fl_engine.h" 43 | "fl_json_message_codec.h" 44 | "fl_json_method_codec.h" 45 | "fl_message_codec.h" 46 | "fl_method_call.h" 47 | "fl_method_channel.h" 48 | "fl_method_codec.h" 49 | "fl_method_response.h" 50 | "fl_plugin_registrar.h" 51 | "fl_plugin_registry.h" 52 | "fl_standard_message_codec.h" 53 | "fl_standard_method_codec.h" 54 | "fl_string_codec.h" 55 | "fl_value.h" 56 | "fl_view.h" 57 | "flutter_linux.h" 58 | ) 59 | list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") 60 | add_library(flutter INTERFACE) 61 | target_include_directories(flutter INTERFACE 62 | "${EPHEMERAL_DIR}" 63 | ) 64 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") 65 | target_link_libraries(flutter INTERFACE 66 | PkgConfig::GTK 67 | PkgConfig::GLIB 68 | PkgConfig::GIO 69 | ) 70 | add_dependencies(flutter flutter_assemble) 71 | 72 | # === Flutter tool backend === 73 | # _phony_ is a non-existent file to force this command to run every time, 74 | # since currently there's no way to get a full input/output list from the 75 | # flutter tool. 76 | add_custom_command( 77 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} 78 | ${CMAKE_CURRENT_BINARY_DIR}/_phony_ 79 | COMMAND ${CMAKE_COMMAND} -E env 80 | ${FLUTTER_TOOL_ENVIRONMENT} 81 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" 82 | ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} 83 | VERBATIM 84 | ) 85 | add_custom_target(flutter_assemble DEPENDS 86 | "${FLUTTER_LIBRARY}" 87 | ${FLUTTER_LIBRARY_HEADERS} 88 | ) 89 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # All lint rules are activated in all_lint_rules.yaml 2 | # In this file only the needed lint rules are disabled 3 | # Roughly based on Mike and Ale's approach explained in details here, with comments: 4 | # https://gist.github.com/rydmike/fdb53ddd933c37d20e6f3188a936cd4c 5 | include: all_lint_rules.yaml 6 | 7 | ########## formatter ########## 8 | formatter: 9 | page_width: 100 10 | 11 | ########## analyzer ########## 12 | analyzer: 13 | exclude: 14 | - "**/*.g.dart" 15 | - "**/*.freezed.dart" 16 | - "test/.test_coverage.dart" 17 | - "bin/cache/**" 18 | - "lib/generated_plugin_registrant.dart" 19 | 20 | # More information at: 21 | # https://dart.dev/guides/language/analysis-options#enabling-additional-type-checks 22 | language: 23 | # enforce explicit cast from dynamic to a more specific type 24 | strict-casts: true 25 | # never choose dynamic type when type can't be determined 26 | strict-inference: true 27 | # never choose dynamic type when type can't be determined omitting the type arguments 28 | strict-raw-types: true 29 | 30 | errors: 31 | # When importing all_lint_rules.yaml, some rules conflict, and we resolve them in this file; 32 | # but this error create warnings about them, so we ignore them. To see the conflicts, 33 | # temporarily set this error to 'warning' 34 | included_file_warning: ignore 35 | 36 | # Error about missing required params 37 | missing_required_param: error 38 | 39 | # Error about missing returns 40 | missing_return: error 41 | 42 | # Warn about reassigning references to parameters of functions or methods 43 | parameter_assignments: warning 44 | 45 | # Allow TODOs 46 | todo: ignore 47 | 48 | ########## linter ########## 49 | linter: 50 | # The lint rules applied to this project can be customized in the 51 | # section below to disable rules 52 | # 53 | # Instead of disabling a lint rule for the entire project in the 54 | # section below, it can also be suppressed for a single line of code 55 | # or a specific dart file by using the `// ignore: name_of_lint` and 56 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 57 | # producing the lint. 58 | rules: 59 | # too strict, makes code too verbose 60 | always_specify_types: false 61 | # incompatible with prefer_relative_imports 62 | always_use_package_imports: false 63 | # incompatible with prefer_final_parameters 64 | avoid_final_parameters: false 65 | # do not avoid types for function expression params 66 | avoid_types_on_closure_parameters: false 67 | # too strict, makes code too verbose 68 | diagnostic_describe_all_properties: false 69 | # incompatible with page_width: 100 70 | lines_longer_than_80_chars: false 71 | # incompatible with prefer_single_quotes 72 | prefer_double_quotes: false 73 | # too strict, preferred but no need to enforce it 74 | prefer_expression_function_bodies: false 75 | # too strict, even if it could make sense to enforce documentation 76 | public_member_api_docs: false 77 | # incompatible with omit_local_variable_types 78 | specify_nonobvious_local_variable_types: false 79 | # incompatible with omit_obvious_property_types 80 | specify_nonobvious_property_types: false 81 | # incompatible with prefer_final_locals 82 | unnecessary_final: false 83 | -------------------------------------------------------------------------------- /example/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # All lint rules are activated in all_lint_rules.yaml 2 | # In this file only the needed lint rules are disabled 3 | # Roughly based on Mike and Ale's approach explained in details here, with comments: 4 | # https://gist.github.com/rydmike/fdb53ddd933c37d20e6f3188a936cd4c 5 | include: all_lint_rules.yaml 6 | 7 | ########## formatter ########## 8 | formatter: 9 | page_width: 100 10 | 11 | ########## analyzer ########## 12 | analyzer: 13 | exclude: 14 | - "**/*.g.dart" 15 | - "**/*.freezed.dart" 16 | - "test/.test_coverage.dart" 17 | - "bin/cache/**" 18 | - "lib/generated_plugin_registrant.dart" 19 | 20 | # More information at: 21 | # https://dart.dev/guides/language/analysis-options#enabling-additional-type-checks 22 | language: 23 | # enforce explicit cast from dynamic to a more specific type 24 | strict-casts: true 25 | # never choose dynamic type when type can't be determined 26 | strict-inference: true 27 | # never choose dynamic type when type can't be determined omitting the type arguments 28 | strict-raw-types: true 29 | 30 | errors: 31 | # When importing all_lint_rules.yaml, some rules conflict, and we resolve them in this file; 32 | # but this error create warnings about them, so we ignore them. To see the conflicts, 33 | # temporarily set this error to 'warning' 34 | included_file_warning: ignore 35 | 36 | # Error about missing required params 37 | missing_required_param: error 38 | 39 | # Error about missing returns 40 | missing_return: error 41 | 42 | # Warn about reassigning references to parameters of functions or methods 43 | parameter_assignments: warning 44 | 45 | # Allow TODOs 46 | todo: ignore 47 | 48 | ########## linter ########## 49 | linter: 50 | # The lint rules applied to this project can be customized in the 51 | # section below to disable rules 52 | # 53 | # Instead of disabling a lint rule for the entire project in the 54 | # section below, it can also be suppressed for a single line of code 55 | # or a specific dart file by using the `// ignore: name_of_lint` and 56 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 57 | # producing the lint. 58 | rules: 59 | # too strict, makes code too verbose 60 | always_specify_types: false 61 | # incompatible with prefer_relative_imports 62 | always_use_package_imports: false 63 | # incompatible with prefer_final_parameters 64 | avoid_final_parameters: false 65 | # do not avoid types for function expression params 66 | avoid_types_on_closure_parameters: false 67 | # too strict, makes code too verbose 68 | diagnostic_describe_all_properties: false 69 | # incompatible with page_width: 100 70 | lines_longer_than_80_chars: false 71 | # incompatible with prefer_single_quotes 72 | prefer_double_quotes: false 73 | # too strict, preferred but no need to enforce it 74 | prefer_expression_function_bodies: false 75 | # too strict, even if it could make sense to enforce documentation 76 | public_member_api_docs: false 77 | # incompatible with omit_local_variable_types 78 | specify_nonobvious_local_variable_types: false 79 | # incompatible with omit_obvious_property_types 80 | specify_nonobvious_property_types: false 81 | # incompatible with prefer_final_locals 82 | unnecessary_final: false 83 | -------------------------------------------------------------------------------- /example/lib/src/chroma_counter/views/pages/chroma_counter_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:intl/intl.dart'; 5 | 6 | import '../../notifiers/chroma_counter_notifier.dart'; 7 | import '../widgets/chroma_counter.dart'; 8 | 9 | class ChromaCounterPage extends StatelessWidget { 10 | const ChromaCounterPage({super.key}); 11 | 12 | @override 13 | Widget build(final BuildContext context) { 14 | return Scaffold( 15 | appBar: AppBar( 16 | title: const Text('Chroma Counter'), 17 | ), 18 | body: Center( 19 | child: Column( 20 | mainAxisAlignment: MainAxisAlignment.center, 21 | spacing: 32, 22 | children: [ 23 | const _SelectedCount(), 24 | const ChromaCounter(), 25 | ElevatedButton( 26 | onPressed: () { 27 | unawaited( 28 | showModalBottomSheet( 29 | context: context, 30 | builder: (final context) => const _BottomSheetContent(), 31 | isScrollControlled: true, 32 | constraints: BoxConstraints( 33 | maxHeight: MediaQuery.of(context).size.height * 0.9, 34 | ), 35 | useSafeArea: true, 36 | ), 37 | ); 38 | }, 39 | child: const Text('Reveal'), 40 | ), 41 | ], 42 | ), 43 | ), 44 | floatingActionButton: const _Button(), 45 | floatingActionButtonLocation: FloatingActionButtonLocation.endFloat, 46 | ); 47 | } 48 | } 49 | 50 | class _SelectedCount extends StatelessWidget { 51 | const _SelectedCount(); 52 | static final _formatter = DateFormat('mm:ss'); 53 | 54 | @override 55 | Widget build(final BuildContext context) { 56 | final notifier = chromaCounterManager.notifier; 57 | 58 | return ListenableBuilder( 59 | listenable: notifier.select((final state) => state.milestone), 60 | builder: (final context, final _) { 61 | final now = _formatter.format(DateTime.now()); 62 | return Text( 63 | 'Selected: ${notifier.state.milestone} at $now', 64 | style: Theme.of(context).textTheme.titleMedium, 65 | ); 66 | }, 67 | ); 68 | } 69 | } 70 | 71 | class _BottomSheetContent extends StatelessWidget { 72 | const _BottomSheetContent(); 73 | 74 | @override 75 | Widget build(final BuildContext context) { 76 | return const SizedBox( 77 | width: double.infinity, 78 | height: 400, 79 | child: Stack( 80 | clipBehavior: Clip.none, 81 | children: [ 82 | Center( 83 | child: ChromaCounter(), 84 | ), 85 | Positioned( 86 | right: 16, 87 | bottom: 32, 88 | child: _Button(), 89 | ), 90 | ], 91 | ), 92 | ); 93 | } 94 | } 95 | 96 | class _Button extends StatelessWidget { 97 | const _Button(); 98 | 99 | @override 100 | Widget build(final BuildContext context) { 101 | return FloatingActionButton( 102 | onPressed: () => chromaCounterManager.notifier.nextMetamorph(), 103 | child: const Icon(Icons.refresh), 104 | ); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /example/lib/src/minimal_app.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_svg/flutter_svg.dart'; 5 | 6 | import 'chroma_counter/views/pages/chroma_counter_page.dart'; 7 | import 'counter/views/pages/counter_page.dart'; 8 | import 'todos/views/pages/todos_page.dart'; 9 | import 'version/views/widgets/version_panel.dart'; 10 | 11 | class MinimalApp extends StatelessWidget { 12 | const MinimalApp({super.key}); 13 | 14 | @override 15 | Widget build(final BuildContext context) { 16 | return MaterialApp( 17 | theme: ThemeData.light().copyWith( 18 | appBarTheme: const AppBarTheme( 19 | backgroundColor: Color(0xFF1E293B), // Dark blue-gray 20 | foregroundColor: Colors.white, // For text and icons 21 | ), 22 | ), 23 | home: const HomePage(), 24 | ); 25 | } 26 | } 27 | 28 | class HomePage extends StatelessWidget { 29 | const HomePage({super.key}); 30 | 31 | @override 32 | Widget build(final BuildContext context) { 33 | return Scaffold( 34 | appBar: AppBar( 35 | centerTitle: true, 36 | title: Row( 37 | mainAxisSize: MainAxisSize.min, 38 | children: [ 39 | SvgPicture.asset( 40 | 'assets/minimal.svg', 41 | width: 24, 42 | height: 24, 43 | ), 44 | const SizedBox(width: 8), 45 | const Text('Minimal'), 46 | ], 47 | ), 48 | ), 49 | body: Stack( 50 | children: [ 51 | Center( 52 | child: Column( 53 | mainAxisAlignment: MainAxisAlignment.center, 54 | spacing: 16, 55 | children: [ 56 | ElevatedButton( 57 | onPressed: () { 58 | unawaited( 59 | Navigator.of(context).push( 60 | MaterialPageRoute( 61 | builder: (final context) => const CounterPage(), 62 | ), 63 | ), 64 | ); 65 | }, 66 | child: const Text('Counter'), 67 | ), 68 | ElevatedButton( 69 | onPressed: () { 70 | unawaited( 71 | Navigator.of(context).push( 72 | MaterialPageRoute( 73 | builder: (final context) => const ChromaCounterPage(), 74 | ), 75 | ), 76 | ); 77 | }, 78 | child: const Text('Chroma Counter'), 79 | ), 80 | ElevatedButton( 81 | onPressed: () { 82 | unawaited( 83 | Navigator.of(context).push( 84 | MaterialPageRoute( 85 | builder: (final context) => const TodosPage(), 86 | ), 87 | ), 88 | ); 89 | }, 90 | child: const Text('Todos'), 91 | ), 92 | ], 93 | ), 94 | ), 95 | const Positioned( 96 | right: 16, 97 | bottom: 16, 98 | child: VersionPanel(), 99 | ), 100 | ], 101 | ), 102 | ); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /example/windows/runner/Runner.rc: -------------------------------------------------------------------------------- 1 | // Microsoft Visual C++ generated resource script. 2 | // 3 | #pragma code_page(65001) 4 | #include "resource.h" 5 | 6 | #define APSTUDIO_READONLY_SYMBOLS 7 | ///////////////////////////////////////////////////////////////////////////// 8 | // 9 | // Generated from the TEXTINCLUDE 2 resource. 10 | // 11 | #include "winres.h" 12 | 13 | ///////////////////////////////////////////////////////////////////////////// 14 | #undef APSTUDIO_READONLY_SYMBOLS 15 | 16 | ///////////////////////////////////////////////////////////////////////////// 17 | // English (United States) resources 18 | 19 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) 20 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US 21 | 22 | #ifdef APSTUDIO_INVOKED 23 | ///////////////////////////////////////////////////////////////////////////// 24 | // 25 | // TEXTINCLUDE 26 | // 27 | 28 | 1 TEXTINCLUDE 29 | BEGIN 30 | "resource.h\0" 31 | END 32 | 33 | 2 TEXTINCLUDE 34 | BEGIN 35 | "#include ""winres.h""\r\n" 36 | "\0" 37 | END 38 | 39 | 3 TEXTINCLUDE 40 | BEGIN 41 | "\r\n" 42 | "\0" 43 | END 44 | 45 | #endif // APSTUDIO_INVOKED 46 | 47 | 48 | ///////////////////////////////////////////////////////////////////////////// 49 | // 50 | // Icon 51 | // 52 | 53 | // Icon with lowest ID value placed first to ensure application icon 54 | // remains consistent on all systems. 55 | IDI_APP_ICON ICON "resources\\app_icon.ico" 56 | 57 | 58 | ///////////////////////////////////////////////////////////////////////////// 59 | // 60 | // Version 61 | // 62 | 63 | #if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) 64 | #define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD 65 | #else 66 | #define VERSION_AS_NUMBER 1,0,0,0 67 | #endif 68 | 69 | #if defined(FLUTTER_VERSION) 70 | #define VERSION_AS_STRING FLUTTER_VERSION 71 | #else 72 | #define VERSION_AS_STRING "1.0.0" 73 | #endif 74 | 75 | VS_VERSION_INFO VERSIONINFO 76 | FILEVERSION VERSION_AS_NUMBER 77 | PRODUCTVERSION VERSION_AS_NUMBER 78 | FILEFLAGSMASK VS_FFI_FILEFLAGSMASK 79 | #ifdef _DEBUG 80 | FILEFLAGS VS_FF_DEBUG 81 | #else 82 | FILEFLAGS 0x0L 83 | #endif 84 | FILEOS VOS__WINDOWS32 85 | FILETYPE VFT_APP 86 | FILESUBTYPE 0x0L 87 | BEGIN 88 | BLOCK "StringFileInfo" 89 | BEGIN 90 | BLOCK "040904e4" 91 | BEGIN 92 | VALUE "CompanyName", "com.example" "\0" 93 | VALUE "FileDescription", "minimal_example" "\0" 94 | VALUE "FileVersion", VERSION_AS_STRING "\0" 95 | VALUE "InternalName", "minimal_example" "\0" 96 | VALUE "LegalCopyright", "Copyright (C) 2025 com.example. All rights reserved." "\0" 97 | VALUE "OriginalFilename", "minimal_example.exe" "\0" 98 | VALUE "ProductName", "minimal_example" "\0" 99 | VALUE "ProductVersion", VERSION_AS_STRING "\0" 100 | END 101 | END 102 | BLOCK "VarFileInfo" 103 | BEGIN 104 | VALUE "Translation", 0x409, 1252 105 | END 106 | END 107 | 108 | #endif // English (United States) resources 109 | ///////////////////////////////////////////////////////////////////////////// 110 | 111 | 112 | 113 | #ifndef APSTUDIO_INVOKED 114 | ///////////////////////////////////////////////////////////////////////////// 115 | // 116 | // Generated from the TEXTINCLUDE 3 resource. 117 | // 118 | 119 | 120 | ///////////////////////////////////////////////////////////////////////////// 121 | #endif // not APSTUDIO_INVOKED 122 | -------------------------------------------------------------------------------- /example/windows/runner/win32_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_WIN32_WINDOW_H_ 2 | #define RUNNER_WIN32_WINDOW_H_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | // A class abstraction for a high DPI-aware Win32 Window. Intended to be 11 | // inherited from by classes that wish to specialize with custom 12 | // rendering and input handling 13 | class Win32Window { 14 | public: 15 | struct Point { 16 | unsigned int x; 17 | unsigned int y; 18 | Point(unsigned int x, unsigned int y) : x(x), y(y) {} 19 | }; 20 | 21 | struct Size { 22 | unsigned int width; 23 | unsigned int height; 24 | Size(unsigned int width, unsigned int height) 25 | : width(width), height(height) {} 26 | }; 27 | 28 | Win32Window(); 29 | virtual ~Win32Window(); 30 | 31 | // Creates a win32 window with |title| that is positioned and sized using 32 | // |origin| and |size|. New windows are created on the default monitor. Window 33 | // sizes are specified to the OS in physical pixels, hence to ensure a 34 | // consistent size this function will scale the inputted width and height as 35 | // as appropriate for the default monitor. The window is invisible until 36 | // |Show| is called. Returns true if the window was created successfully. 37 | bool Create(const std::wstring& title, const Point& origin, const Size& size); 38 | 39 | // Show the current window. Returns true if the window was successfully shown. 40 | bool Show(); 41 | 42 | // Release OS resources associated with window. 43 | void Destroy(); 44 | 45 | // Inserts |content| into the window tree. 46 | void SetChildContent(HWND content); 47 | 48 | // Returns the backing Window handle to enable clients to set icon and other 49 | // window properties. Returns nullptr if the window has been destroyed. 50 | HWND GetHandle(); 51 | 52 | // If true, closing this window will quit the application. 53 | void SetQuitOnClose(bool quit_on_close); 54 | 55 | // Return a RECT representing the bounds of the current client area. 56 | RECT GetClientArea(); 57 | 58 | protected: 59 | // Processes and route salient window messages for mouse handling, 60 | // size change and DPI. Delegates handling of these to member overloads that 61 | // inheriting classes can handle. 62 | virtual LRESULT MessageHandler(HWND window, 63 | UINT const message, 64 | WPARAM const wparam, 65 | LPARAM const lparam) noexcept; 66 | 67 | // Called when CreateAndShow is called, allowing subclass window-related 68 | // setup. Subclasses should return false if setup fails. 69 | virtual bool OnCreate(); 70 | 71 | // Called when Destroy is called. 72 | virtual void OnDestroy(); 73 | 74 | private: 75 | friend class WindowClassRegistrar; 76 | 77 | // OS callback called by message pump. Handles the WM_NCCREATE message which 78 | // is passed when the non-client area is being created and enables automatic 79 | // non-client DPI scaling so that the non-client area automatically 80 | // responds to changes in DPI. All other messages are handled by 81 | // MessageHandler. 82 | static LRESULT CALLBACK WndProc(HWND const window, 83 | UINT const message, 84 | WPARAM const wparam, 85 | LPARAM const lparam) noexcept; 86 | 87 | // Retrieves a class instance pointer for |window| 88 | static Win32Window* GetThisFromHandle(HWND const window) noexcept; 89 | 90 | // Update the window frame's theme to match the system theme. 91 | static void UpdateTheme(HWND const window); 92 | 93 | bool quit_on_close_ = false; 94 | 95 | // window handle for top level window. 96 | HWND window_handle_ = nullptr; 97 | 98 | // window handle for hosted content. 99 | HWND child_content_ = nullptr; 100 | }; 101 | 102 | #endif // RUNNER_WIN32_WINDOW_H_ 103 | -------------------------------------------------------------------------------- /example/lib/src/todos/models/data/task.mapper.dart: -------------------------------------------------------------------------------- 1 | // coverage:ignore-file 2 | // GENERATED CODE - DO NOT MODIFY BY HAND 3 | // ignore_for_file: type=lint 4 | // ignore_for_file: unused_element, unnecessary_cast, override_on_non_overriding_member 5 | // ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter 6 | 7 | part of 'task.dart'; 8 | 9 | class TaskMapper extends ClassMapperBase { 10 | TaskMapper._(); 11 | 12 | static TaskMapper? _instance; 13 | static TaskMapper ensureInitialized() { 14 | if (_instance == null) { 15 | MapperContainer.globals.use(_instance = TaskMapper._()); 16 | } 17 | return _instance!; 18 | } 19 | 20 | @override 21 | final String id = 'Task'; 22 | 23 | static String _$id(Task v) => v.id; 24 | static const Field _f$id = Field('id', _$id); 25 | static String _$label(Task v) => v.label; 26 | static const Field _f$label = Field('label', _$label); 27 | static bool _$isDone(Task v) => v.isDone; 28 | static const Field _f$isDone = 29 | Field('isDone', _$isDone, opt: true, def: false); 30 | 31 | @override 32 | final MappableFields fields = const { 33 | #id: _f$id, 34 | #label: _f$label, 35 | #isDone: _f$isDone, 36 | }; 37 | 38 | static Task _instantiate(DecodingData data) { 39 | return Task( 40 | id: data.dec(_f$id), 41 | label: data.dec(_f$label), 42 | isDone: data.dec(_f$isDone)); 43 | } 44 | 45 | @override 46 | final Function instantiate = _instantiate; 47 | 48 | static Task fromMap(Map map) { 49 | return ensureInitialized().decodeMap(map); 50 | } 51 | 52 | static Task fromJson(String json) { 53 | return ensureInitialized().decodeJson(json); 54 | } 55 | } 56 | 57 | mixin TaskMappable { 58 | String toJson() { 59 | return TaskMapper.ensureInitialized().encodeJson(this as Task); 60 | } 61 | 62 | Map toMap() { 63 | return TaskMapper.ensureInitialized().encodeMap(this as Task); 64 | } 65 | 66 | TaskCopyWith get copyWith => 67 | _TaskCopyWithImpl(this as Task, $identity, $identity); 68 | @override 69 | String toString() { 70 | return TaskMapper.ensureInitialized().stringifyValue(this as Task); 71 | } 72 | 73 | @override 74 | bool operator ==(Object other) { 75 | return TaskMapper.ensureInitialized().equalsValue(this as Task, other); 76 | } 77 | 78 | @override 79 | int get hashCode { 80 | return TaskMapper.ensureInitialized().hashValue(this as Task); 81 | } 82 | } 83 | 84 | extension TaskValueCopy<$R, $Out> on ObjectCopyWith<$R, Task, $Out> { 85 | TaskCopyWith<$R, Task, $Out> get $asTask => 86 | $base.as((v, t, t2) => _TaskCopyWithImpl<$R, $Out>(v, t, t2)); 87 | } 88 | 89 | abstract class TaskCopyWith<$R, $In extends Task, $Out> 90 | implements ClassCopyWith<$R, $In, $Out> { 91 | $R call({String? id, String? label, bool? isDone}); 92 | TaskCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); 93 | } 94 | 95 | class _TaskCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, Task, $Out> 96 | implements TaskCopyWith<$R, Task, $Out> { 97 | _TaskCopyWithImpl(super.value, super.then, super.then2); 98 | 99 | @override 100 | late final ClassMapperBase $mapper = TaskMapper.ensureInitialized(); 101 | @override 102 | $R call({String? id, String? label, bool? isDone}) => 103 | $apply(FieldCopyWithData({ 104 | if (id != null) #id: id, 105 | if (label != null) #label: label, 106 | if (isDone != null) #isDone: isDone 107 | })); 108 | @override 109 | Task $make(CopyWithData data) => Task( 110 | id: data.get(#id, or: $value.id), 111 | label: data.get(#label, or: $value.label), 112 | isDone: data.get(#isDone, or: $value.isDone)); 113 | 114 | @override 115 | TaskCopyWith<$R2, Task, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t) => 116 | _TaskCopyWithImpl<$R2, $Out2>($value, $cast, t); 117 | } 118 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 43 | 49 | 50 | 51 | 52 | 53 | 64 | 66 | 72 | 73 | 74 | 75 | 81 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 43 | 49 | 50 | 51 | 52 | 53 | 64 | 66 | 72 | 73 | 74 | 75 | 81 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /example/windows/flutter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file controls Flutter-level build steps. It should not be edited. 2 | cmake_minimum_required(VERSION 3.14) 3 | 4 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") 5 | 6 | # Configuration provided via flutter tool. 7 | include(${EPHEMERAL_DIR}/generated_config.cmake) 8 | 9 | # TODO: Move the rest of this into files in ephemeral. See 10 | # https://github.com/flutter/flutter/issues/57146. 11 | set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") 12 | 13 | # Set fallback configurations for older versions of the flutter tool. 14 | if (NOT DEFINED FLUTTER_TARGET_PLATFORM) 15 | set(FLUTTER_TARGET_PLATFORM "windows-x64") 16 | endif() 17 | 18 | # === Flutter Library === 19 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") 20 | 21 | # Published to parent scope for install step. 22 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) 23 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) 24 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) 25 | set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) 26 | 27 | list(APPEND FLUTTER_LIBRARY_HEADERS 28 | "flutter_export.h" 29 | "flutter_windows.h" 30 | "flutter_messenger.h" 31 | "flutter_plugin_registrar.h" 32 | "flutter_texture_registrar.h" 33 | ) 34 | list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") 35 | add_library(flutter INTERFACE) 36 | target_include_directories(flutter INTERFACE 37 | "${EPHEMERAL_DIR}" 38 | ) 39 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") 40 | add_dependencies(flutter flutter_assemble) 41 | 42 | # === Wrapper === 43 | list(APPEND CPP_WRAPPER_SOURCES_CORE 44 | "core_implementations.cc" 45 | "standard_codec.cc" 46 | ) 47 | list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") 48 | list(APPEND CPP_WRAPPER_SOURCES_PLUGIN 49 | "plugin_registrar.cc" 50 | ) 51 | list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") 52 | list(APPEND CPP_WRAPPER_SOURCES_APP 53 | "flutter_engine.cc" 54 | "flutter_view_controller.cc" 55 | ) 56 | list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") 57 | 58 | # Wrapper sources needed for a plugin. 59 | add_library(flutter_wrapper_plugin STATIC 60 | ${CPP_WRAPPER_SOURCES_CORE} 61 | ${CPP_WRAPPER_SOURCES_PLUGIN} 62 | ) 63 | apply_standard_settings(flutter_wrapper_plugin) 64 | set_target_properties(flutter_wrapper_plugin PROPERTIES 65 | POSITION_INDEPENDENT_CODE ON) 66 | set_target_properties(flutter_wrapper_plugin PROPERTIES 67 | CXX_VISIBILITY_PRESET hidden) 68 | target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) 69 | target_include_directories(flutter_wrapper_plugin PUBLIC 70 | "${WRAPPER_ROOT}/include" 71 | ) 72 | add_dependencies(flutter_wrapper_plugin flutter_assemble) 73 | 74 | # Wrapper sources needed for the runner. 75 | add_library(flutter_wrapper_app STATIC 76 | ${CPP_WRAPPER_SOURCES_CORE} 77 | ${CPP_WRAPPER_SOURCES_APP} 78 | ) 79 | apply_standard_settings(flutter_wrapper_app) 80 | target_link_libraries(flutter_wrapper_app PUBLIC flutter) 81 | target_include_directories(flutter_wrapper_app PUBLIC 82 | "${WRAPPER_ROOT}/include" 83 | ) 84 | add_dependencies(flutter_wrapper_app flutter_assemble) 85 | 86 | # === Flutter tool backend === 87 | # _phony_ is a non-existent file to force this command to run every time, 88 | # since currently there's no way to get a full input/output list from the 89 | # flutter tool. 90 | set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") 91 | set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) 92 | add_custom_command( 93 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} 94 | ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} 95 | ${CPP_WRAPPER_SOURCES_APP} 96 | ${PHONY_OUTPUT} 97 | COMMAND ${CMAKE_COMMAND} -E env 98 | ${FLUTTER_TOOL_ENVIRONMENT} 99 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" 100 | ${FLUTTER_TARGET_PLATFORM} $ 101 | VERBATIM 102 | ) 103 | add_custom_target(flutter_assemble DEPENDS 104 | "${FLUTTER_LIBRARY}" 105 | ${FLUTTER_LIBRARY_HEADERS} 106 | ${CPP_WRAPPER_SOURCES_CORE} 107 | ${CPP_WRAPPER_SOURCES_PLUGIN} 108 | ${CPP_WRAPPER_SOURCES_APP} 109 | ) 110 | -------------------------------------------------------------------------------- /example/lib/src/todos/views/ui_states/tasks_ui_state.mapper.dart: -------------------------------------------------------------------------------- 1 | // coverage:ignore-file 2 | // GENERATED CODE - DO NOT MODIFY BY HAND 3 | // ignore_for_file: type=lint 4 | // ignore_for_file: unused_element, unnecessary_cast, override_on_non_overriding_member 5 | // ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter 6 | 7 | part of 'tasks_ui_state.dart'; 8 | 9 | class TasksUIStateMapper extends ClassMapperBase { 10 | TasksUIStateMapper._(); 11 | 12 | static TasksUIStateMapper? _instance; 13 | static TasksUIStateMapper ensureInitialized() { 14 | if (_instance == null) { 15 | MapperContainer.globals.use(_instance = TasksUIStateMapper._()); 16 | TaskMapper.ensureInitialized(); 17 | } 18 | return _instance!; 19 | } 20 | 21 | @override 22 | final String id = 'TasksUIState'; 23 | 24 | static List _$tasks(TasksUIState v) => v.tasks; 25 | static const Field> _f$tasks = 26 | Field('tasks', _$tasks, opt: true, def: const []); 27 | 28 | @override 29 | final MappableFields fields = const { 30 | #tasks: _f$tasks, 31 | }; 32 | 33 | static TasksUIState _instantiate(DecodingData data) { 34 | return TasksUIState(tasks: data.dec(_f$tasks)); 35 | } 36 | 37 | @override 38 | final Function instantiate = _instantiate; 39 | 40 | static TasksUIState fromMap(Map map) { 41 | return ensureInitialized().decodeMap(map); 42 | } 43 | 44 | static TasksUIState fromJson(String json) { 45 | return ensureInitialized().decodeJson(json); 46 | } 47 | } 48 | 49 | mixin TasksUIStateMappable { 50 | String toJson() { 51 | return TasksUIStateMapper.ensureInitialized() 52 | .encodeJson(this as TasksUIState); 53 | } 54 | 55 | Map toMap() { 56 | return TasksUIStateMapper.ensureInitialized() 57 | .encodeMap(this as TasksUIState); 58 | } 59 | 60 | TasksUIStateCopyWith get copyWith => 61 | _TasksUIStateCopyWithImpl( 62 | this as TasksUIState, $identity, $identity); 63 | @override 64 | String toString() { 65 | return TasksUIStateMapper.ensureInitialized() 66 | .stringifyValue(this as TasksUIState); 67 | } 68 | 69 | @override 70 | bool operator ==(Object other) { 71 | return TasksUIStateMapper.ensureInitialized() 72 | .equalsValue(this as TasksUIState, other); 73 | } 74 | 75 | @override 76 | int get hashCode { 77 | return TasksUIStateMapper.ensureInitialized() 78 | .hashValue(this as TasksUIState); 79 | } 80 | } 81 | 82 | extension TasksUIStateValueCopy<$R, $Out> 83 | on ObjectCopyWith<$R, TasksUIState, $Out> { 84 | TasksUIStateCopyWith<$R, TasksUIState, $Out> get $asTasksUIState => 85 | $base.as((v, t, t2) => _TasksUIStateCopyWithImpl<$R, $Out>(v, t, t2)); 86 | } 87 | 88 | abstract class TasksUIStateCopyWith<$R, $In extends TasksUIState, $Out> 89 | implements ClassCopyWith<$R, $In, $Out> { 90 | ListCopyWith<$R, Task, TaskCopyWith<$R, Task, Task>> get tasks; 91 | $R call({List? tasks}); 92 | TasksUIStateCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); 93 | } 94 | 95 | class _TasksUIStateCopyWithImpl<$R, $Out> 96 | extends ClassCopyWithBase<$R, TasksUIState, $Out> 97 | implements TasksUIStateCopyWith<$R, TasksUIState, $Out> { 98 | _TasksUIStateCopyWithImpl(super.value, super.then, super.then2); 99 | 100 | @override 101 | late final ClassMapperBase $mapper = 102 | TasksUIStateMapper.ensureInitialized(); 103 | @override 104 | ListCopyWith<$R, Task, TaskCopyWith<$R, Task, Task>> get tasks => 105 | ListCopyWith( 106 | $value.tasks, (v, t) => v.copyWith.$chain(t), (v) => call(tasks: v)); 107 | @override 108 | $R call({List? tasks}) => 109 | $apply(FieldCopyWithData({if (tasks != null) #tasks: tasks})); 110 | @override 111 | TasksUIState $make(CopyWithData data) => 112 | TasksUIState(tasks: data.get(#tasks, or: $value.tasks)); 113 | 114 | @override 115 | TasksUIStateCopyWith<$R2, TasksUIState, $Out2> $chain<$R2, $Out2>( 116 | Then<$Out2, $R2> t) => 117 | _TasksUIStateCopyWithImpl<$R2, $Out2>($value, $cast, t); 118 | } 119 | -------------------------------------------------------------------------------- /example/windows/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Project-level configuration. 2 | cmake_minimum_required(VERSION 3.14) 3 | project(minimal_example LANGUAGES CXX) 4 | 5 | # The name of the executable created for the application. Change this to change 6 | # the on-disk name of your application. 7 | set(BINARY_NAME "minimal_example") 8 | 9 | # Explicitly opt in to modern CMake behaviors to avoid warnings with recent 10 | # versions of CMake. 11 | cmake_policy(VERSION 3.14...3.25) 12 | 13 | # Define build configuration option. 14 | get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) 15 | if(IS_MULTICONFIG) 16 | set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" 17 | CACHE STRING "" FORCE) 18 | else() 19 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 20 | set(CMAKE_BUILD_TYPE "Debug" CACHE 21 | STRING "Flutter build mode" FORCE) 22 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS 23 | "Debug" "Profile" "Release") 24 | endif() 25 | endif() 26 | # Define settings for the Profile build mode. 27 | set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") 28 | set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") 29 | set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") 30 | set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") 31 | 32 | # Use Unicode for all projects. 33 | add_definitions(-DUNICODE -D_UNICODE) 34 | 35 | # Compilation settings that should be applied to most targets. 36 | # 37 | # Be cautious about adding new options here, as plugins use this function by 38 | # default. In most cases, you should add new options to specific targets instead 39 | # of modifying this function. 40 | function(APPLY_STANDARD_SETTINGS TARGET) 41 | target_compile_features(${TARGET} PUBLIC cxx_std_17) 42 | target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") 43 | target_compile_options(${TARGET} PRIVATE /EHsc) 44 | target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") 45 | target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") 46 | endfunction() 47 | 48 | # Flutter library and tool build rules. 49 | set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") 50 | add_subdirectory(${FLUTTER_MANAGED_DIR}) 51 | 52 | # Application build; see runner/CMakeLists.txt. 53 | add_subdirectory("runner") 54 | 55 | 56 | # Generated plugin build rules, which manage building the plugins and adding 57 | # them to the application. 58 | include(flutter/generated_plugins.cmake) 59 | 60 | 61 | # === Installation === 62 | # Support files are copied into place next to the executable, so that it can 63 | # run in place. This is done instead of making a separate bundle (as on Linux) 64 | # so that building and running from within Visual Studio will work. 65 | set(BUILD_BUNDLE_DIR "$") 66 | # Make the "install" step default, as it's required to run. 67 | set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) 68 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) 69 | set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) 70 | endif() 71 | 72 | set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") 73 | set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") 74 | 75 | install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" 76 | COMPONENT Runtime) 77 | 78 | install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 79 | COMPONENT Runtime) 80 | 81 | install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 82 | COMPONENT Runtime) 83 | 84 | if(PLUGIN_BUNDLED_LIBRARIES) 85 | install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" 86 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 87 | COMPONENT Runtime) 88 | endif() 89 | 90 | # Copy the native assets provided by the build.dart from all packages. 91 | set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") 92 | install(DIRECTORY "${NATIVE_ASSETS_DIR}" 93 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 94 | COMPONENT Runtime) 95 | 96 | # Fully re-copy the assets directory on each build to avoid having stale files 97 | # from a previous install. 98 | set(FLUTTER_ASSET_DIR_NAME "flutter_assets") 99 | install(CODE " 100 | file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") 101 | " COMPONENT Runtime) 102 | install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" 103 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) 104 | 105 | # Install the AOT library on non-Debug builds only. 106 | install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 107 | CONFIGURATIONS Profile;Release 108 | COMPONENT Runtime) 109 | -------------------------------------------------------------------------------- /lib/src/mm_notifier.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | 3 | /// Callback invoked when a notifier has no more subscribers 4 | typedef OnUnsubscribedCallback = void Function(); 5 | 6 | /// Minimal notifier for the MVN (Model-View-Notifier) pattern 7 | /// 8 | /// A minimal notifier manages state and business logic. It extends 9 | /// [ChangeNotifier] to notify widgets when the state changes 10 | /// 11 | /// Example: 12 | /// ```dart 13 | /// class CounterNotifier extends MMNotifier { 14 | /// CounterNotifier() : super(const CounterState(count: 0)); 15 | /// 16 | /// void increment() => notify(state.copyWith(count: state.count + 1)); 17 | /// } 18 | /// ``` 19 | abstract class MMNotifier extends ChangeNotifier { 20 | /// Creates a minimal notifier with the given initial state 21 | MMNotifier(this._state); 22 | 23 | var _listenersCount = 0; 24 | var _disposed = false; 25 | T _state; 26 | 27 | /// Whether this notifier has been disposed. Subclasses can check this before 28 | /// performing asynchronous operations that might occur after disposal 29 | /// 30 | /// Example: 31 | /// ```dart 32 | /// void increment() { 33 | /// if (disposed) return; 34 | /// notify(state.copyWith(value: state.value + 1)); 35 | /// } 36 | /// ``` 37 | @protected 38 | bool get disposed => _disposed; 39 | 40 | /// The current state managed by this notifier 41 | /// 42 | /// This getter provides read-only access to the current state. To update 43 | /// the state, use [notify] instead 44 | /// 45 | /// Example: 46 | /// ```dart 47 | /// final notifier = counterManager.notifier; 48 | /// print(notifier.state.count); 49 | /// ``` 50 | T get state => _state; 51 | 52 | /// Selects part of the state, so that only the selected part is notified 53 | /// when it changes 54 | /// 55 | /// This is useful for optimizing rebuilds in the UI. The returned 56 | /// [ValueListenable] will only notify its listeners when the selected value 57 | /// actually changes 58 | /// 59 | /// Example: 60 | /// ```dart 61 | /// // Only rebuilds when backgroundColor changes 62 | /// final colorNotifier = notifier.select((state) => state.backgroundColor); 63 | /// return ListenableBuilder( 64 | /// listenable: colorNotifier, 65 | /// builder: (context, _) => Container( 66 | /// color: colorNotifier.value, 67 | /// child: Text('Color changed!'), 68 | /// ), 69 | /// ); 70 | /// ``` 71 | ValueListenable select(final S Function(T state) selector) { 72 | final notifier = _MMSelector(() => selector(_state)); 73 | // the moment selector is subscribed, start listening to the notifier 74 | // for its changes 75 | notifier.onAddListener = () => addListener(notifier.notify); 76 | // the moment selector is unsubscribed, stop listening to the notifier 77 | // for its changes 78 | // ignore: cascade_invocations 79 | notifier.onRemoveListener = () => removeListener(notifier.notify); 80 | return notifier; 81 | } 82 | 83 | /// Mutates the notifier's state and notifies its listeners 84 | /// 85 | /// This is the preferred way to update state as it ensures all listeners 86 | /// are notified. In case the new state is the same as the current state, 87 | /// the notification is skipped to avoid unnecessary rebuilds 88 | /// 89 | /// Example: 90 | /// ```dart 91 | /// void increment() => notify(state.copyWith(value: state.value + 1)); 92 | /// ``` 93 | @protected 94 | void notify(final T value) { 95 | if (_state != value) { 96 | _state = value; 97 | notifyListeners(); 98 | } 99 | } 100 | 101 | /// Callback that will be invoked when this notifier has no more 102 | /// subscribers. There shouldn't be any reason to use this callback directly 103 | OnUnsubscribedCallback? onUnsubscribed; 104 | 105 | @override 106 | void addListener(final VoidCallback listener) { 107 | super.addListener(listener); 108 | _listenersCount++; 109 | } 110 | 111 | @override 112 | void removeListener(final VoidCallback listener) { 113 | super.removeListener(listener); 114 | _listenersCount--; 115 | if (_listenersCount <= 0) { 116 | _listenersCount = 0; 117 | onUnsubscribed?.call(); 118 | } 119 | } 120 | 121 | @override 122 | @mustCallSuper 123 | void dispose() { 124 | _disposed = true; 125 | super.dispose(); 126 | } 127 | } 128 | 129 | class _MMSelector extends ValueNotifier { 130 | _MMSelector(this._getValue) : super(_getValue()); 131 | 132 | final T Function() _getValue; 133 | VoidCallback? onAddListener; 134 | VoidCallback? onRemoveListener; 135 | 136 | void notify() { 137 | value = _getValue(); 138 | } 139 | 140 | @override 141 | void addListener(final VoidCallback listener) { 142 | onAddListener?.call(); 143 | super.addListener(listener); 144 | } 145 | 146 | @override 147 | void removeListener(final VoidCallback listener) { 148 | onRemoveListener?.call(); 149 | super.removeListener(listener); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Minimal Logo 3 |

4 | 5 | [![Pub Version](https://img.shields.io/pub/v/minimal_mvn?label=minimal_mvn&labelColor=333940&logo=dart)](https://pub.dev/packages/minimal_mvn) [![License](https://img.shields.io/badge/License-BSD%203--Clause-blue.svg)](https://opensource.org/licenses/BSD-3-Clause) 6 | 7 | # Minimal MVN 8 | 9 | A minimal state management package for Flutter. Part of a minimalistic architecture based on the MVN (Model-View-Notifier) pattern. 10 | 11 | This package aims for the simplest possible architecture, making it easier to understand and use, while offering an alternative to the growing complexity found in many other state management solutions, in an attempt to minimize side effects. 12 | 13 | ## Try it 14 | 15 | Check out the live example at [https://alesalv.github.io/minimal/](https://alesalv.github.io/minimal/) 16 | 17 | **Counter** 18 | 19 | The classical Flutter counter app demonstrates basic state management. The counter on the top is a non disposable notifier, so it keeps memory when re-entering the page. The one on the bottom is a disposable notifier, so it resets when re-entering the page. 20 | 21 | **Chroma Counter** 22 | 23 | A more advanced counter widget, which increases the counter, and randomly changes color and shape. The reveal button shows a second widget using the same notifier. Thanks to the autodispose feature, the notifier is disposed only when both the widgets are not visible anymore. The string on top of the choma counter updates every 10 counts, thanks to the select feature, which is used to avoid unnecessary rebuilds. 24 | 25 | The complete source code is in the [example folder](/example). 26 | 27 | ## Getting Started 28 | 29 | Add Minimal to your pubspec.yaml: 30 | ```bash 31 | dart pub add minimal_mvn 32 | # or 33 | flutter pub add minimal_mvn 34 | ``` 35 | 36 | and import the package: 37 | ```dart 38 | import 'package:minimal_mvn/minimal_mvn.dart'; 39 | ``` 40 | 41 | You can now start using Minimal's MVN pattern in your application. The quickest way is to follow the [4 steps below](#state-management-in-4-steps). 42 | 43 | ## Features 44 | 45 | - 🎯 MVN (Model-View-Notifier) pattern 46 | - 🚀 Lazy initialization of notifiers 47 | - 🔄 Optional autodispose for notifiers 48 | - ⚡ State selection for optimized rebuilds 49 | - 📦 Dependency injection with locator 50 | 51 | ## State Management in 4 Steps 52 | 53 | ### 1. Create an immutable UI state 54 | 55 | ```dart 56 | @MappableClass() 57 | class ChromaCounterUIState with ChromaCounterUIStateMappable { 58 | const ChromaCounterUIState({ 59 | this.backgroundColor = Colors.blue, 60 | this.count = 0, 61 | }); 62 | final Color backgroundColor; 63 | final int count; 64 | } 65 | ``` 66 | 67 | ### 2. Create a notifier to hold your state 68 | 69 | ```dart 70 | class ChromaCounterNotifier extends MMNotifier { 71 | ChromaCounterNotifier() : super(const ChromaCounterUIState()); 72 | 73 | void nextMetamorph() => notify( 74 | state.copyWith( 75 | backgroundColor: _randomColor(), 76 | count: state.count + 1, 77 | ), 78 | ); 79 | } 80 | ``` 81 | 82 | ### 3. Create a manager to access your notifier 83 | 84 | ``` 85 | final MMManager chromaCounterManager = 86 | MMManager(ChromaCounterNotifier.new, autodispose: true); 87 | ``` 88 | 89 | ### 4 Use the notifier from UI 90 | 91 | ###### Access the notifier upon user's actions 92 | 93 | ```dart 94 | FloatingActionButton( 95 | onPressed: () => chromaCounterManager.notifier.nextMetamorph(), 96 | ); 97 | ``` 98 | 99 | ###### Rebuild the UI when state changes 100 | 101 | ```dart 102 | final notifier = chromaCounterManager.notifier; 103 | return ListenableBuilder( 104 | listenable: notifier, 105 | builder: (context, _) => Container( 106 | color: notifier.state.backgroundColor, 107 | child: const Text('Count: ${notifier.state.count}'), 108 | ), 109 | ); 110 | ``` 111 | 112 | ###### (Optimized) Rebuild the UI only when part of the state changes 113 | 114 | ```dart 115 | final notifier = chromaCounterManager.notifier; 116 | return ListenableBuilder( 117 | listenable: notifier.select((state) => state.backgroundColor), 118 | builder: (context, _) => Container( 119 | color: notifier.state.backgroundColor, 120 | ), 121 | ); 122 | ``` 123 | 124 | ## Testing 125 | 126 | ### Widget Testing 127 | 128 | In tests, you can override the notifier with a mock notifier through the minimal manager: 129 | 130 | ```dart 131 | testWidgets('should update UI when state changes', (tester) async { 132 | // Use the minimal manager to override the notifier 133 | chromaCounterManager.override(MockCounterNotifier.new); 134 | 135 | await tester.pumpWidget(const MaterialApp(home: ChromaCounter())); 136 | 137 | // Change state through the mock notifier 138 | chromaCounterManager.notifier.nextMetamorph(); 139 | await tester.pump(); 140 | 141 | final newColor = _getContainerColor(tester); 142 | // Test the exact color instead of a random one 143 | expect(newColor, equals(Colors.red)); 144 | }); 145 | ``` 146 | 147 | See the [example app tests](/example/test) for more testing examples. 148 | -------------------------------------------------------------------------------- /example/linux/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Project-level configuration. 2 | cmake_minimum_required(VERSION 3.13) 3 | project(runner LANGUAGES CXX) 4 | 5 | # The name of the executable created for the application. Change this to change 6 | # the on-disk name of your application. 7 | set(BINARY_NAME "minimal_example") 8 | # The unique GTK application identifier for this application. See: 9 | # https://wiki.gnome.org/HowDoI/ChooseApplicationID 10 | set(APPLICATION_ID "com.example.minimal_example") 11 | 12 | # Explicitly opt in to modern CMake behaviors to avoid warnings with recent 13 | # versions of CMake. 14 | cmake_policy(SET CMP0063 NEW) 15 | 16 | # Load bundled libraries from the lib/ directory relative to the binary. 17 | set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") 18 | 19 | # Root filesystem for cross-building. 20 | if(FLUTTER_TARGET_PLATFORM_SYSROOT) 21 | set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) 22 | set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) 23 | set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) 24 | set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) 25 | set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) 26 | set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) 27 | endif() 28 | 29 | # Define build configuration options. 30 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 31 | set(CMAKE_BUILD_TYPE "Debug" CACHE 32 | STRING "Flutter build mode" FORCE) 33 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS 34 | "Debug" "Profile" "Release") 35 | endif() 36 | 37 | # Compilation settings that should be applied to most targets. 38 | # 39 | # Be cautious about adding new options here, as plugins use this function by 40 | # default. In most cases, you should add new options to specific targets instead 41 | # of modifying this function. 42 | function(APPLY_STANDARD_SETTINGS TARGET) 43 | target_compile_features(${TARGET} PUBLIC cxx_std_14) 44 | target_compile_options(${TARGET} PRIVATE -Wall -Werror) 45 | target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") 46 | target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") 47 | endfunction() 48 | 49 | # Flutter library and tool build rules. 50 | set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") 51 | add_subdirectory(${FLUTTER_MANAGED_DIR}) 52 | 53 | # System-level dependencies. 54 | find_package(PkgConfig REQUIRED) 55 | pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) 56 | 57 | # Application build; see runner/CMakeLists.txt. 58 | add_subdirectory("runner") 59 | 60 | # Run the Flutter tool portions of the build. This must not be removed. 61 | add_dependencies(${BINARY_NAME} flutter_assemble) 62 | 63 | # Only the install-generated bundle's copy of the executable will launch 64 | # correctly, since the resources must in the right relative locations. To avoid 65 | # people trying to run the unbundled copy, put it in a subdirectory instead of 66 | # the default top-level location. 67 | set_target_properties(${BINARY_NAME} 68 | PROPERTIES 69 | RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" 70 | ) 71 | 72 | 73 | # Generated plugin build rules, which manage building the plugins and adding 74 | # them to the application. 75 | include(flutter/generated_plugins.cmake) 76 | 77 | 78 | # === Installation === 79 | # By default, "installing" just makes a relocatable bundle in the build 80 | # directory. 81 | set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") 82 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) 83 | set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) 84 | endif() 85 | 86 | # Start with a clean build bundle directory every time. 87 | install(CODE " 88 | file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") 89 | " COMPONENT Runtime) 90 | 91 | set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") 92 | set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") 93 | 94 | install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" 95 | COMPONENT Runtime) 96 | 97 | install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 98 | COMPONENT Runtime) 99 | 100 | install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 101 | COMPONENT Runtime) 102 | 103 | foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) 104 | install(FILES "${bundled_library}" 105 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 106 | COMPONENT Runtime) 107 | endforeach(bundled_library) 108 | 109 | # Copy the native assets provided by the build.dart from all packages. 110 | set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") 111 | install(DIRECTORY "${NATIVE_ASSETS_DIR}" 112 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 113 | COMPONENT Runtime) 114 | 115 | # Fully re-copy the assets directory on each build to avoid having stale files 116 | # from a previous install. 117 | set(FLUTTER_ASSET_DIR_NAME "flutter_assets") 118 | install(CODE " 119 | file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") 120 | " COMPONENT Runtime) 121 | install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" 122 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) 123 | 124 | # Install the AOT library on non-Debug builds only. 125 | if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") 126 | install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 127 | COMPONENT Runtime) 128 | endif() 129 | -------------------------------------------------------------------------------- /example/linux/runner/my_application.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | #include 4 | #ifdef GDK_WINDOWING_X11 5 | #include 6 | #endif 7 | 8 | #include "flutter/generated_plugin_registrant.h" 9 | 10 | struct _MyApplication { 11 | GtkApplication parent_instance; 12 | char** dart_entrypoint_arguments; 13 | }; 14 | 15 | G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) 16 | 17 | // Implements GApplication::activate. 18 | static void my_application_activate(GApplication* application) { 19 | MyApplication* self = MY_APPLICATION(application); 20 | GtkWindow* window = 21 | GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); 22 | 23 | // Use a header bar when running in GNOME as this is the common style used 24 | // by applications and is the setup most users will be using (e.g. Ubuntu 25 | // desktop). 26 | // If running on X and not using GNOME then just use a traditional title bar 27 | // in case the window manager does more exotic layout, e.g. tiling. 28 | // If running on Wayland assume the header bar will work (may need changing 29 | // if future cases occur). 30 | gboolean use_header_bar = TRUE; 31 | #ifdef GDK_WINDOWING_X11 32 | GdkScreen* screen = gtk_window_get_screen(window); 33 | if (GDK_IS_X11_SCREEN(screen)) { 34 | const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); 35 | if (g_strcmp0(wm_name, "GNOME Shell") != 0) { 36 | use_header_bar = FALSE; 37 | } 38 | } 39 | #endif 40 | if (use_header_bar) { 41 | GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); 42 | gtk_widget_show(GTK_WIDGET(header_bar)); 43 | gtk_header_bar_set_title(header_bar, "minimal_example"); 44 | gtk_header_bar_set_show_close_button(header_bar, TRUE); 45 | gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); 46 | } else { 47 | gtk_window_set_title(window, "minimal_example"); 48 | } 49 | 50 | gtk_window_set_default_size(window, 1280, 720); 51 | gtk_widget_show(GTK_WIDGET(window)); 52 | 53 | g_autoptr(FlDartProject) project = fl_dart_project_new(); 54 | fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); 55 | 56 | FlView* view = fl_view_new(project); 57 | gtk_widget_show(GTK_WIDGET(view)); 58 | gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); 59 | 60 | fl_register_plugins(FL_PLUGIN_REGISTRY(view)); 61 | 62 | gtk_widget_grab_focus(GTK_WIDGET(view)); 63 | } 64 | 65 | // Implements GApplication::local_command_line. 66 | static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { 67 | MyApplication* self = MY_APPLICATION(application); 68 | // Strip out the first argument as it is the binary name. 69 | self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); 70 | 71 | g_autoptr(GError) error = nullptr; 72 | if (!g_application_register(application, nullptr, &error)) { 73 | g_warning("Failed to register: %s", error->message); 74 | *exit_status = 1; 75 | return TRUE; 76 | } 77 | 78 | g_application_activate(application); 79 | *exit_status = 0; 80 | 81 | return TRUE; 82 | } 83 | 84 | // Implements GApplication::startup. 85 | static void my_application_startup(GApplication* application) { 86 | //MyApplication* self = MY_APPLICATION(object); 87 | 88 | // Perform any actions required at application startup. 89 | 90 | G_APPLICATION_CLASS(my_application_parent_class)->startup(application); 91 | } 92 | 93 | // Implements GApplication::shutdown. 94 | static void my_application_shutdown(GApplication* application) { 95 | //MyApplication* self = MY_APPLICATION(object); 96 | 97 | // Perform any actions required at application shutdown. 98 | 99 | G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); 100 | } 101 | 102 | // Implements GObject::dispose. 103 | static void my_application_dispose(GObject* object) { 104 | MyApplication* self = MY_APPLICATION(object); 105 | g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); 106 | G_OBJECT_CLASS(my_application_parent_class)->dispose(object); 107 | } 108 | 109 | static void my_application_class_init(MyApplicationClass* klass) { 110 | G_APPLICATION_CLASS(klass)->activate = my_application_activate; 111 | G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; 112 | G_APPLICATION_CLASS(klass)->startup = my_application_startup; 113 | G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; 114 | G_OBJECT_CLASS(klass)->dispose = my_application_dispose; 115 | } 116 | 117 | static void my_application_init(MyApplication* self) {} 118 | 119 | MyApplication* my_application_new() { 120 | // Set the program name to the application ID, which helps various systems 121 | // like GTK and desktop environments map this running application to its 122 | // corresponding .desktop file. This ensures better integration by allowing 123 | // the application to be recognized beyond its binary name. 124 | g_set_prgname(APPLICATION_ID); 125 | 126 | return MY_APPLICATION(g_object_new(my_application_get_type(), 127 | "application-id", APPLICATION_ID, 128 | "flags", G_APPLICATION_NON_UNIQUE, 129 | nullptr)); 130 | } 131 | -------------------------------------------------------------------------------- /example/lib/src/chroma_counter/views/ui_states/chroma_counter_ui_state.mapper.dart: -------------------------------------------------------------------------------- 1 | // coverage:ignore-file 2 | // GENERATED CODE - DO NOT MODIFY BY HAND 3 | // ignore_for_file: type=lint 4 | // ignore_for_file: unused_element, unnecessary_cast, override_on_non_overriding_member 5 | // ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter 6 | 7 | part of 'chroma_counter_ui_state.dart'; 8 | 9 | class ChromaCounterUIStateMapper extends ClassMapperBase { 10 | ChromaCounterUIStateMapper._(); 11 | 12 | static ChromaCounterUIStateMapper? _instance; 13 | static ChromaCounterUIStateMapper ensureInitialized() { 14 | if (_instance == null) { 15 | MapperContainer.globals.use(_instance = ChromaCounterUIStateMapper._()); 16 | } 17 | return _instance!; 18 | } 19 | 20 | @override 21 | final String id = 'ChromaCounterUIState'; 22 | 23 | static Color _$backgroundColor(ChromaCounterUIState v) => v.backgroundColor; 24 | static const Field _f$backgroundColor = 25 | Field('backgroundColor', _$backgroundColor, opt: true, def: Colors.blue); 26 | static BorderRadius _$borderRadius(ChromaCounterUIState v) => v.borderRadius; 27 | static const Field _f$borderRadius = 28 | Field('borderRadius', _$borderRadius, opt: true, def: BorderRadius.zero); 29 | static int _$count(ChromaCounterUIState v) => v.count; 30 | static const Field _f$count = 31 | Field('count', _$count, opt: true, def: 0); 32 | static int _$milestone(ChromaCounterUIState v) => v.milestone; 33 | static const Field _f$milestone = 34 | Field('milestone', _$milestone, mode: FieldMode.member); 35 | 36 | @override 37 | final MappableFields fields = const { 38 | #backgroundColor: _f$backgroundColor, 39 | #borderRadius: _f$borderRadius, 40 | #count: _f$count, 41 | #milestone: _f$milestone, 42 | }; 43 | 44 | static ChromaCounterUIState _instantiate(DecodingData data) { 45 | return ChromaCounterUIState( 46 | backgroundColor: data.dec(_f$backgroundColor), 47 | borderRadius: data.dec(_f$borderRadius), 48 | count: data.dec(_f$count)); 49 | } 50 | 51 | @override 52 | final Function instantiate = _instantiate; 53 | 54 | static ChromaCounterUIState fromMap(Map map) { 55 | return ensureInitialized().decodeMap(map); 56 | } 57 | 58 | static ChromaCounterUIState fromJson(String json) { 59 | return ensureInitialized().decodeJson(json); 60 | } 61 | } 62 | 63 | mixin ChromaCounterUIStateMappable { 64 | String toJson() { 65 | return ChromaCounterUIStateMapper.ensureInitialized() 66 | .encodeJson(this as ChromaCounterUIState); 67 | } 68 | 69 | Map toMap() { 70 | return ChromaCounterUIStateMapper.ensureInitialized() 71 | .encodeMap(this as ChromaCounterUIState); 72 | } 73 | 74 | ChromaCounterUIStateCopyWith get copyWith => _ChromaCounterUIStateCopyWithImpl< 76 | ChromaCounterUIState, ChromaCounterUIState>( 77 | this as ChromaCounterUIState, $identity, $identity); 78 | @override 79 | String toString() { 80 | return ChromaCounterUIStateMapper.ensureInitialized() 81 | .stringifyValue(this as ChromaCounterUIState); 82 | } 83 | 84 | @override 85 | bool operator ==(Object other) { 86 | return ChromaCounterUIStateMapper.ensureInitialized() 87 | .equalsValue(this as ChromaCounterUIState, other); 88 | } 89 | 90 | @override 91 | int get hashCode { 92 | return ChromaCounterUIStateMapper.ensureInitialized() 93 | .hashValue(this as ChromaCounterUIState); 94 | } 95 | } 96 | 97 | extension ChromaCounterUIStateValueCopy<$R, $Out> 98 | on ObjectCopyWith<$R, ChromaCounterUIState, $Out> { 99 | ChromaCounterUIStateCopyWith<$R, ChromaCounterUIState, $Out> 100 | get $asChromaCounterUIState => $base.as( 101 | (v, t, t2) => _ChromaCounterUIStateCopyWithImpl<$R, $Out>(v, t, t2)); 102 | } 103 | 104 | abstract class ChromaCounterUIStateCopyWith< 105 | $R, 106 | $In extends ChromaCounterUIState, 107 | $Out> implements ClassCopyWith<$R, $In, $Out> { 108 | $R call({Color? backgroundColor, BorderRadius? borderRadius, int? count}); 109 | ChromaCounterUIStateCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>( 110 | Then<$Out2, $R2> t); 111 | } 112 | 113 | class _ChromaCounterUIStateCopyWithImpl<$R, $Out> 114 | extends ClassCopyWithBase<$R, ChromaCounterUIState, $Out> 115 | implements ChromaCounterUIStateCopyWith<$R, ChromaCounterUIState, $Out> { 116 | _ChromaCounterUIStateCopyWithImpl(super.value, super.then, super.then2); 117 | 118 | @override 119 | late final ClassMapperBase $mapper = 120 | ChromaCounterUIStateMapper.ensureInitialized(); 121 | @override 122 | $R call({Color? backgroundColor, BorderRadius? borderRadius, int? count}) => 123 | $apply(FieldCopyWithData({ 124 | if (backgroundColor != null) #backgroundColor: backgroundColor, 125 | if (borderRadius != null) #borderRadius: borderRadius, 126 | if (count != null) #count: count 127 | })); 128 | @override 129 | ChromaCounterUIState $make(CopyWithData data) => ChromaCounterUIState( 130 | backgroundColor: data.get(#backgroundColor, or: $value.backgroundColor), 131 | borderRadius: data.get(#borderRadius, or: $value.borderRadius), 132 | count: data.get(#count, or: $value.count)); 133 | 134 | @override 135 | ChromaCounterUIStateCopyWith<$R2, ChromaCounterUIState, $Out2> 136 | $chain<$R2, $Out2>(Then<$Out2, $R2> t) => 137 | _ChromaCounterUIStateCopyWithImpl<$R2, $Out2>($value, $cast, t); 138 | } 139 | -------------------------------------------------------------------------------- /test/src/mm_notifier_test.dart: -------------------------------------------------------------------------------- 1 | // Let's make the tests more readable and explicit 2 | // ignore_for_file: cascade_invocations 3 | import 'package:flutter/foundation.dart'; 4 | import 'package:flutter_test/flutter_test.dart'; 5 | import 'package:minimal_mvn/minimal_mvn.dart'; 6 | 7 | import 'utils/test_common.dart'; 8 | 9 | void main() { 10 | group('MMNotifier', () { 11 | test('notifies listeners upon state changes', () { 12 | var notified = 0; 13 | void l() => notified++; 14 | 15 | final notifier = TNotifier(); 16 | notifier.addListener(l); 17 | expect(notified, 0); 18 | expect(notifier.state.value, 0); 19 | 20 | notifier.increment(); 21 | expect(notified, 1); 22 | expect(notifier.state.value, 1); 23 | 24 | notifier.increment(); 25 | expect(notified, 2); 26 | expect(notifier.state.value, 2); 27 | }); 28 | 29 | test('stops notifying removed listeners', () { 30 | var notified = 0; 31 | void l() => notified++; 32 | 33 | final notifier = TNotifier(); 34 | notifier.addListener(l); 35 | expect(notified, 0); 36 | expect(notifier.state.value, 0); 37 | 38 | notifier.increment(); 39 | expect(notified, 1); 40 | expect(notifier.state.value, 1); 41 | 42 | notifier.removeListener(l); 43 | notifier.increment(); 44 | expect(notified, 1); 45 | expect(notifier.state.value, 2); 46 | }); 47 | }); 48 | 49 | group('MMNotifier select', () { 50 | test('cannot be mutated', () { 51 | var selectNotified = 0; 52 | void selectionL() => selectNotified++; 53 | 54 | final notifier = TNotifier(); 55 | 56 | final selected = notifier.select((final state) => state.text); 57 | selected.addListener(selectionL); 58 | 59 | // ValueListenable is read-only, so it can't be mutated 60 | // ignore: unnecessary_type_check 61 | expect(selected is ValueListenable, true); 62 | notifier.append('parent update'); 63 | expect(selected.value, 'parent update'); 64 | }); 65 | 66 | test('notifies listeners only when selection changes', () { 67 | var stateNotified = 0; 68 | void stateL() => stateNotified++; 69 | 70 | var selectNotified = 0; 71 | void selectionL() => selectNotified++; 72 | 73 | final notifier = TNotifier(); 74 | notifier.addListener(stateL); 75 | 76 | // selected tracks changes in the text part of the state 77 | final selected = notifier.select((final state) => state.text); 78 | selected.addListener(selectionL); 79 | 80 | notifier.increment(); 81 | expect(stateNotified, 1); 82 | // selected not notified 83 | expect(selected.value, ''); 84 | expect(selectNotified, 0); 85 | 86 | notifier.increment(); 87 | expect(stateNotified, 2); 88 | // selected not notified 89 | expect(selected.value, ''); 90 | expect(selectNotified, 0); 91 | 92 | notifier.append('mutated'); 93 | expect(stateNotified, 3); 94 | expect(notifier.state.value, 2); 95 | expect(notifier.state.text, 'mutated'); 96 | // selected notified 97 | expect(selectNotified, 1); 98 | expect(selected.value, 'mutated'); 99 | }); 100 | 101 | test('stops notifying removed listeners', () { 102 | var stateNotified = 0; 103 | void stateL() => stateNotified++; 104 | 105 | var selectNotified = 0; 106 | void selectionL() => selectNotified++; 107 | 108 | final notifier = TNotifier(); 109 | notifier.addListener(stateL); 110 | 111 | // selected tracks changes in the text part of the state 112 | final selected = notifier.select((final state) => state.text); 113 | selected.addListener(selectionL); 114 | 115 | notifier.append('one'); 116 | expect(stateNotified, 1); 117 | // selected notified 118 | expect(selectNotified, 1); 119 | expect(selected.value, 'one'); 120 | 121 | notifier.append('Two'); 122 | expect(stateNotified, 2); 123 | // selected notified 124 | expect(selectNotified, 2); 125 | expect(selected.value, 'oneTwo'); 126 | 127 | selected.removeListener(selectionL); 128 | 129 | notifier.append('Three'); 130 | expect(stateNotified, 3); 131 | expect(notifier.state.value, 0); 132 | expect(notifier.state.text, 'oneTwoThree'); 133 | // selected not notified 134 | expect(selectNotified, 2); 135 | expect(selected.value, 'oneTwo'); 136 | }); 137 | }); 138 | 139 | group('MMNotifier dispose', () { 140 | test('state is tracked', () { 141 | final notifier = TNotifier(); 142 | expect(notifier.isDisposed, false); 143 | 144 | notifier.dispose(); 145 | expect(notifier.isDisposed, true); 146 | }); 147 | 148 | test('when all listeners are removed', () { 149 | final manager = MMManager( 150 | TNotifier.new, 151 | autodispose: true, 152 | ); 153 | 154 | var notified = 0; 155 | void l1() => notified++; 156 | void l2() => notified++; 157 | 158 | final notifier = manager.notifier; 159 | notifier.addListener(l1); 160 | notifier.addListener(l2); 161 | 162 | notifier.increment(); 163 | notifier.increment(); 164 | expect(notifier.isDisposed, false); 165 | 166 | notifier.removeListener(l1); 167 | expect(notifier.isDisposed, false); 168 | 169 | notifier.removeListener(l2); 170 | expect(notifier.isDisposed, true); 171 | }); 172 | 173 | test('when all listeners are removed including selections', () { 174 | final manager = MMManager( 175 | TNotifier.new, 176 | autodispose: true, 177 | ); 178 | 179 | var stateNotified = 0; 180 | void stateL() => stateNotified++; 181 | 182 | var selectNotified = 0; 183 | void selectionL() => selectNotified++; 184 | 185 | final notifier = manager.notifier; 186 | notifier.addListener(stateL); 187 | 188 | final selected = notifier.select((final state) => state.text); 189 | selected.addListener(selectionL); 190 | 191 | notifier.increment(); 192 | notifier.append('mutated'); 193 | expect(notifier.isDisposed, false); 194 | 195 | notifier.removeListener(stateL); 196 | expect(notifier.isDisposed, false); 197 | 198 | notifier.removeListener(selectionL); 199 | expect(notifier.isDisposed, true); 200 | }); 201 | }); 202 | } 203 | -------------------------------------------------------------------------------- /all_lint_rules.yaml: -------------------------------------------------------------------------------- 1 | # All lint rules are activated in this file. Latest rules can be found at: 2 | # https://dart.dev/tools/linter-rules/all 3 | linter: 4 | rules: 5 | - always_declare_return_types 6 | - always_put_control_body_on_new_line 7 | - always_put_required_named_parameters_first 8 | - always_specify_types 9 | - always_use_package_imports 10 | - annotate_overrides 11 | - annotate_redeclares 12 | - avoid_annotating_with_dynamic 13 | - avoid_bool_literals_in_conditional_expressions 14 | - avoid_catches_without_on_clauses 15 | - avoid_catching_errors 16 | - avoid_classes_with_only_static_members 17 | - avoid_double_and_int_checks 18 | - avoid_dynamic_calls 19 | - avoid_empty_else 20 | - avoid_equals_and_hash_code_on_mutable_classes 21 | - avoid_escaping_inner_quotes 22 | - avoid_field_initializers_in_const_classes 23 | - avoid_final_parameters 24 | - avoid_function_literals_in_foreach_calls 25 | - avoid_futureor_void 26 | - avoid_implementing_value_types 27 | - avoid_init_to_null 28 | - avoid_js_rounded_ints 29 | - avoid_multiple_declarations_per_line 30 | - avoid_null_checks_in_equality_operators 31 | - avoid_positional_boolean_parameters 32 | - avoid_print 33 | - avoid_private_typedef_functions 34 | - avoid_redundant_argument_values 35 | - avoid_relative_lib_imports 36 | - avoid_renaming_method_parameters 37 | - avoid_return_types_on_setters 38 | - avoid_returning_null_for_void 39 | - avoid_returning_this 40 | - avoid_setters_without_getters 41 | - avoid_shadowing_type_parameters 42 | - avoid_single_cascade_in_expression_statements 43 | - avoid_slow_async_io 44 | - avoid_type_to_string 45 | - avoid_types_as_parameter_names 46 | - avoid_types_on_closure_parameters 47 | - avoid_unnecessary_containers 48 | - avoid_unused_constructor_parameters 49 | - avoid_void_async 50 | - avoid_web_libraries_in_flutter 51 | - await_only_futures 52 | - camel_case_extensions 53 | - camel_case_types 54 | - cancel_subscriptions 55 | - cascade_invocations 56 | - cast_nullable_to_non_nullable 57 | - close_sinks 58 | - collection_methods_unrelated_type 59 | - combinators_ordering 60 | - comment_references 61 | - conditional_uri_does_not_exist 62 | - constant_identifier_names 63 | - control_flow_in_finally 64 | - curly_braces_in_flow_control_structures 65 | - dangling_library_doc_comments 66 | - depend_on_referenced_packages 67 | - deprecated_consistency 68 | - deprecated_member_use_from_same_package 69 | - diagnostic_describe_all_properties 70 | - directives_ordering 71 | - discarded_futures 72 | - do_not_use_environment 73 | - document_ignores 74 | - empty_catches 75 | - empty_constructor_bodies 76 | - empty_statements 77 | - eol_at_end_of_file 78 | - exhaustive_cases 79 | - file_names 80 | - flutter_style_todos 81 | - hash_and_equals 82 | - implementation_imports 83 | - implicit_call_tearoffs 84 | - implicit_reopen 85 | - invalid_case_patterns 86 | - invalid_runtime_check_with_js_interop_types 87 | - join_return_with_assignment 88 | - leading_newlines_in_multiline_strings 89 | - library_annotations 90 | - library_names 91 | - library_prefixes 92 | - library_private_types_in_public_api 93 | - lines_longer_than_80_chars 94 | - literal_only_boolean_expressions 95 | - matching_super_parameters 96 | - missing_code_block_language_in_doc_comment 97 | - missing_whitespace_between_adjacent_strings 98 | - no_adjacent_strings_in_list 99 | - no_default_cases 100 | - no_duplicate_case_values 101 | - no_leading_underscores_for_library_prefixes 102 | - no_leading_underscores_for_local_identifiers 103 | - no_literal_bool_comparisons 104 | - no_logic_in_create_state 105 | - no_runtimeType_toString 106 | - no_self_assignments 107 | - no_wildcard_variable_uses 108 | - non_constant_identifier_names 109 | - noop_primitive_operations 110 | - null_check_on_nullable_type_parameter 111 | - null_closures 112 | - omit_local_variable_types 113 | - omit_obvious_local_variable_types 114 | - omit_obvious_property_types 115 | - one_member_abstracts 116 | - only_throw_errors 117 | - overridden_fields 118 | - package_names 119 | - package_prefixed_library_names 120 | - parameter_assignments 121 | - prefer_adjacent_string_concatenation 122 | - prefer_asserts_in_initializer_lists 123 | - prefer_asserts_with_message 124 | - prefer_collection_literals 125 | - prefer_conditional_assignment 126 | - prefer_const_constructors 127 | - prefer_const_constructors_in_immutables 128 | - prefer_const_declarations 129 | - prefer_const_literals_to_create_immutables 130 | - prefer_constructors_over_static_methods 131 | - prefer_contains 132 | - prefer_double_quotes 133 | - prefer_expression_function_bodies 134 | - prefer_final_fields 135 | - prefer_final_in_for_each 136 | - prefer_final_locals 137 | - prefer_final_parameters 138 | - prefer_for_elements_to_map_fromIterable 139 | - prefer_foreach 140 | - prefer_function_declarations_over_variables 141 | - prefer_generic_function_type_aliases 142 | - prefer_if_elements_to_conditional_expressions 143 | - prefer_if_null_operators 144 | - prefer_initializing_formals 145 | - prefer_inlined_adds 146 | - prefer_int_literals 147 | - prefer_interpolation_to_compose_strings 148 | - prefer_is_empty 149 | - prefer_is_not_empty 150 | - prefer_is_not_operator 151 | - prefer_iterable_whereType 152 | - prefer_mixin 153 | - prefer_null_aware_method_calls 154 | - prefer_null_aware_operators 155 | - prefer_relative_imports 156 | - prefer_single_quotes 157 | - prefer_spread_collections 158 | - prefer_typing_uninitialized_variables 159 | - prefer_void_to_null 160 | - provide_deprecation_message 161 | - public_member_api_docs 162 | - recursive_getters 163 | - require_trailing_commas 164 | - secure_pubspec_urls 165 | - sized_box_for_whitespace 166 | - sized_box_shrink_expand 167 | - slash_for_doc_comments 168 | - sort_child_properties_last 169 | - sort_constructors_first 170 | - sort_pub_dependencies 171 | - sort_unnamed_constructors_first 172 | - specify_nonobvious_local_variable_types 173 | - specify_nonobvious_property_types 174 | - strict_top_level_inference 175 | - test_types_in_equals 176 | - throw_in_finally 177 | - tighten_type_of_initializing_formals 178 | - type_annotate_public_apis 179 | - type_init_formals 180 | - type_literal_in_constant_pattern 181 | - unawaited_futures 182 | - unintended_html_in_doc_comment 183 | - unnecessary_async 184 | - unnecessary_await_in_return 185 | - unnecessary_brace_in_string_interps 186 | - unnecessary_breaks 187 | - unnecessary_const 188 | - unnecessary_constructor_name 189 | - unnecessary_final 190 | - unnecessary_getters_setters 191 | - unnecessary_ignore 192 | - unnecessary_lambdas 193 | - unnecessary_late 194 | - unnecessary_library_directive 195 | - unnecessary_library_name 196 | - unnecessary_new 197 | - unnecessary_null_aware_assignments 198 | - unnecessary_null_aware_operator_on_extension_on_nullable 199 | - unnecessary_null_checks 200 | - unnecessary_null_in_if_null_operators 201 | - unnecessary_nullable_for_final_variable_declarations 202 | - unnecessary_overrides 203 | - unnecessary_parenthesis 204 | - unnecessary_raw_strings 205 | - unnecessary_statements 206 | - unnecessary_string_escapes 207 | - unnecessary_string_interpolations 208 | - unnecessary_this 209 | - unnecessary_to_list_in_spreads 210 | - unnecessary_underscores 211 | - unreachable_from_main 212 | - unrelated_type_equality_checks 213 | - unsafe_variance 214 | - use_build_context_synchronously 215 | - use_colored_box 216 | - use_decorated_box 217 | - use_enums 218 | - use_full_hex_values_for_flutter_colors 219 | - use_function_type_syntax_for_parameters 220 | - use_if_null_to_convert_nulls_to_bools 221 | - use_is_even_rather_than_modulo 222 | - use_key_in_widget_constructors 223 | - use_late_for_private_fields_and_variables 224 | - use_named_constants 225 | - use_null_aware_elements 226 | - use_raw_strings 227 | - use_rethrow_when_possible 228 | - use_setters_to_change_properties 229 | - use_string_buffers 230 | - use_string_in_part_of_directives 231 | - use_super_parameters 232 | - use_test_throws_matchers 233 | - use_to_and_as_if_applicable 234 | - use_truncating_division 235 | - valid_regexps 236 | - void_checks 237 | --------------------------------------------------------------------------------