├── .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 |
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 |
3 |
4 |
5 | [](https://pub.dev/packages/minimal_mvn) [](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 |
--------------------------------------------------------------------------------