├── example ├── router_example │ ├── linux │ │ ├── .gitignore │ │ ├── main.cc │ │ ├── flutter │ │ │ ├── generated_plugin_registrant.cc │ │ │ ├── generated_plugin_registrant.h │ │ │ └── generated_plugins.cmake │ │ └── my_application.h │ ├── lib │ │ ├── datamodels │ │ │ ├── home_type.dart │ │ │ ├── clashable_two.dart │ │ │ ├── clashable_one.dart │ │ │ └── human.dart │ │ ├── services │ │ │ ├── iepoch_service.dart │ │ │ ├── factory_service.dart │ │ │ ├── information_service.dart │ │ │ ├── epoch_service.dart │ │ │ └── shared_preferences_service.dart │ │ ├── ui │ │ │ ├── bottom_nav │ │ │ │ ├── profile │ │ │ │ │ ├── profile_viewmodel.dart │ │ │ │ │ └── profile_view.dart │ │ │ │ ├── history │ │ │ │ │ ├── history_viewmodel.dart │ │ │ │ │ └── history_view.dart │ │ │ │ ├── favorites │ │ │ │ │ ├── favorites_viewmodel.dart │ │ │ │ │ └── favorites_view.dart │ │ │ │ ├── bottom_nav_example_viewmodel.dart │ │ │ │ └── bottom_nav_example.dart │ │ │ ├── form │ │ │ │ ├── custom_text_field.dart │ │ │ │ ├── validators.dart │ │ │ │ └── example_form_viewmodel.dart │ │ │ ├── drop_down_menu_from │ │ │ │ └── select_location_viewmodel.dart │ │ │ ├── builder_widget_example │ │ │ │ ├── builder_widget_example_viewmodel.dart │ │ │ │ └── builder_widget_example_view.dart │ │ │ ├── dialogs │ │ │ │ └── basic_dialog.dart │ │ │ ├── startup │ │ │ │ ├── startup_viewmodel.dart │ │ │ │ └── startup_view.dart │ │ │ ├── future_example_view │ │ │ │ ├── future_example_viewmodel.dart │ │ │ │ └── future_example_view.dart │ │ │ ├── dumb_widgets │ │ │ │ ├── duplicate_name_widget.dart │ │ │ │ ├── title_section.dart │ │ │ │ ├── description_section.dart │ │ │ │ └── full_name_widget.dart │ │ │ ├── smart_widgets │ │ │ │ ├── widget_two │ │ │ │ │ ├── widget_two_viewmodel.dart │ │ │ │ │ └── widget_two.dart │ │ │ │ └── widget_one │ │ │ │ │ ├── widget_one_viewmodel.dart │ │ │ │ │ └── widget_one.dart │ │ │ ├── nonreactive │ │ │ │ ├── nonreactive_viewmodel.dart │ │ │ │ └── nonreactive_view.dart │ │ │ ├── multiple_futures_example │ │ │ │ └── multiple_futures_example_viewmodel.dart │ │ │ ├── home │ │ │ │ ├── home_view_multiple_widgets.dart │ │ │ │ └── home_view_traditional.dart │ │ │ ├── stream_view │ │ │ │ ├── stream_counter_viewmodel.dart │ │ │ │ └── stream_counter_view.dart │ │ │ └── multiple_streams_example │ │ │ │ └── multiple_streams_example_viewmodel.dart │ │ ├── app │ │ │ ├── custom_route_transition.dart │ │ │ ├── app.dialogs.dart │ │ │ ├── app.dialog.dart │ │ │ └── app.bottomsheets.dart │ │ └── main.dart │ ├── ios │ │ ├── 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 │ │ │ ├── AppDelegate.swift │ │ │ └── Base.lproj │ │ │ │ └── Main.storyboard │ │ ├── Flutter │ │ │ ├── Debug.xcconfig │ │ │ ├── Release.xcconfig │ │ │ └── AppFrameworkInfo.plist │ │ ├── Runner.xcodeproj │ │ │ └── project.xcworkspace │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ └── xcshareddata │ │ │ │ ├── WorkspaceSettings.xcsettings │ │ │ │ └── IDEWorkspaceChecks.plist │ │ ├── Runner.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── WorkspaceSettings.xcsettings │ │ │ │ └── IDEWorkspaceChecks.plist │ │ ├── RunnerTests │ │ │ └── RunnerTests.swift │ │ ├── .gitignore │ │ └── Podfile │ ├── web │ │ ├── favicon.png │ │ ├── icons │ │ │ ├── Icon-192.png │ │ │ ├── Icon-512.png │ │ │ ├── Icon-maskable-192.png │ │ │ └── Icon-maskable-512.png │ │ └── manifest.json │ ├── macos │ │ ├── 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 │ │ │ ├── AppDelegate.swift │ │ │ ├── Release.entitlements │ │ │ ├── DebugProfile.entitlements │ │ │ ├── MainFlutterWindow.swift │ │ │ └── Info.plist │ │ ├── .gitignore │ │ ├── Flutter │ │ │ ├── Flutter-Debug.xcconfig │ │ │ ├── Flutter-Release.xcconfig │ │ │ └── GeneratedPluginRegistrant.swift │ │ ├── Runner.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ └── IDEWorkspaceChecks.plist │ │ ├── Runner.xcodeproj │ │ │ └── project.xcworkspace │ │ │ │ └── xcshareddata │ │ │ │ └── IDEWorkspaceChecks.plist │ │ ├── RunnerTests │ │ │ └── RunnerTests.swift │ │ └── Podfile │ ├── android │ │ ├── gradle.properties │ │ ├── .gitignore │ │ ├── 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 │ │ │ │ │ ├── values │ │ │ │ │ │ └── styles.xml │ │ │ │ │ ├── drawable │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ ├── drawable-v21 │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ └── values-night │ │ │ │ │ │ └── styles.xml │ │ │ │ ├── kotlin │ │ │ │ │ └── com │ │ │ │ │ │ └── example │ │ │ │ │ │ ├── router_example │ │ │ │ │ │ └── MainActivity.kt │ │ │ │ │ │ └── new_architecture │ │ │ │ │ │ └── MainActivity.kt │ │ │ │ └── AndroidManifest.xml │ │ │ │ ├── debug │ │ │ │ └── AndroidManifest.xml │ │ │ │ └── profile │ │ │ │ └── AndroidManifest.xml │ │ ├── gradle │ │ │ └── wrapper │ │ │ │ └── gradle-wrapper.properties │ │ ├── settings.gradle │ │ └── build.gradle │ ├── test_driver │ │ └── integration_test.dart │ ├── windows │ │ ├── runner │ │ │ ├── resources │ │ │ │ └── app_icon.ico │ │ │ ├── resource.h │ │ │ ├── utils.h │ │ │ ├── runner.exe.manifest │ │ │ ├── flutter_window.h │ │ │ └── main.cpp │ │ ├── flutter │ │ │ ├── generated_plugin_registrant.cc │ │ │ ├── generated_plugin_registrant.h │ │ │ └── generated_plugins.cmake │ │ └── .gitignore │ ├── build.yaml │ ├── test │ │ └── stacked_example_test.dart │ ├── README.md │ ├── .gitignore │ ├── analysis_options.yaml │ └── .metadata └── navigator_example │ ├── ios │ ├── 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 │ │ ├── AppDelegate.swift │ │ ├── Base.lproj │ │ │ └── Main.storyboard │ │ └── Info.plist │ ├── Flutter │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ └── AppFrameworkInfo.plist │ ├── Runner.xcodeproj │ │ └── project.xcworkspace │ │ │ └── contents.xcworkspacedata │ ├── Runner.xcworkspace │ │ └── contents.xcworkspacedata │ ├── .gitignore │ └── Podfile │ ├── lib │ ├── datamodels │ │ ├── home_type.dart │ │ ├── clashable_two.dart │ │ ├── clashable_one.dart │ │ └── human.dart │ ├── services │ │ ├── iepoch_service.dart │ │ ├── factory_service.dart │ │ ├── information_service.dart │ │ └── epoch_service.dart │ ├── ui │ │ ├── startup │ │ │ ├── startup_viewmodel.dart │ │ │ └── startup_view.dart │ │ ├── bottom_nav │ │ │ ├── profile │ │ │ │ ├── profile_viewmodel.dart │ │ │ │ └── profile_view.dart │ │ │ ├── history │ │ │ │ ├── history_viewmodel.dart │ │ │ │ └── history_view.dart │ │ │ ├── favorites │ │ │ │ ├── favorites_viewmodel.dart │ │ │ │ └── favorites_view.dart │ │ │ ├── bottom_nav_example_viewmodel.dart │ │ │ └── bottom_nav_example.dart │ │ ├── form │ │ │ ├── custom_text_field.dart │ │ │ ├── validators.dart │ │ │ └── example_form_viewmodel.dart │ │ ├── drop_down_menu_from │ │ │ └── select_location_viewmodel.dart │ │ ├── builder_widget_example │ │ │ ├── builder_widget_example_viewmodel.dart │ │ │ └── builder_widget_example_view.dart │ │ ├── dialogs │ │ │ └── basic_dialog.dart │ │ ├── future_example_view │ │ │ ├── future_example_viewmodel.dart │ │ │ └── future_example_view.dart │ │ ├── dumb_widgets │ │ │ ├── duplicate_name_widget.dart │ │ │ ├── title_section.dart │ │ │ ├── description_section.dart │ │ │ └── full_name_widget.dart │ │ ├── smart_widgets │ │ │ ├── widget_two │ │ │ │ ├── widget_two_viewmodel.dart │ │ │ │ └── widget_two.dart │ │ │ └── widget_one │ │ │ │ ├── widget_one_viewmodel.dart │ │ │ │ └── widget_one.dart │ │ ├── nonreactive │ │ │ ├── nonreactive_viewmodel.dart │ │ │ └── nonreactive_view.dart │ │ ├── multiple_futures_example │ │ │ ├── multiple_futures_example_viewmodel.dart │ │ │ └── multiple_futures_example_view.dart │ │ ├── home │ │ │ ├── home_viewmodel.dart │ │ │ ├── home_view_multiple_widgets.dart │ │ │ └── home_view_traditional.dart │ │ ├── stream_view │ │ │ ├── stream_counter_viewmodel.dart │ │ │ └── stream_counter_view.dart │ │ └── multiple_streams_example │ │ │ └── multiple_streams_example_viewmodel.dart │ ├── app │ │ ├── custom_route_transition.dart │ │ ├── app.dialogs.dart │ │ ├── app.dialog.dart │ │ └── app.bottomsheets.dart │ └── main.dart │ ├── android │ ├── gradle.properties │ ├── .gitignore │ ├── 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 │ │ │ │ ├── values │ │ │ │ │ └── styles.xml │ │ │ │ └── drawable │ │ │ │ │ └── launch_background.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── example │ │ │ │ │ └── new_architecture │ │ │ │ │ └── MainActivity.kt │ │ │ └── AndroidManifest.xml │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ ├── settings.gradle │ └── build.gradle │ ├── test_driver │ └── integration_test.dart │ ├── test │ └── stacked_example_test.dart │ ├── README.md │ └── analysis_options.yaml ├── .DS_Store ├── lib ├── stacked_annotations.dart └── src │ ├── router │ ├── common │ │ ├── common.dart │ │ └── route_wrapper.dart │ ├── router_service_interface.dart │ ├── widgets │ │ ├── empty_router_widgets.dart │ │ └── wrapped_route.dart │ ├── controller │ │ ├── navigation_history │ │ │ ├── navigation_history.dart │ │ │ └── web_navigation_history.dart │ │ └── pageless_routes_observer.dart │ ├── navigation_failure.dart │ ├── route │ │ ├── route_data_scope.dart │ │ └── route_config.dart │ └── transitions │ │ └── stacked_page_route.dart │ ├── utils.dart │ ├── code_generation │ └── router_annotation │ │ ├── uri_extension.dart │ │ ├── stacked_route_wrapper.dart │ │ └── route_def.dart │ ├── reactive │ ├── type_def.dart │ └── reactive_value │ │ └── reactive_value.dart │ ├── view_models │ ├── form_view_model.dart │ ├── helpers │ │ ├── data_state_helper.dart │ │ ├── builders_helpers.dart │ │ ├── message_state_helper.dart │ │ └── form_state_helper.dart │ ├── index_tracking_viewmodel.dart │ ├── selector_view_model_builder.dart │ ├── view_model_widget.dart │ └── selector_view_model_builder_widget.dart │ └── mixins │ ├── reactive_service_mixin.dart │ └── listenable_service_mixin.dart ├── assets └── banner.jpeg ├── renovate.json ├── test └── stacked_test.dart ├── .github ├── FUNDING.yml └── workflows │ ├── release.yml │ └── test.yml ├── .metadata ├── pubspec.yaml ├── .releaserc.json ├── README.md ├── LICENSE └── analysis_options.yaml /example/router_example/linux/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral 2 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/.DS_Store -------------------------------------------------------------------------------- /lib/stacked_annotations.dart: -------------------------------------------------------------------------------- 1 | export 'package:stacked_shared/stacked_shared.dart'; 2 | -------------------------------------------------------------------------------- /assets/banner.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/assets/banner.jpeg -------------------------------------------------------------------------------- /example/router_example/lib/datamodels/home_type.dart: -------------------------------------------------------------------------------- 1 | enum HomeType { apartment, house } 2 | -------------------------------------------------------------------------------- /example/router_example/lib/services/iepoch_service.dart: -------------------------------------------------------------------------------- 1 | abstract class IEpochService {} 2 | -------------------------------------------------------------------------------- /example/navigator_example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" -------------------------------------------------------------------------------- /example/navigator_example/lib/datamodels/home_type.dart: -------------------------------------------------------------------------------- 1 | enum HomeType { apartment, house } 2 | -------------------------------------------------------------------------------- /example/navigator_example/lib/services/iepoch_service.dart: -------------------------------------------------------------------------------- 1 | abstract class IEpochService {} 2 | -------------------------------------------------------------------------------- /example/router_example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" -------------------------------------------------------------------------------- /example/router_example/web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/router_example/web/favicon.png -------------------------------------------------------------------------------- /example/router_example/lib/datamodels/clashable_two.dart: -------------------------------------------------------------------------------- 1 | class Clashable { 2 | final int age; 3 | 4 | const Clashable(this.age); 5 | } 6 | -------------------------------------------------------------------------------- /example/router_example/macos/Runner/Configs/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Debug.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /example/navigator_example/lib/datamodels/clashable_two.dart: -------------------------------------------------------------------------------- 1 | class Clashable { 2 | final int age; 3 | 4 | const Clashable(this.age); 5 | } 6 | -------------------------------------------------------------------------------- /example/router_example/lib/datamodels/clashable_one.dart: -------------------------------------------------------------------------------- 1 | class Clashable { 2 | final String name; 3 | 4 | const Clashable(this.name); 5 | } 6 | -------------------------------------------------------------------------------- /example/router_example/macos/Runner/Configs/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Release.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /example/router_example/web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/router_example/web/icons/Icon-192.png -------------------------------------------------------------------------------- /example/router_example/web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/router_example/web/icons/Icon-512.png -------------------------------------------------------------------------------- /example/navigator_example/lib/datamodels/clashable_one.dart: -------------------------------------------------------------------------------- 1 | class Clashable { 2 | final String name; 3 | 4 | const Clashable(this.name); 5 | } 6 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["local>Stacked-Org/renovate-config"] 4 | } 5 | -------------------------------------------------------------------------------- /test/stacked_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | 3 | void main() { 4 | test('adds one to input values', () {}); 5 | } 6 | -------------------------------------------------------------------------------- /example/router_example/macos/.gitignore: -------------------------------------------------------------------------------- 1 | # Flutter-related 2 | **/Flutter/ephemeral/ 3 | **/Pods/ 4 | 5 | # Xcode-related 6 | **/dgph 7 | **/xcuserdata/ 8 | -------------------------------------------------------------------------------- /example/navigator_example/lib/ui/startup/startup_viewmodel.dart: -------------------------------------------------------------------------------- 1 | import 'package:stacked/stacked.dart'; 2 | 3 | class StartupVieWModel extends BaseViewModel {} 4 | -------------------------------------------------------------------------------- /example/router_example/web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/router_example/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /example/router_example/web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/router_example/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /example/navigator_example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/router_example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | -------------------------------------------------------------------------------- /example/router_example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/router_example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/navigator_example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | -------------------------------------------------------------------------------- /example/navigator_example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/router_example/lib/datamodels/human.dart: -------------------------------------------------------------------------------- 1 | class Human { 2 | final String? name; 3 | final String? surname; 4 | 5 | Human({this.name, this.surname}); 6 | } 7 | -------------------------------------------------------------------------------- /example/router_example/lib/ui/bottom_nav/profile/profile_viewmodel.dart: -------------------------------------------------------------------------------- 1 | import 'package:stacked/stacked.dart'; 2 | 3 | class ProfileViewModel extends BaseViewModel {} 4 | -------------------------------------------------------------------------------- /lib/src/router/common/common.dart: -------------------------------------------------------------------------------- 1 | export 'route_observer.dart'; 2 | export 'route_wrapper.dart'; 3 | export 'parameters.dart'; 4 | export 'transitions_builders.dart'; 5 | -------------------------------------------------------------------------------- /example/navigator_example/lib/datamodels/human.dart: -------------------------------------------------------------------------------- 1 | class Human { 2 | final String? name; 3 | final String? surname; 4 | 5 | Human({this.name, this.surname}); 6 | } 7 | -------------------------------------------------------------------------------- /example/navigator_example/lib/ui/bottom_nav/profile/profile_viewmodel.dart: -------------------------------------------------------------------------------- 1 | import 'package:stacked/stacked.dart'; 2 | 3 | class ProfileViewModel extends BaseViewModel {} 4 | -------------------------------------------------------------------------------- /example/router_example/test_driver/integration_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:integration_test/integration_test_driver.dart'; 2 | 3 | Future main() => integrationDriver(); 4 | -------------------------------------------------------------------------------- /example/router_example/windows/runner/resources/app_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/router_example/windows/runner/resources/app_icon.ico -------------------------------------------------------------------------------- /example/navigator_example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | -------------------------------------------------------------------------------- /example/navigator_example/test_driver/integration_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:integration_test/integration_test_driver.dart'; 2 | 3 | Future main() => integrationDriver(); 4 | -------------------------------------------------------------------------------- /example/router_example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | -------------------------------------------------------------------------------- /example/router_example/build.yaml: -------------------------------------------------------------------------------- 1 | targets: 2 | $default: 3 | builders: 4 | stacked_generator|stackedRouterGenerator: 5 | options: 6 | navigator2: true -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [filledstacks] 4 | custom: [https://dane-mackier-s-school.teachable.com/p/master-flutter-on-the-web] 5 | -------------------------------------------------------------------------------- /example/router_example/macos/Flutter/Flutter-Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/router_example/macos/Flutter/Flutter-Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/router_example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/router_example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/router_example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/router_example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/router_example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/router_example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/navigator_example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/navigator_example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/navigator_example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/navigator_example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/navigator_example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/navigator_example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/router_example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/router_example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/router_example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/router_example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /lib/src/router/router_service_interface.dart: -------------------------------------------------------------------------------- 1 | import 'package:stacked/stacked.dart'; 2 | 3 | abstract class RouterServiceInterface { 4 | RootStackRouter get router; 5 | 6 | RouteData get topRoute; 7 | } 8 | -------------------------------------------------------------------------------- /example/navigator_example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/navigator_example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/navigator_example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/navigator_example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /lib/src/utils.dart: -------------------------------------------------------------------------------- 1 | bool mapNullOrEmpty(Map? map) { 2 | return (map == null || map.isEmpty); 3 | } 4 | 5 | bool listNullOrEmpty(Iterable? iterable) { 6 | return (iterable == null || iterable.isEmpty); 7 | } 8 | -------------------------------------------------------------------------------- /example/router_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/router_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/router_example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/router_example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png -------------------------------------------------------------------------------- /example/router_example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/router_example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png -------------------------------------------------------------------------------- /example/router_example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/router_example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png -------------------------------------------------------------------------------- /example/navigator_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/navigator_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/router_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/router_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/router_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/router_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/router_example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/router_example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png -------------------------------------------------------------------------------- /example/router_example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/router_example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png -------------------------------------------------------------------------------- /example/router_example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/router_example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png -------------------------------------------------------------------------------- /example/router_example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/router_example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png -------------------------------------------------------------------------------- /example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/navigator_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/navigator_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/navigator_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/navigator_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/navigator_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/navigator_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/navigator_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/navigator_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/navigator_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/navigator_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/navigator_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/navigator_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/navigator_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/navigator_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/navigator_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/navigator_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/navigator_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/navigator_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/navigator_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/navigator_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/navigator_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/navigator_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/navigator_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/navigator_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/navigator_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/navigator_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/navigator_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/navigator_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/navigator_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/navigator_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/router_example/linux/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/router_example/android/app/src/main/kotlin/com/example/router_example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.router_example 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /example/navigator_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/navigator_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/navigator_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stacked-Org/stacked/HEAD/example/navigator_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/router_example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/navigator_example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/router_example/android/app/src/main/kotlin/com/example/new_architecture/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.new_architecture 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /example/navigator_example/android/app/src/main/kotlin/com/example/new_architecture/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.new_architecture 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /lib/src/code_generation/router_annotation/uri_extension.dart: -------------------------------------------------------------------------------- 1 | extension UriX on Uri { 2 | String get normalizedPath => 3 | hasQueryParams || hasFragment ? toString() : path; 4 | 5 | bool get hasQueryParams => queryParameters.isNotEmpty == true; 6 | } 7 | -------------------------------------------------------------------------------- /lib/src/router/common/route_wrapper.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart' show BuildContext, Widget; 2 | 3 | // clients will implement this class to provide a wrapped route. 4 | abstract class RouteWrapper { 5 | Widget wrappedRoute(BuildContext context); 6 | } 7 | -------------------------------------------------------------------------------- /lib/src/code_generation/router_annotation/stacked_route_wrapper.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | // clients will implement this class to provide a wrapped route. 4 | abstract class StackedRouteWrapper { 5 | Widget wrappedRoute(BuildContext context); 6 | } 7 | -------------------------------------------------------------------------------- /example/navigator_example/lib/services/factory_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:stacked/stacked_annotations.dart'; 2 | 3 | class FactoryService { 4 | final String? data1; 5 | final double? data2; 6 | 7 | FactoryService(@factoryParam this.data1, {@factoryParam this.data2}); 8 | } 9 | -------------------------------------------------------------------------------- /example/router_example/lib/services/factory_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:stacked_shared/stacked_shared.dart'; 2 | 3 | class FactoryService { 4 | final String? data1; 5 | final double? data2; 6 | 7 | FactoryService(@factoryParam this.data1, {@factoryParam this.data2}); 8 | } 9 | -------------------------------------------------------------------------------- /example/router_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/router_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/router_example/lib/ui/form/custom_text_field.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class CustomEditingController extends TextEditingController { 4 | static CustomEditingController getCustomEditingController() { 5 | return CustomEditingController(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /example/navigator_example/lib/ui/form/custom_text_field.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class CustomEditingController extends TextEditingController { 4 | static CustomEditingController getCustomEditingController() { 5 | return CustomEditingController(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /example/router_example/macos/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | @NSApplicationMain 5 | class AppDelegate: FlutterAppDelegate { 6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 7 | return true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /example/router_example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip 7 | -------------------------------------------------------------------------------- /example/router_example/macos/Runner/Release.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/navigator_example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip 7 | -------------------------------------------------------------------------------- /example/navigator_example/test/stacked_example_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | 3 | void main() { 4 | group('StackedExampleTest -', () { 5 | group('test -', () { 6 | test('just a place holder', () { 7 | expect(true, isTrue); 8 | }); 9 | }); 10 | }); 11 | } 12 | -------------------------------------------------------------------------------- /example/router_example/test/stacked_example_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | 3 | void main() { 4 | group('StackedExampleTest -', () { 5 | group('test -', () { 6 | test('just a place holder', () { 7 | expect(true, isTrue); 8 | }); 9 | }); 10 | }); 11 | } 12 | -------------------------------------------------------------------------------- /example/navigator_example/lib/ui/bottom_nav/history/history_viewmodel.dart: -------------------------------------------------------------------------------- 1 | import 'package:stacked/stacked.dart'; 2 | 3 | class HistoryViewModel extends FutureViewModel { 4 | @override 5 | Future futureToRun() async { 6 | await Future.delayed(const Duration(seconds: 2)); 7 | return 100; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /example/router_example/lib/ui/bottom_nav/history/history_viewmodel.dart: -------------------------------------------------------------------------------- 1 | import 'package:stacked/stacked.dart'; 2 | 3 | class HistoryViewModel extends FutureViewModel { 4 | @override 5 | Future futureToRun() async { 6 | await Future.delayed(const Duration(seconds: 2)); 7 | return 100; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lib/src/router/widgets/empty_router_widgets.dart: -------------------------------------------------------------------------------- 1 | import 'package:stacked/src/router/widgets/nested_router.dart'; 2 | 3 | class EmptyRouterPage extends NestedRouter { 4 | const EmptyRouterPage({super.key}); 5 | } 6 | 7 | class EmptyRouterScreen extends NestedRouter { 8 | const EmptyRouterScreen({super.key}); 9 | } 10 | -------------------------------------------------------------------------------- /example/navigator_example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/router_example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/router_example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/router_example/macos/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/router_example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/router_example/lib/ui/drop_down_menu_from/select_location_viewmodel.dart: -------------------------------------------------------------------------------- 1 | import 'package:stacked/stacked.dart'; 2 | 3 | import '../../app/app.logger.dart'; 4 | 5 | class SelectLocationViewModel extends FormViewModel { 6 | var log = getLogger('SelectLocationViewModel'); 7 | 8 | @override 9 | void setFormStatus() {} 10 | } 11 | -------------------------------------------------------------------------------- /example/navigator_example/lib/ui/drop_down_menu_from/select_location_viewmodel.dart: -------------------------------------------------------------------------------- 1 | import 'package:stacked/stacked.dart'; 2 | 3 | import '../../app/app.logger.dart'; 4 | 5 | class SelectLocationViewModel extends FormViewModel { 6 | var log = getLogger('SelectLocationViewModel'); 7 | 8 | @override 9 | void setFormStatus() {} 10 | } 11 | -------------------------------------------------------------------------------- /example/router_example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/router_example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 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: f139b11009aeb8ed2a3a3aa8b0066e482709dde3 8 | channel: stable 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /example/router_example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/router_example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/navigator_example/lib/ui/builder_widget_example/builder_widget_example_viewmodel.dart: -------------------------------------------------------------------------------- 1 | import 'package:stacked/stacked.dart'; 2 | 3 | class BuilderWidgetExampleViewModel extends BaseViewModel { 4 | String title = 'default'; 5 | 6 | int counter = 0; 7 | void updateTitle() { 8 | counter++; 9 | title = '$counter'; 10 | notifyListeners(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /example/router_example/lib/ui/builder_widget_example/builder_widget_example_viewmodel.dart: -------------------------------------------------------------------------------- 1 | import 'package:stacked/stacked.dart'; 2 | 3 | class BuilderWidgetExampleViewModel extends BaseViewModel { 4 | String title = 'default'; 5 | 6 | int counter = 0; 7 | void updateTitle() { 8 | counter++; 9 | title = '$counter'; 10 | notifyListeners(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/src/reactive/type_def.dart: -------------------------------------------------------------------------------- 1 | typedef ValueCallback = void Function(T v); 2 | 3 | /// A callback with no arguments. 4 | /// 5 | /// Intended to listen to events emitted by [Emitter]. 6 | typedef Callback = dynamic Function(); 7 | 8 | typedef Condition = bool Function(); 9 | 10 | typedef ValueGetter = T Function(); 11 | 12 | typedef ValueSetter = void Function(T val); 13 | -------------------------------------------------------------------------------- /example/router_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/router_example/macos/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import FlutterMacOS 2 | import Cocoa 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/router_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/router_example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /lib/src/view_models/form_view_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:stacked/stacked.dart'; 2 | 3 | /// You can use [BaseViewModel] or [ReactiveViewModel] with a [FormStateHelper] 4 | /// to achive the same result incase you want to combine multiple functionalities 5 | abstract class FormViewModel extends ReactiveViewModel with FormStateHelper { 6 | @override 7 | List get listenableServices => []; 8 | } 9 | -------------------------------------------------------------------------------- /example/navigator_example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/navigator_example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/router_example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/router_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/navigator_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/router_example/lib/services/information_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:stacked/stacked.dart'; 2 | 3 | class InformationService with ListenableServiceMixin { 4 | int _postCount = 0; 5 | int get postCount => _postCount; 6 | 7 | void updatePostCount() { 8 | _postCount++; 9 | notifyListeners(); 10 | } 11 | 12 | void resetCount() { 13 | _postCount = 0; 14 | notifyListeners(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /example/navigator_example/lib/services/information_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:stacked/stacked.dart'; 2 | 3 | class InformationService with ListenableServiceMixin { 4 | int _postCount = 0; 5 | int get postCount => _postCount; 6 | 7 | void updatePostCount() { 8 | _postCount++; 9 | notifyListeners(); 10 | } 11 | 12 | void resetCount() { 13 | _postCount = 0; 14 | notifyListeners(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /example/navigator_example/lib/ui/bottom_nav/favorites/favorites_viewmodel.dart: -------------------------------------------------------------------------------- 1 | import 'package:stacked/stacked.dart'; 2 | 3 | class FavoritesViewModel extends BaseViewModel { 4 | int _counter = 0; 5 | int get counter => _counter; 6 | 7 | void incrementCounter() { 8 | _counter++; 9 | notifyListeners(); 10 | } 11 | 12 | void setCounterTo999() { 13 | _counter = 999; 14 | notifyListeners(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /example/router_example/lib/ui/bottom_nav/favorites/favorites_viewmodel.dart: -------------------------------------------------------------------------------- 1 | import 'package:stacked/stacked.dart'; 2 | 3 | class FavoritesViewModel extends BaseViewModel { 4 | int _counter = 0; 5 | int get counter => _counter; 6 | 7 | void incrementCounter() { 8 | _counter++; 9 | notifyListeners(); 10 | } 11 | 12 | void setCounterTo999() { 13 | _counter = 999; 14 | notifyListeners(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /example/router_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/router_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/router_example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /example/navigator_example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /example/router_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 | -------------------------------------------------------------------------------- /lib/src/router/widgets/wrapped_route.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:stacked/src/router/common/route_wrapper.dart'; 3 | 4 | @optionalTypeArgs 5 | class WrappedRoute extends StatelessWidget { 6 | const WrappedRoute({super.key, required this.child}); 7 | final T child; 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return child.wrappedRoute(context); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/src/view_models/helpers/data_state_helper.dart: -------------------------------------------------------------------------------- 1 | import 'package:stacked/src/view_models/helpers/busy_error_state_helper.dart'; 2 | 3 | /// Helper class to store a data object 4 | mixin DataStateHelper on BusyAndErrorStateHelper { 5 | T? _data; 6 | 7 | T? get data => _data; 8 | 9 | set data(T? data) { 10 | _data = data; 11 | } 12 | 13 | /// Data is ready to be consumed 14 | bool get dataReady => _data != null && !hasError && !isBusy; 15 | } 16 | -------------------------------------------------------------------------------- /example/navigator_example/lib/ui/form/validators.dart: -------------------------------------------------------------------------------- 1 | class FormValidators { 2 | static String? passwordValidator(String? value) { 3 | if (value == null || value.isEmpty) { 4 | return "Password should not be empty"; 5 | } else { 6 | return null; 7 | } 8 | } 9 | 10 | static String? emailValidator(String? value) { 11 | if (value == null || value.isEmpty) { 12 | return 'Email is required'; 13 | } 14 | return null; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /example/router_example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 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/router_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/navigator_example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 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/router_example/linux/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/navigator_example/lib/services/epoch_service.dart: -------------------------------------------------------------------------------- 1 | class EpochService { 2 | Stream epochUpdatesNumbers() async* { 3 | while (true) { 4 | await Future.delayed(const Duration(seconds: 2)); 5 | yield DateTime.now().millisecondsSinceEpoch; 6 | } 7 | } 8 | 9 | Stream epochUpdateNumbersQuickly() async* { 10 | while (true) { 11 | await Future.delayed(const Duration(milliseconds: 200)); 12 | yield DateTime.now().millisecondsSinceEpoch; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /example/router_example/lib/services/epoch_service.dart: -------------------------------------------------------------------------------- 1 | class EpochService { 2 | Stream epochUpdatesNumbers() async* { 3 | while (true) { 4 | await Future.delayed(const Duration(seconds: 2)); 5 | yield DateTime.now().millisecondsSinceEpoch; 6 | } 7 | } 8 | 9 | Stream epochUpdateNumbersQuickly() async* { 10 | while (true) { 11 | await Future.delayed(const Duration(milliseconds: 200)); 12 | yield DateTime.now().millisecondsSinceEpoch; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /example/navigator_example/lib/ui/dialogs/basic_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:stacked_services/stacked_services.dart'; 3 | 4 | class BasicDialog extends StatelessWidget { 5 | final DialogRequest request; 6 | final void Function(DialogResponse) completer; 7 | const BasicDialog({ 8 | super.key, 9 | required this.request, 10 | required this.completer, 11 | }); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return const SizedBox(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /example/router_example/lib/ui/dialogs/basic_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:stacked_services/stacked_services.dart'; 3 | 4 | class BasicDialog extends StatelessWidget { 5 | final DialogRequest request; 6 | final void Function(DialogResponse) completer; 7 | const BasicDialog({ 8 | super.key, 9 | required this.request, 10 | required this.completer, 11 | }); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return const SizedBox(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /example/router_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/router_example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/navigator_example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/router_example/lib/ui/startup/startup_viewmodel.dart: -------------------------------------------------------------------------------- 1 | import 'package:example/app/app.locator.dart'; 2 | import 'package:example/app/app.logger.dart'; 3 | import 'package:example/services/shared_preferences_service.dart'; 4 | import 'package:stacked/stacked.dart'; 5 | 6 | class StartupVieWModel extends BaseViewModel { 7 | final _log = getLogger('StartupVieWModel'); 8 | final _preferences = exampleLocator(); 9 | 10 | void initialise() { 11 | _log.d('isUserLoggedIn:${_preferences.isUserLoggedIn}'); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/navigator_example/lib/app/custom_route_transition.dart: -------------------------------------------------------------------------------- 1 | import 'package:animations/animations.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class CustomRouteTransition { 5 | static Widget sharedAxis(BuildContext context, Animation animation, 6 | Animation secondaryAnimation, Widget child) { 7 | return SharedAxisTransition( 8 | animation: animation, 9 | secondaryAnimation: secondaryAnimation, 10 | transitionType: SharedAxisTransitionType.scaled, 11 | child: child, 12 | ); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /example/router_example/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/router_example/lib/app/custom_route_transition.dart: -------------------------------------------------------------------------------- 1 | import 'package:animations/animations.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class CustomRouteTransition { 5 | static Widget sharedAxis(BuildContext context, Animation animation, 6 | Animation secondaryAnimation, Widget child) { 7 | return SharedAxisTransition( 8 | animation: animation, 9 | secondaryAnimation: secondaryAnimation, 10 | transitionType: SharedAxisTransitionType.scaled, 11 | child: child, 12 | ); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /example/router_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/router_example/lib/ui/future_example_view/future_example_viewmodel.dart: -------------------------------------------------------------------------------- 1 | import 'package:stacked/stacked.dart'; 2 | 3 | class FutureExampleViewModel extends FutureViewModel { 4 | Future getDataFromServer() async { 5 | await Future.delayed(const Duration(seconds: 3)); 6 | // throw Exception('This is an error'); // Uncomment to trigger error UI 7 | return 'This is fetched from everywhere'; 8 | } 9 | 10 | @override 11 | void onError(error) {} 12 | 13 | @override 14 | Future futureToRun() => getDataFromServer(); 15 | } 16 | -------------------------------------------------------------------------------- /example/navigator_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/navigator_example/lib/ui/future_example_view/future_example_viewmodel.dart: -------------------------------------------------------------------------------- 1 | import 'package:stacked/stacked.dart'; 2 | 3 | class FutureExampleViewModel extends FutureViewModel { 4 | Future getDataFromServer() async { 5 | await Future.delayed(const Duration(seconds: 3)); 6 | // throw Exception('This is an error'); // Uncomment to trigger error UI 7 | return 'This is fetched from everywhere'; 8 | } 9 | 10 | @override 11 | void onError(error) {} 12 | 13 | @override 14 | Future futureToRun() => getDataFromServer(); 15 | } 16 | -------------------------------------------------------------------------------- /example/router_example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /example/navigator_example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /example/navigator_example/lib/ui/bottom_nav/profile/profile_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:stacked/stacked.dart'; 3 | 4 | import 'profile_viewmodel.dart'; 5 | 6 | class ProfileView extends StatelessWidget { 7 | const ProfileView({super.key}); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return ViewModelBuilder.reactive( 12 | builder: (context, viewModel, child) => 13 | const Scaffold(body: Center(child: Text('Profile'))), 14 | viewModelBuilder: () => ProfileViewModel(), 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /example/router_example/lib/ui/bottom_nav/profile/profile_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:stacked/stacked.dart'; 3 | 4 | import 'profile_viewmodel.dart'; 5 | 6 | class ProfileView extends StatelessWidget { 7 | const ProfileView({super.key}); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return ViewModelBuilder.reactive( 12 | builder: (context, viewModel, child) => 13 | const Scaffold(body: Center(child: Text('Profile'))), 14 | viewModelBuilder: () => ProfileViewModel(), 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /example/navigator_example/lib/ui/dumb_widgets/duplicate_name_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:example/datamodels/human.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:stacked/stacked.dart'; 4 | 5 | class DuplicateNameWidget extends ViewModelWidget { 6 | const DuplicateNameWidget({super.key}); 7 | 8 | @override 9 | Widget build(BuildContext context, Human viewModel) { 10 | return Row( 11 | children: [ 12 | Text(viewModel.name!), 13 | const SizedBox( 14 | width: 50, 15 | ), 16 | Text(viewModel.name!), 17 | ], 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /example/router_example/lib/ui/dumb_widgets/duplicate_name_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:example/datamodels/human.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:stacked/stacked.dart'; 4 | 5 | class DuplicateNameWidget extends ViewModelWidget { 6 | const DuplicateNameWidget({super.key}); 7 | 8 | @override 9 | Widget build(BuildContext context, Human viewModel) { 10 | return Row( 11 | children: [ 12 | Text(viewModel.name!), 13 | const SizedBox( 14 | width: 50, 15 | ), 16 | Text(viewModel.name!), 17 | ], 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /example/navigator_example/lib/ui/dumb_widgets/title_section.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:example/ui/home/home_viewmodel.dart'; 3 | import 'package:stacked/stacked.dart'; 4 | 5 | class TitleSection extends ViewModelWidget { 6 | const TitleSection({super.key}); 7 | 8 | @override 9 | Widget build(BuildContext context, HomeViewModel viewModel) { 10 | return Row( 11 | children: [ 12 | const Text( 13 | 'Title', 14 | style: TextStyle(fontSize: 20), 15 | ), 16 | Text(viewModel.title), 17 | ], 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /example/router_example/lib/ui/dumb_widgets/title_section.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:example/ui/home/home_viewmodel.dart'; 3 | import 'package:stacked/stacked.dart'; 4 | 5 | class TitleSection extends ViewModelWidget { 6 | const TitleSection({super.key}); 7 | 8 | @override 9 | Widget build(BuildContext context, HomeViewModel viewModel) { 10 | return Row( 11 | children: [ 12 | const Text( 13 | 'Title', 14 | style: TextStyle(fontSize: 20), 15 | ), 16 | Text(viewModel.title), 17 | ], 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /example/navigator_example/README.md: -------------------------------------------------------------------------------- 1 | # new_architecture 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://flutter.dev/docs/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) 13 | 14 | For help getting started with Flutter, view our 15 | [online documentation](https://flutter.dev/docs), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /example/router_example/README.md: -------------------------------------------------------------------------------- 1 | # new_architecture 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://flutter.dev/docs/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) 13 | 14 | For help getting started with Flutter, view our 15 | [online documentation](https://flutter.dev/docs), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /example/navigator_example/lib/ui/startup/startup_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:example/ui/startup/startup_viewmodel.dart'; 3 | import 'package:stacked/stacked.dart'; 4 | 5 | class StartupView extends StatelessWidget { 6 | const StartupView({super.key}); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return ViewModelBuilder.reactive( 11 | viewModelBuilder: () => StartupVieWModel(), 12 | builder: (context, viewModel, child) => const Scaffold( 13 | body: Center( 14 | child: Text('Startup View'), 15 | ), 16 | ), 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /example/router_example/lib/ui/form/validators.dart: -------------------------------------------------------------------------------- 1 | class FormValidators { 2 | static String? passwordValidator(String? value) { 3 | if (value == null || value.isEmpty) { 4 | return "Password should not be empty"; 5 | } 6 | 7 | if (value.length < 6) { 8 | return "Password must has at least 6 alpha characters"; 9 | } 10 | 11 | return null; 12 | } 13 | 14 | static String? emailValidator(String? value) { 15 | if (value == null || value.isEmpty) { 16 | return 'Email is required'; 17 | } 18 | 19 | if (!value.contains('@')) { 20 | return 'Email is not valid'; 21 | } 22 | 23 | return null; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /example/router_example/lib/ui/smart_widgets/widget_two/widget_two_viewmodel.dart: -------------------------------------------------------------------------------- 1 | import 'package:example/app/app.locator.dart'; 2 | import 'package:example/services/information_service.dart'; 3 | import 'package:stacked/stacked.dart'; 4 | 5 | class WidgetTwoViewModel extends ReactiveViewModel { 6 | final _informationService = exampleLocator(); 7 | int get postCount => _informationService.postCount; 8 | 9 | final int id; 10 | WidgetTwoViewModel(this.id); 11 | 12 | void reset() { 13 | _informationService.resetCount(); 14 | } 15 | 16 | @override 17 | List get listenableServices => [_informationService]; 18 | } 19 | -------------------------------------------------------------------------------- /example/navigator_example/lib/ui/smart_widgets/widget_two/widget_two_viewmodel.dart: -------------------------------------------------------------------------------- 1 | import 'package:example/app/app.locator.dart'; 2 | import 'package:example/services/information_service.dart'; 3 | import 'package:stacked/stacked.dart'; 4 | 5 | class WidgetTwoViewModel extends ReactiveViewModel { 6 | final _informationService = exampleLocator(); 7 | int get postCount => _informationService.postCount; 8 | 9 | final int id; 10 | WidgetTwoViewModel(this.id); 11 | 12 | void reset() { 13 | _informationService.resetCount(); 14 | } 15 | 16 | @override 17 | List get listenableServices => [_informationService]; 18 | } 19 | -------------------------------------------------------------------------------- /lib/src/view_models/index_tracking_viewmodel.dart: -------------------------------------------------------------------------------- 1 | // import 'package:stacked/src/mixins/listenable_service_mixin.dart'; 2 | // import 'package:stacked/src/router/router_service_interface.dart'; 3 | // import 'package:stacked/src/view_models/base_view_models.dart'; 4 | import 'package:stacked/stacked.dart'; 5 | 6 | /// You can use [BaseViewModel] or [ReactiveViewModel] with a [IndexTrackingStateHelper] 7 | /// to achive the same result incase you want to combine multiple functionalities 8 | abstract class IndexTrackingViewModel extends ReactiveViewModel 9 | with IndexTrackingStateHelper { 10 | @override 11 | List get listenableServices => []; 12 | } 13 | -------------------------------------------------------------------------------- /example/router_example/lib/ui/dumb_widgets/description_section.dart: -------------------------------------------------------------------------------- 1 | import 'package:example/ui/home/home_viewmodel.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:stacked/stacked.dart'; 4 | 5 | class DescriptionSection extends ViewModelWidget { 6 | const DescriptionSection({super.key}); 7 | 8 | @override 9 | Widget build(BuildContext context, HomeViewModel viewModel) { 10 | return Row( 11 | children: [ 12 | const Text( 13 | 'Description', 14 | style: TextStyle(fontSize: 14, fontWeight: FontWeight.w700), 15 | ), 16 | Text(viewModel.title), 17 | ], 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /example/router_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/navigator_example/lib/ui/dumb_widgets/description_section.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:example/ui/home/home_viewmodel.dart'; 3 | import 'package:stacked/stacked.dart'; 4 | 5 | class DescriptionSection extends ViewModelWidget { 6 | const DescriptionSection({super.key}); 7 | 8 | @override 9 | Widget build(BuildContext context, HomeViewModel viewModel) { 10 | return Row( 11 | children: [ 12 | const Text( 13 | 'Description', 14 | style: TextStyle(fontSize: 14, fontWeight: FontWeight.w700), 15 | ), 16 | Text(viewModel.title), 17 | ], 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /example/navigator_example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /example/router_example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /example/router_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 = router_example 9 | 10 | // The application's bundle identifier 11 | PRODUCT_BUNDLE_IDENTIFIER = com.example.routerExample 12 | 13 | // The copyright displayed in application information 14 | PRODUCT_COPYRIGHT = Copyright © 2023 com.example. All rights reserved. 15 | -------------------------------------------------------------------------------- /lib/src/code_generation/router_annotation/route_def.dart: -------------------------------------------------------------------------------- 1 | import 'router_base.dart'; 2 | 3 | class RouteDef { 4 | final String template; 5 | final RouterBase? generator; 6 | final Pattern pattern; 7 | final Type? page; 8 | 9 | RouteDef( 10 | this.template, { 11 | this.page, 12 | this.generator, 13 | }) : pattern = _buildPathPattern(template); 14 | 15 | bool get isParent => generator != null; 16 | 17 | static Pattern _buildPathPattern(String template) { 18 | var regEx = template.replaceAllMapped(RegExp(r':([^/|?]+)|([*])'), (m) { 19 | if (m[1] != null) { 20 | return '?(?<${m[1]}>[^/]+)'; 21 | } else { 22 | return ".*"; 23 | } 24 | }); 25 | return '^$regEx([/])?'; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /example/router_example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.4.10' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.5.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: stacked 2 | description: The framework to build testable, scalable and maintainable flutter apps 3 | version: 3.5.0 4 | homepage: https://github.com/FilledStacks/stacked 5 | platforms: 6 | android: 7 | ios: 8 | linux: 9 | macos: 10 | web: 11 | windows: 12 | 13 | environment: 14 | sdk: ">=3.5.0 <4.0.0" 15 | 16 | dependencies: 17 | flutter: 18 | sdk: flutter 19 | get_it: ^8.2.0 20 | meta: ^1.16.0 21 | provider: ^6.1.5 22 | collection: ^1.19.1 23 | # Remove the path and use pub one after publish the stacked_shared 24 | stacked_shared: ^1.4.2 25 | universal_io: ^2.2.2 26 | path: ^1.9.1 27 | web: ^1.1.1 28 | 29 | dev_dependencies: 30 | flutter_lints: ^6.0.0 31 | flutter_test: 32 | sdk: flutter 33 | -------------------------------------------------------------------------------- /example/navigator_example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.4.10' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.5.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /lib/src/router/controller/navigation_history/navigation_history.dart: -------------------------------------------------------------------------------- 1 | import 'package:stacked/src/router/controller/routing_controller.dart'; 2 | 3 | import 'navigation_history_base.dart'; 4 | 5 | class NavigationHistoryImpl extends NavigationHistory { 6 | NavigationHistoryImpl(StackRouter router); 7 | @override 8 | void back() { 9 | throw Exception("Stub implementation"); 10 | } 11 | 12 | @override 13 | bool get canNavigateBack => throw Exception("Stub implementation"); 14 | 15 | @override 16 | void forward() { 17 | throw Exception("Stub implementation"); 18 | } 19 | 20 | @override 21 | int get length => throw Exception("Stub implementation"); 22 | 23 | @override 24 | StackRouter get router => throw Exception("Stub implementation"); 25 | } 26 | -------------------------------------------------------------------------------- /lib/src/view_models/helpers/builders_helpers.dart: -------------------------------------------------------------------------------- 1 | /// Essential helper to work with the [ViewModelBuilder] 2 | mixin BuilderHelpers { 3 | bool disposed = false; 4 | 5 | bool _initialised = false; 6 | bool get initialised => _initialised; 7 | 8 | bool _onModelReadyCalled = false; 9 | bool get onModelReadyCalled => _onModelReadyCalled; 10 | 11 | /// Sets the initialised value for the ViewModel to true. This is called after 12 | /// the first initialise special ViewModel call 13 | void setInitialised(bool value) { 14 | _initialised = value; 15 | } 16 | 17 | /// Sets the onModelReadyCalled value to true. This is called after this first onModelReady call 18 | void setOnModelReadyCalled(bool value) { 19 | _onModelReadyCalled = value; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /example/router_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 | -------------------------------------------------------------------------------- /example/router_example/lib/ui/builder_widget_example/builder_widget_example_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:example/ui/home/home_viewmodel.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:stacked/stacked.dart'; 4 | 5 | class BuilderWidgetExampleView extends StackedView { 6 | const BuilderWidgetExampleView({super.key}); 7 | 8 | @override 9 | Widget builder(BuildContext context, HomeViewModel viewModel, Widget? child) { 10 | return Scaffold( 11 | body: Center( 12 | child: Text(viewModel.title), 13 | ), 14 | floatingActionButton: FloatingActionButton( 15 | onPressed: () => viewModel.updateTitle(), 16 | ), 17 | ); 18 | } 19 | 20 | @override 21 | HomeViewModel viewModelBuilder(BuildContext context) => HomeViewModel(); 22 | } 23 | -------------------------------------------------------------------------------- /example/navigator_example/lib/ui/builder_widget_example/builder_widget_example_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:example/ui/home/home_viewmodel.dart'; 3 | import 'package:stacked/stacked.dart'; 4 | 5 | class BuilderWidgetExampleView extends StackedView { 6 | const BuilderWidgetExampleView({super.key}); 7 | 8 | @override 9 | Widget builder(BuildContext context, HomeViewModel viewModel, Widget? child) { 10 | return Scaffold( 11 | body: Center( 12 | child: Text(viewModel.title), 13 | ), 14 | floatingActionButton: FloatingActionButton( 15 | onPressed: () => viewModel.updateTitle(), 16 | ), 17 | ); 18 | } 19 | 20 | @override 21 | HomeViewModel viewModelBuilder(BuildContext context) => HomeViewModel(); 22 | } 23 | -------------------------------------------------------------------------------- /example/router_example/lib/ui/startup/startup_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:example/ui/startup/startup_viewmodel.dart'; 3 | import 'package:stacked/stacked.dart'; 4 | 5 | class StartupView extends StackedView { 6 | const StartupView({super.key}); 7 | 8 | @override 9 | Widget builder( 10 | BuildContext context, 11 | StartupVieWModel viewModel, 12 | Widget? child, 13 | ) { 14 | return const Scaffold(body: Center(child: Text('Startup View'))); 15 | } 16 | 17 | @override 18 | StartupVieWModel viewModelBuilder(BuildContext context) { 19 | return StartupVieWModel(); 20 | } 21 | 22 | @override 23 | void onViewModelReady(StartupVieWModel viewModel) { 24 | viewModel.initialise(); 25 | super.onViewModelReady(viewModel); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /example/navigator_example/lib/ui/dumb_widgets/full_name_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:example/datamodels/human.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:stacked/stacked.dart'; 4 | 5 | class FullNameWidget extends ViewModelWidget { 6 | const FullNameWidget({super.key}); 7 | 8 | @override 9 | Widget build(BuildContext context, Human viewModel) { 10 | return Row( 11 | children: [ 12 | Text( 13 | viewModel.name!, 14 | style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 30), 15 | ), 16 | const SizedBox( 17 | width: 50, 18 | ), 19 | Text( 20 | viewModel.surname!, 21 | style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 30), 22 | ), 23 | ], 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /example/router_example/lib/ui/dumb_widgets/full_name_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:example/datamodels/human.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:stacked/stacked.dart'; 4 | 5 | class FullNameWidget extends ViewModelWidget { 6 | const FullNameWidget({super.key}); 7 | 8 | @override 9 | Widget build(BuildContext context, Human viewModel) { 10 | return Row( 11 | children: [ 12 | Text( 13 | viewModel.name!, 14 | style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 30), 15 | ), 16 | const SizedBox( 17 | width: 50, 18 | ), 19 | Text( 20 | viewModel.surname!, 21 | style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 30), 22 | ), 23 | ], 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /example/navigator_example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:stacked_services/stacked_services.dart'; 3 | import 'package:stacked_shared/stacked_shared.dart'; 4 | 5 | import 'app/app.locator.dart'; 6 | import 'app/app.router.dart'; 7 | 8 | void main() { 9 | setupExampleLocator(environment: Environment.dev); 10 | runApp(const MyApp()); 11 | } 12 | 13 | class MyApp extends StatelessWidget { 14 | const MyApp({super.key}); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return MaterialApp( 19 | title: 'Flutter Demo', 20 | navigatorKey: StackedService.navigatorKey, 21 | onGenerateRoute: StackedRouter().onGenerateRoute, 22 | initialRoute: Routes.homeView, 23 | theme: ThemeData( 24 | primarySwatch: Colors.blue, 25 | ), 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /example/router_example/lib/ui/smart_widgets/widget_one/widget_one_viewmodel.dart: -------------------------------------------------------------------------------- 1 | import 'package:example/app/app.locator.dart'; 2 | import 'package:example/services/information_service.dart'; 3 | import 'package:stacked/stacked.dart'; 4 | 5 | class WidgetOneViewModel extends ReactiveViewModel { 6 | final InformationService _informationService = 7 | exampleLocator(); 8 | int get postCount => _informationService.postCount; 9 | 10 | void updatePostCount() { 11 | _informationService.updatePostCount(); 12 | } 13 | 14 | Future longUpdateStuff() async { 15 | await runBusyFuture(updateStuff()); 16 | } 17 | 18 | Future updateStuff() { 19 | return Future.delayed(const Duration(seconds: 3)); 20 | } 21 | 22 | @override 23 | List get listenableServices => [_informationService]; 24 | } 25 | -------------------------------------------------------------------------------- /example/navigator_example/lib/ui/smart_widgets/widget_one/widget_one_viewmodel.dart: -------------------------------------------------------------------------------- 1 | import 'package:example/app/app.locator.dart'; 2 | import 'package:example/services/information_service.dart'; 3 | import 'package:stacked/stacked.dart'; 4 | 5 | class WidgetOneViewModel extends ReactiveViewModel { 6 | final InformationService _informationService = 7 | exampleLocator(); 8 | int get postCount => _informationService.postCount; 9 | 10 | void updatePostCount() { 11 | _informationService.updatePostCount(); 12 | } 13 | 14 | Future longUpdateStuff() async { 15 | await runBusyFuture(updateStuff()); 16 | } 17 | 18 | Future updateStuff() { 19 | return Future.delayed(const Duration(seconds: 3)); 20 | } 21 | 22 | @override 23 | List get listenableServices => [_informationService]; 24 | } 25 | -------------------------------------------------------------------------------- /example/router_example/lib/ui/nonreactive/nonreactive_viewmodel.dart: -------------------------------------------------------------------------------- 1 | import 'package:stacked/stacked.dart'; 2 | import 'package:stacked_services/stacked_services.dart'; 3 | 4 | import '../../app/app.locator.dart'; 5 | import '../../app/app.router.dart'; 6 | import '../../datamodels/clashable_two.dart'; 7 | 8 | class NonReactiveViewModel extends BaseViewModel { 9 | final _routerService = exampleLocator(); 10 | String title = 'This should not change'; 11 | 12 | void updateTitle() { 13 | title += '. This has changed'; 14 | notifyListeners(); 15 | } 16 | 17 | void navigateToNewView() { 18 | _routerService.navigateTo( 19 | StreamCounterViewRoute(clashableTwo: const [Clashable(22)]), 20 | ); 21 | } 22 | 23 | void navigateBackHome() { 24 | _routerService.clearStackAndShow(HomeViewRoute(title: 'Home')); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /example/navigator_example/lib/app/app.dialogs.dart: -------------------------------------------------------------------------------- 1 | // dart format width=80 2 | // GENERATED CODE - DO NOT MODIFY BY HAND 3 | 4 | // ************************************************************************** 5 | // StackedDialogGenerator 6 | // ************************************************************************** 7 | 8 | import 'package:stacked_services/stacked_services.dart'; 9 | 10 | import 'app.locator.dart'; 11 | import '../ui/dialogs/basic_dialog.dart'; 12 | 13 | enum DialogType { 14 | basic, 15 | } 16 | 17 | void setupDialogUi() { 18 | final dialogService = exampleLocator(); 19 | 20 | final Map builders = { 21 | DialogType.basic: (context, request, completer) => 22 | BasicDialog(request: request, completer: completer), 23 | }; 24 | 25 | dialogService.registerCustomDialogBuilders(builders); 26 | } 27 | -------------------------------------------------------------------------------- /example/router_example/lib/app/app.dialogs.dart: -------------------------------------------------------------------------------- 1 | // dart format width=80 2 | // GENERATED CODE - DO NOT MODIFY BY HAND 3 | 4 | // ************************************************************************** 5 | // StackedDialogGenerator 6 | // ************************************************************************** 7 | 8 | import 'package:stacked_services/stacked_services.dart'; 9 | 10 | import 'app.locator.dart'; 11 | import '../ui/dialogs/basic_dialog.dart'; 12 | 13 | enum DialogType { 14 | basic, 15 | } 16 | 17 | void setupDialogUi() { 18 | final dialogService = exampleLocator(); 19 | 20 | final Map builders = { 21 | DialogType.basic: (context, request, completer) => 22 | BasicDialog(request: request, completer: completer), 23 | }; 24 | 25 | dialogService.registerCustomDialogBuilders(builders); 26 | } 27 | -------------------------------------------------------------------------------- /example/navigator_example/lib/app/app.dialog.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | // ************************************************************************** 4 | // StackedDialogGenerator 5 | // ************************************************************************** 6 | 7 | import 'package:stacked_services/stacked_services.dart'; 8 | import 'app.locator.dart'; 9 | 10 | import '../ui/dialogs/basic_dialog.dart'; 11 | 12 | enum DialogType { 13 | basicDialog, 14 | } 15 | 16 | void setupDialogUi() { 17 | var dialogService = exampleLocator(); 18 | 19 | final builders = { 20 | DialogType.basicDialog: (context, DialogRequest request, 21 | void Function(DialogResponse) completer) => 22 | BasicDialog(request: request, completer: completer), 23 | }; 24 | 25 | dialogService.registerCustomDialogBuilders(builders); 26 | } 27 | -------------------------------------------------------------------------------- /example/router_example/lib/app/app.dialog.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | // ************************************************************************** 4 | // StackedDialogGenerator 5 | // ************************************************************************** 6 | 7 | import 'package:stacked_services/stacked_services.dart'; 8 | import 'app.locator.dart'; 9 | 10 | import '../ui/dialogs/basic_dialog.dart'; 11 | 12 | enum DialogType { 13 | basicDialog, 14 | } 15 | 16 | void setupDialogUi() { 17 | var dialogService = exampleLocator(); 18 | 19 | final builders = { 20 | DialogType.basicDialog: (context, DialogRequest request, 21 | void Function(DialogResponse) completer) => 22 | BasicDialog(request: request, completer: completer), 23 | }; 24 | 25 | dialogService.registerCustomDialogBuilders(builders); 26 | } 27 | -------------------------------------------------------------------------------- /example/router_example/lib/ui/bottom_nav/history/history_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:example/app/app.locator.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:stacked/stacked.dart'; 4 | 5 | import 'history_viewmodel.dart'; 6 | 7 | class HistoryView extends StatelessWidget { 8 | const HistoryView({super.key}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return ViewModelBuilder.reactive( 13 | initialiseSpecialViewModelsOnce: true, 14 | disposeViewModel: false, 15 | builder: (context, viewModel, child) => Scaffold( 16 | body: Center( 17 | child: viewModel.isBusy 18 | ? const CircularProgressIndicator() 19 | : Text(viewModel.data.toString()))), 20 | viewModelBuilder: () => exampleLocator(), 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/navigator_example/lib/ui/bottom_nav/history/history_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:example/app/app.locator.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:stacked/stacked.dart'; 4 | 5 | import 'history_viewmodel.dart'; 6 | 7 | class HistoryView extends StatelessWidget { 8 | const HistoryView({super.key}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return ViewModelBuilder.reactive( 13 | initialiseSpecialViewModelsOnce: true, 14 | disposeViewModel: false, 15 | builder: (context, viewModel, child) => Scaffold( 16 | body: Center( 17 | child: viewModel.isBusy 18 | ? const CircularProgressIndicator() 19 | : Text(viewModel.data.toString()))), 20 | viewModelBuilder: () => exampleLocator(), 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/router_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/router_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 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release Package 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - next 8 | 9 | jobs: 10 | package_and_publish: 11 | name: Package and Publish 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v6 15 | with: 16 | token: ${{ secrets.REPO_DEPLOYMENT_TOKEN }} 17 | - uses: subosito/flutter-action@v2 18 | with: 19 | channel: 'stable' 20 | - run: flutter pub get 21 | - name: release 22 | uses: cycjimmy/semantic-release-action@v4 23 | with: 24 | extra_plugins: | 25 | @semantic-release/exec 26 | @semantic-release/git 27 | @semantic-release/changelog 28 | env: 29 | GITHUB_TOKEN: ${{ secrets.REPO_DEPLOYMENT_TOKEN }} 30 | CREDENTIALS: ${{ secrets.PUB_DEV_DEPLOYMENT_CREDENTIALS }} 31 | -------------------------------------------------------------------------------- /example/router_example/.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 | **/doc/api/ 26 | **/ios/Flutter/.last_build_id 27 | .dart_tool/ 28 | .flutter-plugins 29 | .flutter-plugins-dependencies 30 | .packages 31 | .pub-cache/ 32 | .pub/ 33 | /build/ 34 | 35 | # Symbolication related 36 | app.*.symbols 37 | 38 | # Obfuscation related 39 | app.*.map.json 40 | 41 | # Android Studio will place build artifacts here 42 | /android/app/debug 43 | /android/app/profile 44 | /android/app/release 45 | -------------------------------------------------------------------------------- /example/router_example/macos/Flutter/GeneratedPluginRegistrant.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | import FlutterMacOS 6 | import Foundation 7 | 8 | import device_info_plus 9 | import firebase_core 10 | import firebase_crashlytics 11 | import package_info_plus 12 | import shared_preferences_foundation 13 | 14 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { 15 | DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) 16 | FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) 17 | FLTFirebaseCrashlyticsPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCrashlyticsPlugin")) 18 | FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin")) 19 | SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) 20 | } 21 | -------------------------------------------------------------------------------- /example/navigator_example/lib/ui/nonreactive/nonreactive_viewmodel.dart: -------------------------------------------------------------------------------- 1 | import 'package:stacked/stacked.dart'; 2 | import 'package:stacked_services/stacked_services.dart'; 3 | 4 | import '../../app/app.locator.dart'; 5 | import '../../app/app.router.dart'; 6 | import '../../datamodels/clashable_two.dart'; 7 | 8 | class NonReactiveViewModel extends BaseViewModel { 9 | final _navigationService = exampleLocator(); 10 | String title = 'This should not change'; 11 | 12 | void updateTitle() { 13 | title += '. This has changed'; 14 | notifyListeners(); 15 | } 16 | 17 | void navigateToNewView() { 18 | _navigationService 19 | .navigateToStreamCounterView(clashableTwo: [const Clashable(22)]); 20 | } 21 | 22 | void navigateBackHome() { 23 | _navigationService.clearStackAndShow( 24 | Routes.homeView, 25 | arguments: const HomeViewArguments(title: 'Home'), 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /example/router_example/lib/app/app.bottomsheets.dart: -------------------------------------------------------------------------------- 1 | // dart format width=80 2 | // GENERATED CODE - DO NOT MODIFY BY HAND 3 | 4 | // ************************************************************************** 5 | // StackedBottomsheetGenerator 6 | // ************************************************************************** 7 | 8 | import 'package:stacked_services/stacked_services.dart'; 9 | 10 | import 'app.locator.dart'; 11 | import '../ui/bottomsheets/generic_bottomsheet.dart'; 12 | 13 | enum BottomSheetType { 14 | genericBottom, 15 | } 16 | 17 | void setupBottomSheetUi() { 18 | final bottomsheetService = exampleLocator(); 19 | 20 | final Map builders = { 21 | BottomSheetType.genericBottom: (context, request, completer) => 22 | GenericBottomSheet(request: request, completer: completer), 23 | }; 24 | 25 | bottomsheetService.setCustomSheetBuilders(builders); 26 | } 27 | -------------------------------------------------------------------------------- /example/navigator_example/lib/app/app.bottomsheets.dart: -------------------------------------------------------------------------------- 1 | // dart format width=80 2 | // GENERATED CODE - DO NOT MODIFY BY HAND 3 | 4 | // ************************************************************************** 5 | // StackedBottomsheetGenerator 6 | // ************************************************************************** 7 | 8 | import 'package:stacked_services/stacked_services.dart'; 9 | 10 | import 'app.locator.dart'; 11 | import '../ui/bottomsheets/generic_bottomsheet.dart'; 12 | 13 | enum BottomSheetType { 14 | genericBottom, 15 | } 16 | 17 | void setupBottomSheetUi() { 18 | final bottomsheetService = exampleLocator(); 19 | 20 | final Map builders = { 21 | BottomSheetType.genericBottom: (context, request, completer) => 22 | GenericBottomSheet(request: request, completer: completer), 23 | }; 24 | 25 | bottomsheetService.setCustomSheetBuilders(builders); 26 | } 27 | -------------------------------------------------------------------------------- /example/router_example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 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 | 11.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/navigator_example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 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 | 9.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /.releaserc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/semantic-release", 3 | "branches": [ 4 | "master", 5 | { 6 | "name": "next", 7 | "prerelease": "pre" 8 | } 9 | ], 10 | "plugins": [ 11 | "@semantic-release/commit-analyzer", 12 | "@semantic-release/release-notes-generator", 13 | "@semantic-release/changelog", 14 | [ 15 | "@semantic-release/exec", 16 | { 17 | "verifyConditionsCmd": "if [ -z \"$CREDENTIALS\" ]; then exit 1; fi && mkdir -p ~/.config/dart && echo \"$CREDENTIALS\" > ~/.config/dart/pub-credentials.json", 18 | "prepareCmd": "sed -i 's/^version: .*$/version: ${nextRelease.version}/' pubspec.yaml", 19 | "publishCmd": "flutter pub publish -f" 20 | } 21 | ], 22 | [ 23 | "@semantic-release/git", 24 | { 25 | "assets": ["./CHANGELOG.md", "./pubspec.yaml"] 26 | } 27 | ], 28 | "@semantic-release/github" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /example/router_example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:example/app/app.router.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:stacked_shared/stacked_shared.dart'; 4 | import 'package:url_strategy/url_strategy.dart'; 5 | 6 | import 'app/app.locator.dart'; 7 | 8 | Future main() async { 9 | WidgetsFlutterBinding.ensureInitialized(); 10 | setPathUrlStrategy(); 11 | await setupExampleLocator( 12 | environment: Environment.dev, 13 | stackedRouter: stackedRouter, 14 | ); 15 | runApp(const MyApp()); 16 | } 17 | 18 | class MyApp extends StatelessWidget { 19 | const MyApp({super.key}); 20 | 21 | @override 22 | Widget build(BuildContext context) { 23 | return MaterialApp.router( 24 | title: 'Flutter Demo', 25 | routerDelegate: stackedRouter.delegate(), 26 | routeInformationParser: stackedRouter.defaultRouteParser(), 27 | theme: ThemeData( 28 | primarySwatch: Colors.blue, 29 | ), 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /example/router_example/windows/runner/runner.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PerMonitorV2 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /lib/src/view_models/selector_view_model_builder.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:provider/provider.dart'; 3 | 4 | class SelectorViewModelBuilder 5 | extends StatelessWidget { 6 | const SelectorViewModelBuilder({ 7 | super.key, 8 | required this.selector, 9 | required this.builder, 10 | this.child, 11 | this.shouldRebuild, 12 | }); 13 | 14 | final K Function(T viewModel) selector; 15 | final Widget Function(BuildContext context, K value, Widget? child) builder; 16 | final Widget? child; 17 | final bool Function(K, K)? shouldRebuild; 18 | @override 19 | Widget build(BuildContext context) { 20 | return Selector( 21 | key: key, 22 | shouldRebuild: shouldRebuild, 23 | selector: (BuildContext context, T viewModel) => selector(viewModel), 24 | builder: (BuildContext _, K value, Widget? child) => 25 | builder(_, value, child), 26 | child: child, 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/src/router/navigation_failure.dart: -------------------------------------------------------------------------------- 1 | import 'package:stacked/src/router/controller/routing_controller.dart'; 2 | import 'package:stacked/src/router/matcher/route_match.dart'; 3 | import 'package:stacked/src/router/route/page_route_info.dart'; 4 | 5 | typedef OnNavigationFailure = void Function(NavigationFailure failure); 6 | 7 | abstract class NavigationFailure { 8 | const NavigationFailure(); 9 | } 10 | 11 | class RouteNotFoundFailure extends NavigationFailure { 12 | final PageRouteInfo route; 13 | 14 | const RouteNotFoundFailure(this.route); 15 | 16 | @override 17 | String toString() { 18 | return "Failed to navigate to ${route.fullPath}"; 19 | } 20 | } 21 | 22 | class RejectedByGuardFailure extends NavigationFailure { 23 | final RouteMatch route; 24 | final StackedRouteGuard guard; 25 | 26 | const RejectedByGuardFailure(this.route, this.guard); 27 | 28 | @override 29 | String toString() { 30 | return '${route.stringMatch} rejected by guard ${guard.runtimeType}'; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test Package 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - '**' 7 | workflow_dispatch: 8 | 9 | jobs: 10 | lint_and_test: 11 | name: Linting and Testing 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v6 15 | with: 16 | submodules: true 17 | - uses: subosito/flutter-action@v2 18 | with: 19 | channel: 'stable' 20 | - run: flutter pub get 21 | - run: dart fix --apply 22 | # - run: flutter analyze # removing analyze for now, we only care about test passing 23 | - run: flutter test --coverage 24 | - name: Setup LCOV 25 | uses: hrishikesh-kadam/setup-lcov@v1 26 | - name: Report code coverage 27 | uses: zgosalvez/github-actions-report-lcov@v5 28 | with: 29 | coverage-files: coverage/lcov*.info 30 | artifact-name: code-coverage-report 31 | github-token: ${{ secrets.GITHUB_TOKEN }} 32 | update-comment: true 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Stacked [![Pub Version](https://img.shields.io/pub/v/stacked)](https://pub.dev/packages/stacked) 2 | 3 | ## [Checkout out the new and improved Docs](https://stacked.filledstacks.com/) 4 | 5 | Old doc can be found [here](https://github.com/Stacked-Org/stacked/blob/master/README_old.md) 6 | 7 | # What is Stacked 8 | 9 | Stacked is a Flutter Framework for building Production Applications. It is a complete frontend architecture for Flutter. The framework is created to build testable and maintainable code. 10 | 11 | Checkout the [official docs](https://stacked.filledstacks.com/) for more information. 12 | 13 | --- 14 | 15 | ## Maintenance Schedule 16 | 17 | This schedule indicates when work will be done on Stacked. Work includes fixing issues, building new features, improving the code stability or implementing automation. If there is no urgent breaking bug you can expect responses to your issues on the days listed below from one of the maintainers. 18 | 19 | - [Fernando](https://github.com/ferrarafer): Monday 20 | 21 | 22 | -------------------------------------------------------------------------------- /example/router_example/lib/ui/multiple_futures_example/multiple_futures_example_viewmodel.dart: -------------------------------------------------------------------------------- 1 | import 'package:stacked/stacked.dart'; 2 | 3 | const String _numberDelayFuture = 'delayedNumber'; 4 | const String _stringDelayFuture = 'delayedString'; 5 | 6 | class MultipleFuturesExampleViewModel extends MultipleFutureViewModel { 7 | int get fetchedNumber => dataMap![_numberDelayFuture]; 8 | String get fetchedString => dataMap![_stringDelayFuture]; 9 | 10 | bool get fetchingNumber => busy(_numberDelayFuture); 11 | bool get fetchingString => busy(_stringDelayFuture); 12 | 13 | @override 14 | Map get futuresMap => { 15 | _numberDelayFuture: getNumberAfterDelay, 16 | _stringDelayFuture: getStringAfterDelay, 17 | }; 18 | 19 | Future getNumberAfterDelay() async { 20 | await Future.delayed(const Duration(seconds: 2)); 21 | return 3; 22 | } 23 | 24 | Future getStringAfterDelay() async { 25 | await Future.delayed(const Duration(seconds: 3)); 26 | return 'String data'; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /example/navigator_example/lib/ui/multiple_futures_example/multiple_futures_example_viewmodel.dart: -------------------------------------------------------------------------------- 1 | import 'package:stacked/stacked.dart'; 2 | 3 | const String _numberDelayFuture = 'delayedNumber'; 4 | const String _stringDelayFuture = 'delayedString'; 5 | 6 | class MultipleFuturesExampleViewModel extends MultipleFutureViewModel { 7 | int get fetchedNumber => dataMap![_numberDelayFuture]; 8 | String get fetchedString => dataMap![_stringDelayFuture]; 9 | 10 | bool get fetchingNumber => busy(_numberDelayFuture); 11 | bool get fetchingString => busy(_stringDelayFuture); 12 | 13 | @override 14 | Map get futuresMap => { 15 | _numberDelayFuture: getNumberAfterDelay, 16 | _stringDelayFuture: getStringAfterDelay, 17 | }; 18 | 19 | Future getNumberAfterDelay() async { 20 | await Future.delayed(const Duration(seconds: 2)); 21 | return 3; 22 | } 23 | 24 | Future getStringAfterDelay() async { 25 | await Future.delayed(const Duration(seconds: 3)); 26 | return 'String data'; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/src/router/route/route_data_scope.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:stacked/src/router/controller/routing_controller.dart'; 3 | 4 | class RouteDataScope extends InheritedWidget { 5 | final RouteData routeData; 6 | 7 | const RouteDataScope({ 8 | super.key, 9 | required this.routeData, 10 | required super.child, 11 | }); 12 | 13 | static RouteDataScope of(BuildContext context) { 14 | var scope = context.findAncestorWidgetOfExactType(); 15 | assert(() { 16 | if (scope == null) { 17 | throw FlutterError( 18 | 'RouteData operation requested with a context that does not include an RouteData.\n' 19 | 'The context used to retrieve the RouteData must be that of a widget that ' 20 | 'is a descendant of a StackedPage.'); 21 | } 22 | return true; 23 | }()); 24 | return scope!; 25 | } 26 | 27 | @override 28 | bool updateShouldNotify(covariant RouteDataScope oldWidget) { 29 | return routeData.route != oldWidget.routeData.route; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /example/router_example/lib/ui/bottom_nav/bottom_nav_example_viewmodel.dart: -------------------------------------------------------------------------------- 1 | import 'package:example/app/app.locator.dart'; 2 | import 'package:example/app/app.logger.dart'; 3 | import 'package:example/app/app.router.dart'; 4 | import 'package:stacked/stacked.dart'; 5 | import 'package:stacked_services/stacked_services.dart'; 6 | 7 | class BottomNavExampleViewModel extends IndexTrackingViewModel { 8 | final log = getLogger('BottomNavExampleViewModel'); 9 | final _routerService = exampleLocator(); 10 | 11 | BottomNavExampleViewModel() { 12 | setCurrentWebPageIndex(_routerService); 13 | } 14 | 15 | void handleNavigation(int index) { 16 | log.i('handleNavigation: $index'); 17 | setIndex(index); 18 | switch (index) { 19 | case 0: 20 | _routerService.navigateTo(FavoritesViewRoute()); 21 | break; 22 | case 1: 23 | _routerService.navigateTo(HistoryViewRoute()); 24 | break; 25 | case 2: 26 | _routerService.navigateTo(ProfileViewRoute()); 27 | break; 28 | default: 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/src/view_models/view_model_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:provider/provider.dart'; 3 | 4 | /// A widget that provides a value passed through a provider as a parameter of the build function. 5 | abstract class ViewModelWidget extends Widget { 6 | final bool reactive; 7 | 8 | const ViewModelWidget({super.key, this.reactive = true}); 9 | 10 | @protected 11 | Widget build(BuildContext context, T viewModel); 12 | 13 | @override 14 | DataProviderElement createElement() => DataProviderElement(this); 15 | } 16 | 17 | class DataProviderElement extends ComponentElement { 18 | DataProviderElement(ViewModelWidget super.widget); 19 | 20 | @override 21 | ViewModelWidget get widget => super.widget as ViewModelWidget; 22 | 23 | @override 24 | Widget build() => 25 | widget.build(this, Provider.of(this, listen: widget.reactive)); 26 | 27 | @override 28 | void update(ViewModelWidget newWidget) { 29 | super.update(newWidget); 30 | assert(widget == newWidget); 31 | rebuild(force: true); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /example/router_example/web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "router_example", 3 | "short_name": "router_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/router_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/router_example/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/router_example/lib/ui/bottom_nav/favorites/favorites_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:example/app/app.locator.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:stacked/stacked.dart'; 4 | 5 | import 'favorites_viewmodel.dart'; 6 | 7 | class FavoritesView extends StatelessWidget { 8 | final String? id; 9 | const FavoritesView({super.key, this.id}); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return ViewModelBuilder.reactive( 14 | builder: (context, viewModel, child) => Scaffold( 15 | floatingActionButton: FloatingActionButton( 16 | onPressed: () => viewModel.incrementCounter(), 17 | ), 18 | body: Center( 19 | child: Text( 20 | viewModel.counter.toString(), 21 | style: const TextStyle(fontSize: 30), 22 | ))), 23 | viewModelBuilder: () => exampleLocator(), 24 | onViewModelReady: (viewModel) => viewModel.setCounterTo999(), 25 | disposeViewModel: false, 26 | fireOnViewModelReadyOnce: true, 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /example/navigator_example/lib/ui/bottom_nav/favorites/favorites_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:example/app/app.locator.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:stacked/stacked.dart'; 4 | 5 | import 'favorites_viewmodel.dart'; 6 | 7 | class FavoritesView extends StatelessWidget { 8 | final String? id; 9 | const FavoritesView({super.key, this.id}); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return ViewModelBuilder.reactive( 14 | builder: (context, viewModel, child) => Scaffold( 15 | floatingActionButton: FloatingActionButton( 16 | onPressed: () => viewModel.incrementCounter(), 17 | ), 18 | body: Center( 19 | child: Text( 20 | viewModel.counter.toString(), 21 | style: const TextStyle(fontSize: 30), 22 | ))), 23 | viewModelBuilder: () => exampleLocator(), 24 | onViewModelReady: (viewModel) => viewModel.setCounterTo999(), 25 | disposeViewModel: false, 26 | fireOnViewModelReadyOnce: true, 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /example/navigator_example/lib/ui/home/home_viewmodel.dart: -------------------------------------------------------------------------------- 1 | import 'package:example/app/app.locator.dart'; 2 | import 'package:example/app/app.logger.dart'; 3 | import 'package:example/app/app.router.dart'; 4 | import 'package:stacked/stacked.dart'; 5 | import 'package:stacked_services/stacked_services.dart'; 6 | 7 | class HomeViewModel extends BaseViewModel with MessageStateHelper { 8 | final log = getLogger('HomeViewModel'); 9 | final NavigationService _navigationService = 10 | exampleLocator(); 11 | 12 | String title = 'default'; 13 | int counter = 0; 14 | 15 | void navigate() { 16 | _navigationService.navigateToNonReactiveView(); 17 | } 18 | 19 | void initialise() { 20 | setMessage('initialise'); 21 | log.i('initialise'); 22 | title = 'Initialised'; 23 | notifyListeners(); 24 | } 25 | 26 | void updateTitle() { 27 | counter++; 28 | title = '$counter'; 29 | notifyListeners(); 30 | } 31 | 32 | void updateData() { 33 | notifyListeners(); 34 | } 35 | 36 | void updateTile(String value) { 37 | title = value; 38 | notifyListeners(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /example/router_example/lib/ui/home/home_view_multiple_widgets.dart: -------------------------------------------------------------------------------- 1 | import 'package:example/ui/dumb_widgets/description_section.dart'; 2 | import 'package:example/ui/dumb_widgets/title_section.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:stacked/stacked.dart'; 5 | 6 | import 'home_viewmodel.dart'; 7 | 8 | class HomeViewMultipleWidgets extends StatelessWidget { 9 | const HomeViewMultipleWidgets({super.key}); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return ViewModelBuilder.reactive( 14 | viewModelBuilder: () => HomeViewModel(), 15 | onViewModelReady: (viewModel) => viewModel.initialise(), 16 | builder: (context, viewModel, _) => Scaffold( 17 | floatingActionButton: FloatingActionButton( 18 | onPressed: () { 19 | viewModel.updateTitle(); 20 | }, 21 | ), 22 | body: const Column( 23 | mainAxisAlignment: MainAxisAlignment.center, 24 | children: [ 25 | TitleSection(), 26 | DescriptionSection(), 27 | ], 28 | ), 29 | ), 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /example/navigator_example/lib/ui/home/home_view_multiple_widgets.dart: -------------------------------------------------------------------------------- 1 | import 'package:example/ui/dumb_widgets/description_section.dart'; 2 | import 'package:example/ui/dumb_widgets/title_section.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:stacked/stacked.dart'; 5 | 6 | import 'home_viewmodel.dart'; 7 | 8 | class HomeViewMultipleWidgets extends StatelessWidget { 9 | const HomeViewMultipleWidgets({super.key}); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return ViewModelBuilder.reactive( 14 | viewModelBuilder: () => HomeViewModel(), 15 | onViewModelReady: (viewModel) => viewModel.initialise(), 16 | builder: (context, viewModel, _) => Scaffold( 17 | floatingActionButton: FloatingActionButton( 18 | onPressed: () { 19 | viewModel.updateTitle(); 20 | }, 21 | ), 22 | body: Column( 23 | mainAxisAlignment: MainAxisAlignment.center, 24 | children: const [ 25 | TitleSection(), 26 | DescriptionSection(), 27 | ], 28 | ), 29 | ), 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /example/router_example/lib/ui/future_example_view/future_example_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:stacked/stacked.dart'; 3 | 4 | import 'future_example_viewmodel.dart'; 5 | 6 | class FutureExampleView extends StatelessWidget { 7 | const FutureExampleView({super.key}); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return ViewModelBuilder.reactive( 12 | builder: (context, viewModel, child) => Scaffold( 13 | body: viewModel.hasError 14 | ? Container( 15 | color: Colors.red, 16 | alignment: Alignment.center, 17 | child: const Text( 18 | 'An error has occered while running the future', 19 | style: TextStyle(color: Colors.white), 20 | ), 21 | ) 22 | : Center( 23 | child: viewModel.isBusy 24 | ? const CircularProgressIndicator() 25 | : Text(viewModel.data!), 26 | ), 27 | ), 28 | viewModelBuilder: () => FutureExampleViewModel(), 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Dane Mackier and other contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /example/navigator_example/lib/ui/future_example_view/future_example_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:stacked/stacked.dart'; 3 | 4 | import 'future_example_viewmodel.dart'; 5 | 6 | class FutureExampleView extends StatelessWidget { 7 | const FutureExampleView({super.key}); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return ViewModelBuilder.reactive( 12 | builder: (context, viewModel, child) => Scaffold( 13 | body: viewModel.hasError 14 | ? Container( 15 | color: Colors.red, 16 | alignment: Alignment.center, 17 | child: const Text( 18 | 'An error has occered while running the future', 19 | style: TextStyle(color: Colors.white), 20 | ), 21 | ) 22 | : Center( 23 | child: viewModel.isBusy 24 | ? const CircularProgressIndicator() 25 | : Text(viewModel.data!), 26 | ), 27 | ), 28 | viewModelBuilder: () => FutureExampleViewModel(), 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/src/router/controller/pageless_routes_observer.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | 3 | class PagelessRoutesObserver extends NavigatorObserver with ChangeNotifier { 4 | bool _hasPagelessTopRoute = false; 5 | Route? current; 6 | 7 | bool get hasPagelessTopRoute => _hasPagelessTopRoute; 8 | 9 | set hasPagelessTopRoute(value) { 10 | if (value != _hasPagelessTopRoute) { 11 | _hasPagelessTopRoute = value; 12 | notifyListeners(); 13 | } 14 | } 15 | 16 | void _checkCurrentRoute(Route? route) { 17 | current = route; 18 | if (route != null) { 19 | hasPagelessTopRoute = route.settings is! Page; 20 | } 21 | } 22 | 23 | @override 24 | void didPush(Route route, Route? previousRoute) { 25 | _checkCurrentRoute(route); 26 | } 27 | 28 | @override 29 | void didPop(Route route, Route? previousRoute) { 30 | _checkCurrentRoute(previousRoute); 31 | } 32 | 33 | @override 34 | void didRemove(Route route, Route? previousRoute) { 35 | _checkCurrentRoute(previousRoute); 36 | } 37 | 38 | @override 39 | void didReplace({Route? newRoute, Route? oldRoute}) { 40 | _checkCurrentRoute(newRoute); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/src/router/route/route_config.dart: -------------------------------------------------------------------------------- 1 | import 'package:stacked/src/router/controller/routing_controller.dart'; 2 | 3 | import '../matcher/route_matcher.dart'; 4 | 5 | class RouteConfig { 6 | final String name; 7 | final String path; 8 | final bool fullMatch; 9 | final RouteCollection? _children; 10 | final String? redirectTo; 11 | final List guards; 12 | final bool usesPathAsKey; 13 | final String? parent; 14 | final Map meta; 15 | final bool deferredLoading; 16 | 17 | RouteConfig( 18 | this.name, { 19 | required this.path, 20 | this.usesPathAsKey = false, 21 | this.guards = const [], 22 | this.fullMatch = false, 23 | this.redirectTo, 24 | this.parent, 25 | this.meta = const {}, 26 | this.deferredLoading = false, 27 | List? children, 28 | }) : _children = children != null ? RouteCollection.from(children) : null; 29 | 30 | bool get hasSubTree => _children != null; 31 | 32 | RouteCollection? get children => _children; 33 | 34 | bool get isRedirect => redirectTo != null; 35 | 36 | @override 37 | String toString() { 38 | return 'RouteConfig{name: $name}'; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /example/router_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/navigator_example/lib/ui/form/example_form_viewmodel.dart: -------------------------------------------------------------------------------- 1 | import 'package:example/app/app.locator.dart'; 2 | import 'package:example/app/app.logger.dart'; 3 | import 'package:example/app/app.router.dart'; 4 | import 'package:stacked/stacked.dart'; 5 | import 'package:stacked_services/stacked_services.dart'; 6 | 7 | import 'example_form_view.form.dart'; 8 | 9 | // #5: extend from FormViewModel 10 | class ExampleFormViewModel extends FormViewModel { 11 | final log = getLogger('FormViewModel'); 12 | final _navigationService = exampleLocator(); 13 | 14 | @override 15 | void setFormStatus() { 16 | log.i('Set form Status with data: $formValueMap'); 17 | 18 | // Set a validation message for the entire form 19 | if (hasPasswordValidationMessage) { 20 | setValidationMessage('Error in the form, please check again'); 21 | } 22 | } 23 | 24 | // If the dev doesn't want realtime validation then they can 25 | // simply validate in the function that they'll use to submit the 26 | // data to the backend or db. 27 | 28 | Future? saveData() { 29 | return null; 30 | 31 | // here we can run custom functionality to save to our api 32 | } 33 | 34 | void navigateToNewView() { 35 | _navigationService.replaceWith(Routes.bottomNavExample); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/src/router/controller/navigation_history/web_navigation_history.dart: -------------------------------------------------------------------------------- 1 | import 'dart:js_interop'; 2 | import 'dart:js_interop_unsafe'; 3 | 4 | import 'package:stacked/src/router/controller/routing_controller.dart'; 5 | // ignore: avoid_web_libraries_in_flutter 6 | import 'package:web/web.dart'; 7 | 8 | import 'navigation_history_base.dart'; 9 | 10 | class NavigationHistoryImpl extends NavigationHistory { 11 | NavigationHistoryImpl(this.router); 12 | 13 | @override 14 | final StackRouter router; 15 | 16 | final _history = window.history; 17 | 18 | @override 19 | void back() { 20 | _history.back(); 21 | } 22 | 23 | int get _currentIndex { 24 | final state = _history.state; 25 | if (state != null) { 26 | try { 27 | final stateObj = state as JSObject; 28 | final serialCount = stateObj.getProperty('serialCount'.toJS); 29 | if (serialCount != null && serialCount.typeofEquals('number')) { 30 | return (serialCount as JSNumber).toDartInt; 31 | } 32 | } catch (e) { 33 | return 0; 34 | } 35 | } 36 | return 0; 37 | } 38 | 39 | @override 40 | bool get canNavigateBack => _currentIndex > 0; 41 | 42 | @override 43 | void forward() => _history.forward(); 44 | 45 | @override 46 | int get length => _history.length; 47 | } 48 | -------------------------------------------------------------------------------- /lib/src/view_models/helpers/message_state_helper.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | 3 | mixin MessageStateHelper on ChangeNotifier { 4 | final Map _messageStates = {}; 5 | 6 | /// Returns the message for an object if it exists. Returns null if not present 7 | String? message(Object object) => _messageStates[object.hashCode]; 8 | 9 | /// Returns the message status of the ViewModel 10 | bool get hasMessage => message(this) != null; 11 | 12 | /// Returns the message status of the ViewModel 13 | String? get modelMessage => message(this); 14 | 15 | /// Returns a boolean that indicates if the ViewModel has an message for the key 16 | bool hasMessageForKey(Object key) => message(key) != null; 17 | 18 | void clearMessages() { 19 | _messageStates.clear(); 20 | } 21 | 22 | /// Sets the message for the ViewModel 23 | void setMessage(String? message) { 24 | setMessageForObject(this, message); 25 | } 26 | 27 | /// Sets the message for the object equal to the value passed in and notifies Listeners 28 | /// If you're using a primitive type the value SHOULD NOT BE CHANGED, since Hashcode uses == value 29 | void setMessageForObject(Object object, String? value) { 30 | _messageStates[object.hashCode] = value; 31 | notifyListeners(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /example/navigator_example/lib/ui/home/home_view_traditional.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:stacked/stacked.dart'; 3 | 4 | import 'home_viewmodel.dart'; 5 | 6 | class HomeViewTraditional extends StatelessWidget { 7 | const HomeViewTraditional({super.key}); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | // Using the withConsumer constructor gives you the traditional viewmodel 12 | // binding which will rebuild when notifyListeners is called. This is used 13 | // when the model does not have to be consumed by multiple different UI's. 14 | return ViewModelBuilder.reactive( 15 | viewModelBuilder: () => HomeViewModel(), 16 | onViewModelReady: (viewModel) => viewModel.initialise(), 17 | builder: (context, viewModel, child) => Scaffold( 18 | floatingActionButton: const UpdateTitleButton(), 19 | body: Center( 20 | child: Text(viewModel.title), 21 | ), 22 | ), 23 | ); 24 | } 25 | } 26 | 27 | class UpdateTitleButton extends ViewModelWidget { 28 | const UpdateTitleButton({ 29 | super.key, 30 | }) : super(reactive: false); 31 | 32 | @override 33 | Widget build(BuildContext context, viewModel) { 34 | return FloatingActionButton( 35 | onPressed: () { 36 | viewModel.updateTitle(); 37 | }, 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /example/router_example/lib/ui/home/home_view_traditional.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:stacked/stacked.dart'; 3 | 4 | import 'home_viewmodel.dart'; 5 | 6 | class HomeViewTraditional extends StatelessWidget { 7 | const HomeViewTraditional({super.key}); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | // Using the withConsumer constructor gives you the traditional viewmodel 12 | // binding which will rebuild when notifyListeners is called. This is used 13 | // when the model does not have to be consumed by multiple different UI's. 14 | return ViewModelBuilder.reactive( 15 | viewModelBuilder: () => HomeViewModel(), 16 | onViewModelReady: (viewModel) => viewModel.initialise(), 17 | builder: (context, viewModel, child) => Scaffold( 18 | floatingActionButton: const UpdateTitleButton(), 19 | body: Center( 20 | child: Text(viewModel.title), 21 | ), 22 | ), 23 | ); 24 | } 25 | } 26 | 27 | class UpdateTitleButton extends ViewModelWidget { 28 | const UpdateTitleButton({ 29 | super.key, 30 | }) : super(reactive: false); 31 | 32 | @override 33 | Widget build(BuildContext context, viewModel) { 34 | return FloatingActionButton( 35 | onPressed: () { 36 | viewModel.updateTitle(); 37 | }, 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /example/router_example/lib/ui/smart_widgets/widget_two/widget_two.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:stacked/stacked.dart'; 3 | 4 | import 'widget_two_viewmodel.dart'; 5 | 6 | class WidgetTwo extends StatelessWidget { 7 | final int id; 8 | const WidgetTwo({ 9 | super.key, 10 | required this.id, 11 | }); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return ViewModelBuilder.reactive( 16 | viewModelBuilder: () => WidgetTwoViewModel(id), 17 | builder: (context, viewModel, child) => GestureDetector( 18 | onTap: () => viewModel.reset(), 19 | child: Container( 20 | width: 100, 21 | height: 100, 22 | color: Colors.red[((id % 10) * 100)], 23 | alignment: Alignment.center, 24 | child: Column( 25 | mainAxisSize: MainAxisSize.min, 26 | children: [ 27 | const Text( 28 | 'Tap to Reset', 29 | style: TextStyle(fontSize: 10), 30 | ), 31 | Text( 32 | viewModel.postCount.toString(), 33 | style: const TextStyle( 34 | fontWeight: FontWeight.bold, 35 | fontSize: 40, 36 | ), 37 | ), 38 | ], 39 | ), 40 | ), 41 | ), 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /example/navigator_example/lib/ui/smart_widgets/widget_two/widget_two.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:stacked/stacked.dart'; 3 | 4 | import 'widget_two_viewmodel.dart'; 5 | 6 | class WidgetTwo extends StatelessWidget { 7 | final int id; 8 | const WidgetTwo({ 9 | super.key, 10 | required this.id, 11 | }); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return ViewModelBuilder.reactive( 16 | viewModelBuilder: () => WidgetTwoViewModel(id), 17 | builder: (context, viewModel, child) => GestureDetector( 18 | onTap: () => viewModel.reset(), 19 | child: Container( 20 | width: 100, 21 | height: 100, 22 | color: Colors.red[((id % 10) * 100)], 23 | alignment: Alignment.center, 24 | child: Column( 25 | mainAxisSize: MainAxisSize.min, 26 | children: [ 27 | const Text( 28 | 'Tap to Reset', 29 | style: TextStyle(fontSize: 10), 30 | ), 31 | Text( 32 | viewModel.postCount.toString(), 33 | style: const TextStyle( 34 | fontWeight: FontWeight.bold, 35 | fontSize: 40, 36 | ), 37 | ), 38 | ], 39 | ), 40 | ), 41 | ), 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lib/src/router/transitions/stacked_page_route.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class StackedPageRouteBuilder extends PageRoute { 4 | StackedPageRouteBuilder({ 5 | this.transitionBuilder, 6 | this.transitionDuration = const Duration(milliseconds: 300), 7 | required this.child, 8 | super.fullscreenDialog, 9 | }); 10 | 11 | final RouteTransitionsBuilder? transitionBuilder; 12 | final Widget child; 13 | 14 | @override 15 | Color? get barrierColor => null; 16 | 17 | @override 18 | String? get barrierLabel => null; 19 | 20 | @override 21 | Widget buildPage(BuildContext context, Animation animation, 22 | Animation secondaryAnimation) { 23 | return child; 24 | } 25 | 26 | @override 27 | Widget buildTransitions(BuildContext context, Animation animation, 28 | Animation secondaryAnimation, Widget child) { 29 | if (transitionBuilder != null) { 30 | return transitionBuilder!( 31 | context, 32 | animation, 33 | secondaryAnimation, 34 | child, 35 | ); 36 | } 37 | final theme = Theme.of(context); 38 | return theme.pageTransitionsTheme.buildTransitions( 39 | this, 40 | context, 41 | animation, 42 | secondaryAnimation, 43 | child, 44 | ); 45 | } 46 | 47 | @override 48 | bool get maintainState => true; 49 | 50 | @override 51 | final Duration transitionDuration; 52 | } 53 | -------------------------------------------------------------------------------- /lib/src/mixins/reactive_service_mixin.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:stacked/stacked.dart'; 3 | 4 | /// Adds functionality to easily listen to all reactive values in a service 5 | @Deprecated("use ListenableServiceMixin instead") 6 | mixin ReactiveServiceMixin { 7 | final List _listeners = List.empty(growable: true); 8 | 9 | /// List to the values and react when there are any changes 10 | void listenToReactiveValues(List reactiveValues) { 11 | for (var reactiveValue in reactiveValues) { 12 | if (reactiveValue is ReactiveValue) { 13 | reactiveValue.values.listen((value) => notifyListeners()); 14 | } else if (reactiveValue is ReactiveList) { 15 | reactiveValue.onChange.listen((event) => notifyListeners()); 16 | } else if (reactiveValue is ChangeNotifier) { 17 | reactiveValue.addListener(notifyListeners); 18 | } 19 | } 20 | } 21 | 22 | /// Registers a listener with this service 23 | void addListener(void Function() listener) { 24 | _listeners.add(listener); 25 | } 26 | 27 | /// Removes a listener from the service 28 | void removeListener(void Function() listener) { 29 | _listeners.remove(listener); 30 | } 31 | 32 | /// Notifies all the listeners attached to this service 33 | @protected 34 | @visibleForTesting 35 | void notifyListeners() { 36 | for (var listener in _listeners) { 37 | listener(); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /example/router_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"router_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/router_example/lib/ui/nonreactive/nonreactive_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:stacked/stacked.dart'; 3 | 4 | import 'nonreactive_viewmodel.dart'; 5 | 6 | class NonReactiveView extends StatelessWidget { 7 | const NonReactiveView({super.key}); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return ViewModelBuilder.nonReactive( 12 | builder: (context, viewModel, child) => Scaffold( 13 | appBar: AppBar( 14 | title: const Text('Non Reactive View'), 15 | centerTitle: true, 16 | leading: IconButton( 17 | onPressed: () { 18 | viewModel.navigateBackHome(); 19 | }, 20 | icon: const Icon(Icons.arrow_back_ios)), 21 | ), 22 | floatingActionButton: FloatingActionButton( 23 | onPressed: viewModel.updateTitle, 24 | ), 25 | body: Center( 26 | child: Column( 27 | mainAxisAlignment: MainAxisAlignment.center, 28 | children: [ 29 | ElevatedButton( 30 | onPressed: viewModel.navigateToNewView, 31 | child: const Text('Go to stream counter view'), 32 | ), 33 | const SizedBox(height: 10), 34 | Text(viewModel.title), 35 | ], 36 | ), 37 | ), 38 | ), 39 | viewModelBuilder: () => NonReactiveViewModel(), 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /example/navigator_example/lib/ui/nonreactive/nonreactive_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:stacked/stacked.dart'; 3 | 4 | import 'nonreactive_viewmodel.dart'; 5 | 6 | class NonReactiveView extends StatelessWidget { 7 | const NonReactiveView({super.key}); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return ViewModelBuilder.nonReactive( 12 | builder: (context, viewModel, child) => Scaffold( 13 | appBar: AppBar( 14 | title: const Text('Non Reactive View'), 15 | centerTitle: true, 16 | leading: IconButton( 17 | onPressed: () { 18 | viewModel.navigateBackHome(); 19 | }, 20 | icon: const Icon(Icons.arrow_back_ios)), 21 | ), 22 | floatingActionButton: FloatingActionButton( 23 | onPressed: viewModel.updateTitle, 24 | ), 25 | body: Center( 26 | child: Column( 27 | mainAxisAlignment: MainAxisAlignment.center, 28 | children: [ 29 | ElevatedButton( 30 | onPressed: viewModel.navigateToNewView, 31 | child: const Text('Go to stream counter view'), 32 | ), 33 | const SizedBox(height: 10), 34 | Text(viewModel.title), 35 | ], 36 | ), 37 | ), 38 | ), 39 | viewModelBuilder: () => NonReactiveViewModel(), 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /example/router_example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /example/router_example/lib/ui/bottom_nav/bottom_nav_example.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:stacked/stacked.dart'; 3 | 4 | import 'bottom_nav_example_viewmodel.dart'; 5 | 6 | class BottomNavExample extends StatefulWidget { 7 | const BottomNavExample({super.key}); 8 | 9 | @override 10 | BottomNavExampleState createState() => BottomNavExampleState(); 11 | } 12 | 13 | class BottomNavExampleState extends State { 14 | @override 15 | Widget build(BuildContext context) { 16 | return ViewModelBuilder.reactive( 17 | builder: (context, viewModel, child) => Scaffold( 18 | body: const NestedRouter(), 19 | bottomNavigationBar: BottomNavigationBar( 20 | elevation: 6, 21 | backgroundColor: Colors.white, 22 | currentIndex: viewModel.currentIndex, 23 | onTap: viewModel.handleNavigation, 24 | items: const [ 25 | BottomNavigationBarItem( 26 | icon: Icon(Icons.favorite), 27 | label: 'Favorites', 28 | ), 29 | BottomNavigationBarItem( 30 | icon: Icon(Icons.history), 31 | label: 'History', 32 | ), 33 | BottomNavigationBarItem( 34 | icon: Icon(Icons.person), 35 | label: 'Profile', 36 | ), 37 | ], 38 | ), 39 | ), 40 | viewModelBuilder: () => BottomNavExampleViewModel(), 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /example/navigator_example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /example/navigator_example/lib/ui/bottom_nav/bottom_nav_example_viewmodel.dart: -------------------------------------------------------------------------------- 1 | import 'package:example/app/app.locator.dart'; 2 | import 'package:example/app/app.logger.dart'; 3 | import 'package:example/app/app.router.dart'; 4 | import 'package:example/ui/bottom_nav/favorites/favorites_view.dart'; 5 | import 'package:example/ui/bottom_nav/history/history_view.dart'; 6 | import 'package:stacked/stacked.dart'; 7 | import 'package:stacked_services/stacked_services.dart'; 8 | 9 | class BottomNavExampleViewModel extends IndexTrackingViewModel { 10 | final log = getLogger('BottomNavExampleViewModel'); 11 | final _navigationService = exampleLocator(); 12 | 13 | void handleNavigation(int index) { 14 | log.i('handleNavigation: $index'); 15 | setIndex(index); 16 | switch (index) { 17 | case 0: 18 | _navigationService.replaceWithTransition( 19 | const FavoritesView(), 20 | transitionStyle: 21 | reverse ? Transition.rightToLeft : Transition.leftToRight, 22 | id: 1, 23 | ); 24 | break; 25 | case 1: 26 | _navigationService.clearStackAndShowView( 27 | const HistoryView(), 28 | id: 1, 29 | ); 30 | break; 31 | case 2: 32 | _navigationService.pushNamedAndRemoveUntil( 33 | BottomNavExampleRoutes.profileView, 34 | predicate: (route) => route.isFirst, 35 | id: 1, 36 | ); 37 | break; 38 | default: 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/src/view_models/selector_view_model_builder_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:provider/provider.dart'; 3 | 4 | abstract class SelectorViewModelWidget 5 | extends Widget { 6 | const SelectorViewModelWidget({super.key}); 7 | 8 | K selector(T viewModel); 9 | Widget? get staticChild => null; 10 | bool shouldRebuild(K v1, K v2) => v1 != v2; 11 | Widget build(BuildContext context, K value); 12 | 13 | @override 14 | // ignore: library_private_types_in_public_api 15 | _DataProviderElement createElement() => 16 | _DataProviderElement(this); 17 | } 18 | 19 | class _DataProviderElement 20 | extends ComponentElement { 21 | _DataProviderElement(SelectorViewModelWidget super.widget); 22 | 23 | @override 24 | SelectorViewModelWidget get widget => 25 | super.widget as SelectorViewModelWidget; 26 | 27 | @override 28 | Widget build() { 29 | return Selector( 30 | key: widget.key, 31 | shouldRebuild: widget.shouldRebuild, 32 | selector: (BuildContext context, T viewModel) => 33 | widget.selector(viewModel), 34 | builder: (BuildContext _, K value, Widget? child) => 35 | widget.build(this, value), 36 | child: widget.staticChild, 37 | ); 38 | } 39 | 40 | @override 41 | void update(SelectorViewModelWidget newWidget) { 42 | super.update(newWidget); 43 | assert(widget == newWidget); 44 | rebuild(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /lib/src/mixins/listenable_service_mixin.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:stacked/stacked.dart'; 3 | 4 | /// Adds functionality to easily listen to all reactive values in a service 5 | mixin ListenableServiceMixin implements Listenable { 6 | final List _listeners = List.empty(growable: true); 7 | 8 | int get listenersCount => _listeners.length; 9 | 10 | /// List to the values and react when there are any changes 11 | void listenToReactiveValues(List reactiveValues) { 12 | for (var reactiveValue in reactiveValues) { 13 | if (reactiveValue is ChangeNotifier) { 14 | reactiveValue.addListener(notifyListeners); 15 | } else if (reactiveValue is ReactiveValue) { 16 | reactiveValue.values.listen((value) => notifyListeners()); 17 | } else if (reactiveValue is ReactiveList) { 18 | reactiveValue.onChange.listen((event) => notifyListeners()); 19 | } 20 | } 21 | } 22 | 23 | /// Registers a listener with this service 24 | @override 25 | void addListener(void Function() listener) { 26 | _listeners.add(listener); 27 | } 28 | 29 | /// Removes a listener from the service 30 | @override 31 | void removeListener(void Function() listener) { 32 | _listeners.remove(listener); 33 | } 34 | 35 | /// Notifies all the listeners attached to this service 36 | @protected 37 | @visibleForTesting 38 | void notifyListeners() { 39 | for (var listener in _listeners) { 40 | listener(); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /example/navigator_example/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | end 36 | 37 | post_install do |installer| 38 | installer.pods_project.targets.each do |target| 39 | flutter_additional_ios_build_settings(target) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /example/router_example/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '11.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | end 36 | 37 | post_install do |installer| 38 | installer.pods_project.targets.each do |target| 39 | flutter_additional_ios_build_settings(target) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /example/router_example/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at 17 | # https://dart-lang.github.io/linter/lints/index.html. 18 | # 19 | # Instead of disabling a lint rule for the entire project in the 20 | # section below, it can also be suppressed for a single line of code 21 | # or a specific dart file by using the `// ignore: name_of_lint` and 22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 23 | # producing the lint. 24 | rules: 25 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 26 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 27 | 28 | # Additional information about this file can be found at 29 | # https://dart.dev/guides/language/analysis-options 30 | -------------------------------------------------------------------------------- /example/router_example/macos/Podfile: -------------------------------------------------------------------------------- 1 | platform :osx, '10.14' 2 | 3 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 4 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 5 | 6 | project 'Runner', { 7 | 'Debug' => :debug, 8 | 'Profile' => :release, 9 | 'Release' => :release, 10 | } 11 | 12 | def flutter_root 13 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) 14 | unless File.exist?(generated_xcode_build_settings_path) 15 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" 16 | end 17 | 18 | File.foreach(generated_xcode_build_settings_path) do |line| 19 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 20 | return matches[1].strip if matches 21 | end 22 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" 23 | end 24 | 25 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 26 | 27 | flutter_macos_podfile_setup 28 | 29 | target 'Runner' do 30 | use_frameworks! 31 | use_modular_headers! 32 | 33 | flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) 34 | target 'RunnerTests' do 35 | inherit! :search_paths 36 | end 37 | end 38 | 39 | post_install do |installer| 40 | installer.pods_project.targets.each do |target| 41 | flutter_additional_macos_build_settings(target) 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /example/navigator_example/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at 17 | # https://dart-lang.github.io/linter/lints/index.html. 18 | # 19 | # Instead of disabling a lint rule for the entire project in the 20 | # section below, it can also be suppressed for a single line of code 21 | # or a specific dart file by using the `// ignore: name_of_lint` and 22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 23 | # producing the lint. 24 | rules: 25 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 26 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 27 | 28 | # Additional information about this file can be found at 29 | # https://dart.dev/guides/language/analysis-options 30 | -------------------------------------------------------------------------------- /example/navigator_example/lib/ui/smart_widgets/widget_one/widget_one.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:stacked/stacked.dart'; 3 | 4 | import 'widget_one_viewmodel.dart'; 5 | 6 | class WidgetOne extends StatelessWidget { 7 | const WidgetOne({super.key}); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return ViewModelBuilder.reactive( 12 | viewModelBuilder: () => WidgetOneViewModel(), 13 | builder: (context, viewModel, child) => GestureDetector( 14 | onTap: () => viewModel.updatePostCount(), 15 | child: Container( 16 | width: 100, 17 | height: 100, 18 | color: Colors.green, 19 | alignment: Alignment.center, 20 | child: !viewModel.busy(viewModel) 21 | ? Column( 22 | mainAxisSize: MainAxisSize.min, 23 | children: [ 24 | const Text( 25 | 'Tap to increment', 26 | style: TextStyle(fontSize: 10), 27 | ), 28 | Text( 29 | viewModel.postCount.toString(), 30 | style: const TextStyle( 31 | fontWeight: FontWeight.bold, 32 | fontSize: 40, 33 | ), 34 | ), 35 | ], 36 | ) 37 | : const Center( 38 | child: CircularProgressIndicator(), 39 | ), 40 | ), 41 | ), 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /example/router_example/lib/ui/smart_widgets/widget_one/widget_one.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:stacked/stacked.dart'; 3 | 4 | import 'widget_one_viewmodel.dart'; 5 | 6 | class WidgetOne extends StatelessWidget { 7 | const WidgetOne({super.key}); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return ViewModelBuilder.reactive( 12 | viewModelBuilder: () => WidgetOneViewModel(), 13 | builder: (context, viewModel, child) => GestureDetector( 14 | onTap: () => viewModel.updatePostCount(), 15 | child: Container( 16 | width: 100, 17 | height: 100, 18 | color: Colors.green, 19 | alignment: Alignment.center, 20 | child: !viewModel.busy(viewModel) 21 | ? Column( 22 | mainAxisSize: MainAxisSize.min, 23 | children: [ 24 | const Text( 25 | 'Tap to increment', 26 | style: TextStyle(fontSize: 10), 27 | ), 28 | Text( 29 | viewModel.postCount.toString(), 30 | style: const TextStyle( 31 | fontWeight: FontWeight.bold, 32 | fontSize: 40, 33 | ), 34 | ), 35 | ], 36 | ) 37 | : const Center( 38 | child: CircularProgressIndicator(), 39 | ), 40 | ), 41 | ), 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /example/router_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 | -------------------------------------------------------------------------------- /example/router_example/lib/ui/form/example_form_viewmodel.dart: -------------------------------------------------------------------------------- 1 | import 'package:example/app/app.locator.dart'; 2 | import 'package:example/app/app.logger.dart'; 3 | import 'package:example/app/app.router.dart'; 4 | import 'package:example/services/shared_preferences_service.dart'; 5 | import 'package:stacked/stacked.dart'; 6 | import 'package:stacked_services/stacked_services.dart'; 7 | 8 | import 'example_form_view.form.dart'; 9 | 10 | // #5: extend from FormViewModel 11 | class ExampleFormViewModel extends FormViewModel { 12 | final log = getLogger('FormViewModel'); 13 | final _routerService = exampleLocator(); 14 | final _preferences = exampleLocator(); 15 | 16 | ExampleFormViewModel() { 17 | log.f('hash:${_preferences.hashCode}'); 18 | } 19 | 20 | void populateForm() { 21 | DoYouLoveFoodValueToTitleMap.addAll({'MaybeDr': 'Maybe'}); 22 | setDoYouLoveFood(DoYouLoveFoodValueToTitleMap.keys.first); 23 | } 24 | 25 | @override 26 | void setFormStatus() { 27 | log.i('Set form Status with data: $formValueMap'); 28 | 29 | // Set a validation message for the entire form 30 | if (hasPasswordValidationMessage) { 31 | setValidationMessage('Error in the form, please check again'); 32 | } 33 | } 34 | 35 | // If the dev doesn't want realtime validation then they can 36 | // simply validate in the function that they'll use to submit the 37 | // data to the backend or db. 38 | 39 | Future saveData() async { 40 | if (!isFormValid) return; 41 | 42 | // here we can run custom functionality to save to our api 43 | 44 | _routerService.replaceWith(BottomNavExampleRoute()); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at 17 | # https://dart-lang.github.io/linter/lints/index.html. 18 | # 19 | # Instead of disabling a lint rule for the entire project in the 20 | # section below, it can also be suppressed for a single line of code 21 | # or a specific dart file by using the `// ignore: name_of_lint` and 22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 23 | # producing the lint. 24 | # TODO: review these settings. 25 | rules: 26 | avoid_shadowing_type_parameters: false 27 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 28 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 29 | # Additional information about this file can be found at 30 | # https://dart.dev/guides/language/analysis-options 31 | 32 | analyzer: 33 | exclude: 34 | - example/ 35 | -------------------------------------------------------------------------------- /example/navigator_example/lib/ui/stream_view/stream_counter_viewmodel.dart: -------------------------------------------------------------------------------- 1 | import 'package:example/app/app.locator.dart'; 2 | import 'package:example/datamodels/clashable_one.dart'; 3 | import 'package:example/services/epoch_service.dart'; 4 | import 'package:example/ui/form/example_form_view.dart'; 5 | import 'package:stacked/stacked.dart'; 6 | import 'package:stacked_services/stacked_services.dart'; 7 | 8 | class StreamCounterViewModel extends StreamViewModel { 9 | final _navigationService = exampleLocator(); 10 | 11 | String get title => 'This is the time since epoch in seconds \n $data'; 12 | 13 | late Stream _currentSource; 14 | bool isSlowEpochNumbers = true; 15 | 16 | String get streamSource => isSlowEpochNumbers ? 'Slow' : 'Fast'; 17 | 18 | StreamCounterViewModel() { 19 | _setSource(); 20 | } 21 | 22 | void _setSource() { 23 | _currentSource = isSlowEpochNumbers 24 | ? exampleLocator().epochUpdatesNumbers() 25 | : exampleLocator().epochUpdateNumbersQuickly(); 26 | } 27 | 28 | @override 29 | Stream get stream => _currentSource; 30 | 31 | @override 32 | void onData(int? data) {} 33 | 34 | @override 35 | void onCancel() {} 36 | 37 | @override 38 | void onSubscribed() {} 39 | 40 | @override 41 | void onError(error) {} 42 | 43 | void changeStreamSources() { 44 | isSlowEpochNumbers = !isSlowEpochNumbers; 45 | _setSource(); 46 | notifySourceChanged(); 47 | } 48 | 49 | void navigateToNewView() { 50 | _navigationService.navigateWithTransition( 51 | ExampleFormView(clashableOne: const Clashable('one')), 52 | transitionStyle: Transition.zoom); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /example/router_example/lib/ui/stream_view/stream_counter_viewmodel.dart: -------------------------------------------------------------------------------- 1 | import 'package:example/app/app.locator.dart'; 2 | import 'package:example/datamodels/clashable_one.dart'; 3 | import 'package:example/services/epoch_service.dart'; 4 | import 'package:example/ui/form/example_form_view.dart'; 5 | import 'package:stacked/stacked.dart'; 6 | import 'package:stacked_services/stacked_services.dart'; 7 | 8 | class StreamCounterViewModel extends StreamViewModel { 9 | final _routerService = exampleLocator(); 10 | 11 | String get title => 'This is the time since epoch in seconds \n $data'; 12 | 13 | late Stream _currentSource; 14 | bool isSlowEpochNumbers = true; 15 | 16 | String get streamSource => isSlowEpochNumbers ? 'Slow' : 'Fast'; 17 | 18 | StreamCounterViewModel() { 19 | _setSource(); 20 | } 21 | 22 | void _setSource() { 23 | _currentSource = isSlowEpochNumbers 24 | ? exampleLocator().epochUpdatesNumbers() 25 | : exampleLocator().epochUpdateNumbersQuickly(); 26 | } 27 | 28 | @override 29 | Stream get stream => _currentSource; 30 | 31 | @override 32 | void onData(int? data) {} 33 | 34 | @override 35 | void onCancel() {} 36 | 37 | @override 38 | void onSubscribed() {} 39 | 40 | @override 41 | void onError(error) {} 42 | 43 | void changeStreamSources() { 44 | isSlowEpochNumbers = !isSlowEpochNumbers; 45 | _setSource(); 46 | notifySourceChanged(); 47 | } 48 | 49 | void navigateToNewView() { 50 | _routerService.navigateWithTransition( 51 | ExampleFormView(clashableOne: const Clashable('one')), 52 | transitionBuilder: TransitionsBuilders.zoomIn); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /example/navigator_example/lib/ui/stream_view/stream_counter_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:example/datamodels/clashable_two.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:example/ui/stream_view/stream_counter_viewmodel.dart'; 4 | import 'package:stacked/stacked.dart'; 5 | 6 | class StreamCounterView extends StatelessWidget { 7 | final List clashableTwo; 8 | const StreamCounterView({super.key, required this.clashableTwo}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return ViewModelBuilder.reactive( 13 | builder: (context, viewModel, child) => Scaffold( 14 | appBar: AppBar( 15 | title: const Text('Stream Counter View'), 16 | centerTitle: true, 17 | ), 18 | body: Center( 19 | child: Column( 20 | mainAxisAlignment: MainAxisAlignment.center, 21 | children: [ 22 | ElevatedButton( 23 | onPressed: viewModel.changeStreamSources, 24 | child: const Text('Change Stream Sources'), 25 | ), 26 | const SizedBox(height: 10), 27 | Text( 28 | viewModel.title, 29 | textAlign: TextAlign.center, 30 | ), 31 | const SizedBox(height: 20), 32 | Text( 33 | viewModel.streamSource, 34 | textAlign: TextAlign.center, 35 | ), 36 | ], 37 | ), 38 | ), 39 | floatingActionButton: FloatingActionButton( 40 | onPressed: viewModel.navigateToNewView, 41 | ), 42 | ), 43 | viewModelBuilder: () => StreamCounterViewModel(), 44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /example/router_example/lib/ui/stream_view/stream_counter_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:example/datamodels/clashable_two.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:example/ui/stream_view/stream_counter_viewmodel.dart'; 4 | import 'package:stacked/stacked.dart'; 5 | 6 | class StreamCounterView extends StatelessWidget { 7 | final List clashableTwo; 8 | const StreamCounterView({super.key, required this.clashableTwo}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return ViewModelBuilder.reactive( 13 | builder: (context, viewModel, child) => Scaffold( 14 | appBar: AppBar( 15 | title: const Text('Stream Counter View'), 16 | centerTitle: true, 17 | ), 18 | body: Center( 19 | child: Column( 20 | mainAxisAlignment: MainAxisAlignment.center, 21 | children: [ 22 | ElevatedButton( 23 | onPressed: viewModel.changeStreamSources, 24 | child: const Text('Change Stream Sources'), 25 | ), 26 | const SizedBox(height: 10), 27 | Text( 28 | viewModel.title, 29 | textAlign: TextAlign.center, 30 | ), 31 | const SizedBox(height: 20), 32 | Text( 33 | viewModel.streamSource, 34 | textAlign: TextAlign.center, 35 | ), 36 | ], 37 | ), 38 | ), 39 | floatingActionButton: FloatingActionButton( 40 | onPressed: viewModel.navigateToNewView, 41 | ), 42 | ), 43 | viewModelBuilder: () => StreamCounterViewModel(), 44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /example/router_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 | -------------------------------------------------------------------------------- /example/navigator_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 | -------------------------------------------------------------------------------- /example/router_example/lib/ui/multiple_streams_example/multiple_streams_example_viewmodel.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:stacked/stacked.dart'; 4 | 5 | const String _numbersStreamKey = 'numbers-stream'; 6 | const String _stringStreamKey = 'string-stream'; 7 | 8 | class MultipleStreamsExampleViewModel extends MultipleStreamViewModel { 9 | int get number => dataMap![_numbersStreamKey]; 10 | bool get hasNumberData => dataReady(_numbersStreamKey); 11 | 12 | String get randomString => dataMap![_stringStreamKey]; 13 | bool get hasRandomString => dataReady(_stringStreamKey); 14 | 15 | Stream numbersStream([int delay = 500]) async* { 16 | var random = Random(); 17 | while (true) { 18 | await Future.delayed(Duration(milliseconds: delay)); 19 | yield random.nextInt(999); 20 | } 21 | } 22 | 23 | Stream stringStream([int delay = 2000]) async* { 24 | var random = Random(); 25 | while (true) { 26 | await Future.delayed(Duration(milliseconds: delay)); 27 | var randomLength = random.nextInt(50); 28 | var randomString = ''; 29 | for (var i = 0; i < randomLength; i++) { 30 | randomString += String.fromCharCode(random.nextInt(50)); 31 | } 32 | yield randomString; 33 | } 34 | } 35 | 36 | int numbersStreamDelay = 500; 37 | int stringStreamDelay = 2000; 38 | 39 | @override 40 | Map get streamsMap => { 41 | _numbersStreamKey: StreamData(numbersStream(numbersStreamDelay)), 42 | _stringStreamKey: StreamData(stringStream(stringStreamDelay)), 43 | }; 44 | 45 | void swapStreams() { 46 | numbersStreamDelay -= 100; 47 | stringStreamDelay -= 500; 48 | notifySourceChanged(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /example/navigator_example/lib/ui/multiple_streams_example/multiple_streams_example_viewmodel.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:stacked/stacked.dart'; 4 | 5 | const String _numbersStreamKey = 'numbers-stream'; 6 | const String _stringStreamKey = 'string-stream'; 7 | 8 | class MultipleStreamsExampleViewModel extends MultipleStreamViewModel { 9 | int get number => dataMap![_numbersStreamKey]; 10 | bool get hasNumberData => dataReady(_numbersStreamKey); 11 | 12 | String get randomString => dataMap![_stringStreamKey]; 13 | bool get hasRandomString => dataReady(_stringStreamKey); 14 | 15 | Stream numbersStream([int delay = 500]) async* { 16 | var random = Random(); 17 | while (true) { 18 | await Future.delayed(Duration(milliseconds: delay)); 19 | yield random.nextInt(999); 20 | } 21 | } 22 | 23 | Stream stringStream([int delay = 2000]) async* { 24 | var random = Random(); 25 | while (true) { 26 | await Future.delayed(Duration(milliseconds: delay)); 27 | var randomLength = random.nextInt(50); 28 | var randomString = ''; 29 | for (var i = 0; i < randomLength; i++) { 30 | randomString += String.fromCharCode(random.nextInt(50)); 31 | } 32 | yield randomString; 33 | } 34 | } 35 | 36 | int numbersStreamDelay = 500; 37 | int stringStreamDelay = 2000; 38 | 39 | @override 40 | Map get streamsMap => { 41 | _numbersStreamKey: StreamData(numbersStream(numbersStreamDelay)), 42 | _stringStreamKey: StreamData(stringStream(stringStreamDelay)), 43 | }; 44 | 45 | void swapStreams() { 46 | numbersStreamDelay -= 100; 47 | stringStreamDelay -= 500; 48 | notifySourceChanged(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /lib/src/view_models/helpers/form_state_helper.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /// Provides functionality to reduce the code required in order to move user input 4 | /// into the [ViewModel] 5 | mixin FormStateHelper on ChangeNotifier { 6 | bool _showValidationMessage = false; 7 | bool get showValidationMessage => _showValidationMessage; 8 | 9 | String? _validationMessage; 10 | String? get validationMessage => _validationMessage; 11 | 12 | /// Stores the mapping of the form key to the value entered by the user 13 | Map formValueMap = {}; 14 | Map fieldsValidationMessages = {}; 15 | 16 | void setValidationMessage(String? value) { 17 | _validationMessage = value; 18 | _showValidationMessage = _validationMessage?.isNotEmpty ?? false; 19 | } 20 | 21 | void setData(Map data) { 22 | // Save the data from the controllers 23 | formValueMap = data; 24 | 25 | // Reset the form status 26 | setValidationMessage(null); 27 | 28 | // Reset each field status 29 | for (var fieldName in data.keys) { 30 | fieldsValidationMessages.remove(fieldName); 31 | } 32 | 33 | // Set the new form status 34 | setFormStatus(); 35 | 36 | // Rebuild the UI 37 | notifyListeners(); 38 | } 39 | 40 | void setValidationMessages(Map validationMessages) { 41 | fieldsValidationMessages = validationMessages; 42 | fieldsValidationMessages.removeWhere((key, value) => value == null); 43 | setFormStatus(); 44 | notifyListeners(); 45 | } 46 | 47 | /// Called after the [formValueMap] has been updated and allows you to set 48 | /// values relating to the forms status. 49 | void setFormStatus() {} 50 | } 51 | -------------------------------------------------------------------------------- /example/navigator_example/lib/ui/bottom_nav/bottom_nav_example.dart: -------------------------------------------------------------------------------- 1 | import 'package:example/app/app.router.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:stacked/stacked.dart'; 4 | import 'package:stacked_services/stacked_services.dart'; 5 | 6 | import 'bottom_nav_example_viewmodel.dart'; 7 | 8 | class BottomNavExample extends StatefulWidget { 9 | const BottomNavExample({super.key}); 10 | 11 | @override 12 | BottomNavExampleState createState() => BottomNavExampleState(); 13 | } 14 | 15 | class BottomNavExampleState extends State { 16 | @override 17 | Widget build(BuildContext context) { 18 | return ViewModelBuilder.reactive( 19 | builder: (context, viewModel, child) => Scaffold( 20 | body: ExtendedNavigator( 21 | navigatorKey: StackedService.nestedNavigationKey(1), 22 | initialRoute: BottomNavExampleRoutes.favoritesView, 23 | router: BottomNavExampleRouter(), 24 | ), 25 | bottomNavigationBar: BottomNavigationBar( 26 | elevation: 6, 27 | backgroundColor: Colors.white, 28 | currentIndex: viewModel.currentIndex, 29 | onTap: viewModel.handleNavigation, 30 | items: const [ 31 | BottomNavigationBarItem( 32 | icon: Icon(Icons.favorite), 33 | label: 'Favorites', 34 | ), 35 | BottomNavigationBarItem( 36 | icon: Icon(Icons.history), 37 | label: 'History', 38 | ), 39 | BottomNavigationBarItem( 40 | icon: Icon(Icons.person), 41 | label: 'Profile', 42 | ), 43 | ], 44 | ), 45 | ), 46 | viewModelBuilder: () => BottomNavExampleViewModel(), 47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /example/navigator_example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | new_architecture 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | CADisableMinimumFrameDurationOnPhone 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /example/router_example/lib/services/shared_preferences_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:example/app/app.logger.dart'; 2 | import 'package:shared_preferences/shared_preferences.dart'; 3 | import 'package:stacked/stacked_annotations.dart'; 4 | 5 | class SharedPreferencesService implements InitializableDependency { 6 | static const _isUserLoggedInKey = 'isUserLoggedIn'; 7 | 8 | final bool enableLogs; 9 | SharedPreferencesService({this.enableLogs = false}); 10 | 11 | final _log = getLogger('SharedPreferencesService'); 12 | 13 | late SharedPreferences _preferences; 14 | 15 | @override 16 | Future init() async { 17 | _log.d('Initialized'); 18 | _preferences = await SharedPreferences.getInstance(); 19 | } 20 | 21 | bool get isUserLoggedIn => 22 | (getFromDisk(_isUserLoggedInKey) as bool?) ?? false; 23 | 24 | set isUserLoggedIn(bool value) => saveToDisk(_isUserLoggedInKey, value); 25 | 26 | Set getKeys() => _preferences.getKeys(); 27 | 28 | Object? getFromDisk(String key) { 29 | final value = _preferences.get(key); 30 | if (enableLogs) _log.t('key:$key value:$value'); 31 | return value; 32 | } 33 | 34 | void saveToDisk(String key, dynamic content) { 35 | if (enableLogs) _log.t('key:$key value:$content'); 36 | 37 | if (content is String) { 38 | _preferences.setString(key, content); 39 | } 40 | if (content is bool) { 41 | _preferences.setBool(key, content); 42 | } 43 | if (content is int) { 44 | _preferences.setInt(key, content); 45 | } 46 | if (content is double) { 47 | _preferences.setDouble(key, content); 48 | } 49 | if (content is List) { 50 | _preferences.setStringList(key, content); 51 | } 52 | } 53 | 54 | void dispose() { 55 | _log.i(''); 56 | _preferences.clear(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /lib/src/reactive/reactive_value/reactive_value.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import '../type_def.dart'; 4 | import 'proxy_value.dart'; 5 | import 'stored_value.dart'; 6 | 7 | /// Interface of an Reactive value of type [T] 8 | abstract class ReactiveValue { 9 | factory ReactiveValue(T initial) => StoredValue(initial); 10 | factory ReactiveValue.proxy(ValueGetter getterProxy) => 11 | ProxyValue(getterProxy); 12 | 13 | /// Get current value 14 | T get value; 15 | 16 | /// Set value 17 | set value(T val); 18 | 19 | /// Cast [val] to [T] before setting 20 | void setCast(dynamic val); 21 | 22 | /// Stream of record of [Change]s of value 23 | Stream> get onChange; 24 | 25 | /// Stream of changes of value 26 | Stream get values; 27 | 28 | /// Binds if [other] is [Stream] or [RxValue] of type [T]. Sets if [other] is 29 | /// instance of [T] 30 | void bindOrSet(/* T | Stream | Reactive */ other); 31 | 32 | /// Binds [other] to this 33 | void bind(ReactiveValue other); 34 | 35 | /// Binds the [stream] to this 36 | void bindStream(Stream stream); 37 | 38 | /// Calls [callback] with current value, when the value changes. 39 | StreamSubscription listen(ValueCallback callback); 40 | 41 | /// Maps the changes into a [Stream] of [R] 42 | Stream map(R Function(T data) mapper); 43 | } 44 | 45 | /// A record of change in [RxValue] 46 | class Change { 47 | /// Value before change 48 | final T old; 49 | 50 | /// Value after change 51 | final T neu; 52 | 53 | final DateTime time; 54 | 55 | final int batch; 56 | 57 | Change( 58 | this.neu, 59 | this.old, 60 | this.batch, { 61 | DateTime? time, 62 | }) : time = DateTime.now(); 63 | 64 | @override 65 | String toString() => 'Change(new: $neu, old: $old)'; 66 | } 67 | -------------------------------------------------------------------------------- /example/router_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. 5 | 6 | version: 7 | revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff 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: 9cd3d0d9ff05768afa249e036acc66e8abe93bff 17 | base_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff 18 | - platform: android 19 | create_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff 20 | base_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff 21 | - platform: ios 22 | create_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff 23 | base_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff 24 | - platform: linux 25 | create_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff 26 | base_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff 27 | - platform: macos 28 | create_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff 29 | base_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff 30 | - platform: web 31 | create_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff 32 | base_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff 33 | - platform: windows 34 | create_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff 35 | base_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff 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/navigator_example/lib/ui/multiple_futures_example/multiple_futures_example_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:example/ui/multiple_futures_example/multiple_futures_example_viewmodel.dart'; 3 | import 'package:stacked/stacked.dart'; 4 | 5 | class MultipleFuturesExampleView extends StatelessWidget { 6 | const MultipleFuturesExampleView({super.key}); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return ViewModelBuilder.reactive( 11 | builder: (context, viewModel, child) => Scaffold( 12 | body: Center( 13 | child: Row( 14 | mainAxisSize: MainAxisSize.min, 15 | children: [ 16 | Container( 17 | width: 50, 18 | height: 50, 19 | alignment: Alignment.center, 20 | color: Colors.yellow, 21 | child: viewModel.fetchingNumber 22 | ? const CircularProgressIndicator() 23 | : Text(viewModel.fetchedNumber.toString()), 24 | ), 25 | const SizedBox( 26 | width: 20, 27 | ), 28 | Container( 29 | width: 50, 30 | height: 50, 31 | alignment: Alignment.center, 32 | color: Colors.red, 33 | child: viewModel.fetchingString 34 | ? const CircularProgressIndicator() 35 | : Text(viewModel.fetchedString), 36 | ), 37 | ], 38 | ), 39 | ), 40 | ), 41 | viewModelBuilder: () => MultipleFuturesExampleViewModel()); 42 | } 43 | } 44 | --------------------------------------------------------------------------------