Repository: jonataslaw/getx Branch: master Commit: 7bfcd9c3711c Files: 391 Total size: 2.4 MB Directory structure: gitextract_zev5bl50/ ├── .github/ │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ ├── config.yml │ │ └── feature_request.md │ ├── pull_request_template.md │ └── workflows/ │ └── main.yml ├── .gitignore ├── .metadata ├── .vscode/ │ ├── launch.json │ └── settings.json ├── CHANGELOG.md ├── LICENSE ├── README-ar.md ├── README-bn.md ├── README-es.md ├── README-fr.md ├── README-hi.md ├── README-ne.md ├── README-vi.md ├── README.id-ID.md ├── README.ja-JP.md ├── README.ko-kr.md ├── README.md ├── README.pl.md ├── README.pt-br.md ├── README.ru.md ├── README.tr-TR.md ├── README.ur-PK.md ├── README.zh-cn.md ├── _config.yml ├── analysis_options.yaml ├── documentation/ │ ├── ar_EG/ │ │ ├── dependency_management.md │ │ ├── route_management.md │ │ └── state_management.md │ ├── en_US/ │ │ ├── dependency_management.md │ │ ├── route_management.md │ │ └── state_management.md │ ├── es_ES/ │ │ ├── dependency_management.md │ │ ├── route_management.md │ │ └── state_management.md │ ├── fr_FR/ │ │ ├── dependency_management.md │ │ ├── route_management.md │ │ └── state_management.md │ ├── id_ID/ │ │ ├── dependency_management.md │ │ ├── route_management.md │ │ └── state_management.md │ ├── ja_JP/ │ │ ├── dependency_management.md │ │ ├── route_management.md │ │ └── state_management.md │ ├── kr_KO/ │ │ ├── dependency_management.md │ │ ├── route_management.md │ │ └── state_management.md │ ├── pt_BR/ │ │ ├── dependency_management.md │ │ ├── route_management.md │ │ └── state_management.md │ ├── ru_RU/ │ │ ├── dependency_management.md │ │ ├── route_management.md │ │ └── state_management.md │ ├── tr_TR/ │ │ ├── dependency_management.md │ │ ├── route_management.md │ │ └── state_management.md │ ├── vi_VI/ │ │ ├── dependency_management.md │ │ ├── route_management.md │ │ └── state_management.md │ └── zh_CN/ │ ├── dependency_management.md │ ├── route_management.md │ └── state_management.md ├── example/ │ ├── .gitignore │ ├── .metadata │ ├── README.md │ ├── analysis_options.yaml │ ├── example.md │ ├── lib/ │ │ ├── lang/ │ │ │ ├── en_US.dart │ │ │ ├── pt_BR.dart │ │ │ └── translation_service.dart │ │ ├── main.dart │ │ ├── pages/ │ │ │ └── home/ │ │ │ ├── bindings/ │ │ │ │ ├── details_binding.dart │ │ │ │ └── home_binding.dart │ │ │ ├── data/ │ │ │ │ ├── home_api_provider.dart │ │ │ │ └── home_repository.dart │ │ │ ├── domain/ │ │ │ │ ├── adapters/ │ │ │ │ │ └── repository_adapter.dart │ │ │ │ └── entity/ │ │ │ │ └── country_model.dart │ │ │ └── presentation/ │ │ │ ├── controllers/ │ │ │ │ ├── details_controller.dart │ │ │ │ └── home_controller.dart │ │ │ └── views/ │ │ │ ├── details_view.dart │ │ │ └── home_view.dart │ │ ├── routes/ │ │ │ ├── app_pages.dart │ │ │ └── app_routes.dart │ │ └── shared/ │ │ ├── constants/ │ │ │ └── endpoints.dart │ │ └── logger/ │ │ └── logger_utils.dart │ ├── pubspec.yaml │ └── test/ │ ├── main_test.dart │ └── widget_test.dart ├── example_nav2/ │ ├── .gitignore │ ├── .metadata │ ├── README.md │ ├── analysis_options.yaml │ ├── android/ │ │ ├── .gitignore │ │ ├── app/ │ │ │ ├── build.gradle.kts │ │ │ └── src/ │ │ │ ├── debug/ │ │ │ │ └── AndroidManifest.xml │ │ │ ├── main/ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ ├── kotlin/ │ │ │ │ │ └── com/ │ │ │ │ │ └── example/ │ │ │ │ │ └── example_nav2/ │ │ │ │ │ └── MainActivity.kt │ │ │ │ └── res/ │ │ │ │ ├── drawable/ │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable-v21/ │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── values/ │ │ │ │ │ └── styles.xml │ │ │ │ └── values-night/ │ │ │ │ └── styles.xml │ │ │ └── profile/ │ │ │ └── AndroidManifest.xml │ │ ├── build.gradle.kts │ │ ├── gradle/ │ │ │ └── wrapper/ │ │ │ └── gradle-wrapper.properties │ │ ├── gradle.properties │ │ ├── local.properties │ │ └── settings.gradle.kts │ ├── ios/ │ │ ├── .gitignore │ │ ├── Flutter/ │ │ │ ├── AppFrameworkInfo.plist │ │ │ ├── Debug.xcconfig │ │ │ ├── Generated.xcconfig │ │ │ ├── Release.xcconfig │ │ │ └── flutter_export_environment.sh │ │ ├── Runner/ │ │ │ ├── AppDelegate.swift │ │ │ ├── Assets.xcassets/ │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ └── Contents.json │ │ │ │ └── LaunchImage.imageset/ │ │ │ │ ├── Contents.json │ │ │ │ └── README.md │ │ │ ├── Base.lproj/ │ │ │ │ ├── LaunchScreen.storyboard │ │ │ │ └── Main.storyboard │ │ │ ├── GeneratedPluginRegistrant.h │ │ │ ├── GeneratedPluginRegistrant.m │ │ │ ├── Info.plist │ │ │ └── Runner-Bridging-Header.h │ │ ├── Runner.xcodeproj/ │ │ │ ├── project.pbxproj │ │ │ ├── project.xcworkspace/ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ └── xcshareddata/ │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ └── WorkspaceSettings.xcsettings │ │ │ └── xcshareddata/ │ │ │ └── xcschemes/ │ │ │ └── Runner.xcscheme │ │ ├── Runner.xcworkspace/ │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata/ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ │ └── RunnerTests/ │ │ └── RunnerTests.swift │ ├── lib/ │ │ ├── app/ │ │ │ ├── middleware/ │ │ │ │ └── auth_middleware.dart │ │ │ ├── modules/ │ │ │ │ ├── dashboard/ │ │ │ │ │ ├── bindings/ │ │ │ │ │ │ └── dashboard_binding.dart │ │ │ │ │ ├── controllers/ │ │ │ │ │ │ └── dashboard_controller.dart │ │ │ │ │ └── views/ │ │ │ │ │ └── dashboard_view.dart │ │ │ │ ├── home/ │ │ │ │ │ ├── bindings/ │ │ │ │ │ │ └── home_binding.dart │ │ │ │ │ ├── controllers/ │ │ │ │ │ │ └── home_controller.dart │ │ │ │ │ └── views/ │ │ │ │ │ └── home_view.dart │ │ │ │ ├── login/ │ │ │ │ │ ├── bindings/ │ │ │ │ │ │ └── login_binding.dart │ │ │ │ │ ├── controllers/ │ │ │ │ │ │ └── login_controller.dart │ │ │ │ │ └── views/ │ │ │ │ │ └── login_view.dart │ │ │ │ ├── product_details/ │ │ │ │ │ ├── bindings/ │ │ │ │ │ │ └── product_details_binding.dart │ │ │ │ │ ├── controllers/ │ │ │ │ │ │ └── product_details_controller.dart │ │ │ │ │ └── views/ │ │ │ │ │ └── product_details_view.dart │ │ │ │ ├── products/ │ │ │ │ │ ├── bindings/ │ │ │ │ │ │ └── products_binding.dart │ │ │ │ │ ├── controllers/ │ │ │ │ │ │ └── products_controller.dart │ │ │ │ │ └── views/ │ │ │ │ │ └── products_view.dart │ │ │ │ ├── profile/ │ │ │ │ │ ├── bindings/ │ │ │ │ │ │ └── profile_binding.dart │ │ │ │ │ ├── controllers/ │ │ │ │ │ │ └── profile_controller.dart │ │ │ │ │ └── views/ │ │ │ │ │ └── profile_view.dart │ │ │ │ ├── root/ │ │ │ │ │ ├── bindings/ │ │ │ │ │ │ └── root_binding.dart │ │ │ │ │ ├── controllers/ │ │ │ │ │ │ └── root_controller.dart │ │ │ │ │ └── views/ │ │ │ │ │ ├── drawer.dart │ │ │ │ │ └── root_view.dart │ │ │ │ └── settings/ │ │ │ │ ├── bindings/ │ │ │ │ │ └── settings_binding.dart │ │ │ │ ├── controllers/ │ │ │ │ │ └── settings_controller.dart │ │ │ │ └── views/ │ │ │ │ └── settings_view.dart │ │ │ └── routes/ │ │ │ ├── app_pages.dart │ │ │ └── app_routes.dart │ │ ├── main.dart │ │ ├── models/ │ │ │ └── demo_product.dart │ │ └── services/ │ │ └── auth_service.dart │ ├── linux/ │ │ ├── .gitignore │ │ ├── CMakeLists.txt │ │ ├── flutter/ │ │ │ ├── CMakeLists.txt │ │ │ ├── generated_plugin_registrant.cc │ │ │ ├── generated_plugin_registrant.h │ │ │ └── generated_plugins.cmake │ │ ├── main.cc │ │ ├── my_application.cc │ │ ├── my_application.h │ │ └── runner/ │ │ ├── CMakeLists.txt │ │ ├── main.cc │ │ ├── my_application.cc │ │ └── my_application.h │ ├── macos/ │ │ ├── .gitignore │ │ ├── Flutter/ │ │ │ ├── Flutter-Debug.xcconfig │ │ │ ├── Flutter-Release.xcconfig │ │ │ └── GeneratedPluginRegistrant.swift │ │ ├── Runner/ │ │ │ ├── AppDelegate.swift │ │ │ ├── Assets.xcassets/ │ │ │ │ └── AppIcon.appiconset/ │ │ │ │ └── Contents.json │ │ │ ├── Base.lproj/ │ │ │ │ └── MainMenu.xib │ │ │ ├── Configs/ │ │ │ │ ├── AppInfo.xcconfig │ │ │ │ ├── Debug.xcconfig │ │ │ │ ├── Release.xcconfig │ │ │ │ └── Warnings.xcconfig │ │ │ ├── DebugProfile.entitlements │ │ │ ├── Info.plist │ │ │ ├── MainFlutterWindow.swift │ │ │ └── Release.entitlements │ │ ├── Runner.xcodeproj/ │ │ │ ├── project.pbxproj │ │ │ ├── project.xcworkspace/ │ │ │ │ └── xcshareddata/ │ │ │ │ └── IDEWorkspaceChecks.plist │ │ │ └── xcshareddata/ │ │ │ └── xcschemes/ │ │ │ └── Runner.xcscheme │ │ ├── Runner.xcworkspace/ │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata/ │ │ │ └── IDEWorkspaceChecks.plist │ │ └── RunnerTests/ │ │ └── RunnerTests.swift │ ├── pubspec.yaml │ ├── test/ │ │ └── widget_test.dart │ ├── web/ │ │ ├── index.html │ │ └── manifest.json │ └── windows/ │ ├── .gitignore │ ├── CMakeLists.txt │ ├── flutter/ │ │ ├── CMakeLists.txt │ │ ├── generated_plugin_registrant.cc │ │ ├── generated_plugin_registrant.h │ │ └── generated_plugins.cmake │ └── runner/ │ ├── CMakeLists.txt │ ├── Runner.rc │ ├── flutter_window.cpp │ ├── flutter_window.h │ ├── main.cpp │ ├── resource.h │ ├── run_loop.cpp │ ├── run_loop.h │ ├── runner.exe.manifest │ ├── utils.cpp │ ├── utils.h │ ├── win32_window.cpp │ └── win32_window.h ├── lib/ │ ├── get.dart │ ├── get_animations/ │ │ ├── animations.dart │ │ ├── extensions.dart │ │ ├── get_animated_builder.dart │ │ └── index.dart │ ├── get_common/ │ │ └── get_reset.dart │ ├── get_connect/ │ │ ├── connect.dart │ │ ├── http/ │ │ │ └── src/ │ │ │ ├── certificates/ │ │ │ │ └── certificates.dart │ │ │ ├── exceptions/ │ │ │ │ └── exceptions.dart │ │ │ ├── http/ │ │ │ │ ├── html/ │ │ │ │ │ ├── file_decoder_html.dart │ │ │ │ │ └── http_request_html.dart │ │ │ │ ├── interface/ │ │ │ │ │ └── request_base.dart │ │ │ │ ├── io/ │ │ │ │ │ ├── file_decoder_io.dart │ │ │ │ │ └── http_request_io.dart │ │ │ │ ├── mock/ │ │ │ │ │ └── http_request_mock.dart │ │ │ │ ├── request/ │ │ │ │ │ └── http_request.dart │ │ │ │ ├── stub/ │ │ │ │ │ ├── file_decoder_stub.dart │ │ │ │ │ └── http_request_stub.dart │ │ │ │ └── utils/ │ │ │ │ └── body_decoder.dart │ │ │ ├── http.dart │ │ │ ├── interceptors/ │ │ │ │ └── get_modifiers.dart │ │ │ ├── multipart/ │ │ │ │ ├── form_data.dart │ │ │ │ └── multipart_file.dart │ │ │ ├── request/ │ │ │ │ └── request.dart │ │ │ ├── response/ │ │ │ │ ├── client_response.dart │ │ │ │ └── response.dart │ │ │ ├── status/ │ │ │ │ └── http_status.dart │ │ │ └── utils/ │ │ │ └── utils.dart │ │ └── sockets/ │ │ ├── sockets.dart │ │ └── src/ │ │ ├── socket_notifier.dart │ │ ├── sockets_html.dart │ │ ├── sockets_io.dart │ │ └── sockets_stub.dart │ ├── get_connect.dart │ ├── get_core/ │ │ ├── get_core.dart │ │ └── src/ │ │ ├── flutter_engine.dart │ │ ├── get_interface.dart │ │ ├── get_main.dart │ │ ├── log.dart │ │ ├── smart_management.dart │ │ └── typedefs.dart │ ├── get_instance/ │ │ ├── get_instance.dart │ │ └── src/ │ │ ├── bindings_interface.dart │ │ ├── extension_instance.dart │ │ └── lifecycle.dart │ ├── get_navigation/ │ │ ├── get_navigation.dart │ │ └── src/ │ │ ├── bottomsheet/ │ │ │ └── bottomsheet.dart │ │ ├── dialog/ │ │ │ └── dialog_route.dart │ │ ├── extension_navigation.dart │ │ ├── root/ │ │ │ ├── get_cupertino_app.dart │ │ │ ├── get_material_app.dart │ │ │ ├── get_root.dart │ │ │ └── internacionalization.dart │ │ ├── router_report.dart │ │ ├── routes/ │ │ │ ├── circular_reveal_clipper.dart │ │ │ ├── custom_transition.dart │ │ │ ├── default_route.dart │ │ │ ├── default_transitions.dart │ │ │ ├── get_information_parser.dart │ │ │ ├── get_navigation_interface.dart │ │ │ ├── get_navigator.dart │ │ │ ├── get_route.dart │ │ │ ├── get_router_delegate.dart │ │ │ ├── get_transition_mixin.dart │ │ │ ├── index.dart │ │ │ ├── modules.dart │ │ │ ├── new_path_route.dart │ │ │ ├── observers/ │ │ │ │ └── route_observer.dart │ │ │ ├── page_settings.dart │ │ │ ├── parse_route.dart │ │ │ ├── route_middleware.dart │ │ │ ├── route_report.dart │ │ │ ├── router_outlet.dart │ │ │ ├── test_kit.dart │ │ │ ├── transitions_type.dart │ │ │ └── url_strategy/ │ │ │ ├── impl/ │ │ │ │ ├── io_url.dart │ │ │ │ ├── stub_url.dart │ │ │ │ └── web_url.dart │ │ │ └── url_strategy.dart │ │ └── snackbar/ │ │ ├── snackbar.dart │ │ └── snackbar_controller.dart │ ├── get_rx/ │ │ ├── get_rx.dart │ │ └── src/ │ │ ├── rx_stream/ │ │ │ ├── mini_stream.dart │ │ │ └── rx_stream.dart │ │ ├── rx_typedefs/ │ │ │ └── rx_typedefs.dart │ │ ├── rx_types/ │ │ │ ├── rx_core/ │ │ │ │ ├── rx_impl.dart │ │ │ │ ├── rx_interface.dart │ │ │ │ ├── rx_num.dart │ │ │ │ └── rx_string.dart │ │ │ ├── rx_iterables/ │ │ │ │ ├── rx_list.dart │ │ │ │ ├── rx_map.dart │ │ │ │ └── rx_set.dart │ │ │ └── rx_types.dart │ │ └── rx_workers/ │ │ ├── rx_workers.dart │ │ └── utils/ │ │ └── debouncer.dart │ ├── get_state_manager/ │ │ ├── get_state_manager.dart │ │ └── src/ │ │ ├── rx_flutter/ │ │ │ ├── rx_getx_widget.dart │ │ │ ├── rx_notifier.dart │ │ │ ├── rx_obx_widget.dart │ │ │ └── rx_ticket_provider_mixin.dart │ │ └── simple/ │ │ ├── get_controllers.dart │ │ ├── get_responsive.dart │ │ ├── get_state.dart │ │ ├── get_view.dart │ │ ├── get_widget_cache.dart │ │ ├── list_notifier.dart │ │ ├── mixin_builder.dart │ │ └── simple_builder.dart │ ├── get_utils/ │ │ ├── get_utils.dart │ │ └── src/ │ │ ├── equality/ │ │ │ └── equality.dart │ │ ├── extensions/ │ │ │ ├── context_extensions.dart │ │ │ ├── double_extensions.dart │ │ │ ├── duration_extensions.dart │ │ │ ├── dynamic_extensions.dart │ │ │ ├── event_loop_extensions.dart │ │ │ ├── export.dart │ │ │ ├── int_extensions.dart │ │ │ ├── internacionalization.dart │ │ │ ├── iterable_extensions.dart │ │ │ ├── num_extensions.dart │ │ │ ├── string_extensions.dart │ │ │ └── widget_extensions.dart │ │ ├── get_utils/ │ │ │ └── get_utils.dart │ │ ├── platform/ │ │ │ ├── platform.dart │ │ │ ├── platform_io.dart │ │ │ ├── platform_stub.dart │ │ │ └── platform_web.dart │ │ ├── queue/ │ │ │ └── get_queue.dart │ │ └── widgets/ │ │ └── optimized_listview.dart │ ├── instance_manager.dart │ ├── route_manager.dart │ ├── src/ │ │ └── responsive/ │ │ └── size_percent_extension.dart │ ├── state_manager.dart │ └── utils.dart ├── pubspec.yaml └── test/ ├── animations/ │ ├── extensions_test.dart │ └── get_animated_builder_test.dart ├── benchmarks/ │ └── benckmark_test.dart ├── instance/ │ ├── get_instance_test.dart │ └── util/ │ └── matcher.dart ├── internationalization/ │ └── internationalization_test.dart ├── navigation/ │ ├── bottomsheet_test.dart │ ├── dialog_test.dart │ ├── dispose_dependencies_test.dart │ ├── get_main_test.dart │ ├── middleware_test.dart │ ├── parse_route_test.dart │ ├── root_widget_test.dart │ ├── routes_test.dart │ ├── snackbar_test.dart │ └── utils/ │ └── wrapper.dart ├── rx/ │ └── rx_workers_test.dart ├── state_manager/ │ ├── get_mixin_state_test.dart │ ├── get_obx_test.dart │ ├── get_rxstate_test.dart │ └── get_state_test.dart └── utils/ ├── extensions/ │ ├── context_extensions_test.dart │ ├── double_extensions_test.dart │ ├── dynamic_extensions_test.dart │ ├── int_extensions_test.dart │ ├── num_extensions_test.dart │ ├── string_extensions_test.dart │ └── widget_extensions_test.dart ├── get_utils_test.dart ├── platform_test.dart └── platform_web_test.dart ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] patreon: # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: jonataslaw tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry liberapay: # Replace with a single Liberapay username issuehunt: # Replace with a single IssueHunt username otechie: # Replace with a single Otechie username custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report to help us improve title: '' labels: '' assignees: jonataslaw --- **ATTENTION: DO NOT USE THIS FIELD TO ASK SUPPORT QUESTIONS. USE THE PLATFORM CHANNELS FOR THIS. THIS SPACE IS DEDICATED ONLY FOR BUGS DESCRIPTION.** **Fill in the template. Issues that do not respect the model will be closed.** **Describe the bug** A clear and concise description of what the bug is. **Reproduction code NOTE: THIS IS MANDATORY, IF YOUR ISSUE DOES NOT CONTAIN IT, IT WILL BE CLOSED PRELIMINARY)** example: ```dart void main() => runApp(MaterialApp(home: Home())); class Home extends StatelessWidget { final count = 0.obs; @override Widget build(context) => Scaffold( appBar: AppBar(title: Text("counter")), body: Center( child: Obx(() => Text("$count")), ), floatingActionButton: FloatingActionButton( child: Icon(Icons.add), onPressed: () => count.value++, )); } ``` **To Reproduce** Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error **Expected behavior** A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. **Flutter Version:** Enter the version of the Flutter you are using **Getx Version:** Enter the version of the Getx you are using **Describe on which device you found the bug:** ex: Moto z2 - Android. **Minimal reproduce code** Provide a minimum reproduction code for the problem ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest an idea for this project title: '' labels: '' assignees: '' --- **ATTENTION: DO NOT USE THIS FIELD TO ASK SUPPORT QUESTIONS. USE THE PLATFORM CHANNELS FOR THIS. THIS SPACE IS DEDICATED ONLY FOR FEATURE REQUESTS** **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. ================================================ FILE: .github/pull_request_template.md ================================================ *Replace this paragraph with a description of what this PR is changing or adding, and why. Consider including before/after screenshots.* Every PR must update the corresponding documentation in the `code`, and also the readme in english with the following changes. ## Pre-launch Checklist - [ ] I updated/added relevant documentation (doc comments with `///`). - [ ] I added new tests to check the change I am making or feature I am adding, or @jonataslaw said the PR is test-exempt. - [ ] All existing and new tests are passing. ================================================ FILE: .github/workflows/main.yml ================================================ #The name of your workflow. name: build # Trigger the workflow on push or pull request on: push: branches: [master] pull_request: branches: [master] #A workflow run is made up of one or more jobs. Jobs run in parallel by default. jobs: test: #The type of machine to run the job on. [windows,macos, ubuntu , self-hosted] defaults: run: working-directory: ./ runs-on: ubuntu-latest #sequence of tasks called steps: # The branch or tag ref that triggered the workflow will be checked out. # https://github.com/actions/checkout - uses: actions/checkout@v4 # Setup a flutter environment. # https://github.com/marketplace/actions/flutter-action - uses: subosito/flutter-action@v2 with: flutter-version: "3.32.1" channel: "stable" - run: flutter pub get #- run: flutter analyze # run flutter widgets tests and unit tests - run: flutter test --coverage # Upload coverage reports to Codecov # https://github.com/marketplace/actions/codecov - uses: codecov/codecov-action@v4 ================================================ FILE: .gitignore ================================================ # See https://www.dartlang.org/guides/libraries/private-files # See https://www.dartlang.org/guides/libraries/private-files # Files and directories created by pub .dart_tool/ .packages build/ # If you're building an application, you may want to check-in your pubspec.lock pubspec.lock .pub/ # Directory created by dartdoc # If you don't generate documentation locally you can remove this line. doc/api/ # Avoid committing generated Javascript files: *.dart.js *.info.json # Produced by the --dump-info flag. *.js # When generated by dart2js. Don't specify *.js if your # project includes source files written in JavaScript. *.js_ *.js.deps *.js.map # Files and directories created when test or run example example/android/local.properties example/ios/ example/.dart_tool/ example/.packages # IntelliJ *.iml .idea/* #.idea/workspace.xml #.idea/tasks.xml #.idea/gradle.xml #.idea/assetWizardSettings.xml #.idea/dictionaries #.idea/libraries #.idea/caches # User-specific stuff .idea/**/workspace.xml .idea/**/tasks.xml .idea/**/dictionaries .idea/**/shelf # Sensitive or high-churn files .idea/**/dataSources/ .idea/**/dataSources.ids .idea/**/dataSources.local.xml .idea/**/sqlDataSources.xml .idea/**/dynamic.xml .idea/**/uiDesigner.xml .idea/**/dbnavigator.xml # Gradle .idea/**/gradle.xml .idea/**/libraries # Android Studio Navigation editor temp files .navigation/ # Android Studio captures folder captures/ # External native build folder generated in Android Studio 2.2 and later .externalNativeBuild ### https://raw.github.com/github/gitignore/80a8803b004013d17291196825a327b9e871f009/Global/VisualStudioCode.gitignore .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json example/macos/Flutter/ephemeral/Flutter-Generated.xcconfig example/macos/Flutter/ephemeral/ example/macos/Flutter/GeneratedPluginRegistrant.swift # Coverage files coverage/ ================================================ FILE: .metadata ================================================ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # # This file should be version controlled and should not be manually edited. version: revision: 60bd88df915880d23877bfc1602e8ddcf4c4dd2a channel: stable project_type: app ================================================ FILE: .vscode/launch.json ================================================ { // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "getx", "request": "launch", "type": "dart" }, { "name": "example", "cwd": "example", "request": "launch", "type": "dart" }, { "name": "example_nav2", "cwd": "example_nav2", "request": "launch", "type": "dart" }, { "name": "example_nav2 WEB", "cwd": "example_nav2", "request": "launch", "type": "dart", "deviceId": "Chrome" } ] } ================================================ FILE: .vscode/settings.json ================================================ { "cSpell.enableFiletypes": ["!markdown"] } ================================================ FILE: CHANGELOG.md ================================================ ## [5.0.0-release-candidate-9.3.2] - Fix pana score ## [5.0.0-release-candidate-9.3.1] - Fix lint errors ## [5.0.0-release-candidate-9.3.0] - Support for Flutter 3.29.0 - Remove remaining dart:html references ## [5.0.0-release-candidate-9.2.1] - Remove remaining dart:html references ## [5.0.0-release-candidate-9.2] - Remove dart:html references ## [5.0.0-release-candidate-9.1] - Add canPop to GetPage - Fix Get.offNamedUntil - Fix GetObserver ## [5.0.0-release-candidate-9] - Fix redirectDelegate middleware - Fix Get.until ## [5.0.0-release-candidate-8] - Add wasm compilation support to GetConnect - Refactor example ## [5.0.0-release-candidate-7] - Fix latest flutter version ## [5.0.0-release-candidate-6] -Fix Snackbar, upgrade to flutter 3.22 @Aniketkhote, improve parse route @korutx, fix popScope @wowbox, improve defaultDialog @leeyi, add support to wasm compilation @Ty, fix typos @CodeWithEmad, fix snackbar cancel @seungsuyoo update GetConnect @DaZealous and @ wheeOs, add bengali language @aratheunseen, fix lint issues: @MuhammamdArslanKhan ## [5.0.0-release-candidate-5] -Fix nested route issues, fixed issues in the latest flutter version ## [5.0.0-release-candidate-4] -Fix changeThemeMode and RxList ## [5.0.0-release-candidate-3] -Fix changeTheme ## [5.0.0-release-candidate-2] This version adds built-in support for animation in Flutter in an easy, clear way, and without having to create a StatefulWidget with controllers and animations. All you need to do is call the name of the animation. If you want to add a "fadeIn" effect to any widget, simply add .fadeIn() to the end of it. ```dart Container( color: Colors.blue, height: 100, width: 100, ).fadeIn(), ``` https://user-images.githubusercontent.com/35742643/221383556-075a0b71-1617-4a31-a3c7-1acc68732f59.mp4 Maybe you want to merge two or more animations, just concatenate them at the end of the widget. ```dart Container( color: Colors.blue, height: 100, width: 100, ).fadeIn().bounce(begin: -0.8, end: 0.3), ``` https://user-images.githubusercontent.com/35742643/221383613-9044c92f-7c6b-48c4-aa79-0a0c20d4068a.mp4 Creating animation sequences in Flutter is one of the most painful things to do with the framework. You need to create tons of AnimationControllers. Well, using GetX 5 you just need to tell your animation that it is sequential. Just like that. ```dart const FlutterLogo(size: 110) .bounce(begin: -0.8, end: 0.4) .fadeIn() .spin(isSequential: true) .wobble(isSequential: true, begin: 0, end: 8) .flip(isSequential: true) .fadeOut(isSequential: true), ``` Result: https://user-images.githubusercontent.com/35742643/221393968-20cb2411-516b-44a7-8b85-45090bece532.mp4 ## [5.0.0-release-candidate] Refactor StateManager, RouteManager and InstanceManager from scratch Fixed Bugs Added a Scopped DI Api now uses Navigator 2 Added new RouteOutlet Added a new futurize method to StateMixin, that tracks updates, errors, and states programatically, ## [4.6.1] Fix GetConnect on Flutter web ## [4.6.0] Add useInheritedMediaQuery to GetMaterialApp and GetCupertinoApp (@davidhole) Add Circular reveal Transition (@parmarravi) Add request to failed response (@heftekharm) Fix internationalization with only country code (@codercengiz) Add GetTickerProviderStateMixin when multiple AnimationController objects are used (@NatsuOnFire) Add the followRedirects and maxRedirects fields to the Request object (@wei53881) Fix to rx.trigger fires twice (@gslender) Add proxy setting support to GetConnect (@jtans) Fix markAsDirty used on permanent controllers (@zenalex) Update Korean readme (@dumbokim) ## [4.5.1] Fix Snackbar when it have action and icon the same time ## [4.5.0] - Big Update To have a page-agnostic snackbar, we used OverlayRoute to display a partial route. However this had several problems: 1: There was no possibility to close the page without closing the snackbar 2: Get.back() could cause problems with tests of Get.isSnackbarOpen not being properly invoked 3: Sometimes when using iOS popGesture with an open snackbar, some visual inconsistency might appear. 4: When going to another route, the snackbar was not displayed on the new page, and if the user clicked on the new route as soon as he received a Snackbar, he could not read it. We remade the Snackbar from scratch, having its Api based on Overlay, and now opening a Snackbar won't be tied to a route, you can normally navigate routes while a Snackbar is shown at the top (or bottom), and even the PopGesture of the iOS is not influenced by it. Using Get.back() is handy, it's a small command, which closes routes, dialogs, snackbars, bottomsheets, etc, however Getx 5 will prioritize code safety, and splitting will reduce the check code as well. Currently we have to check if a snackbar is open, to close the snackbar and prevent the app from going back a page, all this boilerplate code will be removed, at the cost of having what it closes in front of Get.back command. For backwards compatibility, Get.back() still works for closing routes and overlays, however two new commands have been added: Get.closeCurrentSnackbar() and Get.closeAllSnackbars(). Maybe we will have a clearer api in GetX 5, and maybe Get.back() will continue to do everything like it does today. The community will be consulted about the desired api. However version 5 will definitely have commands like: Get.closeCurrentSnackbar, Get.closeCurrentDialog etc. There is also the possibility to close a specific snackbar using the return of Get.snackbar, which will no longer return a void, and now return a SnackbarController. Snackbars now also have a Queue, and no longer stack one on top of the other, preventing viewing. GetX now has flexible, customizable, route-independent, and completely stable Snackbars. Fixed bugs where the snackbar showed an error in debug mode for a fraction of a second. We found that Flutter has a bug with blur below 0.001, so we set the minimum overlayBlur value to this value if it is ==true. Errors with internationalization were also fixed, where if you are in UK, and the app had the en_US language, you didn't have American English by default. Now, if the country code is not present, it will automatically fetch the language code before fetching a fallbackLanguage. Update locale also now returns a Future, allowing you to perform an action only when the language has already changed (@MHosssam) We are very happy to announce that GetX is now documented in Japanese as well, thanks to (@toshi-kuji) GetX has always been focused on transparency. You can tell what's going on with your app just by reading the logs on the console. However, these logs shouldn't appear in production, so it now only appears in debug mode (@maxzod) @maxzod has also started translating the docs into Arabic, we hope the documentation will be complete soon. Some remaining package logs have been moved to Get.log (@gairick-saha) RxList.removeWhere received performance optimizations (@zuvola) Optimizations in GetConnect and added the ability to modify all request items in GetConnect (@rodrigorahman) The current route could be inconsistent if a dialog were opened after a transition, fixed by @xiangzy1 Fixed try/catch case missed in socket_notifier (@ShookLyngs) Also we had fixes in the docs: @DeathGun3344 @pinguluk GetX also surpassed the incredible mark of more than 7000 likes, being the most liked package in all pub.dev, went from 99% to 100% popularity, and has more than 5.3k stars on github. Documentation is now available in 12 languages, and we're happy for all the engagement from your community. This update is a preparation update for version 5, which will be released later this year. Breaking and Depreciation: GetBar is now deprecated, use GetSnackbar instead. dismissDirection now gets a DismissDirection, making the Snackbar more customizable. ## [4.3.8] - Fix nav2 toNamed remove the route ## [4.3.7] - Fix wrong currentRoute when a route is removed - Remove take that limits the router outlet depth (@steven-spiel) ## [4.3.6] - Fix error with autodispose of additional dependencies beyond GetxController - Added ability to add your own delegate to RouterOutlet (@steven-spiel) - Added listenAndPump to Rx to give Rx the same behavior as BehaviorSubject (@steven-spiel) ## [4.3.5] - Fix GetConnect timeout (@jasonlaw) - Improve Vietnamese docs (@hp1909) - Refactor placeholder name route to unnamed routes (@roipeker). - Fix: Navigate to a page identical to Get.offNamed. - Fix: Wrong nameRoute after a route is removed - Added assert to prevent the user from starting a route name without slash. ## [4.3.4] - Improve docs ## [4.3.3] - Fix Get.reset ## [4.3.2] - Fix nullable on internacionalization (@jmguillens) - Fix nullable on Rx.stream (@steven-spiel) ## [4.3.1] - Fix controller is not removed when keyboard is open. - Improved: Safe removal and insertion of controllers. ## [4.3.0] - Added GetResponsiveWidget (@ahmednfwela) - Added `Get.replace()` (@jwelmac) - Added Improve korean doc (@sejun2) - Fix multiple middlewares redirect (@liasica) - Added gestureWidth and showCupertinoParallax to GetPage to customize cupertino transitions ## [4.2.5] - Added anchorRoute and filterPages to GetRouterOutlet (@ahmednfwela) - Added scrollBehavior and scaffoldMessengerKey to GetMaterialapp(@ejabu and @alionour) - Fix error when child on MaterialApp is null (@ahmednfwela) - Fix Korean docs (@rws08) - Fix error with onClose called before routeTransition on Get.offNamed ## [4.2.4] - Fix Get.offAll removing GetxServices from memory ## [4.2.3] - Fix back button on navigator 2 - Added parameters and arguments to Get.rootDelegate ## [4.2.1] - Remove [] from docs to try fix pub score ## [4.2.0] - Big update This update fixes important bugs as well as integrates with Navigator 2. It also adds GetRouterOutlet, similar to angular RouterOutlet thanks to @ahmednfwela. Also, the documentation translation for Vietnamese (@khangahs) has been added, making the GetX documentation available for 11 different languages, which is just fantastic for any opensource project. GetX has achieved more than 5.4k likes from the pub, and more than 4k stars on github, has videos about it with 48k on youtube, and has communities in the 4 hemispheres of the earth, besides having a large list of contributors as you see bellow. We're all happy to facilitate development with dart and flutter, and that making programming hassle-free has been taken around the world. Changes in this version: - Fix: Navigating to the same page with Get.offNamed does not delete the controller from that page using Get.lazyPut. - Fix Readme GetMiddleware typos by @nivisi - Fix url replace error by @KevinZhang19870314 - Changed response default encoding from latin1 to utf8 by @heftekharm - Add Duration in ExtensionBottomSheet by @chanonpingpong - Added compatibility with dart-lang/mockito by @lifez - Added extensions methods to convert value in percent value by @kauemurakami - Set darkTheme equal theme when darkTheme is null by @eduardoFlorence - Add padding to 'defaultDialog' by @KevinZhang19870314 - GraphQLResponse inherit Response info by @jasonlaw - Fix Redundant concatenating base url by @jasonlaw - Add content type and length into the headers when the content type is 'application/x-www-form-urlencoded' by @calvingit - Make withCredentials configurable by @jasonlaw - Fix flutter 2.0 error by @yunchiri - Allow deleting all registered instances by @lemps - Refactor/rx interface notify children @by kranfix - Fixed parameter parsing and middleware sorting by @ahmednfwela - Improvements to router outlet by @ahmednfwela - Minor improvements and bug fixes by @ahmednfwela - Adding route guards and improving navigation by @ahmednfwela - Fix RxInterface.proxy losing its previous value on exception by @WillowWisp - Added dispose() for bottomSheet. by @furkankurt - Added Pull request template by @unacorbatanegra - Fix and update documentation: @Farid566, @galaxykhh, @arslee07, @GoStaRoff, @BondarenkoArtur, @denisrudnei, @Charly6596, @nateshmbhat, @hrithikrtiwari, @Undeadlol1, @rws08, @inuyashaaa, @broccolism, @aadarshadhakalg, @ZeroMinJeon ## [4.1.4] - Adjust operator + and - to RxInt (@eduardoflorence) - Fix dark theme (@eduardoflorence) - Fix form-urlencoded on GetConnect (@aramayyes) ## [4.1.3] - Fix "Error: A value of type 'Locale?' can't be returned from a function"on flutter web (@nickwri) - Fix plural translations to expressions >1 (@WolfVic) ## [4.1.2] - Fix warning ˜can add data to a closed stream˜ when GetBuilder and Obx are nested - Fix get_connect decoder can not be null (@Goddchen) - Migrate example code (@3lB4rt0) - Fix initial value of nullables (@RafaRuiz) - Improve error message to navigation (@maxzod) - Fix typo on docs (@Rahulshahare) - Fixed darktheme being changed only through Get.changeTheme and not through the DarkTheme theme property in MaterialApp (@GoldenSoju) - Fix controller is removed when navigate to same page (@eduardoflorence) - Fix missing reload() and reloadAll() to Get extensions (@lkloon123) ## [4.1.1] - Remove mandatory initialValue to nullables types ## [4.1.0] - Added Rxn to non nullables reactives types ## [4.0.3] - Added new linter rules to improve score ## [4.0.2] - Removed "!" of if else conditions until the null-safety of the dart is consistent for using it. ## [4.0.1] - Fix changelog ## [4.0.0] - Added append function to StateMixin. Now is possible track loading, success and error handle of your application with ONE LINE OF CODE. Ex: append(()=> api.getUser); - Migrate to null-safety - Added ScrollMixin to controllers - Added loadingMore status to RxStatus - Fix content-type qual null (@katekko) - Made GetInstance non nullable (@eduardoflorence) - Fix multi-parameters url (@iMrLopez) - Fix Expected value of SkDeletable error (@obadajasm) - Added triggers, an Rx method that triggers events, even if they are the same as the previous event (@RafaRuiz) - Improve docs: (@CNAD666), (@dhhAndroid), (@Jackylee1992), Switching to null-safety: You can continue using GetX as normal, with as little breaking changes as possible. It is still possible to declare the var.obs variable, and this remains the preferred way, forcing null-safety and giving you all the security that sound null-safety delivers to your app. However, if you need to use null, we also provide a solution for you. Declare the variables with `?` Ex: `final Rx count = 0.obs`. You can also use custom Rxn types with null-safety: `RxInt` == not nullable `RxnInt` == nullable. ## [3.25.6] - Added documentation in French (@kamazoun) - Fix logs messages (@damphat) - Fix plural to zero on internacionalization (@RafaRuiz) - Fix error when body hasn't content on GetConnect (@jasonlaw) - Fix typos on readme (@bashleigh) - Fix group updates to GetBuilder ## [3.25.5] - Fix Get.isDialogOpen when two or more open dialogs are closed ## [3.25.4] - Added logs and tests to unknownRoute ## [3.25.3] - Fix bindStream error 'Object.noSuchMethod'. ## [3.25.2] - Improved Workers system to accept a list of works ## [3.25.1] - Improved the log system to display the tag used in the controller that was created. ## [3.25.0] - Big update - Added [reload] and [reloadAll] methods to reload your Controller to original values - Added [FullLifeCycleController] - A GetxController capable of observing all the life cycles of your application. FullLifeCycleController has the life cycles: - onInit: called when the controller enters the application's memory - onReady: called after onInit, when build method from widget relationed to controller is done. - onClose: called when controller is deleted from memory. - onPaused: called when the application is not currently visible to the user, and running in the background. - onInactive: called when the application is in an inactive state and is not receiving user input, when the user receives a call, for example - onResumed: The application is now visible and in the foreground - onDetached: The application is still hosted on a flutter engine but is detached from any host views. - didChangeMetrics: called when the window size is changed - Added SuperController, a complete life circle controller with StateMixin - Improve Iterable Rx Api. Now, you can to use dart List, Map and Set as reactive, like: List names = ['juan', 'pedro', 'maria'].obs; - Added assign and assignAll extensions to default dart List - Added parameters options from Get.toNamed, Get.offNamed, and Get.offAllNamed (@enghitalo) - Improve Rx disposal logic to completely prevent memory leaks - Improve Capitalize methods from GetUtils (@eduardoflorence) - Prevent a close snackbar from close a Screen with double tap (@eduardoflorence) - Includes GetLifeCycleBase mixin on delete/dispose (@saviogrossi) - Added internacionalization example to sample app (@rodriguesJeff) - Added headers to Graphql query and mutation(@asalvi0) - Added translation with parameter extension (@CpdnCristiano) - Added Get.parameter access to Middleware (@eduardoflorence) - Fix RxBool typo (@emanuelmutschlechner) - Added Filter to GetBuilder - Added debouce to GetBuilder update - Added ability to insert an Enum, class, or type of an object as a GetBuilder's Id - Improve upload time from GetConnect - Create minified version to DartPad(@roipeker) - Suggested to use `Get.to(() => Page())` instead of `Get.to(Page())`. - Added more status codes to GetConnect (@romavic) - Fix and improve docs: @unacorbatanegra, @lsm, @nivisi, @ThinkDigitalSoftware, @martwozniak, @UsamaElgendy, @@DominusKelvin, @jintak0401, @goondeal ## [3.24.0] - GetWidget has been completely redesigned. Throughout its lifetime, GetWidget has always been mentioned in the documentation as "something you shouldn't use unless you're sure you need it", and it had a very small use case. A short time ago we realized that it could have some unexpected behaviors, when compared to GetView, so we decided to rebuild it from scratch, creating a really useful widget for the ecosystem. Objectively, GetWidget is now a Widget that caches the controller and protects children from their parents' reconstructions. This means that if you have a ListView or gridview, you can add items to it without the child (being a GetWidget) being rebuilt. The api is now more concise, as you can use Get.put / Get.lazyput for global dependencies, and Get.create with GetWidget for ephemeral dependencies, or when you need several identical controllers for the same widget, eliminating the need for tags for most cases. - Workers now have error handlers, so if an error occurs in your stream, you can recover it from your workers. - `isTrue` and `isFalse` setters were added to [RxBool], this will make the code more readable, and will mitigate the use of ".value" in Booleans. - [Patch] method was added in GetConnect. - Native methods for RxString (trim, contains, startWith, etc.) have been added. - Standard constructors for RxList and RxMap have been added (RxList.generate, RxList.from, Map.of, Map.from, etc). - Added "onEmpty" status in StateMixin (@alizera) - Added query and mutation methods of graphql for getconnect. - Added body string for content-type application/x-www-form-urlencoded on GetConnect (@eduardoflorence) ## [3.23.1] - Fix allowSelfSigned on Flutter web ## [3.23.0] - Add GetResponsive (@SchabanBo) - Update tests, fix predicate for offNamedUntil (@vbuberen) - Added Urdu Version for Pakistani Developers (@UsamaSarwar) - Handle for List field with native datatype on GetConnect(@jasonlaw) - Added WillPopScope to defaultDialog (@rakeshlanjewar) - Fix optional query params not attach on createUri from GetConnect (@reinaldowebdev) - Effective Get.testMode from navigator on tests (@eduardoflorence) - Fix Navigator 2.0 on GetMaterialApp and CupertinoMaterialApp (@SchabanBo) - Added Middlewares with initial Routes (@SchabanBo) - Improve PT-br Docs (@eduardoflorence) - Added the allowSelfSigned parameter to GetSocket(@eduardoflorence) - Added Indonesian version to Indonesian Developers (@pratamatama) ## [3.22.2] - Fix overlayEntries is null on Master/Dev branch of Flutter ## [3.22.1] - Improve: auto jsonDecode occurs only if response.header.contentType is "application/json" - Improve and fix requests types (@eduardoflorence) - Fix HeaderValue variables with same name (@haidang93) ## [3.22.0] - Added: more multipart options. Now you can send as multipart: File: 'file':MultipartFile(File('./images/avatar.png'), filename: 'avatar.png'), String path: 'file':MultipartFile('./images/avatar.png', filename: 'avatar.png'), Or bytes (Flutter web work only with bytes): 'file':MultipartFile(File('file').readAsBytesSync(), filename: 'avatar.png'), - Added: Upload Progress to MultipartRequest - Added support to List (@jasonlaw) ## [3.21.3] - Improve multipart file and defaultDecoder on GetConnect ## [3.21.2] - Fix GetConnect.request returning a PUT request ## [3.21.1] - Allow null body to POST method on GetConnect ## [3.21.0] - Big update - This update attaches two nice features developed by (@SchabanBo): _GetPage Children_ And _GetMiddleware_ In previous versions, to create child pages, you should do something like: ```dart GetPage( name: '/home', page: () => HomeView(), binding: HomeBinding(), ), GetPage( name: '/home/products', page: () => ProductsView(), binding: ProductsBinding(), ), GetPage( name: '/home/products/electronics', page: () => ElectronicsView(), binding: ElectronicsBinding(), ), ``` Although the feature works well, it could be improved in several ways: 1- If you had many pages, the page file could become huge and difficult to read. Besides, it was difficult to know which page was the daughter of which module. 2- It was not possible to delegate the function of naming routes to a subroutine file. With this update, it is possible to create a declarative structure, very similar to the Flutter widget tree for your route, which might look like this: ```dart GetPage( name: '/home', page: () => HomeView(), binding: HomeBinding(), children: [ GetPage( name: '/products', page: () => ProductsView(), binding: ProductsBinding(), children: [ GetPage( name: '/electronics', page: () => ElectronicsView(), binding: ElectronicsBinding(), ), ], ), ], ); ``` Thus, when accessing the url: '/home/products/electronics' Or use Get.toNamed('/home/products/electronics') it will go directly to the page [ElectronicsView], because the child pages, automatically inherit the name of the ancestral page, so _with any small change on any father in the tree all children will be updated._ If you change [/products] to [/accessories], you don't nesse update on all child links. However, the most powerful feature of this version is _GetMiddlewares_. The GetPage has now new property that takes a list of GetMiddleWare than can perform actions and run them in the specific order. ### Priority The Order of the Middlewares to run can pe set by the priority in the GetMiddleware. ```dart final middlewares = [ GetMiddleware(priority: 2), GetMiddleware(priority: 5), GetMiddleware(priority: 4), GetMiddleware(priority: -8), ]; ``` those middlewares will be run in this order **-8 => 2 => 4 => 5** ### Redirect This function will be called when the page of the called route is being searched for. It takes RouteSettings as a result to redirect to. Or give it null and there will be no redirecting. ```dart GetPage redirect( ) { final authService = Get.find(); return authService.authed.value ? null : RouteSettings(name: '/login') } ``` ### onPageCalled This function will be called when this Page is called before anything created you can use it to change something about the page or give it new page ```dart GetPage onPageCalled(GetPage page) { final authService = Get.find(); return page.copyWith(title: 'Welcome ${authService.UserName}'); } ``` ### OnBindingsStart This function will be called right before the Bindings are initialize. Here you can change Bindings for this page. ```dart List onBindingsStart(List bindings) { final authService = Get.find(); if (authService.isAdmin) { bindings.add(AdminBinding()); } return bindings; } ``` ### OnPageBuildStart This function will be called right after the Bindings are initialize. Here you can do something after that you created the bindings and before creating the page widget. ```dart GetPageBuilder onPageBuildStart(GetPageBuilder page) { print('bindings are ready'); return page; } ``` ### OnPageBuilt This function will be called right after the GetPage.page function is called and will give you the result of the function. and take the widget that will be showed. ### OnPageDispose This function will be called right after disposing all the related objects (Controllers, views, ...) of the page. ## [3.20.1] - Fix wrong reference with unnamed routes and added more tests ## [3.20.0] - Big update - Added GetConnect. * GetConnect is an easy way to communicate from your back to your front. With it you can: * Communicate through websockets * Send messages and events via websockets. * Listen to messages and events via websockets. * Make http requests (GET, PUT, POST, DELETE). * Add request modifiers (like attaching a token to each request made). * Add answer modifiers (how to change a value field whenever the answer arrives) * Add an authenticator, if the answer is 401, you can configure the renewal of your JWT, for example, and then it will again make the http request. * Set the number of attempts for the authenticator * Define a baseUrl for all requests * Define a standard encoder for your Model. * Note1: You will never need to use jsonEncoder. It will always be called automatically with each request. If you define an encoder for your model, it will return the instance of your model class ALREADY FILLED with server data. * Note2: all requests are safety, you do not need to insert try / catch in requests. It will always return a response. In case of an error code, Response.hasError will return true. The error code will always be returned, unless the error was a connection error, which will be returned Response.hasError, but with error code null. * These are relatively new features, and also inserted in separate containers. You don't have to use it if you don't want to. As it is relatively new, some functions, such as specific http methods, may be missing. - Translation to Korean (@rws08) - Fix Overlays state (@eduardoflorence) - Update chinese docs (@jonahzheng) - Added context.isDarkMode to context extensions ## [3.17.1] - Allow list.assignAll, map.assignAll and set.assignAll operate with null values ## [3.17.0] - Added GetCupertinoApp - Added initial suport to navigator 2.0 ## [3.16.2] - Clean RxList, RxMap and RxSet implementation - Now when declaring an `RxList()`, it will be started empty. If you want to start a null RxList, you must use `RxList(null)`. Improved GetStream to receive the same parameters as the StreamController, such as `onListen`, `onPause`, `onResume` and `onCancel`. - Improve docs ## [3.16.1] - Fix compilation error on master ## [3.16.0] - Documentation translated into Russian language. (@Renat Fakhrutdinov, @Doaxan and @BatttA) - Added error message callback for StateMixin (@eduardoflorence) - Fix incorrect Get.reference when pop route (@4mb1t) - Added Uppercase/Capital letter on GetUtils (@AleFachini) - Redraw the Streams api to use GetStream instead of StreamControllers. Why this change? Dart provides a Streams API that is really rich. However, asynchronous streams add extra latency to ensure that events are delivered in the exact order. It is not yet known whether this latency has any performance impact in mobile applications, and probably not, however, as GetX is also a server-side framework, we need to have the lowest latency at all, since our base is shared. Dart also has a Synchronous Streams api that has very low latency, however, it is not suitable for use in state management for two reasons: 1- Synchronous Streams can only have one listen (see the issue opened by Hixie on dart lang for reference: https://github.com/dart-lang/sdk/issues/22240). This means that we cannot use this api for more than one listener, which is the basis of global state management, where we aim to change the state of more than one location. You can test this with this simple snippet: ```dart void main() { var controller = StreamController(sync: true); var stream = controller.stream; stream.listen((data) { print('$data'); if (data == 'test4') controller.add('test5'); }); print('test1'); controller.add('test2'); stream.listen((event) {}); // second listen throws a exception print('test3'); controller.add('test4'); print('test6'); controller.add('test7'); print("test8"); } ``` 2- Even with a single listener, the dart's Synchronous Streams api cannot deliver events in the exact order. We plan to work on a PR in the future at dart-lang to address this. So if we remove the line above that causes the exception, we will have the following output in the log: ```dart void main() { var controller = StreamController(sync: true); var stream = controller.stream; stream.listen((data) { print('$data'); if (data == 'test4') controller.add('test5'); }); print('test1'); controller.add('test2'); // stream.listen((event) {}); // second listen throws a exception print('test3'); controller.add('test4'); print('test6'); controller.add('test7'); print("test8"); } ///////////////////// log: test1 test2 test3 test4 test6 test8 test5 ``` As we can see, test 4 skips to test 6, which skips to test 8, which skips to test 5. Note that test 7 didn't even appear in the log. However, if we work with GetStream, everything works as expected: ```dart void main() { var controller = GetStream(); var stream = controller.stream; stream.listen((data) { print('$data'); if (data == 'test4') controller.add('test5'); }); print('test1'); controller.add('test2'); // stream.listen((event) {}); // second listen throws a exception print('test3'); controller.add('test4'); print('test6'); controller.add('test7'); print("test8"); } ///////////////////// log: test1 test2 test3 test4 test5 test6 test7 test8 ``` The dart documentation is clear that this api should be used with caution, and in view of these tests, we were sure that it is not stable enough to be used as the core of our state management, nor of the websockets notifications and get_server requests. Clarification about the controversy over benchmarks: In a version prior to changeLog, we talked about the 9000% difference in performance between Streams, and GetStreams that ended up causing a lot of controversy in the community. Initially, we would like to clarify that this does not mean that you will have mobile applications 9000% faster. Only that one of our main resources, showed itself with a high rate of requests, 9000% faster than using traditional streams. In a real world scenario, you will hardly have so many simultaneous requests. Skia renders frames on new devices at up to 120fps. This means that if you have a 10 second animation, you will have 1200 reconstructions. Unless you are working with animations, or something that requires rendering at the skia boundary, you won't need that much power. So this does not mean that we are revolutionizing the mobile world, only that we have created an alternative to Stream Sincronas, which works as expected, and which has satisfactory performance for low latency resources. The benchmarks are real, but that does not mean that you will have mobile applications 9000% faster, but that you have a new feature that performs at this level to use. For reference only, the benchmark can be found ([HERE](https://github.com/jonataslaw/getx/blob/master/test/benchmarks/benckmark_test.dart)) In short: asynchronous streams from dart work perfectly, but add a latency that we want to remove on Get_server. Synchronous dart streams have unexpected behaviors, cannot have more than 1 listener and do not deliver events in the correct order, which completely prevents their use in mobile state managements, since you run the risk of displaying data on the wrong screen, since the last event will not always be the last event entered by the sink. The 9000% figures are real, however, they refer to the gross performance between Streams and GetStreams. This does not mean that this number will impact your applications, because you are unlikely to use all of that power. ## [3.15.0] - Big update - **Improve Performance**: We made modifications to make GetBuilder even faster. We have improved the structure behind it so that listeners are notified faster. Perhaps in version 4.0 everything will be based on this new structure, but maintaining the power and compatibility with streams. If you want to know how much Getx is faster than pure streams or ChangeNotifier (even after the last update using LinkedList), you can create run the repository tests at: (https://github.com/jonataslaw/getx/blob/master/test/benchmarks/benckmark_test.dart) - **Added StateMixin** StateMixin allows you to change the state of the controller, and display a loading, an error message, or a widget you want with 0 boilerplate. This makes things like API/Rest communication or websocket absurdly simple, and it's a real revolution in how state management has behaved so far. You no longer need to have a ternary in your code, and you don't need a widget like FutureBuilder, StreamBuilder or even Obx/GetBuilder to encompass your Visibility. This will change with the way you manage the state of your controllers, decrease your boilerplate absurdly, and give you more security in your code. - **Added GetNotifier** GetNotifier is a super and powerful ValueNotifier, which in addition to having the life cycle of the controllers, is extremely fast, and can manage a single state, as a simplified immutable state management solution. In theory, the only difference between it and GetxController is the possibility of setting an initial value in the constructor's super (exactly as ValueNotifier does). If the initial value is null, use GetxController. If you need a starting value, GetNotifier can be more useful and have less boilerplate, but both serve the same purpose: to decouple your visualization layer from your presentation logic. - Other Fixes and improvements: - Fixed GetxController is closed twice when smartManagement.full is turn on - Fixed phone number validation - Fixed some inconsistencies in GetWidget and the life cycle of controllers - It made controller testing completely safe with navigation. - Improve docs (@eduardoflorence) - Improve security types on routes (@unacorbatanegra) - Improve code structure with less duplicate code: (@kranfix) - Fix named route erroring when route does not exist (@FiercestT) ## [3.13.2] - Reunification of the package. During the 2 week period, we try to keep this package as a compilation of smaller packages. We were successful in separating, getx is well decoupled and it was only necessary to send the internal folders as packages to pub.dev, however, it became very complicated to contribute to the package. This is because it was necessary to clone the repository, replace all pubspec packages with local paths, and after modification, return the original paths to do the PR. With that, the frequency of updates, which was about 4 to 5 days, became almost 2 weeks, and this is not legal for a community as active as Getx, which uses this package precisely in addition to being modern and performance, be constantly improving. This led contributors to the conclusion that getx works best together. Additional packages will continue to be maintained, and will have the same base as the main package, however, development will take place in the full and main package, and as the addition of new features or bug fixes arrives, we will migrate to the individual packages . Getx reached the mark of 50 contributors today, more than 1500 likes in the pub, and will continue to make development easy. ## [3.13.1] - Remove spaces whitespaces from dart files - ## [3.13.0] - Fix typos on code and docs (@wbemanuel and @Goddchen) - Improve: typedef to GetBuilder and Getx widgets - Improve behaviour of null route on lastest flutter version (@FiercestT) - Fix onReady called twice on smartManagement.onlyBuilders - Fix onClose called twice when GetBuilder is used - Fix default customTransitions, and defaultDuration be ignored on unnamedRoutes - Transition.native use default Flutter transitions - Added Get.testMode to use contextless elements on unit tests - Added Get.appUpdate and improve Get.forceAppUpdate ## [3.12.1] - Remove spaces whitespaces from dart files ## [3.12.0] - Added BottomSheet Duration && Export SingleGetTickerProvider (@unacorbatanegra) - Improve docs from dependencies management (@ngxingyu) - Fix unknownRoute with null Custom Transition (@marcosfons) - Optimize capitalize method (@zl910627) - Added Chinese documentation (@idootop) - Added TextDirection property on GetMaterialApp to improve RTL layout (@justkawal) - Remove unnecessary files on git (@nipodemos) - Fix tags on Get.create() and GetWidget() (@roipeker) - Update mockito dependency on getTests - Added GetStatelessWidget, a StatelessWidget base to GetWidget with lifecycle control of controllers. Note: It's a base class, you don't need change to use it or change your GetView, GetWidget StatelessWidget to It. ## [3.11.1] - Fix docs ## [3.11.0] - Refactor structure from scratch to split GetX completely into separate packages. When using the main package (get) you will have everything working perfectly together. However, if you only want one of the resources, you can use the packages separately. - Improve Rx types - Added RTL support - Added GetTests, a set of tools to help you create unit tests using Getx - RAM consumption improved by dividing resources into smaller components, preventing related classes that are unnecessary from being loaded - Fix example app (missing activity) (@Grohden) - Added Get.create() lifecycle (@roipeker) - Added section Contribution videos and articles in Readme (@stefandevo) - fix isNullOrBlank extension - Added all operators overload (@grohden) - Fixes subscription for Rx::bindStream (@roipeker) - Added Ability to use tags with GetX widgets (@na2axl) - Change Arguments from Object to dynamic (@roipeker) - Added Persistent bottomsheet (@mohak852) - Improve extensions tests (@Nipodemos) - Refactor Route Observer (@grohden) - Added print extensions (@unacorbatanegra) - Update PT-br Readme (@eduardoflorence) - Fix analyzer crash (@eduardoflorence) - Fix for switch types usages in GetUtils (@grohden) - Improvement: RxList, RxSet and RxMap null check in the constructor (@Hitsu91) - Improve readme example (@dafinoer) ## [3.10.2] - Fixed the use of tags with lazyPut and added Ability to overwrite "tag" in GetView and GetWidget. ## [3.10.1] - Fix analyzer ## [3.10.0] Getx 3.10 released with CLI and Get Server. - Added: analyser + effective dart (@Grohden) - Added TextStyle to generalDialog title and message (@roipeker) - renamed and added defaults transition duration and types in "GetInterface" (@roipeker) - added missing parameters in Get.to/Get.offAll (@roipeker) - added optional transitionDuration and transitionCurve to Get.dialog() (@roipeker) - Changed HashMap to HashSet and allow update IDs groups on GetBuilder (@roipeker) - Added a internal VoidCallback in GetStateUpdaterMixin::getUpdate (@roipeker) - Added Curve property to routes (@roipeker) - Improve docs, code cleanup, new GetStateUpdaterMixin and GetStateUpdate in favour of StateSetter on GetxController, GetBuilder, SimpleBuilder. (@roipeker) - Added RxBool.toggle() as an easy shortcut for switching true/false values. (@roipeker) - Added \_RxImp.nil() to easily set the value to null (@roipeker) - Added missing docs to Rx classes. (@roipeker) - Added Get.delete(force:false) to Get extensions (@roipeker) - Added Docs and comments (@nipodemos) - Added docs to PT-br and fix typos (@eduardoflorence) - Cleanup route code (@justkawal) - Extension to facilitate insert widgets inside a CustomScrollView (@alexkharech) - Fix docs .obs examples (@kai-oswald) - Added tag capability to GetView - Improve code separation of RouteManagement and Internacionalization ## [3.8.0] - Added: Snackbar Status: Open, Opening, Closing and Closed example: ```dart Get.snackbar('title', 'message', snackbarStatus: (status) { if (status == SnackbarStatus.CLOSED) { // made anything } }); ``` ## [3.7.0] - Added: RxSet. Sets can now also be reactive. - Added isDesktop/isMobile (@roipeker) - Improve GetPlatform: It is now possible to know which device the user is using if GetPlatform.isWeb is true. context.responsiveValue used device orientation based on web and non-web applications. Now it checks if it is a desktop application (web or desktop application) to do the responsiveness calculation. (@roipeker) - Change: The documentation previously stated that Iterables should not access the ".value" property. However, many users did not pay attention to this fact, and ended up generating unnecessary issues and bugs in their application. In this version, we focus on code security. Now ".value" is protected, so it cannot be accessed externally by Lists, Maps or Sets. - Change: Observable lists are now Dart Lists. There is no difference in your use: `RxList list = [].obs;` And you use `List list = [].obs;` - Change: You do not need to access the ".value" property of primitives. For Strings you need interpolation. For num, int, double, you will have the normal operators, and use it as dart types. This way, `.value` can be used exclusively in ModelClasses. Example: ```dart var name = "Jonny" .obs; // usage: Text ("$name"); var count = 0.obs; // usage: increment() => count ++; Text("$count"); ``` Thus: List, Map, Set, num, int, double and String, as of this release, will no longer use the .value property. NOTE: The changes were not break changes, however, you may have missed the details of the documentation, so if you faced the message: "The member 'value' can only be used within instance members of subclasses of 'rx_list.dart' "you just need to remove the" .value "property from your list, and everything will work as planned. The same goes for Maps and Sets. ## [3.6.2] - Fix more formatting issues ## [3.6.1] - Fix formatting issues ## [3.6.0] - Added RxSet - Change default logger to developer.log (@jorgegaticav) - Added BindingsBuilder, ValueBuilder, and ObxValue (@roipeker) - Fix fallback locale not working if missing country code (@thaihuynhxyz) - Fix validation of email ".com.br" ## [3.5.1] - Remove unnecessary whitespaces ## [3.5.0] - Added logwritter (@stefandevo) - Added responsiveValue (@juanjoseleca) - Fixed ghost url for snackbar, bottomsheets, and dialogs and unnamed navigation. ## [3.4.6] - Fix TextField dispose throw on last Flutter hotfix ## [3.4.5] - Fix typo on RxList.remove that could cause type errors. - Remove initialization console print ## [3.4.4] - Fix exception 'isInit called null' when tags are used in conjunction with dependencies. (@djade007) - Fix typos (@tiagocpeixoto) ## [3.4.3] - Fix onInit fired only first time - Fix language callback(@lundin) - Fix docs (@nipodemos) ## [3.4.2] - Fix individual imports ## [3.4.1] - Structure organization, and improvements. ## [3.4.0] - Added '[everAll]' Worker: Listen a List of '.obx' - Added Workers dispose - Fix transition.noTransition - Fix TextField and VideoPlayController dispose before transition animation ## [3.3.0] - Fix extensions (@stefandevo) - Added CPF to utils options (@kauemurakami) - Added fenix mode to Get.lazyPut. Use `Get.lazyPut(()=> Controller(), fenix:true)` to have a controller that after being destroyed, has the ability to be recreated in case someone needs it. This is a function that already exists in smartManagement.keepFactory which is now also possible in full mode. - Fix native transition on android ## [3.2.2] - Improve transitions and refactor route system ## [3.2.1] - Prevent black blackground on cupertino fullscreenDialog ## [3.2.0] - Improve GetBuilder ram usage - Added method update to Rx Now you no longer need to make an entire class reactive to get an element update from it, you can simply call the update method of its instance, like this: ```dart class User{ User(this.name = '', this.age = 0); String name; int age; } final user = User().obs; Obx(()=> Text("Name ${user.value.name}: Age: ${user.value.age}")) // To update: user.update((user){ user.name = 'Jonny'; user.age = 18; }); ``` Now is also possible to access a value without using the ".value". Just open and close parentheses. In the previous example, you could do: ```dart user().name; // before: user.value.name ``` And it is also possible to set a value without using the value, inserting the value directly into the variable. ```dart user(User('João', 35)); // before: user.value = User('João', 35) ``` Added fenix mode to Get.lazyPut. ## [3.1.4] - Update readme banner ## [3.1.3] - Activate unknownRoute on version 3 - Go back transitions.size and transitions.cupertino ## [3.1.2] - Expose GetInstance ## [3.1.1] - Improvement .obs methods ## [3.1.0] - Added extensions to GetUtils and fix typo on GetUtils.isEmail (@stefandevo) - Added .gitignore file (@hdeyana) ## [3.0.1] - Breaking changes on Rx api and GetController and RxController were merged, and now you only have the 'GetxController' - Refactor routing system. Now you can add custom transitions and more - Improved the use of dynamic routes, you can now define two different pages according to your arguments. - Added GetView widget - Added internacionalization - Added validations - Added Get queqe - Added GetStorage (with separated package) - Minor bug fixes. ## [2.14.0] - Added getPages API. - Deprecated namedPages - Fix default transition - Added Duration on Get.offAll(@kluverua) ## [2.13.1] - Added sort to ListX - Prepared the framework for version 3 ## [2.13.0] - Added Get.focusScope ## [2.13.0] - Update docs - Fix Bindings list on GetPageRoute ## [2.12.5] - Update readme ## [2.12.4] - Prevent exceptions on onReady with nullables ## [2.12.3] - Fix List lenght == null ## [2.12.2] - Fix Workers ## [2.12.1] - Added: onReady on Controllers LifeCycle - Added: Observable maps - Refactor: observable variables that now consume even less RAM. ## [2.11.3] - Type parameters and added docs ## [2.11.2] - Added docs - Improvement performance of Obx ## [2.11.1] - Fixed: oninit calling only once. ## [2.11.0] - Added Permissions: You can now revoke permissions to SmartManagement so that it cannot delete a particular controller. Add to Get.put (Controller(), permanent: true); to make it indelible. Get.lazyPut() will not receive this resource. Initially he had it, but we saw in internal tests that it could cause problems with the bindings API. Bindings were created to initialize and delete an instance, if it were allowed to make a controller started with lazyPut permanent, copies of that Controller would be created every time Binding was called. For the safety of users, especially new users who could easily do this, it was decided that this feature will only be present in Get.put. - Improve: Now a controller's life cycle has no connection with the View life cycle. It is no longer called internally in an "initState", it is now called when the Controller enters memory. This means that now onInit will always be called, regardless of where you started your dependency. - removed: this property of the update() method has been permanently removed. ## [2.10.3] - GetBuilder refactor. 11% reduction in RAM consumption and 2% in CPU consumption for the sample application. (using as base Flutter for linux desktop). - The "this" property of the "update" method has been deprecated and will be removed in the next update. Please don't use it anymore. Just use "update()" now. ## [2.10.2] - Fix Get.generalDialog default options ## [2.10.1] - Fix broken links on pub - Fix List empty error ## [2.10.0] - Added SmartManagement, your application's memory is managed intelligently like never before! - Added Obx, a widget that knows when to rebuild a child, without needing any type. - Added MixinBuilder - If you need to use GetBuilder in conjunction with GetX, use GetxController with this widget, and the changes will occur either using update (this) or changing some reactive variable. Use only if necessary, for better RAM consumption, prefer widgets in that order: Obx => GetX => GetBuilder => MixinBuilder. Obx is the lightest of all, and MixinBuilder is a mix of the other 3, whenever possible, use the specific widget. - Refactor: StateManager of Get. - Changed: full List API refactor, now value is no longer needed. - Added Workers: You can hear changes to a variable and trigger custom callbacks. - Added Bindings API docs. - Added Portuguese language to readme(@Nipodemos) # [2.7.1] - Improve list to set and get methods ## [2.7.0] - Added obx, a simple state interceptor. - Improve Bindings, ListX, and - fix docs typos e broken code (@ghprod) ## [2.6.3] - Flutter currently has a problem on some devices where using showModalBottomSheet() can cause TextFields to be hidden behind the keyboard (https://github.com/flutter/flutter/issues/18564) this issue is closed, even users reporting that the problem still occurs. The problem happens casually, as well as the problem of the snackbar on the iPhone SE 2, and checking the code, I realized that a padding with MediaQuery.of(context).viewInsets.bottom is missing inside the bottomSheet to make it work correctly, since it does not have any constraint with the keyboard. For stability, I decided not to use the standard Flutter bottomSheet, which contains many bugs, mainly related to keyboard padding, and the lack of respect for topBar's safeArea, and to use a proprietary bottomSheet implementation that is more stable. The Flutter dialog has no problem, so it will be used as the basis for Get.dialog. The bottomSheet will be based on the Flutter bottomSheet Raw API (\_ModalBottomSheetRoute), applying bug fixes. - Added Get.isSnackbarOpen tests ## [2.6.2] - Refactor Bindings API ## [2.6.1] - Expose Bindings API ## [2.6.0] - Added bindings. You can now add bindings from your controllers to your routes, to prepare GetBuilder or GetX to create a dependency already declared in a Binding class. This feature is in an experimental phase, and will not be documented until the end of the tests. ## [2.5.10] - Removed remnants of previousArgs on routeObserver. This feature had been deprecated in previous updates, and was removed in version 2.5.8. Some remaining references on the routeObserver were causing exceptions in version 2.5.9, and were removed completely in version 2.5.10. ## [2.5.9] - Fix Get.find with named instance ## [2.5.8] - Added docs - Added tests(@chimon2000) ## [2.5.7] - Fix Get.generalDialog optionals - Added GetX onInit support ## [2.5.6] - GetBuilder refactor to work with lazyPut. Now you can list your controllers in advance with Get.lazyPut, and only when it is called for the first time will it be relocated in memory. - Fix english typos(@gumbarros) ## [2.5.5] - Fix arguments broken by new methods ## [2.5.4] - Refactor methods ## [2.5.3] - Fix snackbar padding on iPhone SE 2. - Added themes docs - Added ThemeMode (@RodBr) ## [2.5.2] - Fix: key not found when Get.key is used with no MaterialApp ## [2.5.1] - Improve - GetBuilder uses 18% less ram on more of 20 controllers. ## [2.5.0] - Added List.obs - Now you can transform any class on obs ## [2.4.0] - Added GetX, state manager rxDart based. - Fix error on add for non global controllers ## [2.3.2] - Fix close method called on not root GetBuilder ## [2.3.1] - Auto close stream inside close method - Added docs ## [2.3.0] - Added interface to GetX support ## [2.2.8] - Added api to platform brightness ## [2.2.7] - Fix typos ## [2.2.6] - Fix cancel button on defaultDialog don't appear when widget implementation usage ## [2.2.5] - Refator defaultDialog ## [2.2.4] - Clean code - Fix Get.LazyPut ## [2.2.3] - Remove defaultDialog type ## [2.2.2] - Fix GetRoute not found ## [2.2.1] - Improve lazyPut and fix tag to lazyput(@rochadaniel) ## [2.2.0] - Added: Ability to choose or delay a widget's state change according to its ID. - Added: Ability to fire triggers when loading materialApp. - Added: Ability to change theme dynamically. - Added: Ability to rebuild the entire app with one command. - Added: Ability to trigger events on the MaterialApp. - Added: Get.lazyPut (lazy loading of dependencies). - Added: Get.creator - a factory of dependencies . - Added: Capability of define abstract class on dependencies. ## [2.1.2] - Get.defaultDialog refactor ## [2.1.1] - fix typo ## [2.1.0] - Added Get.rawSnackbar - Added instantInit config to snackbars - Refactor Get Instance Manager - Improved performance and bug fix to Get State Manager - Improved performance of GetRoute on namedRoutes - Hotfix on namedRoutes ## [2.0.10] - Bump new Flutter version - Added Get.generalDialog ## [2.0.6] - Fix typo on readme ## [2.0.5] - Changing the bottomsheet API to comply with the documentation. ## [2.0.4] - Fix type not found in some versions of Flutter stable ## [2.0.3] - Update Docs ## [2.0.2] - Update GetObserver ## [2.0.1] - Fix docs and typos ## [2.0.0] - Added easy state manager - Change dialog API - Added GetMaterialApp - Added new experimental APIs - Improve Observer - Added default duration on Transitions - Added new routeNamed sistem - Added Global stateManager config - Improve Get instance manager - Added routingCallback - Added closeOverlays to Get.back - Added dynamic urls - Cleaner code - Improve lib performance - Many others minor APIs added ## [1.20.1] - Improve: Get.finds ## [1.20.0] - Added Get Instance Manager Get.put / Get.find / Get.delete ## [1.19.1] - Fix default transitions for namedRoutes ## [1.19.0] - Added nested navigators ## [1.18.0] - Added SafeArea to bottomsheets - Added docs ## [1.17.0] - Added experimental APIs ## [1.16.1] - Improve: GetObserver ## [1.16.0-dev] - Added Get config - Added logEnable - Added Default transition - Added default popGesture behaviour - Added overlayContext - Fix Duration transition ## [1.14.1-dev] - Fix ternary on new dart version ## [1.14.0-dev] - Added compatibility with Flutter 1.17.1 - Added back popGesture to iOS (default) and Android (optional) - Improve performance - Decrease lib size to 94.9kb (25.4k after compiled on release) ## [1.13.1-dev] - Fix back function ## [1.13.0-dev] - Plugin refactor - Added GetPlatform ## [1.12.0-dev] -Compatibility with Dev branch ## [1.11.4] - Refactor code of library ## [1.11.3] -Added docs ## [1.11.2] -Fix flutter web platform and added GetPlatform ## [1.11.1] -Improve swipe to back on iOS devices ## [1.11.0] -Added experimental GetCupertino ## [1.10.5] -Added setKey to improve modular compatibility -Added ability to define transition duration directly when calling the new route. ## [1.10.4] -Improve Get.offAll() - predicate now is optional ## [1.10.3] -Improve default color from dialogs ## [1.10.2] -Improve snackbar text color -Added background color to snackbar (@claudneysessa) ## [1.10.1] -Backdrop improvement ## [1.10.0] -Added backdrop ## [1.9.2] -Added docs to GetObserver ## [1.9.1] -Fix typo on snackbar route ## [1.9.0] -Added: Navigator observer -Added: Get.args to named routes -Improve snackbar performance ## [1.8.1] -Fix new snackbar features ## [1.8.0] -Add Get.close method. -Add many Snackbars features ## [1.7.4] -Fix dialog child error ## [1.7.3] -Added transitions docs ## [1.7.2] -Fix bottomsheet on macos ## [1.7.1] -Fix docs ## [1.7.0] - Improve geral performance. Get.to Wrap now consumes even less RAM and CPU. In an application with 20 screens, it obtained 82% less RAM usage compared to the traditional method Navigator.push and had a CPU normalization of 23% in a Moto z2, against 64% CPU usage in Navigator.push with MaterialPageRoute. Test it for yourself! - Added BottomSheet with no context - Added modern Blur Snackbar - Added customs transitions - Improve dialogs performance ## [1.6.4] - Improve performance. ## [1.6.3] - Clean code. ## [1.6.2] - Fix bugs on blurred Snackbars ## [1.6.1] - Add docs and improve performance ## [1.6.0] - Add support to snackbars ## [1.5.0+1] - Add color and opacity to dialogs ## [1.5.0] - Add support to dialogs ## [1.4.0+7] - Add more documentation ## [1.4.0+6] - Improve performance and bug fix ## [1.4.0] - Added Get.removeRoute // ability to remove one route. Get.until // back repeatedly until the predicate returns true. Get.offUntil // go to next route and remove all the previous routes until the predicate returns true. Get.offNamedUntil // go to next named route and remove all the previous routes until the predicate returns true. ## [1.3.4] - Improve performance ## [1.3.3] - Fix Get.back arguments ## [1.3.2] - Improve performance ## [1.3.1] - Update docs ## [1.3.0] - Update docs, readme, and add full support to flutter_web ## [1.2.1] - Fix bug currentState = null ## [1.2.0] - Add routes navigation with no context ## [1.1.0] - Add support to named routes ## [1.0.3] - Improve Performance ## [1.0.2] - Add examples ## [1.0.1] - Doc changes ## [1.0.0] - initial release ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2019 Jonny Borges Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README-ar.md ================================================ ![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/get.png) [![pub package](https://img.shields.io/pub/v/get.svg?label=get&color=blue)](https://pub.dev/packages/get) [![popularity](https://badges.bar/get/popularity)](https://pub.dev/packages/sentry/score) [![likes](https://badges.bar/get/likes)](https://pub.dev/packages/get/score) [![pub points](https://badges.bar/get/pub%20points)](https://pub.dev/packages/get/score) ![building](https://github.com/jonataslaw/get/workflows/build/badge.svg) [![style: effective dart](https://img.shields.io/badge/style-effective_dart-40c4ff.svg)](https://pub.dev/packages/effective_dart) [![Discord Shield](https://img.shields.io/discord/722900883784073290.svg?logo=discord)](https://discord.com/invite/9Hpt99N) [![Get on Slack](https://img.shields.io/badge/slack-join-orange.svg)](https://communityinviter.com/apps/getxworkspace/getx) [![Telegram](https://img.shields.io/badge/chat-on%20Telegram-blue.svg)](https://t.me/joinchat/PhdbJRmsZNpAqSLJL6bH7g) Awesome Flutter Buy Me A Coffee ![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/getx.png)
**Languages:** [![English](https://img.shields.io/badge/Language-English-blueviolet?style=for-the-badge)](README.md) [![Vietnamese](https://img.shields.io/badge/Language-Vietnamese-blueviolet?style=for-the-badge)](README-vi.md) [![Indonesian](https://img.shields.io/badge/Language-Indonesian-blueviolet?style=for-the-badge)](README.id-ID.md) [![Urdu](https://img.shields.io/badge/Language-Urdu-blueviolet?style=for-the-badge)](README.ur-PK.md) [![Chinese](https://img.shields.io/badge/Language-Chinese-blueviolet?style=for-the-badge)](README.zh-cn.md) [![Portuguese](https://img.shields.io/badge/Language-Portuguese-blueviolet?style=for-the-badge)](README.pt-br.md) [![Spanish](https://img.shields.io/badge/Language-Spanish-blueviolet?style=for-the-badge)](README-es.md) [![Russian](https://img.shields.io/badge/Language-Russian-blueviolet?style=for-the-badge)](README.ru.md) [![Polish](https://img.shields.io/badge/Language-Polish-blueviolet?style=for-the-badge)](README.pl.md) [![Korean](https://img.shields.io/badge/Language-Korean-blueviolet?style=for-the-badge)](README.ko-kr.md) [![French](https://img.shields.io/badge/Language-French-blueviolet?style=for-the-badge)](README-fr.md) [![العربيه](https://img.shields.io/badge/Language-arabic-blueviolet?style=for-the-badge)](README-ar.md)
- [عن المكتبة](#عن-المكتبة) - [التركيب](#التركيب) - [بناء تطبيق العداد 🔢](#بناء-تطبيق-العداد-) - [The Three pillars](#the-three-pillars) - [State management](#state-management) - [Reactive State Manager](#reactive-state-manager) - [More details about state management](#more-details-about-state-management) - [Route management](#route-management) - [More details about route management](#more-details-about-route-management) - [Dependency management](#dependency-management) - [More details about dependency management](#more-details-about-dependency-management) - [Utils](#utils) - [Internationalization](#internationalization) - [Translations](#translations) - [Using translations](#using-translations) - [Using translation with singular and plural](#using-translation-with-singular-and-plural) - [Using translation with parameters](#using-translation-with-parameters) - [Locales](#locales) - [Change locale](#change-locale) - [System locale](#system-locale) - [Change Theme](#change-theme) - [GetConnect](#getconnect) - [Default configuration](#default-configuration) - [Custom configuration](#custom-configuration) - [GetPage Middleware](#getpage-middleware) - [Priority](#priority) - [Redirect](#redirect) - [onPageCalled](#onpagecalled) - [OnBindingsStart](#onbindingsstart) - [OnPageBuildStart](#onpagebuildstart) - [OnPageBuilt](#onpagebuilt) - [OnPageDispose](#onpagedispose) - [Other Advanced APIs](#other-advanced-apis) - [Optional Global Settings and Manual configurations](#optional-global-settings-and-manual-configurations) - [Local State Widgets](#local-state-widgets) - [ValueBuilder](#valuebuilder) - [ObxValue](#obxvalue) - [Useful tips](#useful-tips) - [StateMixin](#statemixin) - [GetView](#getview) - [GetResponsiveView](#getresponsiveview) - [How to use it](#how-to-use-it) - [GetWidget](#getwidget) - [GetxService](#getxservice) - [Tests](#tests) - [Tips](#tips) - [Mockito or mocktail](#mockito-or-mocktail) - [Using Get.reset()](#using-getreset) - [Get.testMode](#gettestmode) - [Breaking changes from 2.0](#breaking-changes-from-20) - [Why Getx?](#why-getx) - [Community](#community) - [Community channels](#community-channels) - [How to contribute](#how-to-contribute) - [Articles and videos](#articles-and-videos) # عن المكتبة - `GetX` مكتبه خفيفه وقوية لفلاتر , توفر المكتبه السرعه العاليه في التحكم في الحاله , نظام حقن `Ddependency injection` ذكي , والتحكم في التنقل بين الصفحات بسرعه وسهوله - `GetX` - تعتمد علي 3 نقاط اساسية . **الانتاجية والسرعه والتنظيم** - **السرعه:** `GetX` تركز علي السرعه واقل استخدام للموارد,`GetX` لا تستخدم `Streams` او `ChangeNotifier`. - **الانتاجية:** `GetX` تستخدم طريقه سهله ومريحة في كتابة الكود , لا يهم ماذا تريد انت تبني , يوجد دائما طريقه اسهل لبناء باستخدام `GetX` , ستوفر ساعات من العمل وتوفر لك اعلي سرعه يمكن الوصل لها في تطبيقاتك عموما , يجب ان يهتم المطور بالتخلص من الموارد الغير مستخدمه من الذاكرة , مع `GetX` هذا غير ضروري لانه يتم التخلص من الموارد الغير مستخدمه من الذاكره تلقائيا, اذا اردت تركهم دائما في الذاكرة يمكنك ذلك لكن يجب عليك ان تستخدم `permanent: true` بالاضافه الي توفير الوقت تم تقليل امكانية ترك الموارد في الذاكره بدون التخلص منها , يتم حقن الموارد `lazy` افتراضيا - **التنظيم:** `GetX` تسمح لك بفصل الـ `view` عن الـ `presentation logic` و `business logic` باكامل, بالنسبة للحقن `dependency injection` و التنقل بين الشاشات لا تحتاج فيهم `context` للتنقل بين الصفحات , ولا تحتاك `context` للوصول للموارد عن طريق widget tree, لذلك يتم الفصل بالكامل بين `presentation logic` و `business logic` لا تحتاج لحقن ال `Controllers/Models/Blocs` داخل شجره العناصر `Widget Tree` خلال `MultiProvider`s. لان , `GetX` تستخدم نظام حقن خاص بها ويمكنك من فصل الـ `DI` عن الوجهات بالكامل . - مع `Getx` تعرف ايه يكون الكود الخاص ب كل جزء في التطبيق , تساعدك في كتابة كود نظيف , بالاضافه الي سهوله التطوير مستقبلا , وهذا يمكنك من مشاركه الاجزاء `modules` امر صعب ليصبح سهل جدا . `BLOC` كان نقطه البداية لهذا الامر وتظيم الكود بهذه الطريقه في فلاتر , عن طريق فصل كود البزنس عن الواجهات , `GetX` هي التطور لذلك الامر , وذلك عن طريق الاضافه الي ذلك فصل حقن الموارد وفصل التنقل بين الشاشات ايضا , وطبقه البيانات بالكامل ايضا , تعلم اين يكون كل شي في المشروع - `Getx` توفر لك السهوله في بناء المشروع والاستقرار كلما كبر حجم المشروع واقصي سرعه ممكن , توفر لك ايضا نظام كامل يعمل في تجانس تام , سهل للمبتدئين , ومنظم للخبراء , امن , مستقر , ومحدث باستمرار ويوفر لك موجموعه من الادوات لتسهل عليك - `GetX` ليست ضخمه , تمتلك المكتبة العديد من المميزات تجعلك تبدا في البرمجه بدون القلق عن اي شي كل ميزه منهم منقسمه عن الاخري ولا يبداو الا عندما تستخدمهم , اذا استخدمت جزء التحكم في الحاله فقط لن يتم استخدام جزء التنقل بين الشاشات في تطبيقك الا `Compiled` والعكس صحيح ! . -`Getx` لديها نظام شامل , ومجتمع كبير , وعداد كبير من المطورين , وسوف يتم تحديثها باستمرار , تعمل المكتبة علي كل الانظمه بنفس الكود دون تغيير `Android`, `iOS`, `Web`, `Mac`, `Linux`, `Windows` حتي علي الخادم يمكنك استخدام `Getx` لبناء تطبيقات الويب **[Get Server](https://github.com/jonataslaw/get_server)**. **بالاضافه الي ذلك يمكن محاكاه الامر اكثر في فلاتر والخادم عن طريق [Get CLI](https://github.com/jonataslaw/get_cli)**. **وللمزيد من الانتاجية يمكنك استخدام اضافه للـ** - [فيجوال ستوديو كود](https://marketplace.visualstudio.com/items?itemName=get-snippets.get-snippets) - [اندرويد استوديو و انتلج](https://plugins.jetbrains.com/plugin/14975-getx-snippets) # التركيب استخدم المكتبة في ملف `pubspec.yaml`
```yaml dependencies: get: ```
استدعي المكتبة في الملفات الي ستستخدمها
```dart import 'package:get/get.dart'; ```
# بناء تطبيق العداد 🔢 تطبيق العداد الذي يتم انشاء مع كل مشروع جديد يتعدي ال 100 سطر (بالتعليقات) ولكي اريك مدي قوه `GetX` ساوضح لك كيفيه بناء التطبيق مع تغير قيمه العداد مع كل ضغطه زر والتقل بين الشاشات ومشاركه الحاله كل ذلك بطريقه منذمه وفصل تام لكود البزنس عن الواجهات فقط ب 26 سطر من ضمنهم التعليقات 🔥 - الخطوه الاولي : اكتب `Get` امام `MaterialApp` لتصبح `GetMaterialApp`
```dart void main() => runApp(GetMaterialApp(home: Home())); ```
- ملحوظه : هذا لا يعتبر تعديل علي `MaterialApp` لان , `GetMaterialApp` عباره عن عنصر معد مسبقا ويستخدم `MaterialApp` تحت الغطاء , يمكن تغير الاعدادات يدوين لكن هذا غير ضروري لان ``سيقوم بعمل المسارات و حقن العناصر والترجمه وكل شي تحتاجه ولكن اذا كنت تنوي لاستخدام المكتبة فقط للتحكم في الحاله`State managment`فهذه الخطوه غير ضرورية تكون هذه الخطوه ضرورية عندما تريد التنقل بين الشاشات او عرض`snackbars`والترجمه و اي شي يعتمد علي`context`وتقوم`getx` بتوفيره - الخطوه الثانية قم بكتابة الكود داخل `class` وكتابة المتغيرات والدوال , يمكنك جعل المتغير قابلع لاعاده بناء الواجها عند تغير قيمته باستخدام ال `getter` `.obs` .
```dart class Controller extends GetxController{ var count = 0.obs; increment() => count++; } ```
- الخطوه الثالثه ابني الواجهه واستخدم `StatelessWidget` لتوفير الموارد , مع `Getx` يمكنك الاستغناء عن `StatefulWidget`.
```dart class Home extends StatelessWidget { @override Widget build(context) { // Instantiate your class using Get.put() to make it available for all "child" routes there. final c = Get.put(Controller()); return Scaffold( // Use Obx(()=> to update Text() whenever count is changed. appBar: AppBar(title: Obx(() => Text("Clicks: ${c.count}"))), // Replace the 8 lines Navigator.push by a simple Get.to(). You don't need context body: Center(child: ElevatedButton( child: Text("Go to Other"), onPressed: () => Get.to(Other()))), floatingActionButton: FloatingActionButton(child: Icon(Icons.add), onPressed: c.increment)); } } class Other extends StatelessWidget { // You can ask Get to find a Controller that is being used by another page and redirect you to it. final Controller c = Get.find(); @override Widget build(context){ // Access the updated count variable return Scaffold(body: Center(child: Text("${c.count}"))); } } ```
Result: ![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/counter-app-gif.gif) This is a simple project but it already makes clear how powerful Get is. As your project grows, this difference will become more significant. Get was designed to work with teams, but it makes the job of an individual developer simple. Improve your deadlines, deliver everything on time without losing performance. Get is not for everyone, but if you identified with that phrase, Get is for you! # The Three pillars ## State management Get has two different state managers: the simple state manager (we'll call it GetBuilder) and the reactive state manager (GetX/Obx) ### Reactive State Manager Reactive programming can alienate many people because it is said to be complicated. GetX turns reactive programming into something quite simple: - You won't need to create StreamControllers. - You won't need to create a StreamBuilder for each variable - You will not need to create a class for each state. - You will not need to create a get for an initial value. - You will not need to use code generators Reactive programming with Get is as easy as using setState. Let's imagine that you have a name variable and want that every time you change it, all widgets that use it are automatically changed. This is your count variable: ```dart var name = 'Jonatas Borges'; ``` To make it observable, you just need to add ".obs" to the end of it: ```dart var name = 'Jonatas Borges'.obs; ``` And in the UI, when you want to show that value and update the screen whenever the values changes, simply do this: ```dart Obx(() => Text("${controller.name}")); ``` That's all. It's _that_ simple. ### More details about state management **See an more in-depth explanation of state management [here](./documentation/en_US/state_management.md). There you will see more examples and also the difference between the simple state manager and the reactive state manager** You will get a good idea of GetX power. ## Route management If you are going to use routes/snackbars/dialogs/bottomsheets without context, GetX is excellent for you too, just see it: Add "Get" before your MaterialApp, turning it into GetMaterialApp ```dart GetMaterialApp( // Before: MaterialApp( home: MyHome(), ) ``` Navigate to a new screen: ```dart Get.to(NextScreen()); ``` Navigate to new screen with name. See more details on named routes [here](./documentation/en_US/route_management.md#navigation-with-named-routes) ```dart Get.toNamed('/details'); ``` To close snackbars, dialogs, bottomsheets, or anything you would normally close with Navigator.pop(context); ```dart Get.back(); ``` To go to the next screen and no option to go back to the previous screen (for use in SplashScreens, login screens, etc.) ```dart Get.off(NextScreen()); ``` To go to the next screen and cancel all previous routes (useful in shopping carts, polls, and tests) ```dart Get.offAll(NextScreen()); ``` Noticed that you didn't have to use context to do any of these things? That's one of the biggest advantages of using Get route management. With this, you can execute all these methods from within your controller class, without worries. ### More details about route management **Get works with named routes and also offers lower-level control over your routes! There is in-depth documentation [here](./documentation/en_US/route_management.md)** ## Dependency management Get has a simple and powerful dependency manager that allows you to retrieve the same class as your Bloc or Controller with just 1 lines of code, no Provider context, no inheritedWidget: ```dart Controller controller = Get.put(Controller()); // Rather Controller controller = Controller(); ``` - Note: If you are using Get's State Manager, pay more attention to the bindings API, which will make it easier to connect your view to your controller. Instead of instantiating your class within the class you are using, you are instantiating it within the Get instance, which will make it available throughout your App. So you can use your controller (or class Bloc) normally **Tip:** Get dependency management is decoupled from other parts of the package, so if for example, your app is already using a state manager (any one, it doesn't matter), you don't need to rewrite it all, you can use this dependency injection with no problems at all ```dart controller.fetchApi(); ``` Imagine that you have navigated through numerous routes, and you need data that was left behind in your controller, you would need a state manager combined with the Provider or Get_it, correct? Not with Get. You just need to ask Get to "find" for your controller, you don't need any additional dependencies: ```dart Controller controller = Get.find(); //Yes, it looks like Magic, Get will find your controller, and will deliver it to you. You can have 1 million controllers instantiated, Get will always give you the right controller. ``` And then you will be able to recover your controller data that was obtained back there: ```dart Text(controller.textFromApi); ``` ### More details about dependency management **See a more in-depth explanation of dependency management [here](./documentation/en_US/dependency_management.md)** # Utils ## Internationalization ### Translations Translations are kept as a simple key-value dictionary map. To add custom translations, create a class and extend `Translations`. ```dart import 'package:get/get.dart'; class Messages extends Translations { @override Map> get keys => { 'en_US': { 'hello': 'Hello World', }, 'de_DE': { 'hello': 'Hallo Welt', } }; } ``` #### Using translations Just append `.tr` to the specified key and it will be translated, using the current value of `Get.locale` and `Get.fallbackLocale`. ```dart Text('title'.tr); ``` #### Using translation with singular and plural ```dart var products = []; Text('singularKey'.trPlural('pluralKey', products.length, Args)); ``` #### Using translation with parameters ```dart import 'package:get/get.dart'; Map> get keys => { 'en_US': { 'logged_in': 'logged in as @name with email @email', }, 'es_ES': { 'logged_in': 'iniciado sesión como @name con e-mail @email', } }; Text('logged_in'.trParams({ 'name': 'Jhon', 'email': 'jhon@example.com' })); ``` ### Locales Pass parameters to `GetMaterialApp` to define the locale and translations. ```dart return GetMaterialApp( translations: Messages(), // your translations locale: Locale('en', 'US'), // translations will be displayed in that locale fallbackLocale: Locale('en', 'UK'), // specify the fallback locale in case an invalid locale is selected. ); ``` #### Change locale Call `Get.updateLocale(locale)` to update the locale. Translations then automatically use the new locale. ```dart var locale = Locale('en', 'US'); Get.updateLocale(locale); ``` #### System locale To read the system locale, you could use `Get.deviceLocale`. ```dart return GetMaterialApp( locale: Get.deviceLocale, ); ``` ## Change Theme Please do not use any higher level widget than `GetMaterialApp` in order to update it. This can trigger duplicate keys. A lot of people are used to the prehistoric approach of creating a "ThemeProvider" widget just to change the theme of your app, and this is definitely NOT necessary with **GetX™**. You can create your custom theme and simply add it within `Get.changeTheme` without any boilerplate for that: ```dart Get.changeTheme(ThemeData.light()); ``` If you want to create something like a button that changes the Theme in `onTap`, you can combine two **GetX™** APIs for that: - The api that checks if the dark `Theme` is being used. - And the `Theme` Change API, you can just put this within an `onPressed`: ```dart Get.changeTheme(Get.isDarkMode? ThemeData.light(): ThemeData.dark()); ``` When `.darkmode` is activated, it will switch to the _light theme_, and when the _light theme_ becomes active, it will change to _dark theme_. ## GetConnect GetConnect is an easy way to communicate from your back to your front with http or websockets ### Default configuration You can simply extend GetConnect and use the GET/POST/PUT/DELETE/SOCKET methods to communicate with your Rest API or websockets. ```dart class UserProvider extends GetConnect { // Get request Future getUser(int id) => get('http://youapi/users/$id'); // Post request Future postUser(Map data) => post('http://youapi/users', body: data); // Post request with File Future> postCases(List image) { final form = FormData({ 'file': MultipartFile(image, filename: 'avatar.png'), 'otherFile': MultipartFile(image, filename: 'cover.png'), }); return post('http://youapi/users/upload', form); } GetSocket userMessages() { return socket('https://yourapi/users/socket'); } } ``` ### Custom configuration GetConnect is highly customizable You can define base Url, as answer modifiers, as Requests modifiers, define an authenticator, and even the number of attempts in which it will try to authenticate itself, in addition to giving the possibility to define a standard decoder that will transform all your requests into your Models without any additional configuration. ```dart class HomeProvider extends GetConnect { @override void onInit() { // All request will pass to jsonEncode so CasesModel.fromJson() httpClient.defaultDecoder = CasesModel.fromJson; httpClient.baseUrl = 'https://api.covid19api.com'; // baseUrl = 'https://api.covid19api.com'; // It define baseUrl to // Http and websockets if used with no [httpClient] instance // It's will attach 'apikey' property on header from all requests httpClient.addRequestModifier((request) { request.headers['apikey'] = '12345678'; return request; }); // Even if the server sends data from the country "Brazil", // it will never be displayed to users, because you remove // that data from the response, even before the response is delivered httpClient.addResponseModifier((request, response) { CasesModel model = response.body; if (model.countries.contains('Brazil')) { model.countries.remove('Brazilll'); } }); httpClient.addAuthenticator((request) async { final response = await get("http://yourapi/token"); final token = response.body['token']; // Set the header request.headers['Authorization'] = "$token"; return request; }); //Autenticator will be called 3 times if HttpStatus is //HttpStatus.unauthorized httpClient.maxAuthRetries = 3; } @override Future> getCases(String path) => get(path); } ``` ## GetPage Middleware The GetPage has now new property that takes a list of GetMiddleWare and run them in the specific order. **Note**: When GetPage has a Middlewares, all the children of this page will have the same middlewares automatically. ### Priority The Order of the Middlewares to run can be set by the priority in the GetMiddleware. ```dart final middlewares = [ GetMiddleware(priority: 2), GetMiddleware(priority: 5), GetMiddleware(priority: 4), GetMiddleware(priority: -8), ]; ``` those middlewares will be run in this order **-8 => 2 => 4 => 5** ### Redirect This function will be called when the page of the called route is being searched for. It takes RouteSettings as a result to redirect to. Or give it null and there will be no redirecting. ```dart RouteSettings redirect(String route) { final authService = Get.find(); return authService.authed.value ? null : RouteSettings(name: '/login') } ``` ### onPageCalled This function will be called when this Page is called before anything created you can use it to change something about the page or give it new page ```dart GetPage onPageCalled(GetPage page) { final authService = Get.find(); return page.copyWith(title: 'Welcome ${authService.UserName}'); } ``` ### OnBindingsStart This function will be called right before the Bindings are initialize. Here you can change Bindings for this page. ```dart List onBindingsStart(List bindings) { final authService = Get.find(); if (authService.isAdmin) { bindings.add(AdminBinding()); } return bindings; } ``` ### OnPageBuildStart This function will be called right after the Bindings are initialize. Here you can do something after that you created the bindings and before creating the page widget. ```dart GetPageBuilder onPageBuildStart(GetPageBuilder page) { print('bindings are ready'); return page; } ``` ### OnPageBuilt This function will be called right after the GetPage.page function is called and will give you the result of the function. and take the widget that will be showed. ### OnPageDispose This function will be called right after disposing all the related objects (Controllers, views, ...) of the page. ## Other Advanced APIs ```dart // give the current args from currentScreen Get.arguments // give name of previous route Get.previousRoute // give the raw route to access for example, rawRoute.isFirst() Get.rawRoute // give access to Routing API from GetObserver Get.routing // check if snackbar is open Get.isSnackbarOpen // check if dialog is open Get.isDialogOpen // check if bottomsheet is open Get.isBottomSheetOpen // remove one route. Get.removeRoute() // back repeatedly until the predicate returns true. Get.until() // go to next route and remove all the previous routes until the predicate returns true. Get.offUntil() // go to next named route and remove all the previous routes until the predicate returns true. Get.offNamedUntil() //Check in what platform the app is running GetPlatform.isAndroid GetPlatform.isIOS GetPlatform.isMacOS GetPlatform.isWindows GetPlatform.isLinux GetPlatform.isFuchsia //Check the device type GetPlatform.isMobile GetPlatform.isDesktop //All platforms are supported independently in web! //You can tell if you are running inside a browser //on Windows, iOS, OSX, Android, etc. GetPlatform.isWeb // Equivalent to : MediaQuery.of(context).size.height, // but immutable. Get.height Get.width // Gives the current context of the Navigator. Get.context // Gives the context of the snackbar/dialog/bottomsheet in the foreground, anywhere in your code. Get.contextOverlay // Note: the following methods are extensions on context. Since you // have access to context in any place of your UI, you can use it anywhere in the UI code // If you need a changeable height/width (like Desktop or browser windows that can be scaled) you will need to use context. context.width context.height // Gives you the power to define half the screen, a third of it and so on. // Useful for responsive applications. // param dividedBy (double) optional - default: 1 // param reducedBy (double) optional - default: 0 context.heightTransformer() context.widthTransformer() /// Similar to MediaQuery.of(context).size context.mediaQuerySize() /// Similar to MediaQuery.of(context).padding context.mediaQueryPadding() /// Similar to MediaQuery.of(context).viewPadding context.mediaQueryViewPadding() /// Similar to MediaQuery.of(context).viewInsets; context.mediaQueryViewInsets() /// Similar to MediaQuery.of(context).orientation; context.orientation() /// Check if device is on landscape mode context.isLandscape() /// Check if device is on portrait mode context.isPortrait() /// Similar to MediaQuery.of(context).devicePixelRatio; context.devicePixelRatio() /// Similar to MediaQuery.of(context).textScaleFactor; context.textScaleFactor() /// Get the shortestSide from screen context.mediaQueryShortestSide() /// True if width be larger than 800 context.showNavbar() /// True if the shortestSide is smaller than 600p context.isPhone() /// True if the shortestSide is largest than 600p context.isSmallTablet() /// True if the shortestSide is largest than 720p context.isLargeTablet() /// True if the current device is Tablet context.isTablet() /// Returns a value according to the screen size /// can give value for: /// watch: if the shortestSide is smaller than 300 /// mobile: if the shortestSide is smaller than 600 /// tablet: if the shortestSide is smaller than 1200 /// desktop: if width is largest than 1200 context.responsiveValue() ``` ### Optional Global Settings and Manual configurations GetMaterialApp configures everything for you, but if you want to configure Get manually. ```dart MaterialApp( navigatorKey: Get.key, navigatorObservers: [GetObserver()], ); ``` You will also be able to use your own Middleware within `GetObserver`, this will not influence anything. ```dart MaterialApp( navigatorKey: Get.key, navigatorObservers: [ GetObserver(MiddleWare.observer) // Here ], ); ``` You can create _Global Settings_ for `Get`. Just add `Get.config` to your code before pushing any route. Or do it directly in your `GetMaterialApp` ```dart GetMaterialApp( enableLog: true, defaultTransition: Transition.fade, opaqueRoute: Get.isOpaqueRouteDefault, popGesture: Get.isPopGestureEnable, transitionDuration: Get.defaultDurationTransition, defaultGlobalState: Get.defaultGlobalState, ); Get.config( enableLog = true, defaultPopGesture = true, defaultTransition = Transitions.cupertino ) ``` You can optionally redirect all the logging messages from `Get`. If you want to use your own, favourite logging package, and want to capture the logs there: ```dart GetMaterialApp( enableLog: true, logWriterCallback: localLogWriter, ); void localLogWriter(String text, {bool isError = false}) { // pass the message to your favourite logging package here // please note that even if enableLog: false log messages will be pushed in this callback // you get check the flag if you want through GetConfig.isLogEnable } ``` ### Local State Widgets These Widgets allows you to manage a single value, and keep the state ephemeral and locally. We have flavours for Reactive and Simple. For instance, you might use them to toggle obscureText in a `TextField`, maybe create a custom Expandable Panel, or maybe modify the current index in `BottomNavigationBar` while changing the content of the body in a `Scaffold`. #### ValueBuilder A simplification of `StatefulWidget` that works with a `.setState` callback that takes the updated value. ```dart ValueBuilder( initialValue: false, builder: (value, updateFn) => Switch( value: value, onChanged: updateFn, // same signature! you could use ( newValue ) => updateFn( newValue ) ), // if you need to call something outside the builder method. onUpdate: (value) => print("Value updated: $value"), onDispose: () => print("Widget unmounted"), ), ``` #### ObxValue Similar to [`ValueBuilder`](#valuebuilder), but this is the Reactive version, you pass a Rx instance (remember the magical .obs?) and updates automatically... isn't it awesome? ```dart ObxValue((data) => Switch( value: data.value, onChanged: data, // Rx has a _callable_ function! You could use (flag) => data.value = flag, ), false.obs, ), ``` ## Useful tips `.obs`ervables (also known as _Rx_ Types) have a wide variety of internal methods and operators. > Is very common to _believe_ that a property with `.obs` **IS** the actual value... but make no mistake! > We avoid the Type declaration of the variable, because Dart's compiler is smart enough, and the code > looks cleaner, but: ```dart var message = 'Hello world'.obs; print( 'Message "$message" has Type ${message.runtimeType}'); ``` Even if `message` _prints_ the actual String value, the Type is **RxString**! So, you can't do `message.substring( 0, 4 )`. You have to access the real `value` inside the _observable_: The most "used way" is `.value`, but, did you know that you can also use... ```dart final name = 'GetX'.obs; // only "updates" the stream, if the value is different from the current one. name.value = 'Hey'; // All Rx properties are "callable" and returns the new value. // but this approach does not accepts `null`, the UI will not rebuild. name('Hello'); // is like a getter, prints 'Hello'. name() ; /// numbers: final count = 0.obs; // You can use all non mutable operations from num primitives! count + 1; // Watch out! this is only valid if `count` is not final, but var count += 1; // You can also compare against values: count > 2; /// booleans: final flag = false.obs; // switches the value between true/false flag.toggle(); /// all types: // Sets the `value` to null. flag.nil(); // All toString(), toJson() operations are passed down to the `value` print( count ); // calls `toString()` inside for RxInt final abc = [0,1,2].obs; // Converts the value to a json Array, prints RxList // Json is supported by all Rx types! print('json: ${jsonEncode(abc)}, type: ${abc.runtimeType}'); // RxMap, RxList and RxSet are special Rx types, that extends their native types. // but you can work with a List as a regular list, although is reactive! abc.add(12); // pushes 12 to the list, and UPDATES the stream. abc[3]; // like Lists, reads the index 3. // equality works with the Rx and the value, but hashCode is always taken from the value final number = 12.obs; print( number == 12 ); // prints > true /// Custom Rx Models: // toJson(), toString() are deferred to the child, so you can implement override on them, and print() the observable directly. class User { String name, last; int age; User({this.name, this.last, this.age}); @override String toString() => '$name $last, $age years old'; } final user = User(name: 'John', last: 'Doe', age: 33).obs; // `user` is "reactive", but the properties inside ARE NOT! // So, if we change some variable inside of it... user.value.name = 'Roi'; // The widget will not rebuild!, // `Rx` don't have any clue when you change something inside user. // So, for custom classes, we need to manually "notify" the change. user.refresh(); // or we can use the `update()` method! user.update((value){ value.name='Roi'; }); print( user ); ``` ## StateMixin Another way to handle your `UI` state is use the `StateMixin` . To implement it, use the `with` to add the `StateMixin` to your controller which allows a T model. ```dart class Controller extends GetController with StateMixin{} ``` The `change()` method change the State whenever we want. Just pass the data and the status in this way: ```dart change(data, status: RxStatus.success()); ``` RxStatus allow these status: ```dart RxStatus.loading(); RxStatus.success(); RxStatus.empty(); RxStatus.error('message'); ``` To represent it in the UI, use: ```dart class OtherClass extends GetView { @override Widget build(BuildContext context) { return Scaffold( body: controller.obx( (state)=>Text(state.name), // here you can put your custom loading indicator, but // by default would be Center(child:CircularProgressIndicator()) onLoading: CustomLoadingIndicator(), onEmpty: Text('No data found'), // here also you can set your own error widget, but by // default will be an Center(child:Text(error)) onError: (error)=>Text(error), ), ); } ``` #### GetView I love this Widget, is so simple, yet, so useful! Is a `const Stateless` Widget that has a getter `controller` for a registered `Controller`, that's all. ```dart class AwesomeController extends GetController { final String title = 'My Awesome View'; } // ALWAYS remember to pass the `Type` you used to register your controller! class AwesomeView extends GetView { @override Widget build(BuildContext context) { return Container( padding: EdgeInsets.all(20), child: Text(controller.title), // just call `controller.something` ); } } ``` #### GetResponsiveView Extend this widget to build responsive view. this widget contains the `screen` property that have all information about the screen size and type. ##### How to use it You have two options to build it. - with `builder` method you return the widget to build. - with methods `desktop`, `tablet`,`phone`, `watch`. the specific method will be built when the screen type matches the method when the screen is [ScreenType.Tablet] the `tablet` method will be exuded and so on. **Note:** If you use this method please set the property `alwaysUseBuilder` to `false` With `settings` property you can set the width limit for the screen types. ![example](https://github.com/SchabanBo/get_page_example/blob/master/docs/Example.gif?raw=true) Code to this screen [code](https://github.com/SchabanBo/get_page_example/blob/master/lib/pages/responsive_example/responsive_view.dart) #### GetWidget Most people have no idea about this Widget, or totally confuse the usage of it. The use case is very rare, but very specific: It `caches` a Controller. Because of the _cache_, can't be a `const Stateless`. > So, when do you need to "cache" a Controller? If you use, another "not so common" feature of **GetX**: `Get.create()`. `Get.create(()=>Controller())` will generate a new `Controller` each time you call `Get.find()`, That's where `GetWidget` shines... as you can use it, for example, to keep a list of Todo items. So, if the widget gets "rebuilt", it will keep the same controller instance. #### GetxService This class is like a `GetxController`, it shares the same lifecycle ( `onInit()`, `onReady()`, `onClose()`). But has no "logic" inside of it. It just notifies **GetX** Dependency Injection system, that this subclass **can not** be removed from memory. So is super useful to keep your "Services" always reachable and active with `Get.find()`. Like: `ApiService`, `StorageService`, `CacheService`. ```dart Future main() async { await initServices(); /// AWAIT SERVICES INITIALIZATION. runApp(SomeApp()); } /// Is a smart move to make your Services intiialize before you run the Flutter app. /// as you can control the execution flow (maybe you need to load some Theme configuration, /// apiKey, language defined by the User... so load SettingService before running ApiService. /// so GetMaterialApp() doesnt have to rebuild, and takes the values directly. void initServices() async { print('starting services ...'); /// Here is where you put get_storage, hive, shared_pref initialization. /// or moor connection, or whatever that's async. await Get.putAsync(() => DbService().init()); await Get.putAsync(SettingsService()).init(); print('All services started...'); } class DbService extends GetxService { Future init() async { print('$runtimeType delays 2 sec'); await 2.delay(); print('$runtimeType ready!'); return this; } } class SettingsService extends GetxService { void init() async { print('$runtimeType delays 1 sec'); await 1.delay(); print('$runtimeType ready!'); } } ``` The only way to actually delete a `GetxService`, is with `Get.reset()` which is like a "Hot Reboot" of your app. So remember, if you need absolute persistence of a class instance during the lifetime of your app, use `GetxService`. ### Tests You can test your controllers like any other class, including their lifecycles: ```dart class Controller extends GetxController { @override void onInit() { super.onInit(); //Change value to name2 name.value = 'name2'; } @override void onClose() { name.value = ''; super.onClose(); } final name = 'name1'.obs; void changeName() => name.value = 'name3'; } void main() { test(''' Test the state of the reactive variable "name" across all of its lifecycles''', () { /// You can test the controller without the lifecycle, /// but it's not recommended unless you're not using /// GetX dependency injection final controller = Controller(); expect(controller.name.value, 'name1'); /// If you are using it, you can test everything, /// including the state of the application after each lifecycle. Get.put(controller); // onInit was called expect(controller.name.value, 'name2'); /// Test your functions controller.changeName(); expect(controller.name.value, 'name3'); /// onClose was called Get.delete(); expect(controller.name.value, ''); }); } ``` #### Tips ##### Mockito or mocktail If you need to mock your GetxController/GetxService, you should extend GetxController, and mixin it with Mock, that way ```dart class NotificationServiceMock extends GetxService with Mock implements NotificationService {} ``` ##### Using Get.reset() If you are testing widgets, or test groups, use Get.reset at the end of your test or in tearDown to reset all settings from your previous test. ##### Get.testMode if you are using your navigation in your controllers, use `Get.testMode = true` at the beginning of your main. # Breaking changes from 2.0 1- Rx types: | Before | After | | ------- | ---------- | | StringX | `RxString` | | IntX | `RxInt` | | MapX | `RxMap` | | ListX | `RxList` | | NumX | `RxNum` | | DoubleX | `RxDouble` | RxController and GetBuilder now have merged, you no longer need to memorize which controller you want to use, just use GetxController, it will work for simple state management and for reactive as well. 2- NamedRoutes Before: ```dart GetMaterialApp( namedRoutes: { '/': GetRoute(page: Home()), } ) ``` Now: ```dart GetMaterialApp( getPages: [ GetPage(name: '/', page: () => Home()), ] ) ``` Why this change? Often, it may be necessary to decide which page will be displayed from a parameter, or a login token, the previous approach was inflexible, as it did not allow this. Inserting the page into a function has significantly reduced the RAM consumption, since the routes will not be allocated in memory since the app was started, and it also allowed to do this type of approach: ```dart GetStorage box = GetStorage(); GetMaterialApp( getPages: [ GetPage(name: '/', page:(){ return box.hasData('token') ? Home() : Login(); }) ] ) ``` # Why Getx? 1- Many times after a Flutter update, many of your packages will break. Sometimes compilation errors happen, errors often appear that there are still no answers about, and the developer needs to know where the error came from, track the error, only then try to open an issue in the corresponding repository, and see its problem solved. Get centralizes the main resources for development (State, dependency and route management), allowing you to add a single package to your pubspec, and start working. After a Flutter update, the only thing you need to do is update the Get dependency, and get to work. Get also resolves compatibility issues. How many times a version of a package is not compatible with the version of another, because one uses a dependency in one version, and the other in another version? This is also not a concern using Get, as everything is in the same package and is fully compatible. 2- Flutter is easy, Flutter is incredible, but Flutter still has some boilerplate that may be unwanted for most developers, such as `Navigator.of(context).push (context, builder [...]`. Get simplifies development. Instead of writing 8 lines of code to just call a route, you can just do it: `Get.to(Home())` and you're done, you'll go to the next page. Dynamic web urls are a really painful thing to do with Flutter currently, and that with GetX is stupidly simple. Managing states in Flutter, and managing dependencies is also something that generates a lot of discussion, as there are hundreds of patterns in the pub. But there is nothing as easy as adding a ".obs" at the end of your variable, and place your widget inside an Obx, and that's it, all updates to that variable will be automatically updated on the screen. 3- Ease without worrying about performance. Flutter's performance is already amazing, but imagine that you use a state manager, and a locator to distribute your blocs/stores/controllers/ etc. classes. You will have to manually call the exclusion of that dependency when you don't need it. But have you ever thought of simply using your controller, and when it was no longer being used by anyone, it would simply be deleted from memory? That's what GetX does. With SmartManagement, everything that is not being used is deleted from memory, and you shouldn't have to worry about anything but programming. You will be assured that you are consuming the minimum necessary resources, without even having created a logic for this. 4- Actual decoupling. You may have heard the concept "separate the view from the business logic". This is not a peculiarity of BLoC, MVC, MVVM, and any other standard on the market has this concept. However, this concept can often be mitigated in Flutter due to the use of context. If you need context to find an InheritedWidget, you need it in the view, or pass the context by parameter. I particularly find this solution very ugly, and to work in teams we will always have a dependence on View's business logic. Getx is unorthodox with the standard approach, and while it does not completely ban the use of StatefulWidgets, InitState, etc., it always has a similar approach that can be cleaner. Controllers have life cycles, and when you need to make an APIREST request for example, you don't depend on anything in the view. You can use onInit to initiate the http call, and when the data arrives, the variables will be populated. As GetX is fully reactive (really, and works under streams), once the items are filled, all widgets that use that variable will be automatically updated in the view. This allows people with UI expertise to work only with widgets, and not have to send anything to business logic other than user events (like clicking a button), while people working with business logic will be free to create and test the business logic separately. This library will always be updated and implementing new features. Feel free to offer PRs and contribute to them. # Community ## Community channels GetX has a highly active and helpful community. If you have questions, or would like any assistance regarding the use of this framework, please join our community channels, your question will be answered more quickly, and it will be the most suitable place. This repository is exclusive for opening issues, and requesting resources, but feel free to be part of GetX Community. | **Slack** | **Discord** | **Telegram** | | :-------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------- | | [![Get on Slack](https://img.shields.io/badge/slack-join-orange.svg)](https://communityinviter.com/apps/getxworkspace/getx) | [![Discord Shield](https://img.shields.io/discord/722900883784073290.svg?logo=discord)](https://discord.com/invite/9Hpt99N) | [![Telegram](https://img.shields.io/badge/chat-on%20Telegram-blue.svg)](https://t.me/joinchat/PhdbJRmsZNpAqSLJL6bH7g) | ## How to contribute _Want to contribute to the project? We will be proud to highlight you as one of our collaborators. Here are some points where you can contribute and make Get (and Flutter) even better._ - Helping to translate the readme into other languages. - Adding documentation to the readme (a lot of Get's functions haven't been documented yet). - Write articles or make videos teaching how to use Get (they will be inserted in the Readme and in the future in our Wiki). - Offering PRs for code/tests. - Including new functions. Any contribution is welcome! ## Articles and videos - [Flutter Getx EcoSystem package for arabic people](https://www.youtube.com/playlist?list=PLV1fXIAyjeuZ6M8m56zajMUwu4uE3-SL0) - Tutorial by [Pesa Coder](https://github.com/UsamaElgendy). - [Dynamic Themes in 3 lines using GetX™](https://medium.com/swlh/flutter-dynamic-themes-in-3-lines-c3b375f292e3) - Tutorial by [Rod Brown](https://github.com/RodBr). - [Complete GetX™ Navigation](https://www.youtube.com/watch?v=RaqPIoJSTtI) - Route management video by Amateur Coder. - [Complete GetX State Management](https://www.youtube.com/watch?v=CNpXbeI_slw) - State management video by Amateur Coder. - [GetX™ Other Features](https://youtu.be/ttQtlX_Q0eU) - Utils, storage, bindings and other features video by Amateur Coder. - [Firestore User with GetX | Todo App](https://www.youtube.com/watch?v=BiV0DcXgk58) - Video by Amateur Coder. - [Firebase Auth with GetX | Todo App](https://www.youtube.com/watch?v=-H-T_BSgfOE) - Video by Amateur Coder. - [The Flutter GetX™ Ecosystem ~ State Management](https://medium.com/flutter-community/the-flutter-getx-ecosystem-state-management-881c7235511d) - State management by [Aachman Garg](https://github.com/imaachman). - [The Flutter GetX™ Ecosystem ~ Dependency Injection](https://medium.com/flutter-community/the-flutter-getx-ecosystem-dependency-injection-8e763d0ec6b9) - Dependency Injection by [Aachman Garg](https://github.com/imaachman). - [GetX, the all-in-one Flutter package](https://www.youtube.com/watch?v=IYQgtu9TM74) - A brief tutorial covering State Management and Navigation by Thad Carnevalli. - [Build a To-do List App from scratch using Flutter and GetX](https://www.youtube.com/watch?v=EcnqFasHf18) - UI + State Management + Storage video by Thad Carnevalli. - [GetX Flutter Firebase Auth Example](https://medium.com/@jeffmcmorris/getx-flutter-firebase-auth-example-b383c1dd1de2) - Article by Jeff McMorris. - [Flutter State Management with GetX – Complete App](https://www.appwithflutter.com/flutter-state-management-with-getx/) - by App With Flutter. - [Flutter Routing with Animation using Get Package](https://www.appwithflutter.com/flutter-routing-using-get-package/) - by App With Flutter. - [A minimal example on dartpad](https://dartpad.dev/2b3d0d6f9d4e312c5fdbefc414c1727e?) - by [Roi Peker](https://github.com/roipeker)
================================================ FILE: README-bn.md ================================================ ![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/get.png) [![pub package](https://img.shields.io/pub/v/get.svg?label=get&color=blue)](https://pub.dev/packages/get) [![popularity](https://img.shields.io/pub/popularity/get?logo=dart)](https://pub.dev/packages/get/score) [![likes](https://img.shields.io/pub/likes/get?logo=dart)](https://pub.dev/packages/get/score) [![pub points](https://img.shields.io/pub/points/sentry?logo=dart)](https://pub.dev/packages/get/score) ![building](https://github.com/jonataslaw/get/workflows/build/badge.svg) [![style: effective dart](https://img.shields.io/badge/style-effective_dart-40c4ff.svg)](https://pub.dev/packages/effective_dart) [![Discord Shield](https://img.shields.io/discord/722900883784073290.svg?logo=discord)](https://discord.com/invite/9Hpt99N) [![Get on Slack](https://img.shields.io/badge/slack-join-orange.svg)](https://communityinviter.com/apps/getxworkspace/getx) [![Telegram](https://img.shields.io/badge/chat-on%20Telegram-blue.svg)](https://t.me/joinchat/PhdbJRmsZNpAqSLJL6bH7g) Awesome Flutter Buy Me A Coffee ![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/getx.png) - [Get সম্পর্কে](#about-get) - [ইনস্টল](#installing) - [GetX দিয়ে কাউন্টার অ্যাপ](#counter-app-with-getx) - [GetX এর তিনটি স্তম্ভ](#the-three-pillars) - [স্টেট ব্যবস্থাপনা](#state-management) - [প্রতিক্রিয়াশীল স্টেট ম্যানেজার](#reactive-state-manager) - [স্টেট ব্যবস্থাপনা সম্পর্কে আরো বিস্তারিত](#more-details-about-state-management) - [রুট ব্যবস্থাপনা](#route-management) - [রুট ব্যবস্থাপনা সম্পর্কে আরো বিস্তারিত](#more-details-about-route-management) - [ডিপেনডেন্সি ব্যবস্থাপনা](#dependency-management) - [ডিপেনডেন্সি ব্যবস্থাপনা সম্পর্কে আরো বিস্তারিত](#more-details-about-dependency-management) - [ইউটিলিটি](#utils) - [আন্তর্জাতিকীকরণ](#internationalization) - [অনুবাদ](#translations) - [অনুবাদের ব্যবহার](#using-translations) - [লোকেল](#locales) - [লোকেল পরিবর্তন করুন](#change-locale) - [লোকেল পদ্ধতি](#system-locale) - [থিম পরিবর্তন করুন](#change-theme) - [গেট কানেক্ট](#getconnect) - [ডিফল্ট কনফিগারেশন](#default-configuration) - [কাস্টম কনফিগারেশন](#custom-configuration) - [গেট পেজ মিডিলওয়্যার](#getpage-middleware) - [অগ্রাধিকার](#priority) - [পুনঃনির্দেশ](#redirect) - [অন-পেজ কলড](#onpagecalled) - [অন-বাইন্ডিং স্টার্ট](#onbindingsstart) - [অন-পেজ বিল্ড স্টার্ট](#onpagebuildstart) - [অন-পেজ বিল্ড](#onpagebuilt) - [অন-পেজ ডিসপোজ](#onpagedispose) - [অন্যান্য এপিআই সমূহ](#other-advanced-apis) - [ঐচ্ছিক গ্লোবাল সেটিংস এবং ম্যানুয়াল কনফিগারেশন](#optional-global-settings-and-manual-configurations) - [লোকাল স্টেট উইজেট](#local-state-widgets) - [ভ্যালু বিল্ডার](#valuebuilder) - [অব এক্স ভ্যালু](#obxvalue) - [প্রয়োজনীয় পরামর্শ](#useful-tips) - [গেট ভিউ](#getview) - [গেট রেস্পন্সিভ ভিউ](#getresponsiveview) - [কিভাবে এটি ব্যবহার করতে হয়](#how-to-use-it) - [গেট উইজেট](#getwidget) - [গেট এক্স সার্ভিস](#getxservice) - [2.0 থেকে পরিবর্তন](#breaking-changes-from-20) # Get সম্পর্কে - GetX হল ফ্লটারের জন্য একটি লাইটওয়েট এবং শক্তিশালী সমাধান। এটি দ্রুত এবং ব্যবহারিকভাবে উচ্চ-পারফরম্যান্স স্টেট ব্যবস্থাপনা, বুদ্ধিমান ডিপেনডেন্সি ইনজেকশন এবং রুট ব্যবস্থাপনাকে একত্রিত করে। - GetX এর ৩টি মৌলিক নীতি রয়েছে: **উৎপাদনশীলতা, কর্মক্ষমতা এবং সংগঠন**। এর মানে হল যে এইগুলি লাইব্রেরির সমস্ত রিসোর্স এর জন্য অগ্রাধিকার। - **কর্মক্ষমতা:** GetX কর্মক্ষমতা এবং রিসোর্স এর ন্যূনতম ব্যবহারের উপর ফোকাস করে। এটি স্ট্রিম বা চেঞ্জনোটিফায়ার ব্যবহার করে না। - **উৎপাদনশীলতা:** GetX একটি সহজ এবং মনোরম সিনট্যাক্স ব্যবহার করে। আপনি যা করতে চান না কেন, Getx এর সাথে সর্বদা একটি সহজ উপায় রয়েছে। এটি ডেভেলপমেন্ট এর সময় সাশ্রয় করবে এবং এটি আপনার অ্যাপ্লিকেশনটি সরবরাহ করতে পারে এমন সর্বাধিক কর্মক্ষমতা সরবরাহ করবে। - **সংগঠন:** GetX ভিউ, প্রেজেন্টেশন লজিক, বিজনেস লজিক, ডিপেন্ডেন্সি ইনজেকশন এবং নেভিগেশনের মোট ডিকপলিং করার অনুমতি দেয়। রুটগুলির মধ্যে নেভিগেট করার জন্য আপনার কনটেক্সট (context) প্রয়োজন নেই, তাই আপনাকে এর জন্য উইজেট ট্রি (ভিজ্যুয়ালাইজেশন) এর উপর নির্ভরশীল হতে হবে না। - GetX এর একটি বিশাল ইকো সিস্টেম, একটি বৃহত সম্প্রদায়, প্রচুর সংখ্যক সহযোগী রয়েছে এবং যতক্ষণ ফ্লাটার বিদ্যমান থাকবে ততক্ষণ রক্ষণাবেক্ষণ করা হবে। গেটএক্স অ্যান্ড্রয়েড, আইওএস, ওয়েব, ম্যাক, লিনাক্স, উইন্ডোজ এবং আপনার সার্ভারে একই কোড দিয়ে চলতে সক্ষম। **[গেট সার্ভার (Get Server)](https://github.com/jonataslaw/get_server) দিয়ে আপনার ফ্রন্টএন্ডে তৈরি কোডটি পুনরায় সম্পূর্ণরূপে ব্যাকএন্ডে ব্যবহার করা সম্ভব।** **এছাড়াও সম্পূর্ণ ডেভেলপমেন্ট প্রক্রিয়া সার্ভারে এবং ফ্রন্টএন্ডে [Get CLI](https://github.com/jonataslaw/get_cli) এর মাধ্যমে স্বয়ংক্রিয়ভাবে করা যেতে পারে**। **এছাড়াও আপনার উত্পাদনশীলতা আরও বাড়াতে, আমাদের রয়েছে [VSCode extension](https://marketplace.visualstudio.com/items?itemName=get-snippets.get-snippets) এবং [Android Studio/Intellij extension](https://plugins.jetbrains.com/plugin/14975-getx-snippets)**। # ইনস্টল আপনার pubspec.yaml ফাইলে get যোগ করুন: ```yaml dependencies: get: ``` যে ফাইল এ ব্যবহার করবেন সেখানে ইম্পোর্ট করুন: ```dart import 'package:get/get.dart'; ``` # GetX দিয়ে কাউন্টার অ্যাপ Flutter-এ নতুন ডিফল্ট তৈরি করা "কাউন্টার" প্রজেক্টে 100 টিরও বেশি লাইন রয়েছে (মন্তব্য সহ)। Get ব্যবহার করে এটি মাত্র ২৬ লাইনে করা সম্ভব (মন্তব্য সহ)। - ধাপ 1: আপনার MaterialApp এর আগে "Get" যোগ করুন, এটিকে GetMaterialApp এ পরিণত করুন ```dart void main() => runApp(GetMaterialApp(home: Home())); ``` - নোট: এটি ফ্লটারের MaterialApp পরিবর্তন করে না, GetMaterialApp একটি পরিবর্তিত MaterialApp নয়, এটি শুধুমাত্র একটি পূর্ব-কনফিগার করা উইজেট, যেটিতে একটি চাইল্ড হিসাবে ডিফল্ট MaterialApp আছে। আপনি এটি ম্যানুয়ালি কনফিগার করতে পারেন, তবে এটি অবশ্যই প্রয়োজনীয় নয়। GetMaterialApp রুট তৈরি করবে, সেগুলিকে ইনজেকশন দেবে, অনুবাদগুলি ইনজেকশন করবে, রুট নেভিগেশনের জন্য আপনার প্রয়োজনীয় সমস্ত কিছু ইনজেক্ট করবে। আপনি যদি শুধুমাত্র স্টেট ব্যবস্থাপনা বা ডিপেন্ডেন্সি ব্যবস্থাপনার জন্য Get ব্যবহার করেন, তাহলে GetMaterialApp ব্যবহার করার প্রয়োজন নেই। GetMaterialApp রুট, স্ন্যাকবার, আন্তর্জাতিকীকরণ, বটমশিট, ডায়ালগ এবং রুট সম্পর্কিত উচ্চ-স্তরের এপিএস এবং প্রসঙ্গ অনুপস্থিতির জন্য প্রয়োজনীয়। - নোট-²: আপনি যদি রুট ম্যানেজমেন্ট ব্যবহার করেন তবেই এই ধাপটি প্রয়োজনীয় (`Get.to()`, `Get.back()` এবং অন্যান্য)। আপনি যদি এটি ব্যবহার না করেন তবে ধাপ-1 করার দরকার নেই - ধাপ 2: আপনার বিজনেস লজিক ক্লাস তৈরি করুন এবং এর ভিতরে সমস্ত ভেরিয়েবল, পদ্ধতি এবং কন্ট্রোলার রাখুন। আপনি একটি সাধারণ ".obs" ব্যবহার করে যেকোনো পরিবর্তনশীলকে পর্যবেক্ষণযোগ্য করতে পারেন। ```dart class Controller extends GetxController{ var count = 0.obs; increment() => count++; } ``` - ধাপ 3: আপনার ভিউ তৈরি করুন, স্টেটলেস উইজেট ব্যবহার করুন এবং কিছু র‌্যাম সেভ করুন, Get এর সাথে আপনাকে হয়তো আর StatefulWidget ব্যবহার করার প্রয়োজন হবে না। ```dart class Home extends StatelessWidget { @override Widget build(context) { // Instantiate your class using Get.put() to make it available for all "child" routes there. final Controller c = Get.put(Controller()); return Scaffold( // Use Obx(()=> to update Text() whenever count is changed. appBar: AppBar(title: Obx(() => Text("Clicks: ${c.count}"))), // Replace the 8 lines Navigator.push by a simple Get.to(). You don't need context body: Center(child: ElevatedButton( child: Text("Go to Other"), onPressed: () => Get.to(Other()))), floatingActionButton: FloatingActionButton(child: Icon(Icons.add), onPressed: c.increment)); } } class Other extends StatelessWidget { // You can ask Get to find a Controller that is being used by another page and redirect you to it. final Controller c = Get.find(); @override Widget build(context){ // Access the updated count variable return Scaffold(body: Center(child: Text("${c.count}"))); } } ``` # GetX এর তিনটি স্তম্ভ ## স্টেট ব্যবস্থাপনা Get দুই ভিন্ন স্টেট ম্যানেজার আছে: সাধারণ স্টেট ম্যানেজার (আমরা একে GetBuilder বলব) and প্রতিক্রিয়াশীল স্টেট ম্যানেজার (GetX/Obx) ### প্রতিক্রিয়াশীল স্টেট ম্যানেজার প্রতিক্রিয়াশীল প্রোগ্রামিং অনেক লোককে উদাসীন করতে পারে কারণ এটি জটিল। GetX প্রতিক্রিয়াশীল প্রোগ্রামিংকে বেশ সহজে পরিণত করে: - আপনাকে StreamControllers তৈরি করতে হবে না। - আপনাকে প্রতিটি ভেরিয়েবলের জন্য একটি StreamBuilder তৈরি করতে হবে না। - আপনাকে প্রতিটি স্টেটের জন্য একটি ক্লাস তৈরি করতে হবে না। - আপনাকে initial value এর জন্য get তৈরি করতে হবে না। - আপনাকে কোড জেনারেটর ব্যবহার করতে হবে না। Get এর সাথে প্রতিক্রিয়াশীল প্রোগ্রামিং setState ব্যবহার করার মতোই সহজ। কল্পনা করুন যে আপনার একটি নাম ভ্যারিয়েবল আছে এবং আপনি চান যে প্রতিবার আপনি এটি পরিবর্তন করবেন, এটি ব্যবহার করে এমন সমস্ত উইজেট স্বয়ংক্রিয়ভাবে পরিবর্তন করতে পারবেন। মনে করুন এটি আপনার নাম ভ্যারিয়েবল: ```dart var name = 'Ashiqur Rahman Alif'; ``` এটিকে observable করতে, আপনাকে এটির শেষে ".obs" যোগ করতে হবে: ```dart var name = 'Ashiqur Rahman Alif'.obs; ``` এবং UI-তে যখন আপনি সেই নামটি দেখাতে চান এবং যখনই মান পরিবর্তন হয় তখন স্ক্রীনটি আপডেট করতে চান, কেবল এটি করুন: ```dart Obx(() => Text("${controller.name}")); ``` এখানেই শেষ। এটা এমনই সহজ। ### স্টেট ব্যবস্থাপনা সম্পর্কে আরো বিস্তারিত **স্টেট পরিচালনার আরও ব্যাখ্যা দেখুন [এখানে](./documentation/en_US/state_management.md)। সেখানে আপনি আরও উদাহরণ দেখতে পাবেন এবং সাধারণ স্টেট ব্যবস্থাপক এবং প্রতিক্রিয়াশীল স্টেট ব্যবস্থাপকের মধ্যে পার্থক্যও দেখতে পাবেন** GetX পাওয়ার সম্পর্কে ভালো ধারণা পাবেন। ## রুট ব্যবস্থাপনা আপনি যদি context ছাড়াই রুট/স্ন্যাকবার/ডায়ালগ/বটমশীট ব্যবহার করতে চান, GetX আপনার জন্য, এটি দেখুন: আপনার MaterialApp এর আগে "Get" যোগ করুন, এটিকে GetMaterialApp এ পরিণত করুন ```dart GetMaterialApp( // Before: MaterialApp( home: MyHome(), ) ``` একটি নতুন স্ক্রিনে নেভিগেট করুন: ```dart Get.to(NextScreen()); ``` নাম সহ নতুন স্ক্রিনে নেভিগেট করুন। নামযুক্ত রুট সম্পর্কিত আরও বিস্তারিত বিবরণ দেখুন [এখানে](./documentation/en_US/route_management.md#navigation-with-named-routes) ```dart Get.toNamed('/details'); ``` স্ন্যাকবার, ডায়ালগ, বটমশীট, বা যেকোনো কিছু বন্ধ করতে আপনি Navigator.pop(context) এর পরিবর্তে ব্যবহার করুন: ```dart Get.back(); ``` পরবর্তী স্ক্রিনে যাওয়ার পর আগের স্ক্রিনে ফিরে যাওয়া বন্ধ করুন (স্প্ল্যাশস্ক্রিন, লগইন স্ক্রিন ইত্যাদিতে ব্যবহারের জন্য) ```dart Get.off(NextScreen()); ``` পরবর্তী স্ক্রিনে যেতে এবং আগের সমস্ত রুট বাতিল করতে (শপিং কার্ট, পোল ইত্যাদিতে ব্যবহারের জন্য) ```dart Get.offAll(NextScreen()); ``` লক্ষ্য করেছেন যে এই জিনিসগুলির কোনটি করার জন্য আপনাকে context ব্যবহার করতে হবে না? এটি Get রুট ম্যানেজমেন্ট ব্যবহার করার সবচেয়ে বড় সুবিধাগুলির মধ্যে একটি। এটির সাহায্যে, আপনি চিন্তা ছাড়াই আপনার controller class এর মধ্যে থেকে এই সমস্ত পদ্ধতিগুলি চালাতে পারেন। ### রুট ব্যবস্থাপনা সম্পর্কে আরো বিস্তারিত **রুট ব্যবস্থাপনা সম্পর্কে আরো বিস্তারিত ডকুমেন্টেশন আছে [এখানে](./documentation/en_US/route_management.md)** ## ডিপেনডেন্সি ব্যবস্থাপনা Get এর একটি সহজ এবং শক্তিশালী ডিপেনডেন্সি পরিচালক রয়েছে যা আপনাকে কোনও Provider context বা inheritedWidget ছাড়াই, মাত্র 1 লাইনের কোডের মাধ্যমে আপনার ব্লক বা কন্ট্রোলারের মতো একই class রিট্রিভ করতে দেয়: ```dart Controller controller = Get.put(Controller()); // Rather Controller controller = Controller(); ``` - দ্রষ্টব্য: আপনি যদি Get's State Manager ব্যবহার করেন, তাহলে bindings API-এ আরও মনোযোগ দিন, যা আপনার কন্ট্রোলারের সাথে আপনার ভিউকে সংযোগ করা সহজ করে তুলবে। আপনি যে ক্লাসটি ব্যবহার করছেন তার মধ্যে আপনার ক্লাসকে ইনস্ট্যান্টিয়েট করার পরিবর্তে Get ইনস্ট্যান্সের মধ্যে ইনস্ট্যান্টিয়েট করুন, যা এটিকে আপনার অ্যাপ জুড়ে উপলব্ধ করবে। তখন আপনি স্বাভাবিকভাবে আপনার controller (বা class Bloc) ব্যবহার করতে পারবেন। **টিপ:** Get ডিপেন্ডেন্সি ম্যানেজমেন্ট প্যাকেজের অন্যান্য অংশ থেকে ডিকপল করা হয়েছে, উদাহরণ স্বরূপ, আপনার অ্যাপ ইতিমধ্যেই একটি স্টেট ম্যানেজার ব্যবহার করে (যেকোনোটি হতে পারে, এটা কোন ব্যাপার না), তবে আপনার পুনরায় সব লেখার দরকার নেই, আপনি কোনও সমস্যা ছাড়াই এই ডিপেন্ডেন্সি ইনজেকশনটি ব্যবহার করতে পারেন। ```dart controller.fetchApi(); ``` কল্পনা করুন যে আপনি অসংখ্য রুটে নেভিগেট করেছেন, এবং আপনার controller এর পিছনে ফেলে আসা ডেটার প্রয়োজন, আপনার প্রোভাইডার বা Get_it এর সাথে মিলিত একটি স্টেট ম্যানেজারের প্রয়োজন হবে, তাই না? Get এর সাথে তা প্রয়োজন নেই। আপনাকে শুধু আপনার controller এর জন্য "find" জিজ্ঞাসা করতে হবে, আপনার কোনো অতিরিক্ত ডিপেন্ডেন্সি প্রয়োজন নেই: ```dart Controller controller = Get.find(); //Yes, it looks like Magic, Get will find your controller, and will deliver it to you. You can have 1 million controllers instantiated, Get will always give you the right controller. ``` এবং তারপরে আপনি আপনার নিয়ামক ডেটা পুনরুদ্ধার করতে সক্ষম হবেন যা সেখানে ফিরে প্রাপ্ত হয়েছিল: ```dart Text(controller.textFromApi); ``` ### ডিপেনডেন্সি ব্যবস্থাপনা সম্পর্কে আরো বিস্তারিত **ডিপেনডেন্সি ব্যবস্থাপনা সম্পর্কে আরো বিস্তারিত ব্যাখ্যা দেখুন [এখানে](./documentation/en_US/dependency_management.md)** # ইউটিলিটি ## আন্তর্জাতিকীকরণ ### অনুবাদ অনুবাদগুলি একটি সাধারণ কী-মানের অভিধান মানচিত্র হিসাবে রাখা হয়। To add custom translations, create a class and extend `Translations`. কাস্টম অনুবাদ যোগ করতে, একটি class তৈরি করুন এবং `Translations` এ extends করুন। ```dart import 'package:get/get.dart'; class Messages extends Translations { @override Map> get keys => { 'en_US': { 'hello': 'Hello World', }, 'de_DE': { 'hello': 'Hallo Welt', } }; } ``` #### অনুবাদের ব্যবহার শুধুমাত্র নির্দিষ্ট key তে `.tr` যোগ করুন এবং এটি অনুবাদ করা হবে, `Get.locale` এবং `Get.fallbackLocale` এর বর্তমান মান ব্যবহার করে। ```dart Text('title'.tr); ``` #### একবচন এবং বহুবচন সহ অনুবাদ ব্যবহার ```dart var products = []; Text('singularKey'.trPlural('pluralKey', products.length, Args)); ``` #### প্যারামিটার সহ অনুবাদ ব্যবহার ```dart import 'package:get/get.dart'; Map> get keys => { 'en_US': { 'logged_in': 'logged in as @name with email @email', }, 'es_ES': { 'logged_in': 'iniciado sesión como @name con e-mail @email', } }; Text('logged_in'.trParams({ 'name': 'Jhon', 'email': 'jhon@example.com' })); ``` ### লোকেল লোকেল এবং অনুবাদ সংজ্ঞায়িত করতে `GetMaterialApp`-এ প্যারামিটার পাস করুন। ```dart return GetMaterialApp( translations: Messages(), // your translations locale: Locale('en', 'US'), // translations will be displayed in that locale fallbackLocale: Locale('en', 'UK'), // specify the fallback locale in case an invalid locale is selected. ); ``` #### লোকেল পরিবর্তন করুন লোকেল আপডেট করতে `Get.updateLocale(locale)` কল করুন। অনুবাদগুলি তখন স্বয়ংক্রিয়ভাবে নতুন লোকেল ব্যবহার করে। ```dart var locale = Locale('en', 'US'); Get.updateLocale(locale); ``` #### লোকেল পদ্ধতি সিস্টেম লোকেল পড়তে, আপনি `Get.deviceLocale` ব্যবহার করতে পারেন। ```dart return GetMaterialApp( locale: Get.deviceLocale, ); ``` ## থিম পরিবর্তন করুন Please do not use any higher level widget than `GetMaterialApp` in order to update it. This can trigger duplicate keys. A lot of people are used to the prehistoric approach of creating a "ThemeProvider" widget just to change the theme of your app, and this is definitely NOT necessary with **GetX™**. You can create your custom theme and simply add it within `Get.changeTheme` without any boilerplate for that: ```dart Get.changeTheme(ThemeData.light()); ``` If you want to create something like a button that changes the Theme in `onTap`, you can combine two **GetX™** APIs for that: - The api that checks if the dark `Theme` is being used. - And the `Theme` Change API, you can just put this within an `onPressed`: ```dart Get.changeTheme(Get.isDarkMode? ThemeData.light(): ThemeData.dark()); ``` When `.darkmode` is activated, it will switch to the _light theme_, and when the _light theme_ becomes active, it will change to _dark theme_. ## গেট কানেক্ট (GetConnect) GetConnect is an easy way to communicate from your back to your front with http or websockets ### ডিফল্ট কনফিগারেশন You can simply extend GetConnect and use the GET/POST/PUT/DELETE/SOCKET methods to communicate with your Rest API or websockets. ```dart class UserProvider extends GetConnect { // Get request Future getUser(int id) => get('http://youapi/users/$id'); // Post request Future postUser(Map data) => post('http://youapi/users', body: data); // Post request with File Future> postCases(List image) { final form = FormData({ 'file': MultipartFile(image, filename: 'avatar.png'), 'otherFile': MultipartFile(image, filename: 'cover.png'), }); return post('http://youapi/users/upload', form); } GetSocket userMessages() { return socket('https://yourapi/users/socket'); } } ``` ### কাস্টম কনফিগারেশন GetConnect is highly customizable You can define base Url, as answer modifiers, as Requests modifiers, define an authenticator, and even the number of attempts in which it will try to authenticate itself, in addition to giving the possibility to define a standard decoder that will transform all your requests into your Models without any additional configuration. ```dart class HomeProvider extends GetConnect { @override void onInit() { // All request will pass to jsonEncode so CasesModel.fromJson() httpClient.defaultDecoder = CasesModel.fromJson; httpClient.baseUrl = 'https://api.covid19api.com'; // baseUrl = 'https://api.covid19api.com'; // It define baseUrl to // Http and websockets if used with no [httpClient] instance // It's will attach 'apikey' property on header from all requests httpClient.addRequestModifier((request) { request.headers['apikey'] = '12345678'; return request; }); // Even if the server sends data from the country "Brazil", // it will never be displayed to users, because you remove // that data from the response, even before the response is delivered httpClient.addResponseModifier((request, response) { CasesModel model = response.body; if (model.countries.contains('Brazil')) { model.countries.remove('Brazilll'); } }); httpClient.addAuthenticator((request) async { final response = await get("http://yourapi/token"); final token = response.body['token']; // Set the header request.headers['Authorization'] = "$token"; return request; }); //Autenticator will be called 3 times if HttpStatus is //HttpStatus.unauthorized httpClient.maxAuthRetries = 3; } @override Future> getCases(String path) => get(path); } ``` ## গেট পেজ মিডিলওয়্যার The GetPage has now new property that takes a list of GetMiddleWare and run them in the specific order. **Note**: When GetPage has a Middlewares, all the children of this page will have the same middlewares automatically. ### অগ্রাধিকার (Priority) The Order of the Middlewares to run can be set by the priority in the GetMiddleware. ```dart final middlewares = [ GetMiddleware(priority: 2), GetMiddleware(priority: 5), GetMiddleware(priority: 4), GetMiddleware(priority: -8), ]; ``` those middlewares will be run in this order **-8 => 2 => 4 => 5** ### পুনঃনির্দেশ (Redirect) This function will be called when the page of the called route is being searched for. It takes RouteSettings as a result to redirect to. Or give it null and there will be no redirecting. ```dart RouteSettings redirect(String route) { final authService = Get.find(); return authService.authed.value ? null : RouteSettings(name: '/login') } ``` ### অন-পেজ কলড (onPageCalled) This function will be called when this Page is called before anything created you can use it to change something about the page or give it new page ```dart GetPage onPageCalled(GetPage page) { final authService = Get.find(); return page.copyWith(title: 'Welcome ${authService.UserName}'); } ``` ### অন-বাইন্ডিং স্টার্ট (OnBindingsStart) This function will be called right before the Bindings are initialize. Here you can change Bindings for this page. ```dart List onBindingsStart(List bindings) { final authService = Get.find(); if (authService.isAdmin) { bindings.add(AdminBinding()); } return bindings; } ``` ### অন-পেজ বিল্ড স্টার্ট (OnPageBuildStart) This function will be called right after the Bindings are initialize. Here you can do something after that you created the bindings and before creating the page widget. ```dart GetPageBuilder onPageBuildStart(GetPageBuilder page) { print('bindings are ready'); return page; } ``` ### অন-পেজ বিল্ড (OnPageBuilt) This function will be called right after the GetPage.page function is called and will give you the result of the function. and take the widget that will be showed. ### অন-পেজ ডিসপোজ (OnPageDispose) This function will be called right after disposing all the related objects (Controllers, views, ...) of the page. ## অন্যান্য এপিআই সমূহ ```dart // give the current args from currentScreen Get.arguments // give name of previous route Get.previousRoute // give the raw route to access for example, rawRoute.isFirst() Get.rawRoute // give access to Routing API from GetObserver Get.routing // check if snackbar is open Get.isSnackbarOpen // check if dialog is open Get.isDialogOpen // check if bottomsheet is open Get.isBottomSheetOpen // remove one route. Get.removeRoute() // back repeatedly until the predicate returns true. Get.until() // go to next route and remove all the previous routes until the predicate returns true. Get.offUntil() // go to next named route and remove all the previous routes until the predicate returns true. Get.offNamedUntil() //Check in what platform the app is running GetPlatform.isAndroid GetPlatform.isIOS GetPlatform.isMacOS GetPlatform.isWindows GetPlatform.isLinux GetPlatform.isFuchsia //Check the device type GetPlatform.isMobile GetPlatform.isDesktop //All platforms are supported independently in web! //You can tell if you are running inside a browser //on Windows, iOS, OSX, Android, etc. GetPlatform.isWeb // Equivalent to : MediaQuery.of(context).size.height, // but immutable. Get.height Get.width // Gives the current context of the Navigator. Get.context // Gives the context of the snackbar/dialog/bottomsheet in the foreground, anywhere in your code. Get.contextOverlay // Note: the following methods are extensions on context. Since you // have access to context in any place of your UI, you can use it anywhere in the UI code // If you need a changeable height/width (like Desktop or browser windows that can be scaled) you will need to use context. context.width context.height // Gives you the power to define half the screen, a third of it and so on. // Useful for responsive applications. // param dividedBy (double) optional - default: 1 // param reducedBy (double) optional - default: 0 context.heightTransformer() context.widthTransformer() /// Similar to MediaQuery.of(context).size context.mediaQuerySize() /// Similar to MediaQuery.of(context).padding context.mediaQueryPadding() /// Similar to MediaQuery.of(context).viewPadding context.mediaQueryViewPadding() /// Similar to MediaQuery.of(context).viewInsets; context.mediaQueryViewInsets() /// Similar to MediaQuery.of(context).orientation; context.orientation() /// Check if device is on landscape mode context.isLandscape() /// Check if device is on portrait mode context.isPortrait() /// Similar to MediaQuery.of(context).devicePixelRatio; context.devicePixelRatio() /// Similar to MediaQuery.of(context).textScaleFactor; context.textScaleFactor() /// Get the shortestSide from screen context.mediaQueryShortestSide() /// True if width be larger than 800 context.showNavbar() /// True if the shortestSide is smaller than 600p context.isPhone() /// True if the shortestSide is largest than 600p context.isSmallTablet() /// True if the shortestSide is largest than 720p context.isLargeTablet() /// True if the current device is Tablet context.isTablet() /// Returns a value according to the screen size /// can give value for: /// watch: if the shortestSide is smaller than 300 /// mobile: if the shortestSide is smaller than 600 /// tablet: if the shortestSide is smaller than 1200 /// desktop: if width is largest than 1200 context.responsiveValue() ``` ### ঐচ্ছিক গ্লোবাল সেটিংস এবং ম্যানুয়াল কনফিগারেশন GetMaterialApp configures everything for you, but if you want to configure Get manually. ```dart MaterialApp( navigatorKey: Get.key, navigatorObservers: [GetObserver()], ); ``` You will also be able to use your own Middleware within `GetObserver`, this will not influence anything. ```dart MaterialApp( navigatorKey: Get.key, navigatorObservers: [ GetObserver(MiddleWare.observer) // Here ], ); ``` You can create _Global Settings_ for `Get`. Just add `Get.config` to your code before pushing any route. Or do it directly in your `GetMaterialApp` ```dart GetMaterialApp( enableLog: true, defaultTransition: Transition.fade, opaqueRoute: Get.isOpaqueRouteDefault, popGesture: Get.isPopGestureEnable, transitionDuration: Get.defaultDurationTransition, defaultGlobalState: Get.defaultGlobalState, ); Get.config( enableLog = true, defaultPopGesture = true, defaultTransition = Transitions.cupertino ) ``` You can optionally redirect all the logging messages from `Get`. If you want to use your own, favourite logging package, and want to capture the logs there: ```dart GetMaterialApp( enableLog: true, logWriterCallback: localLogWriter, ); void localLogWriter(String text, {bool isError = false}) { // pass the message to your favourite logging package here // please note that even if enableLog: false log messages will be pushed in this callback // you get check the flag if you want through GetConfig.isLogEnable } ``` ### লোকাল স্টেট উইজেট These Widgets allows you to manage a single value, and keep the state ephemeral and locally. We have flavours for Reactive and Simple. For instance, you might use them to toggle obscureText in a `TextField`, maybe create a custom Expandable Panel, or maybe modify the current index in `BottomNavigationBar` while changing the content of the body in a `Scaffold`. #### ভ্যালু বিল্ডার (ValueBuilder) A simplification of `StatefulWidget` that works with a `.setState` callback that takes the updated value. ```dart ValueBuilder( initialValue: false, builder: (value, updateFn) => Switch( value: value, onChanged: updateFn, // same signature! you could use ( newValue ) => updateFn( newValue ) ), // if you need to call something outside the builder method. onUpdate: (value) => print("Value updated: $value"), onDispose: () => print("Widget unmounted"), ), ``` #### অবএক্সভ্যালু (ObxValue) Similar to [`ValueBuilder`](#valuebuilder), but this is the Reactive version, you pass a Rx instance (remember the magical .obs?) and updates automatically... isn't it awesome? ```dart ObxValue((data) => Switch( value: data.value, onChanged: data, // Rx has a _callable_ function! You could use (flag) => data.value = flag, ), false.obs, ), ``` ## প্রয়োজনীয় পরামর্শ `.obs`ervables (also known as _Rx_ Types) have a wide variety of internal methods and operators. > Is very common to _believe_ that a property with `.obs` **IS** the actual value... but make no mistake! > We avoid the Type declaration of the variable, because Dart's compiler is smart enough, and the code > looks cleaner, but: ```dart var message = 'Hello world'.obs; print( 'Message "$message" has Type ${message.runtimeType}'); ``` Even if `message` _prints_ the actual String value, the Type is **RxString**! So, you can't do `message.substring( 0, 4 )`. You have to access the real `value` inside the _observable_: The most "used way" is `.value`, but, did you know that you can also use... ```dart final name = 'GetX'.obs; // only "updates" the stream, if the value is different from the current one. name.value = 'Hey'; // All Rx properties are "callable" and returns the new value. // but this approach does not accepts `null`, the UI will not rebuild. name('Hello'); // is like a getter, prints 'Hello'. name() ; /// numbers: final count = 0.obs; // You can use all non mutable operations from num primitives! count + 1; // Watch out! this is only valid if `count` is not final, but var count += 1; // You can also compare against values: count > 2; /// booleans: final flag = false.obs; // switches the value between true/false flag.toggle(); /// all types: // Sets the `value` to null. flag.nil(); // All toString(), toJson() operations are passed down to the `value` print( count ); // calls `toString()` inside for RxInt final abc = [0,1,2].obs; // Converts the value to a json Array, prints RxList // Json is supported by all Rx types! print('json: ${jsonEncode(abc)}, type: ${abc.runtimeType}'); // RxMap, RxList and RxSet are special Rx types, that extends their native types. // but you can work with a List as a regular list, although is reactive! abc.add(12); // pushes 12 to the list, and UPDATES the stream. abc[3]; // like Lists, reads the index 3. // equality works with the Rx and the value, but hashCode is always taken from the value final number = 12.obs; print( number == 12 ); // prints > true /// Custom Rx Models: // toJson(), toString() are deferred to the child, so you can implement override on them, and print() the observable directly. class User { String name, last; int age; User({this.name, this.last, this.age}); @override String toString() => '$name $last, $age years old'; } final user = User(name: 'John', last: 'Doe', age: 33).obs; // `user` is "reactive", but the properties inside ARE NOT! // So, if we change some variable inside of it... user.value.name = 'Roi'; // The widget will not rebuild!, // `Rx` don't have any clue when you change something inside user. // So, for custom classes, we need to manually "notify" the change. user.refresh(); // or we can use the `update()` method! user.update((value){ value.name='Roi'; }); print( user ); ``` ## StateMixin Another way to handle your `UI` state is use the `StateMixin` . To implement it, use the `with` to add the `StateMixin` to your controller which allows a T model. ``` dart class Controller extends GetController with StateMixin{} ``` The `change()` method change the State whenever we want. Just pass the data and the status in this way: ```dart change(data, status: RxStatus.success()); ``` RxStatus allow these status: ``` dart RxStatus.loading(); RxStatus.success(); RxStatus.empty(); RxStatus.error('message'); ``` To represent it in the UI, use: ```dart class OtherClass extends GetView { @override Widget build(BuildContext context) { return Scaffold( body: controller.obx( (state)=>Text(state.name), // here you can put your custom loading indicator, but // by default would be Center(child:CircularProgressIndicator()) onLoading: CustomLoadingIndicator(), onEmpty: Text('No data found'), // here also you can set your own error widget, but by // default will be an Center(child:Text(error)) onError: (error)=>Text(error), ), ); } ``` #### গেট ভিউ (GetView) I love this Widget, is so simple, yet, so useful! Is a `const Stateless` Widget that has a getter `controller` for a registered `Controller`, that's all. ```dart class AwesomeController extends GetController { final String title = 'My Awesome View'; } // ALWAYS remember to pass the `Type` you used to register your controller! class AwesomeView extends GetView { @override Widget build(BuildContext context) { return Container( padding: EdgeInsets.all(20), child: Text(controller.title), // just call `controller.something` ); } } ``` #### গেট রেস্পন্সিভ ভিউ (GetResponsiveView) Extend this widget to build responsive view. this widget contains the `screen` property that have all information about the screen size and type. ##### কিভাবে এটি ব্যবহার করতে হয় You have two options to build it. - with `builder` method you return the widget to build. - with methods `desktop`, `tablet`,`phone`, `watch`. the specific method will be built when the screen type matches the method when the screen is [ScreenType.Tablet] the `tablet` method will be exuded and so on. **Note:** If you use this method please set the property `alwaysUseBuilder` to `false` With `settings` property you can set the width limit for the screen types. ![example](https://github.com/SchabanBo/get_page_example/blob/master/docs/Example.gif?raw=true) Code to this screen [code](https://github.com/SchabanBo/get_page_example/blob/master/lib/pages/responsive_example/responsive_view.dart) #### গেট উইজেট (GetWidget) Most people have no idea about this Widget, or totally confuse the usage of it. The use case is very rare, but very specific: It `caches` a Controller. Because of the _cache_, can't be a `const Stateless`. > So, when do you need to "cache" a Controller? If you use, another "not so common" feature of **GetX**: `Get.create()`. `Get.create(()=>Controller())` will generate a new `Controller` each time you call `Get.find()`, That's where `GetWidget` shines... as you can use it, for example, to keep a list of Todo items. So, if the widget gets "rebuilt", it will keep the same controller instance. #### গেট এক্স সার্ভিস (GetxService) This class is like a `GetxController`, it shares the same lifecycle ( `onInit()`, `onReady()`, `onClose()`). But has no "logic" inside of it. It just notifies **GetX** Dependency Injection system, that this subclass **can not** be removed from memory. So is super useful to keep your "Services" always reachable and active with `Get.find()`. Like: `ApiService`, `StorageService`, `CacheService`. ```dart Future main() async { await initServices(); /// AWAIT SERVICES INITIALIZATION. runApp(SomeApp()); } /// Is a smart move to make your Services intiialize before you run the Flutter app. /// as you can control the execution flow (maybe you need to load some Theme configuration, /// apiKey, language defined by the User... so load SettingService before running ApiService. /// so GetMaterialApp() doesnt have to rebuild, and takes the values directly. void initServices() async { print('starting services ...'); /// Here is where you put get_storage, hive, shared_pref initialization. /// or moor connection, or whatever that's async. await Get.putAsync(() => DbService().init()); await Get.putAsync(SettingsService()).init(); print('All services started...'); } class DbService extends GetxService { Future init() async { print('$runtimeType delays 2 sec'); await 2.delay(); print('$runtimeType ready!'); return this; } } class SettingsService extends GetxService { void init() async { print('$runtimeType delays 1 sec'); await 1.delay(); print('$runtimeType ready!'); } } ``` The only way to actually delete a `GetxService`, is with `Get.reset()` which is like a "Hot Reboot" of your app. So remember, if you need absolute persistence of a class instance during the lifetime of your app, use `GetxService`. ### পরীক্ষা (Tests) You can test your controllers like any other class, including their lifecycles: ```dart class Controller extends GetxController { @override void onInit() { super.onInit(); //Change value to name2 name.value = 'name2'; } @override void onClose() { name.value = ''; super.onClose(); } final name = 'name1'.obs; void changeName() => name.value = 'name3'; } void main() { test(''' Test the state of the reactive variable "name" across all of its lifecycles''', () { /// You can test the controller without the lifecycle, /// but it's not recommended unless you're not using /// GetX dependency injection final controller = Controller(); expect(controller.name.value, 'name1'); /// If you are using it, you can test everything, /// including the state of the application after each lifecycle. Get.put(controller); // onInit was called expect(controller.name.value, 'name2'); /// Test your functions controller.changeName(); expect(controller.name.value, 'name3'); /// onClose was called Get.delete(); expect(controller.name.value, ''); }); } ``` #### পরামর্শ ##### Mockito or mocktail If you need to mock your GetxController/GetxService, you should extend GetxController, and mixin it with Mock, that way ```dart class NotificationServiceMock extends GetxService with Mock implements NotificationService {} ``` ##### Using Get.reset() If you are testing widgets, or test groups, use Get.reset at the end of your test or in tearDown to reset all settings from your previous test. ##### Get.testMode if you are using your navigation in your controllers, use `Get.testMode = true` at the beginning of your main. # 2.0 থেকে পরিবর্তন 1- Rx এর প্রকারভেদ : | পূর্বে | এখন | | ------- | ---------- | | StringX | `RxString` | | IntX | `RxInt` | | MapX | `RxMap` | | ListX | `RxList` | | NumX | `RxNum` | | DoubleX | `RxDouble` | RxController এবং GetBuilder এখন একত্রিত, আপনি কোন নিয়ামক ব্যবহার করতে চান তা আর মুখস্ত করার দরকার নেই, শুধু GetxController ব্যবহার করুন, এটি সাধারণ স্টেট ব্যবস্থাপনা এবং প্রতিক্রিয়াশীল স্টেট ব্যবস্থাপনা এর জন্যও কাজ করবে। 2- নেমড রুটস (NamedRoutes) আগে: ```dart GetMaterialApp( namedRoutes: { '/': GetRoute(page: Home()), } ) ``` এখন: ```dart GetMaterialApp( getPages: [ GetPage(name: '/', page: () => Home()), ] ) ``` কেন এই পরিবর্তন? প্রায়শই, কোন পৃষ্ঠাটি প্যারামিটার বা লগইন টোকেন থেকে প্রদর্শিত হবে তা নির্ধারণ করার প্রয়োজন হতে পারে, পূর্ববর্তী পদ্ধতিটি অনমনীয় ছিল, কারণ এটি সেই অনুমতি দিতো না। এটি ফাংশনে পৃষ্ঠাটি ইনসার্ট করার জন্য উল্লেখযোগ্যভাবে RAM খরচ হ্রাস করেছে, যেহেতু অ্যাপটি শুরু হওয়ার পর থেকে রুটগুলি মেমরিতে বরাদ্দ করা হবে না, এটি এই ধরণের পদ্ধতিরও অনুমতি দেয়: ```dart GetStorage box = GetStorage(); GetMaterialApp( getPages: [ GetPage(name: '/', page:(){ return box.hasData('token') ? Home() : Login(); }) ] ) ``` ================================================ FILE: README-es.md ================================================ ![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/get.png) *Idiomas: Español (este archivo), [Vietnamita](README-vi.md), [Indonesio](README.id-ID.md), [Urdu](README.ur-PK.md), [Lengua china](README.zh-cn.md), [Inglés](README.md), [Portugués de Brasil](README.pt-br.md), [Ruso](README.ru.md), [Polaco](README.pl.md), [Coreano](README.ko-kr.md), [Francés](README-fr.md).* [![pub package](https://img.shields.io/pub/v/get.svg?label=get&color=blue)](https://pub.dev/packages/get) [![popularity](https://badges.bar/get/popularity)](https://pub.dev/packages/sentry/score) [![likes](https://badges.bar/get/likes)](https://pub.dev/packages/get/score) [![pub points](https://badges.bar/get/pub%20points)](https://pub.dev/packages/get/score) ![building](https://github.com/jonataslaw/get/workflows/build/badge.svg) [![style: effective dart](https://img.shields.io/badge/style-effective_dart-40c4ff.svg)](https://pub.dev/packages/effective_dart) [![Discord Shield](https://img.shields.io/discord/722900883784073290.svg?logo=discord)](https://discord.com/invite/9Hpt99N) [![Get on Slack](https://img.shields.io/badge/slack-join-orange.svg)](https://communityinviter.com/apps/getxworkspace/getx) [![Telegram](https://img.shields.io/badge/chat-on%20Telegram-blue.svg)](https://t.me/joinchat/PhdbJRmsZNpAqSLJL6bH7g) Awesome Flutter Buy Me A Coffee ![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/getx.png)

Lamentamos la inconsistencia en la traducción. El paquete GetX se actualiza con bastante frecuencia y es posible que las traducciones a documentos no sean tan rápidas. Entonces, para que esta documentación aún tenga todo el contenido, dejaré aquí todos los textos nuevos sin traducir (considero que es mejor tener los documentos en inglés que no tenerlos), por lo que si alguien quiere traducir, sería de gran ayuda 😁

- [Sobre GetX](#sobre-getx) - [Instalación](#instalación) - [Proyecto contador con GetX](#proyecto-contador-con-getx) - [Los tres pilares](#los-tres-pilares) - [Gestión del Estado](#gestión-del-estado) - [Reactivo STATE_MANAGER](#reactivo-state_manager) - [Más detalles sobre la gestión del estado.](#más-detalles-sobre-la-gestión-del-estado) - [Explicación en video sobre state management](#explicación-en-video-sobre-state-management) - [Gestión de Rutas](#gestión-de-rutas) - [Más detalles sobre la gestión de rutas.](#más-detalles-sobre-la-gestión-de-rutas) - [Explicación del video](#explicación-del-video) - [Gestión de dependencias](#gestión-de-dependencias) - [Más detalles sobre la gestión de dependencias.](#más-detalles-sobre-la-gestión-de-dependencias) - [Utilidades](#utilidades) - [Cambiar de tema](#cambiar-de-tema) - [Otras API avanzadas y configuraciones manuales](#otras-api-avanzadas-y-configuraciones-manuales) - [Configuraciones globales opcionales](#configuraciones-globales-opcionales) - [Video explanation of Other GetX Features](#video-explanation-of-other-getx-features) - [Cambios importantes desde 2.0](#cambios-importantes-desde-20) - [¿Por qué Getx?](#por-qué-getx) - [Comunidad](#comunidad) - [Canales de la comunidad](#canales-de-la-comunidad) - [Cómo contribuir](#cómo-contribuir) - [Artículos y vídeos](#artículos-y-vídeos-inglés) # Sobre GetX - GetX es una solución extra ligera y potente para Flutter. Combina gestión de estádo de alto rendimiento, inyección de dependencia inteligente y gestión de rutas de forma rápida y práctica. - GetX tiene 3 principios básicos, esto significa que esta es la prioridad para todos los recursos de la biblioteca. - **RENDIMIENTO:** GetX se centra en el rendimiento y el consumo mínimo de recursos. Los puntos de referencia casi siempre no son importantes en el mundo real, pero si lo desea, aquí hay un indicador de consumo.([benchmarks](https://github.com/jonataslaw/benchmarks)), donde GetX lo hace mejor que otros enfoques de gestión estatal, por ejemplo. La diferencia no es grande, pero muestra nuestra preocupación por no desperdiciar sus recursos. - **PRODUCTIVIDAD:** GetX utiliza una sintaxis fácil y agradable. - **ORGANIZACIÓN:** GetX permite el desacoplamiento total de la vista de la lógica de negocio. * GetX ahorrará horas de desarrollo y extraerá el máximo rendimiento que su aplicación puede ofrecer, siendo fácil para los principiantes y precisa para los expertos. Navega sin contexto, abre diálogos, snackbars o bottomsheets desde cualquier lugar de tu código, gestiona estados e inyecta dependencias de forma fácil y práctica. Get es seguro, estable, actualizado y ofrece una amplia gama de API que no están presentes en el marco predeterminado. - GetX no es bloated. Tiene una multitud de características que le permiten comenzar a programar sin preocuparse por nada, pero cada una de estas características se encuentran en contenedores separados y solo se inician después de su uso. Si solo usa State Management, solo se compilará State Management. Si solo usa rutas, no se compilará nada de la administración estatal. Puede compilar el repositorio de referencia y verá que al usar solo la administración de estado de Get, la aplicación compilada con Get se ha vuelto más pequeña que todas las demás aplicaciones que solo tienen la administración de estado de otros paquetes, porque nada que no se use se compilará en su código, y cada solución GetX fue diseñada para ser muy liviana. El mérito aquí también proviene del movimiento del árbol de Flutter, que es increíble y logra eliminar los recursos no utilizados como ningún otro marco lo hace. **GetX hace que su desarrollo sea productivo, pero ¿quiere hacerlo aún más productivo? [Agregue la extensión a su VSCode](https://marketplace.visualstudio.com/items?itemName=get-snippets.get-snippets)** # Instalación Añada la librería Get en tu archivo pubspec.yaml: ```yaml dependencies: get: ``` Importe Get en los archivos en los que se utilizará: ```dart import 'package:get/get.dart'; ``` # Proyecto Contador con GetX Vea una explicación más detallada de la administración del estado [aquí](./documentation/es_ES/state_management.md). Allí verá más ejemplos y también la diferencia entre el Gestión del Estado simple y el Gestión del Estado reactivo El proyecto "contador" creado por defecto en un nuevo proyecto en Flutter tiene más de 100 líneas (con comentarios). Para mostrar el poder de GetX, demostraré cómo hacer un "contador" cambiando el estado con cada clic, cambiando de página y compartiendo el estado entre pantallas, todo de manera organizada, separando la vista de la lógica de negocio, SOLO 26 LÍNEAS DE CÓDIGO INCLUIDOS COMENTARIOS. - Paso 1: Agregue "Get" antes de su materialApp, convirtiéndolo en GetMaterialApp ```dart void main() => runApp(GetMaterialApp(home: Home())); ``` **Nota**: esto no modifica el MaterialApp del Flutter, GetMaterialApp no es una MaterialApp modificado, es solo un Widget preconfigurado que tiene como child un MaterialApp por defecto. Puede configurar esto manualmente, pero definitivamente no es necesario. GetMaterialApp creará rutas, las inyectará, inyectará traducciones, inyectará todo lo que necesita para la navegación de rutas. Si usa Get solo para la gestión de estado o dependencias, no es necesario usar GetMaterialApp. GetMaterialApp es necesario para rutas, snackbars, internacionalización, bottomSheets, diálogos y APIs de alto nivel relacionadas con rutas y ausencia de contexto. **Nota²:** Este paso solo es necesario si va a usar route management (`Get.to()`, `Get.back()` y así). Si no lo va a usar, no es necesario que realice el paso 1 - Paso 2: Cree su clase con la lógica de negocio colocando todas las variables, métodos y controladores dentro de ella. Puede hacer que cualquier variable sea observable usando un simple ".obs". ```dart class Controller extends GetxController { var count = 0.obs; increment() => count.value++; } ``` - Paso 3: Cree su vista, use StatelessWidget y ahorre algo de RAM, con GetX ya no necesitará usar StatefulWidget. ```dart class Home extends StatelessWidget { @override Widget build(context) { // Cree una instancia de su clase usando Get.put() para que esté disponible para todas las rutas "secundarias" allí. final Controller c = Get.put(Controller()); return Scaffold( // Utilice Obx(()=> para actualizar Text() siempre que se cambie el recuento. appBar: AppBar(title: Obx(() => Text("Clicks: " + c.count.string))), // Reemplace el Navigator.push de 8 líneas por un simple Get.to(). No necesitas contexto body: Center(child: ElevatedButton( child: Text("Go to Other"), onPressed: () => Get.to(Other()))), floatingActionButton: FloatingActionButton(child: Icon(Icons.add), onPressed: c.increment)); } } class Other extends StatelessWidget { // Puede pedirle a Get que busque un controlador que está siendo utilizado por otra página y le redirija a él. final Controller c = Get.find(); @override Widget build(context){ // Acceder a la variable de recuento actualizada return Scaffold(body: Center(child: Text(c.count.string))); } } ``` Resultado: ![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/counter-app-gif.gif) Este es un proyecto simple pero ya deja en claro cuán poderoso es GetX. A medida que su proyecto crezca, esta diferencia se volverá más significativa. GetX fue diseñado para trabajar con equipos, pero también simplifica el trabajo de un desarrollador individual. Mejore sus plazos, entregue todo a tiempo, sin perder rendimiento. GetX no es para todos, pero si te identificaste con esa frase, ¡GET es para ti! # Los tres pilares ## Gestión del Estado Actualmente hay varios State Managers para Flutter. Sin embargo, con la mayoría de ellos implica utilizar ChangeNotifier para actualizar widgets y este es un enfoque malo y muy malo para el rendimiento de aplicaciones medianas o grandes. Puede verificar en la documentación oficial de Flutter que [ChangeNotifier debe usarse con 1 o un máximo de 2 listeners](https://api.Flutter.dev/Flutter/foundation/ChangeNotifier-class.html), por lo que es prácticamente inutilizable para cualquier aplicación mediana o grande. GetX no es mejor ni peor que cualquier otro gestor de estado, pero debe analizar estos puntos, así como los puntos que se mencionan a continuación, para elegir entre usar GetX en forma pura (vanilla) o usarlo junto con otro gestor de estado. Definitivamente, GetX no es enemigo de ningún otro gestor de estado, porque GetX es más bien un microframework, no solo un gestor de estado, y se puede usar solo o en combinación con ellos. ### Reactivo STATE_MANAGER La programación reactiva puede alienar a muchas personas porque se dice que es complicada. GetX convierte la programación reactiva en algo tan simple que puede ser aprendido y utilizado por aquellos que comenzaron en ese mismo momento en Flutter. No, no necesitará crear StreamControllers. Tampoco necesitará crear un StreamBuilder para cada variable. No necesitará crear una clase para cada estado. No necesitará crear un get para un valor inicial. La programación reactiva con GetX es tan fácil como usar setState (¡o incluso más fácil!). Imaginemos que tiene una variable "name" y desea que cada vez que la modifique, todos los widgets que la usan cambien automáticamente. Ej. esta es tu variable "name": ```dart var name = 'Jonatas Borges'; ``` Para que sea observable, solo necesita agregar ".obs" al final: ```dart var name = 'Jonatas Borges'.obs; ``` ¿StreamBuilder? ¿initialValue? ¿builder? No, solo necesitas jugar con esta variable dentro de un widget Obx. ```dart Obx(() => Text (controller.name)); ``` ### Más detalles sobre la gestión del estado. **Vea una explicación más detallada de la administración del estado [aquí](./documentation/es_ES/state_management.md). Allí verá más ejemplos y también la diferencia entre el Gestión del Estado simple y el Gestión del Estado reactivo** ### Explicación en video sobre state management Darwin Morocho hizo una increíble serie de videos sobre state management! Link: [Complete GetX State Management](https://www.youtube.com/watch?v=PTjj0DFK8BA&list=PLV0nOzdUS5XtParoZLgKoVwNSK9zROwuO) Obtendrá una buena idea de la potencia de GetX. ## Gestión de Rutas Para navegar a una nueva pantalla: ```dart Get.to(NextScreen()); ``` Para cerrar snackbars, dialogs, bottomsheets o cualquier cosa que normalmente cierre con Navigator.pop(contexto); ```dart Get.back(); ``` Para ir a la siguiente pantalla, sin opción a volver (util por ejemplo en SplashScreens, LoginScreen, etc.) ```dart Get.off(NextScreen()); ``` Para ir a la siguiente pantalla y cancelar todas las rutas anteriores (útil en carritos de compras, encuestas y exámenes) ```dart Get.offAll(NextScreen()); ``` Para navegar a la siguiente ruta y recibir o actualizar datos tan pronto como se regrese de ella: ```dart var data = await Get.to(Payment()); ``` ### Más detalles sobre la gestión de rutas. **Vea una explicación más detallada de la Gestión de Rutas [aquí](./documentation/es_ES/route_management.md).** ### Explicación del video Amateur Coder hizo un excelente video que cubre route management con Get! aquí esta el link: [Complete Getx Navigation](https://www.youtube.com/watch?v=RaqPIoJSTtI) ## Gestión de dependencias - Nota: si está utilizando el gestor de estado de GetX, no tiene que preocuparse por esto, solo lea para obtener información, pero preste más atención a la API de bindings, que hará todo esto automáticamente por usted. ¿Ya estás utilizando GetX y quieres que tu proyecto sea lo más ágil posible? GetX tiene un gestor de dependencias simple y poderoso que le permite recuperar la misma clase que su BLoC o Controller con solo una líneas de código, sin contexto de Provider, sin inheritedWidget: ```dart Controller controller = Get.put(Controller()); // Rather Controller controller = Controller(); ``` En lugar de crear una instancia de su clase dentro de la clase que está utilizando, la está creando dentro de la instancia GetX, que la hará disponible en toda su aplicación. Entonces puede usar su Controller (o BLoC) normalmente. ```dart controller.fetchApi(); ``` Imagine que ha navegado a través de numerosas rutas y necesita datos que quedaron en su controlador, necesitaría un gestor de estado combinado con Provider o Get_it, ¿correcto? No con GetX. Solo necesita pedirle a GetX que "encuentre" su controlador, no necesita dependencias adicionales: ```dart Controller controller = Get.find(); //Sí, parece que es magia, Get encontrará su controlador y se lo entregará. Puede tener 1 millón de controladores instanciados, Get siempre le dará el controlador correcto. ``` Y luego podrá recuperar los datos de su controlador que se obtuvieron allí: ```dart Text(controller.textFromApi); ``` ¿Buscando lazy loading? Puede declarar todos sus controladores, y se llamará solo cuando alguien lo necesite. Puedes hacer esto con: ```dart Get.lazyPut(()=> ApiMock()); /// ApiMock solo se llamará cuando alguien use Get.find por primera vez ``` ### Más detalles sobre la gestión de dependencias. **Vea una explicación más detallada de la Gestión de dependencias [aquí](./documentation/es_ES/dependency_management.md).** # Utilidades ## Cambiar de tema No utilice ningún widget de nivel superior que GetMaterialApp para actualizarlo. Esto puede activar claves duplicadas. Mucha gente está acostumbrada al enfoque prehistórico de crear un widget "ThemeProvider" solo para cambiar el tema de su aplicación, y esto definitivamente NO es necesario con GetX. Puede crear su tema personalizado y simplemente agregarlo dentro de Get.changeTheme sin ningún boilerplate para eso: ```dart Get.changeTheme(ThemeData.light()); ``` Si desea crear algo así como un botón que cambia el tema con onTap, puede combinar dos APIs GetX para eso, la API que verifica si se está utilizando el tema oscuro y la API de cambio de tema, simplemente puede poner esto dentro de un onPressed: ```dart Get.changeTheme(Get.isDarkMode? ThemeData.light(): ThemeData.dark()); ``` Cuando el modo oscuro está activado, cambiará al tema claro, y cuando el tema claro esté activado, cambiará a oscuro. Si quieres saber en profundidad cómo cambiar el tema, puedes seguir este tutorial en Medium que incluso enseña la persistencia del tema usando GetX: - [Temas dinámicos en 3 líneas usando GetX](https://medium.com/swlh/flutter-dynamic-themes-in-3-lines-c3b375f292e3) - Tutorial de [Rod Brown](https://github.com/RodBr). ## Otras API avanzadas y configuraciones manuales GetMaterialApp configura todo para usted, pero si desea configurar GetX manualmente utilizando APIs avanzadas. ```dart MaterialApp( navigatorKey: Get.key, navigatorObservers: [GetObserver()], ); ``` También podrá usar su propio Middleware dentro de GetObserver, esto no influirá en nada. ```dart MaterialApp( navigatorKey: Get.key, navigatorObservers: [GetObserver(MiddleWare.observer)], // Here ); ``` ```dart // dar los argumentos actuales de currentScreen Get.arguments // dar el nombre de la ruta anterior Get.previousRoute // dar la ruta sin procesar para acceder, por ejemplo, rawRoute.isFirst() Get.rawRoute // dar acceso a Routing API desde GetObserver Get.routing // comprobar si la cafetería está abierta Get.isSnackbarOpen // comprobar si el diálogo está abierto Get.isDialogOpen // comprobar si bottomsheet está abierto Get.isBottomSheetOpen // eliminar una ruta. Get.removeRoute() // volver repetidamente hasta que predicate devuelva verdadero. Get.until() //ir a la siguiente ruta y eliminar todas las rutas anteriores hasta que predicate devuelva verdadero. Get.offUntil() // ir a la siguiente ruta con nombre y eliminar todas las rutas anteriores hasta que predicate devuelve verdadero. Get.offNamedUntil() //Verifique en qué plataforma se ejecuta la aplicación GetPlatform.isAndroid GetPlatform.isIOS GetPlatform.isWeb // Equivalente al método: MediaQuery.of(context).size.height, pero son inmutables. Get.height Get.width // Da el contexto de la pantalla en primer plano en cualquier parte de su código. Get.context // Da el contexto de la barra de bocadillos / diálogo / hoja inferior en primer plano en cualquier parte de su código. Get.contextOverlay // Note: los siguientes métodos son extensiones de context. Desde que tu // tiene acceso al contexto en cualquier lugar de su interfaz de usuario, puede usarlo en cualquier lugar del código de la interfaz de usuario // Si necesita un cambiable height/width (como las ventanas del navegador que se pueden escalar) necesitará usar context. context.width context.height // le da el poder de definir la mitad de la pantalla ahora, un tercio y así sucesivamente. // Útil para aplicaciones receptivas. // param dividedBy (double) optional - default: 1 // param reducedBy (double) optional - default: 0 context.heightTransformer() context.widthTransformer() /// Similar a MediaQuery.of(context).size context.mediaQuerySize() /// similar a MediaQuery.of(context).padding context.mediaQueryPadding() /// similar a MediaQuery.of(context).viewPadding context.mediaQueryViewPadding() /// similar a MediaQuery.of(context).viewInsets; context.mediaQueryViewInsets() /// similar a MediaQuery.of(context).orientation; context.orientation() /// comprobar si el dispositivo esta en landscape mode context.isLandscape() /// comprobar si el dispositivo esta en portrait mode context.isPortrait() /// similar a MediaQuery.of(context).devicePixelRatio; context.devicePixelRatio() /// similar a MediaQuery.of(context).textScaleFactor; context.textScaleFactor() /// obtener el lado más corto de la pantalla context.mediaQueryShortestSide() /// Verdadero si el ancho es mayor que 800 context.showNavbar() /// Verdadero si el lado más corto es menor que 600p context.isPhone() /// Verdadero si el lado más corto es más grande que 600p context.isSmallTablet() /// Verdadero si el lado más corto es mayor que 720p context.isLargeTablet() /// Verdadero si el dispositivo actual es una tableta context.isTablet() ``` ### Configuraciones globales opcionales Puede crear configuraciones globales para GetX. Simplemente agregue Get.config a su código antes de insertar cualquier ruta o hágalo directamente en su GetMaterialApp ```dart GetMaterialApp( enableLog: true, defaultTransition: Transition.fade, opaqueRoute: Get.isOpaqueRouteDefault, popGesture: Get.isPopGestureEnable, transitionDuration: Get.defaultDurationTransition, defaultGlobalState: Get.defaultGlobalState, ); Get.config( enableLog = true, defaultPopGesture = true, defaultTransition = Transitions.cupertino ) ``` Opcionalmente, puede redirigir todos los mensajes de registro de Get. Si desea utilizar su propio paquete de registro favorito y desea capturar los registros allí. ```dart GetMaterialApp( enableLog: true, logWriterCallback: localLogWriter, ); void localLogWriter(String text, {bool isError = false}) { // pase el mensaje a su paquete de registro favorito aquí //Nota: incluso si los mensajes de registro están desactivados // con el comando "enableLog: false", los mensajes seguirán pasando por aquí // Debe verificar esta configuración manualmente aquí si desea respetarla } ``` ## Video explanation of Other GetX Features Amateur Coder hizo un video asombroso sobre utilidades, almacenamiento, enlaces y otras características! Link: [GetX Other Features](https://youtu.be/ttQtlX_Q0eU) # Cambios importantes desde 2.0 1- Rx types: | Antes | Ahora | | ------- | ---------- | | StringX | `RxString` | | IntX | `RxInt` | | MapX | `RxMap` | | ListX | `RxList` | | NumX | `RxNum` | | DoubleX | `RxDouble` | RxController y GetBuilder ahora se han fusionado, ya no necesita memorizar qué controlador desea usar, solo use GetXController, funcionará para gestión de estádo simple y también para reactivo. 2- Rutas Nombradas Antes: ```dart GetMaterialApp( namedRoutes: { '/': GetRoute(page: Home()), } ) ``` Ahora: ```dart GetMaterialApp( getPages: [ GetPage(name: '/', page: () => Home()), ] ) ``` ¿Por qué este cambio? A menudo, puede ser necesario decidir qué página se mostrará desde un parámetro o un token de inicio de sesión, el enfoque anterior era inflexible, ya que no permitía esto. Insertar la página en una función ha reducido significativamente el consumo de RAM, ya que las rutas no se asignarán en la memoria desde que se inició la aplicación, y también permitió hacer este tipo de enfoque: ```dart GetStorage box = GetStorage(); GetMaterialApp( getPages: [ GetPage(name: '/', page:(){ return box.hasData('token') ? Home() : Login(); }) ] ) ``` # ¿Por qué Getx? 1- Después de una actualización de Flutter, muchos paquetes suelen romperse. A veces se producen errores de compilación, errores de los que aún no hay respuestas y el desarrollador necesita saber el origen del error, poder rastrear, y solo entonces intentar abrir un issue en el repositorio correspondiente, para finalmente ver su problema resuelto. Getx centraliza los principales recursos para el desarrollo (gestión de estado, dependencia y rutas), lo que le permite agregar un único paquete a su pubspec y comenzar a trabajar. Después de una actualización de Flutter, lo único que debe hacer es actualizar la dependencia Get y ponerse a trabajar. Get también resuelve problemas de compatibilidad. ¿Cuántas veces una versión de un paquete no es compatible con la versión de otro, porque una usa una dependencia en una versión y la otra en otra? Tampoco es una preocupación usando Get, ya que todo estará en el mismo paquete y será totalmente compatible. 2- Flutter es fácil, Flutter es increíble, pero todavía tiene algo repetitivo que puede ser no deseado para la mayoría de los desarrolladores, como `Navigator.of(context).push (context, builder [...]`. Get simplifica el desarrollo. En lugar de escribir 8 líneas de código para simplemente llamar a una ruta, simplemente puede hacerlo: `Get.to(Home())` y listo, irá a la página siguiente. Algo doloroso de hacer con Flutter actualmente, mientras que con GetX es estúpidamente simple. Gestionar estados en Flutter y dependencias también es algo que genera mucho debate, ya que hay cientos de patrones en el pub. Pero no hay nada tan fácil como agregar un ".obs" al final de su variable, y colocar su widget dentro de un Obx, y eso es todo, todas las actualizaciones de esa variable se actualizarán automáticamente en la pantalla. 3- Facilidad sin preocuparse por el rendimiento. El rendimiento de Flutter ya es sorprendente, pero imagine que usa un gestor de estado y un localizador para distribuir sus clases de bloc/stores/controllers/ etc. Tendrá que llamar manualmente a la exclusión de esa dependencia cuando no la necesite. Pero, ¿alguna vez pensó en simplemente usar el controlador, y cuando ya no sea necesario, simplemente se elimine de la memoria? Eso es lo que hace GetX. Con SmartManagement, todo lo que no se está utilizando se elimina de la memoria, y no debería tener que preocuparse por nada más que la programación. Se le garantiza el consumo mínimo de recursos, sin siquiera haber creado una lógica para esto. 4- Desacoplamiento real. Es posible que haya escuchado la idea de "separar la vista de la lógica de negocio". Esta no es una peculiaridad de BLoC, MVC, MVVM, cualquier otro estándar en el mercado tiene este concepto. Sin embargo, a menudo se puede mitigar en Flutter debido al uso del contexto. Si necesita contexto para encontrar un InheritedWidget, lo necesita en la vista o pasado por parámetro. En particular, encuentro esta solución muy fea, y para trabajar en equipo siempre tendremos una dependencia de la lógica de negocios de la vista. Getx no es ortodoxo con el enfoque estándar, y aunque no prohíbe completamente el uso de StatefulWidgets, InitState, etc., siempre tiene un enfoque similar que puede ser más limpio. Los controladores tienen ciclos de vida, y cuando necesita hacer una solicitud API REST, por ejemplo, no depende de nada en la vista. Puede usar onInit para iniciar la llamada http, y cuando lleguen los datos, se rellenarán las variables. Como GetX es completamente reactivo (realmente, y funciona bajo streams), una vez que se llenan los elementos, todos los widgets que usan esa variable se actualizarán automáticamente en la vista. Esto permite que las personas con experiencia en IU trabajen solo con widgets y no tengan que enviar nada a la lógica de negocios que no sean eventos de usuario (como hacer clic en un botón), mientras que las personas que trabajan con lógica de negocios podrán crearla y probarla por separado. Esta librería siempre se actualizará e implementará nuevas características. Siéntase libre de ofrecer PRs y contribuir a ellas. # Comunidad ## Canales de la comunidad GetX tiene una comunidad muy activa e implicada. Si tiene dudas, o necesita cualquier tipo de asistencia sobre el uso de este framework, no dude en unirse a nuestr, tu duda será resuelta lo antes posible. Este repositorio es de uso exclusivo para abrir issues, pero siéntase libre de unirse a la Comunidad de GetX. | **Slack (🇬🇧)** | **Discord (🇬🇧 y 🇵🇹)** | **Telegram (🇵🇹)** | | :-------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------- | | [![Get on Slack](https://img.shields.io/badge/slack-join-orange.svg)](https://communityinviter.com/apps/getxworkspace/getx) | [![Discord Shield](https://img.shields.io/discord/722900883784073290.svg?logo=discord)](https://discord.com/invite/9Hpt99N) | [![Telegram](https://img.shields.io/badge/chat-on%20Telegram-blue.svg)](https://t.me/joinchat/PhdbJRmsZNpAqSLJL6bH7g) | # Cómo contribuir _¿Quieres contribuir al proyecto? Estaremos orgullosos de destacarte como uno de nuestros colaboradores. Aquí hay algunos puntos en los que puede contribuir y hacer que GetX (y Flutter) sea aún mejor._ - Ayudando a traducir el archivo Léame a otros idiomas. - Agregar documentación al archivo Léame (ni siquiera la mitad de las funciones de GetX han sido documentadas todavía). - Escriba artículos o haga videos que enseñen cómo usar GetX (se insertarán en el archivo Léame y en el futuro en nuestro Wiki). - Ofreciendo PRs para código/pruebas. - Incluyendo nuevas funciones. ¡Cualquier contribución es bienvenida! ## Artículos y vídeos (inglés) - [Flutter Getx EcoSystem package for arabic people](https://www.youtube.com/playlist?list=PLV1fXIAyjeuZ6M8m56zajMUwu4uE3-SL0) - Tutorial by [Pesa Coder](https://github.com/UsamaElgendy). - [Dynamic Themes in 3 lines using GetX™](https://medium.com/swlh/flutter-dynamic-themes-in-3-lines-c3b375f292e3) - Tutorial by [Rod Brown](https://github.com/RodBr). - [Complete GetX™ Navigation](https://www.youtube.com/watch?v=RaqPIoJSTtI) - Route management video by Amateur Coder. - [Complete GetX State Management](https://www.youtube.com/watch?v=CNpXbeI_slw) - State management video by Amateur Coder. - [GetX™ Other Features](https://youtu.be/ttQtlX_Q0eU) - Utils, storage, bindings and other features video by Amateur Coder. - [Firestore User with GetX | Todo App](https://www.youtube.com/watch?v=BiV0DcXgk58) - Video by Amateur Coder. - [Firebase Auth with GetX | Todo App](https://www.youtube.com/watch?v=-H-T_BSgfOE) - Video by Amateur Coder. - [The Flutter GetX™ Ecosystem ~ State Management](https://medium.com/flutter-community/the-flutter-getx-ecosystem-state-management-881c7235511d) - State management by [Aachman Garg](https://github.com/imaachman). - [The Flutter GetX™ Ecosystem ~ Dependency Injection](https://medium.com/flutter-community/the-flutter-getx-ecosystem-dependency-injection-8e763d0ec6b9) - Dependency Injection by [Aachman Garg](https://github.com/imaachman). - [GetX, the all-in-one Flutter package](https://www.youtube.com/watch?v=IYQgtu9TM74) - A brief tutorial covering State Management and Navigation by Thad Carnevalli. - [Build a To-do List App from scratch using Flutter and GetX](https://www.youtube.com/watch?v=EcnqFasHf18) - UI + State Management + Storage video by Thad Carnevalli. - [GetX Flutter Firebase Auth Example](https://medium.com/@jeffmcmorris/getx-flutter-firebase-auth-example-b383c1dd1de2) - Article by Jeff McMorris. - [Flutter State Management with GetX – Complete App](https://www.appwithflutter.com/flutter-state-management-with-getx/) - by App With Flutter. - [Flutter Routing with Animation using Get Package](https://www.appwithflutter.com/flutter-routing-using-get-package/) - by App With Flutter. - [A minimal example on dartpad](https://dartpad.dev/2b3d0d6f9d4e312c5fdbefc414c1727e?) - by [Roi Peker](https://github.com/roipeker) ================================================ FILE: README-fr.md ================================================ ![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/get.png) **Langues: Français (Ce fichier), [Anglais](README.md), [Vietnamien](README-vi.md), [Indonésien](README.id-ID.md), [Urdu](README.ur-PK.md), [Chinois](README.zh-cn.md), [Portuguais du Brésil](README.pt-br.md), [Espagnol](README-es.md), [Russe](README.ru.md), [Polonais](README.pl.md), [Koréen](README.ko-kr.md).** [![pub package](https://img.shields.io/pub/v/get.svg?label=get&color=blue)](https://pub.dev/packages/get) [![popularity](https://badges.bar/get/popularity)](https://pub.dev/packages/sentry/score) [![likes](https://badges.bar/get/likes)](https://pub.dev/packages/get/score) [![pub points](https://badges.bar/get/pub%20points)](https://pub.dev/packages/get/score) ![building](https://github.com/jonataslaw/get/workflows/build/badge.svg) [![style: effective dart](https://img.shields.io/badge/style-effective_dart-40c4ff.svg)](https://pub.dev/packages/effective_dart) [![Discord Shield](https://img.shields.io/discord/722900883784073290.svg?logo=discord)](https://discord.com/invite/9Hpt99N) [![Get on Slack](https://img.shields.io/badge/slack-join-orange.svg)](https://communityinviter.com/apps/getxworkspace/getx) [![Telegram](https://img.shields.io/badge/chat-on%20Telegram-blue.svg)](https://t.me/joinchat/PhdbJRmsZNpAqSLJL6bH7g) Awesome Flutter Achetez moi un cafe ![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/getx.png) - [A Propos de Get](#a-propos-de-get) - [Installation](#installation) - [L'application 'Counter' avec GetX](#application-counter-avec-getx) - [Les trois pilliers](#les-trois-pilliers) - [Gestion d'état (State management)](#gestion-d-etat) - [Gestionnaire d'état réactif (Reactive State Manager)](#gestionnaire-d-etat-reactif) - [Plus de détails sur la gestion d'état](#plus-de-details-sur-la-gestion-d-etat) - [Gestion de route](#gestion-de-route) - [Plus de détails sur la gestion de route](#plus-de-details-sur-la-gestion-de-route) - [Gestion des dépendances](#gestion-des-dependances) - [Plus de détails sur la gestion des dépendances](#plus-de-details-sur-la-gestion-des-dependances) - [Utils](#utils) - [Internationalization](#internationalization) - [Traductions](#traductions) - [Utiliser les traductions](#utiliser-les-traductions) - [Locales](#locales) - [Changer la locale](#changer-la-locale) - [Locale du Système](#locale-du-systeme) - [Changer le Thème](#changer-le-theme) - [GetConnect](#getconnect) - [Configuration par défaut](#configuration-par-defaut) - [Configuration personnalisée](#configuration-personnalisee) - [Middleware GetPage](#middleware-getpage) - [Priority](#priority) - [Redirect](#redirect) - [onPageCalled](#onpagecalled) - [OnBindingsStart](#onbindingsstart) - [OnPageBuildStart](#onpagebuildstart) - [OnPageBuilt](#onpagebuilt) - [OnPageDispose](#onpagedispose) - [Autres APIs](#autres-apis) - [Paramètres globaux et configurations manuelles facultatifs](#parametres-globaux-et-configurations-manuelles-facultatifs) - [State Widgets Locaux](#state-widgets-locaux) - [ValueBuilder](#valuebuilder) - [ObxValue](#obxvalue) - [Conseils utiles](#conseils-utiles) - [GetView](#getview) - [GetResponsiveView](#getresponsiveview) - [Guide d'utilisation](#guide-d-utilisation) - [GetWidget](#getwidget) - [GetxService](#getxservice) - [Changements par rapport à 2.0](#changements-par-rapport-a-20) - [Pourquoi Getx?](#pourquoi-getx) - [Communité](#communite) - [Chaînes communautaires](#chaines-communautaires) - [Comment contribuer](#comment-contribuer) - [Articles et videos](#articles-et-videos) # A Propos de Get - GetX est une solution extra-légère et puissante pour Flutter. Il combine une gestion d'état (state management) de haute performance, une injection de dépendances (dependency injection) intelligente et une gestion de route (route management) rapide et pratique. - GetX a 3 principes de base. Cela signifie que ces principes sont les priorités pour toutes les ressources de la bibliothèque GetX: **PRODUCTIVITÉ, PERFORMANCE ET ORGANIZATION.** - **PERFORMANCE:** GetX se concentre sur la performance et la consommation minimale de ressources. GetX n'utilise ni Streams ni ChangeNotifier. - **PRODUCTIVITÉ:** GetX utilise une syntaxe simple et agréable. Peu importe ce que vous voulez faire, il existe toujours un moyen plus simple avec GetX. Cela économisera des heures de développement et fournira les performances maximales que votre application peut offrir. En règle générale, le développeur doit s'occuper lui-même de la suppression des contrôleurs de la mémoire. Avec GetX, cela n'est pas nécessaire car les ressources sont, par défaut, supprimées de la mémoire lorsqu'elles ne sont pas utilisées. Si vous souhaitez les conserver en mémoire, vous devez déclarer explicitement "permanent: true" comme paramètre lors de la création de la ressource. De cette façon, en plus de gagner du temps, vous risquez moins d'avoir des ressources inutiles dans la mémoire. L'initialisation des ressources est également 'lazy' par défaut (i.e. se fait seulement lorsque la ressource est nécessaire). - **ORGANIZATION:** GetX permet le découplage total de la Vue (View), de la Logique de Présentation (Presentation Logic), de la Business Logic, de l'injection de dépendances (Dependency Injection) et de la Navigation. Vous n'avez pas besoin de contexte pour naviguer entre les routes, vous n'êtes donc pas dépendant de la hiérarchisation des widgets (visualisation) pour cela. Vous n'avez pas besoin de 'context' pour accéder à vos contrôleurs/blocs via un inheritedWidget, vous dissociez donc complètement votre logique de présentation (Vue) et votre Business logic de votre couche de visualisation. Vous n'avez pas besoin d'injecter vos classes Controlleûrs / Modèles / Blocs le long de la hiérarchie de Widgets via `MultiProvider`. Pour cela, GetX utilise sa propre fonction d'injection de dépendances (DI), découplant complètement la DI de sa Vue. Avec GetX, vous savez où trouver chaque module de votre application, avec un code propre par défaut. En plus de rendre la maintenance facile, cela rend le partage de modules quelque chose qui jusque-là dans Flutter était impensable, quelque chose de totalement possible. BLoC était un point de départ pour organiser le code dans Flutter, il sépare la Business logic de la visualisation. GetX en est une évolution naturelle, séparant non seulement la Business logic mais aussi la logique de présentation. L'injection de dépendances et les routes sont également découplées, et la couche de données est séparée du tout. Vous savez où tout se trouve, et tout cela d'une manière plus facile que de construire un 'Hello World''. GetX est le moyen le plus simple, pratique et évolutif de créer des applications hautes performances avec le SDK Flutter. Il possède un vaste écosystème qui fonctionne parfaitement, c'est facile pour les débutants et précis pour les experts. Il est sécurisé, stable, à jour et offre une vaste gamme d'API intégrées qui ne sont pas présentes dans le SDK Flutter par défaut. - GetX possède une multitude de fonctionnalités qui vous permettent de démarrer la programmation sans vous soucier de quoi que ce soit, mais chacune de ces fonctionnalités se trouve dans des conteneurs séparés et ne démarre qu'après utilisation. Si vous n'utilisez que la gestion des états (State Management), seule la gestion des états sera compilée. Si vous n'utilisez que des routes, rien de la gestion d'état ne sera compilé. - GetX a un énorme écosystème, une grande communauté, un grand nombre de collaborateurs, et sera maintenu tant que Flutter existera. GetX est également capable de fonctionner avec le même code sur Android, iOS, Web, Mac, Linux, Windows et sur votre serveur. Il est possible de réutiliser entièrement votre code créé sur le frontend et le backend avec Get Server. **Il est possible d'entièrement réutiliser votre code écrit sur le frontend, pour le backend avec [Get Server](https://github.com/jonataslaw/get_server)**. **De plus, l'ensemble du processus de développement peut être complètement automatisé, à la fois sur le serveur et sur le front-end avec [Get CLI](https://github.com/jonataslaw/get_cli)**. **De plus, pour augmenter encore votre productivité, nous avons l'[extension pour VSCode](https://marketplace.visualstudio.com/items?itemName=get-snippets.get-snippets) et l'[extension pour Android Studio/Intellij](https://plugins.jetbrains.com/plugin/14975-getx-snippets)** # Installation Ajoutez Get à votre fichier pubspec.yaml: ```yaml dependencies: get: ``` Importez Get dans les fichiers dans lesquels il doit être utilisé: ```dart import 'package:get/get.dart'; ``` # Application Counter avec Getx Le projet "Counter" créé par défaut sur chaque nouveau projet Flutter comporte plus de 100 lignes (avec commentaires). Pour montrer la puissance de Get, je vais vous montrer comment faire un "compteur" changeant d'état à chaque clic, naviguer entre les pages et partager l'état entre les écrans, le tout de manière organisée, en séparant la Business logic de la Vue, en SEULEMENT 26 LIGNES DE CODE INCLUANT LES COMMENTAIRES. - Step 1: Ajoutez "Get" avant MaterialApp, pour le transformer en GetMaterialApp ```dart void main() => runApp(GetMaterialApp(home: Home())); ``` - Note: cela ne modifie pas le MaterialApp de Flutter, GetMaterialApp n'est pas un MaterialApp modifié, il s'agit simplement d'un widget préconfiguré, qui a le MaterialApp par défaut comme enfant (child: ). Vous pouvez le configurer manuellement, mais ce n'est certainement pas nécessaire. GetMaterialApp créera des routes, les injectera, injectera les traductions, injectera tout ce dont vous avez besoin pour la navigation de routes. Si vous utilisez Get uniquement pour la gestion de l'état (State management) ou la gestion des dépendances (DI), il n'est pas nécessaire d'utiliser GetMaterialApp. GetMaterialApp est nécessaire pour les routes, les 'snackbars', l'internationalisation, les 'bottomSheets', les dialogues et les API de haut niveau liés aux routes et à l'absence de 'context'. - Note²: Cette étape n'est nécessaire que si vous allez utiliser la gestion de routes (Get.to(), Get.back(), etc). Si vous ne l'utiliserez pas, il n'est pas nécessaire de faire l'étape 1. - Step 2: Créez votre classe de Business logic et placez-y toutes les variables, méthodes et contrôleurs. Vous pouvez rendre toute variable observable en utilisant un simple ".obs". ```dart class Controller extends GetxController{ var count = 0.obs; increment() => count++; } ``` - Step 3: Créez votre Vue, utilisez StatelessWidget et économisez de la RAM, avec Get, vous n'aurez peut-être plus besoin d'utiliser StatefulWidget. ```dart class Home extends StatelessWidget { @override Widget build(context) { // Instanciez votre classe en utilisant Get.put() pour le rendre disponible pour tous les routes "descendantes". final Controller c = Get.put(Controller()); return Scaffold( // Utilisez Obx(()=> pour mettre à jour Text() chaque fois que count est changé. appBar: AppBar(title: Obx(() => Text("Clicks: ${c.count}"))), // Remplacez les 8 lignes Navigator.push par un simple Get.to(). Vous n'avez pas besoin de 'context' body: Center(child: ElevatedButton( child: Text("Go to Other"), onPressed: () => Get.to(Other()))), floatingActionButton: FloatingActionButton(child: Icon(Icons.add), onPressed: c.increment)); } } class Other extends StatelessWidget { // Vous pouvez demander à Get de trouver un contrôleur utilisé par une autre page et de vous y rediriger. final Controller c = Get.find(); @override Widget build(context){ // Accéder à la variable 'count' qui est mise à jour return Scaffold(body: Center(child: Text("${c.count}"))); } } ``` Résultat: ![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/counter-app-gif.gif) C'est un projet simple mais il montre déjà à quel point Get est puissant. Au fur et à mesure que votre projet se développe, cette différence deviendra plus significative. Get a été conçu pour fonctionner avec des équipes, mais il simplifie le travail d'un développeur individuel. Améliorez vos délais, livrez tout à temps sans perte de performances. Get n'est pas pour tout le monde, mais si vous vous êtes identifié à cette phrase, Get est fait pour vous! # Les trois pilliers ## Gestion d Etat Get a deux gestionnaires d'état différents: le gestionnaire d'état simple (nous l'appellerons GetBuilder) et le gestionnaire d'état réactif (GetX / Obx). ### Gestionnaire d Etat Reactif La programmation réactive peut aliéner de nombreuses personnes car on dit qu'elle est compliquée. GetX fait de la programmation réactive quelque chose d'assez simple: - Vous n'aurez pas besoin de créer des StreamControllers. - Vous n'aurez pas besoin de créer un StreamBuilder pour chaque variable - Vous n'aurez pas besoin de créer une classe pour chaque état. - Vous n'aurez pas besoin de créer un 'get' pour une valeur initiale. - Vous n'aurez pas besoin d'utiliser des générateurs de code La programmation réactive avec Get est aussi simple que d'utiliser setState. Imaginons que vous ayez une variable 'name' et que vous souhaitiez que chaque fois que vous la modifiez, tous les widgets qui l'utilisent soient automatiquement modifiés. Voici votre variable: ```dart var name = 'Jonatas Borges'; ``` Pour la rendre observable, il vous suffit d'ajouter ".obs" à la fin: ```dart var name = 'Jonatas Borges'.obs; ``` Et dans l'interface utilisateur, lorsque vous souhaitez afficher cette valeur et mettre à jour l'écran chaque fois qu'elle change, faites simplement: ```dart Obx(() => Text("${controller.name}")); ``` C'est _tout_. Si simple que ca. ### Plus de details sur la gestion d Etat **Lire une explication plus approfondie de la gestion d'état [ici](./documentation/fr_FR/state_management.md). Là-bas, vous verrez plus d'exemples surtout pour la différence entre le gestionnaire d'état simple et le gestionnaire d'état réactif.** Vous pourrez vous faire une meilleure idée de la puissance de GetX. ## Gestion de route Si vous envisagez d'utiliser des routes/snackbars/dialogs/bottomsheets sans 'context', GetX est également excellent pour vous, voyez par vous-même: Ajoutez "Get" avant votre MaterialApp, en le transformant en GetMaterialApp ```dart GetMaterialApp( // Avant: MaterialApp( home: MyHome(), ) ``` Accédez à un nouvel écran: ```dart Get.to(ÉcranSuivant()); ``` Accédez au nouvel écran par le nom. Voir plus de détails sur les itinéraires nommés (named routes) [ici](./documentation/fr_FR/route_management.md#navigation-avec-des-itinraires-nomms) ```dart Get.toNamed('/details'); ``` Pour fermer des snackbars, dialogs, bottomsheets, ou tout ce que vous auriez normalement fermé avec Navigator.pop(context); ```dart Get.back(); ``` Pour aller à l'écran suivant avec aucune option pour revenir à l'écran précédent (pour une utilisation dans SplashScreens, écrans de connexion, etc.) ```dart Get.off(NextScreen()); ``` Pour aller à l'écran suivant et annuler tous les itinéraires précédents (utile dans les paniers d'achat en ligne, les sondages et les tests) ```dart Get.offAll(NextScreen()); ``` Avez-vous remarqué que vous n'avez eu besoin d'utiliser 'context' pour aucune de ces opérations? C'est l'un des plus grands avantages de l'utilisation de la gestion de route avec Get. Avec cela, vous pouvez appeler toutes ces méthodes à partir de votre classe de contrôleur, sans soucis. ### Plus de details sur la gestion de route **Get fonctionne avec des itinéraires nommés (named routes) et offre également un contrôle plus granulaire de vos routes! Il y a une documentation approfondie [ici](./documentation/fr_FR/route_management.md)** ## Gestion des dependances Get a un gestionnaire de dépendances (dependency manager) simple et puissant qui vous permet de récupérer la même classe que votre Bloc ou Controller avec seulement 1 ligne de code, pas de 'context' Provider, pas d'inheritedWidget: ```dart Controller controller = Get.put(Controller()); // Au lieu de Controller controller = Controller(); ``` - Remarque: Si vous utilisez le gestionnaire d'état de Get, accordez plus d'attention à l'API 'Bindings', qui facilitera la connexion de vos Vues à vos contrôleurs. Au lieu d'instancier votre classe dans la classe que vous utilisez, vous l'instanciez dans l'instance Get, ce qui la rendra disponible dans toute votre application. Vous pouvez donc utiliser votre contrôleur (ou classe Bloc) normalement. **Conseil:** La gestion des dépendances est découplée des autres parties du package, donc si, par exemple, votre application utilise déjà un gestionnaire d'état (n'importe lequel, peu importe), vous n'avez pas besoin de tout réécrire, vous pouvez l'utiliser avec l'injection de dépendance de Get sans aucun problème. ```dart controller.fetchApi(); ``` Imaginez que vous ayez parcouru de nombreuses routes et que vous ayez besoin de données qui ont été laissées dans votre contrôleur, vous auriez besoin d'un gestionnaire d'état combiné avec le 'Provider' ou 'Get_it', n'est-ce pas? Pas avec Get. Il vous suffit de demander à Get de "trouver" votre contrôleur, vous n'avez pas besoin de dépendances supplémentaires: ```dart Controller controller = Get.find(); //Oui, cela ressemble à de la magie. Get trouvera votre contrôleur et vous le livrera. Vous pouvez avoir 1 million de contrôleurs instanciés, Get vous retournera toujours le bon contrôleur. ``` Et puis vous pourrez récupérer les données de votre contrôleur obtenu précédemment: ```dart Text(controller.textFromApi); ``` ### Plus de details sur la gestion des dependances **Trouvez une explication plus détaillée sur la gestion des dépendances [ici](./documentation/fr_FR/dependency_management.md)** # Utils ## Internationalization ### Traductions Les traductions sont enregistrées sous forme de dictionaire clé-valeur simple. Pour ajouter des traductions, créez une classe qui 'extend' `Translations`. ```dart import 'package:get/get.dart'; class Messages extends Translations { @override Map> get keys => { 'en_US': { 'hello': 'Hello World', }, 'de_DE': { 'hello': 'Hallo Welt', } }; } ``` #### Utiliser les traductions Ajouter juste `.tr` à la clé et elle sera traduite selon la valeur actuelle `Get.locale` et de `Get.fallbackLocale`. ```dart Text('title'.tr); ``` #### Utiliser les traductions avec le singulier et le pluriel ```dart var products = []; Text('cléAuSingulier'.trPlural('cléAuPluriel', products.length, Args)); ``` #### Utiliser les traductions avec paramètres ```dart import 'package:get/get.dart'; Map> get keys => { 'en_US': { 'logged_in': 'logged in as @name with email @email', }, 'es_ES': { 'logged_in': 'iniciado sesión como @name con e-mail @email', } }; Text('logged_in'.trParams({ 'name': 'Jhon', 'email': 'jhon@example.com' })); ``` ### Locales 'Locales' signifie lieux. Pour definir les traductions, passer les paramètres 'locale' et 'translations' à GetMaterialApp. ```dart return GetMaterialApp( translations: Messages(), // Vos traductions locale: Locale('en', 'US'), // Les traductions seront faites dans cette 'locale' (langue) fallbackLocale: Locale('en', 'UK'), // definit le 'language de secours' au cas oú un language invalide est sélectionné. ); ``` #### Changer la locale Appelez `Get.updateLocale (locale)` pour mettre à jour la locale. Les traductions utilisent alors automatiquement la nouvelle langue. ```dart var locale = Locale('en', 'US'); Get.updateLocale(locale); ``` #### Locale du systeme Pour lire les paramètres régionaux ('locales') du système, vous pouvez utiliser `Get.deviceLocale`. ```dart return GetMaterialApp( locale: Get.deviceLocale, ); ``` ## Changer le Theme Veuillez ne pas utiliser de widget de niveau supérieur à `GetMaterialApp` pour le mettre à jour. Cela peut créer des clés ('keys') en double. Beaucoup de gens sont habitués à l'approche préhistorique de la création d'un widget "ThemeProvider" juste pour changer le thème de votre application, et ce n'est certainement PAS nécessaire avec **GetX ™**. Vous pouvez créer votre thème personnalisé et l'ajouter simplement dans `Get.changeTheme` sans aucune préconfiguration pour cela: ```dart Get.changeTheme(ThemeData.light()); ``` Si vous voulez créer quelque chose comme un bouton qui change le thème dans `onTap`, vous pouvez combiner deux API **GetX ™** pour cela: - L'API qui vérifie si le "Thème" sombre est utilisé. - Et l'API de changement de thème, vous pouvez simplement le mettre dans un 'onPressed': ```dart Get.changeTheme(Get.isDarkMode? ThemeData.light(): ThemeData.dark()); ``` Lorsque 'onPressed' est appelé, si `.darkmode` est activé, il passera au _thème clair_, et lorsque le _thème clair_ est actif, il passera au _thème sombre_. ## GetConnect GetConnect est un moyen facile de communiquer de votre backend à votre frontend avec http ou websockets. ### Configuration par defaut Vous pouvez simplement 'extends' GetConnect et utiliser les méthodes GET / POST / PUT / DELETE / SOCKET pour communiquer avec votre API Rest ou vos websockets. ```dart class UserProvider extends GetConnect { // Get request Future getUser(int id) => get('http://youapi/users/$id'); // Post request Future postUser(Map data) => post('http://youapi/users', body: data); // Post request with File Future> postCases(List image) { final form = FormData({ 'file': MultipartFile(image, filename: 'avatar.png'), 'otherFile': MultipartFile(image, filename: 'cover.png'), }); return post('http://youapi/users/upload', form); } GetSocket userMessages() { return socket('https://yourapi/users/socket'); } } ``` ### Configuration personnalisee GetConnect est hautement personnalisable. Vous pouvez définir l'URL de base, comme modificateurs de réponse, comme modificateurs de requêtes, définir un authentificateur, et même le nombre de tentatives oú il tentera de s'authentifier, en plus de donner la possibilité de définir un décodeur standard qui transformera toutes vos Requêtes dans vos Modèles sans aucune configuration supplémentaire. ```dart class HomeProvider extends GetConnect { @override void onInit() { // Toute 'Request' passera à jsonEncode donc CasesModel.fromJson() httpClient.defaultDecoder = CasesModel.fromJson; httpClient.baseUrl = 'https://api.covid19api.com'; // baseUrl = 'https://api.covid19api.com'; // Il définit baseUrl pour Http et websockets si utilisé sans instance [httpClient] // Cela attachera la propriété 'apikey' sur l'en-tête ('header') de toutes les 'request's httpClient.addRequestModifier((request) { request.headers['apikey'] = '12345678'; return request; }); // Même si le serveur envoie des données avec le pays "Brésil", // cela ne sera jamais affiché aux utilisateurs, car vous supprimez // ces données de la réponse, même avant que la réponse ne soit délivrée httpClient.addResponseModifier((request, response) { CasesModel model = response.body; if (model.countries.contains('Brazil')) { model.countries.remove('Brazil'); } }); httpClient.addAuthenticator((request) async { final response = await get("http://yourapi/token"); final token = response.body['token']; // Définit l'en-tête request.headers['Authorization'] = "$token"; return request; }); // L'Autenticator sera appelé 3 fois si HttpStatus est HttpStatus.unauthorized httpClient.maxAuthRetries = 3; } @override Future> getCases(String path) => get(path); } ``` ## Middleware GetPage GetPage a maintenant une nouvelle propriété qui prend une liste de GetMiddleWare et les exécute dans l'ordre spécifique. **Note**: Lorsque GetPage a un Middleware, tous les enfants de cette page auront automatiquement les mêmes middlewares. ### Priority L'ordre des middlewares à exécuter peut être défini par la priorité dans GetMiddleware. ```dart final middlewares = [ GetMiddleware(priority: 2), GetMiddleware(priority: 5), GetMiddleware(priority: 4), GetMiddleware(priority: -8), ]; ``` ces middlewares seront exécutés dans cet ordre **-8 => 2 => 4 => 5** ### Redirect Cette fonction sera appelée lors de la recherche de la page de l'itinéraire appelé. Elle reçoit RouteSettings comme résultat vers oú rediriger. Sinon donnez-lui la valeur null et il n'y aura pas de redirection. ```dart RouteSettings redirect(String route) { final authService = Get.find(); return authService.authed.value ? null : RouteSettings(name: '/login'); } ``` ### onPageCalled Cette fonction sera appelée lorsque cette page sera appelée. Vous pouvez l'utiliser pour changer quelque chose sur la page ou lui donner une nouvelle page. ```dart GetPage onPageCalled(GetPage page) { final authService = Get.find(); return page.copyWith(title: 'Welcome ${authService.UserName}'); } ``` ### OnBindingsStart Cette fonction sera appelée juste avant l'initialisation des liaisons ('bidings'). Ici, vous pouvez modifier les liaisons de cette page. ```dart List onBindingsStart(List bindings) { final authService = Get.find(); if (authService.isAdmin) { bindings.add(AdminBinding()); } return bindings; } ``` ### OnPageBuildStart Cette fonction sera appelée juste après l'initialisation des liaisons ('bidings'). Ici, vous pouvez faire quelque chose après avoir créé les liaisons et avant de créer le widget de page. ```dart GetPageBuilder onPageBuildStart(GetPageBuilder page) { print('les liaisons sont prêtes'); return page; } ``` ### OnPageBuilt Cette fonction sera appelée juste après l'appel de la fonction GetPage.page et vous donnera le résultat de la fonction et prendra le widget qui sera affiché. ### OnPageDispose Cette fonction sera appelée juste après avoir disposé tous les objets associés (contrôleurs, vues, ...) à la page. ## Autres APIs ```dart // donne les arguments actuels de currentScreen Get.arguments // donne le nom de l'itinéraire précédent Get.previousRoute // donne la route brute d'accès par exemple, rawRoute.isFirst() Get.rawRoute // donne accès à l'API de routing de GetObserver Get.routing // vérifier si le snackbar est ouvert Get.isSnackbarOpen // vérifier si la boîte de dialogue est ouverte Get.isDialogOpen // vérifie si la bottomSheet est ouverte Get.isBottomSheetOpen // supprime une route. Get.removeRoute() // retourne à plusieurs reprises jusqu'à ce que le prédicat retourne 'true'. Get.until() // passe à la route suivante et supprime toutes les routes précédentes jusqu'à ce que le prédicat retourne 'true'. Get.offUntil() // passe à la route nommée suivante et supprime toutes les routes précédentes jusqu'à ce que le prédicat retourne 'true'. Get.offNamedUntil() // Vérifie sur quelle plate-forme l'application s'exécute GetPlatform.isAndroid GetPlatform.isIOS GetPlatform.isMacOS GetPlatform.isWindows GetPlatform.isLinux GetPlatform.isFuchsia // Vérifie le type d'appareil GetPlatform.isMobile GetPlatform.isDesktop // Toutes les plates-formes sont prises en charge indépendamment, dans le Web! // Vous pouvez dire si vous utilisez un navigateur // sur Windows, iOS, OSX, Android, etc. GetPlatform.isWeb // Équivaut à: MediaQuery.of(context).size.height, // mais immuable. Get.height Get.width // Donne le 'context' actuel de 'Navigator'. Get.context // Donne le contexte du snackbar / dialogue / bottomsheet au premier plan, n'importe où dans votre code. Get.contextOverlay // Remarque: les méthodes suivantes sont des extensions sur le 'context'. Puisque vous // avez accès au contexte à n'importe quel endroit de votre interface utilisateur, vous pouvez l'utiliser n'importe où dans le code de l'interface utilisateur // Si vous avez besoin d'une hauteur / largeur variable (comme les fenêtres de bureau ou de navigateur qui peuvent être mises à l'échelle), vous devrez utiliser le contexte. context.width context.height // Vous donne le pouvoir de définir la moitié de l'écran, un tiers de celui-ci et ainsi de suite. // Utile pour les applications responsives. // paramètre dividedBy (double) optionnel - par défaut: 1 // paramètre reducedBy (double) facultatif - par défaut: 0 context.heightTransformer () context.widthTransformer () /// Similaire à MediaQuery.of(context).size context.mediaQuerySize() /// Similaire à MediaQuery.of(context).padding context.mediaQueryPadding() /// Similaire à MediaQuery.of(context).viewPadding context.mediaQueryViewPadding() /// Similaire à MediaQuery.of(context).viewInsets; context.mediaQueryViewInsets() /// Similaire à MediaQuery.of(context).orientation; context.orientation() /// Vérifie si l'appareil est en mode paysage context.isLandscape() /// Vérifie si l'appareil est en mode portrait context.isPortrait() /// Similaire à MediaQuery.of(context).devicePixelRatio; context.devicePixelRatio() /// Similaire à MediaQuery.of(context).textScaleFactor; context.textScaleFactor() /// Obtenir le côté le plus court de l'écran context.mediaQueryShortestSide() /// Vrai si la largeur est supérieure à 800p context.showNavbar() /// Vrai si le côté le plus court est inférieur à 600p context.isPhone() /// Vrai si le côté le plus court est plus grand que 600p context.isSmallTablet() /// Vrai si le côté le plus court est plus grand que 720p context.isLargeTablet() /// Vrai si l'appareil actuel est une tablette context.isTablet() /// Renvoie une valeur en fonction de la taille de l'écran /// peut donner une valeur pour: /// watch: si le côté le plus court est inférieur à 300 /// mobile: si le côté le plus court est inférieur à 600 /// tablette: si le côté le plus court est inférieur à 1200 /// bureautique: si la largeur est supérieure à 1200 context.responsiveValue() ``` ### Parametres globaux et configurations manuelles facultatifs GetMaterialApp configure tout pour vous, mais si vous souhaitez configurer Get manuellement: ```dart MaterialApp( navigatorKey: Get.key, navigatorObservers: [GetObserver()], ); ``` Vous pourrez également utiliser votre propre middleware dans `GetObserver`, cela n'influencera rien. ```dart MaterialApp( navigatorKey: Get.key, navigatorObservers: [ GetObserver(MiddleWare.observer) // Ici ], ); ``` Vous pouvez créer _Global Settings_ pour `Get`. Ajoutez simplement `Get.config` à votre code avant de changer de route. Ou faites-le directement dans votre `GetMaterialApp` ```dart GetMaterialApp( enableLog: true, defaultTransition: Transition.fade, opaqueRoute: Get.isOpaqueRouteDefault, popGesture: Get.isPopGestureEnable, transitionDuration: Get.defaultDurationTransition, defaultGlobalState: Get.defaultGlobalState, ); Get.config( enableLog = true, defaultPopGesture = true, defaultTransition = Transitions.cupertino ) ``` Vous pouvez éventuellement rediriger tous les messages de journalisation (logging) de `Get`. Si vous souhaitez utiliser votre propre package de journalisation préféré, et souhaitez capturer les logs là-bas: ```dart GetMaterialApp( enableLog: true, logWriterCallback: localLogWriter, ); void localLogWriter(String text, {bool isError = false}) { // transmettez le message à votre package de journalisation préféré ici // veuillez noter que même si enableLog: false, les messages du journal seront poussés dans ce 'callback' // vérifiez le 'flag' si vous le souhaitez via GetConfig.isLogEnable } ``` ### State Widgets Locaux Ces Widgets vous permettent de gérer une valeur unique, et de garder l'état éphémère et localement. Nous avons des saveurs pour réactif et simple. Par exemple, vous pouvez les utiliser pour basculer obscureText dans un `TextField`, peut-être créer un Panneau extensible, ou peut-être modifier l'index actuel dans `BottomNavigationBar` tout en modifiant le contenu de 'body' dans un `Scaffold`. #### ValueBuilder Une simplification de `StatefulWidget` qui fonctionne avec un callback `.setState` qui prend la valeur mise à jour. ```dart ValueBuilder( initialValue: false, builder: (value, updateFn) => Switch( value: value, onChanged: updateFn, // même signature! vous pouvez utiliser (newValue) => updateFn (newValue) ), // si vous devez appeler quelque chose en dehors de la méthode du builder. onUpdate: (value) => print("Valeur mise à jour: $value"), onDispose: () => print("Widget détruit"), ), ``` #### ObxValue Similaire à [`ValueBuilder`](#valuebuilder), mais c'est la version Reactive, vous passez une instance Rx (rappelez-vous les .obs magiques?) et il se met à jour automatiquement ... n'est-ce pas génial? ```dart ObxValue((data) => Switch( value: data.value, onChanged: data, // Rx a une fonction _callable_! Vous pouvez utiliser (flag) => data.value = flag, ), false.obs, ), ``` ## Conseils utiles `.obs`ervables (également appelés types _Rx_) ont une grande variété de méthodes et d'opérateurs internes. > Il est très courant de croire qu'une propriété avec `.obs` ** EST ** la valeur réelle ... mais ne vous y trompez pas! > Nous évitons la déclaration Type de la variable, car le compilateur de Dart est assez intelligent, et le code > semble plus propre, mais: ```dart var message = 'Hello world'.obs; print( 'Message "$message" est de Type ${message.runtimeType}'); ``` Bien que `message` _prints_ la vraie valeur du String, le Type est **RxString**! Donc, vous ne pouvez pas faire `message.substring( 0, 4 )`. Vous devez utiliser la vraie `valeur` dans _observable_: La façon "la plus utilisée" est `.value`, mais, que vous pouviez aussi... ```dart final name = 'GetX'.obs; // "met à jour" le flux, uniquement si la valeur est différente de la valeur actuelle. name.value = 'Hey'; // Toutes les propriétés Rx sont "appelables" et renvoie la nouvelle valeur. // mais cette approche n'accepte pas `null`, l'interface utilisateur ne sera pas reconstruite. name('Hello'); // est comme un getter, affiche `Hello`. name() ; /// nombres: final count = 0.obs; // Vous pouvez utiliser toutes les opérations non mutables à partir de num primitives! count + 1; // Fais attention! ceci n'est valable que si `count` n'est pas final, mais var count += 1; // Vous pouvez également comparer avec des valeurs: count > 2; /// booleans: final flag = false.obs; // bascule la valeur entre true / false flag.toggle(); /// tous les types: // Définit la `valeur` sur null. flag.nil(); // Toutes les opérations toString (), toJson () sont transmises à la `valeur` print( count ); // appelle `toString ()` à l'intérieur de RxInt final abc = [0,1,2].obs; // Convertit la valeur en un Array json, affiche RxList // Json est pris en charge par tous les types Rx! print('json: ${jsonEncode(abc)}, type: ${abc.runtimeType}'); // RxMap, RxList et RxSet sont des types Rx spéciaux, qui étendent leurs types natifs. // mais vous pouvez travailler avec une liste comme une liste régulière, bien qu'elle soit réactive! abc.add(12); // pousse 12 dans la liste et MET À JOUR le flux. abc[3]; // comme Lists, lit l'index 3. // l'égalité fonctionne avec le Rx et la valeur, mais hashCode est toujours pris à partir de la valeur final number = 12.obs; print( number == 12 ); // retource > true /// Modèles Rx personnalisés: // toJson (), toString () sont différés à l'enfant, vous pouvez donc implémenter 'override' sur eux, et print() l'observable directement. class User { String name, last; int age; User({this.name, this.last, this.age}); @override String toString() => '$name $last, $age ans'; } final user = User(name: 'John', last: 'Doe', age: 33).obs; // `user` est" réactif ", mais les propriétés à l'intérieur NE SONT PAS! // Donc, si nous changeons une variable à l'intérieur ... user.value.name = 'Roi'; // Le widget ne se reconstruira pas !, // `Rx` n'a aucun indice lorsque vous changez quelque chose à l'intérieur de l'utilisateur. // Donc, pour les classes personnalisées, nous devons "notifier" manuellement le changement. user.refresh(); // ou utiliser `update()`! user.update((value){ value.name='Roi'; }); print( user ); ``` #### GetView J'adore ce widget. Si simple, mais si utile! C'est un widget `const Stateless` qui a un getter` controller` pour un `Controller` enregistré, c'est tout. ```dart class AwesomeController extends GetxController { final String title = 'My Awesome View'; } // N'oubliez PAS de passer le `Type` que vous avez utilisé pour enregistrer votre contrôleur! class AwesomeView extends GetView { @override Widget build(BuildContext context) { return Container( padding: EdgeInsets.all(20), child: Text(controller.title), // appelez `controller.quelqueChose` ); } } ``` #### GetResponsiveView Étendez ce widget pour créer une vue réactive. ce widget contient la propriété `screen` qui a toutes les informations sur la taille et le type de l'écran. ##### Guide d utilisation Vous avez deux options pour le créer: - avec la méthode `builder` vous renvoyez le widget à construire. - avec les méthodes `desktop`,` tablet`, `phone`,` watch`. la méthode spécifique sera exécutée lorsque le type d'écran correspond à la méthode. Lorsque l'écran est [ScreenType.Tablet], la méthode `tablet` sera exécutée et ainsi de suite. **Note:** Si vous utilisez cette méthode, veuillez définir la propriété `alwaysUseBuilder` à `false` Avec la propriété `settings`, vous pouvez définir la limite de largeur pour les types d'écran. ![exemple](https://github.com/SchabanBo/get_page_example/blob/master/docs/Example.gif?raw=true) Code pour cet écran [code](https://github.com/SchabanBo/get_page_example/blob/master/lib/pages/responsive_example/responsive_view.dart) #### GetWidget La plupart des gens n'ont aucune idée de ce widget ou confondent totalement son utilisation. Le cas d'utilisation est très rare, mais très spécifique: il `met en cache` un contrôleur. En raison du _cache_, ne peut pas être un `const Stateless`. > Alors, quand avez-vous besoin de "mettre en cache" un contrôleur? Si vous utilisez, une autre fonctionnalité "pas si courante" de **GetX**: `Get.create()`. `Get.create(()=>Controller())` générera un nouveau `Controller` chaque fois que vous appelez `Get.find()`. C'est là que `GetWidget` brille ... comme vous pouvez l'utiliser, par exemple, pour conserver une liste de s. Donc, si le widget est "reconstruit", il conservera la même instance de contrôleur. #### GetxService Cette classe est comme un `GetxController`, elle partage le même cycle de vie ( `onInit()`, `onReady()`, `onClose()`), mais n'a pas de "logique" en elle. Il notifie simplement le **GetX** Dependency Injection system, que cette sous-classe **ne peut pas** être supprimé de la mémoire. Donc est très utile pour garder vos "Services" toujours à portée de main et actifs avec `Get.find()`. Comme: `ServiceAPI`, `ServiceDeSauvegarde`, `ServiceDeCaching`. ```dart Future main() async { await initServices(); /// Attend l'initialisation des services. runApp(SomeApp()); } /// Est une démarche intelligente pour que vos services s'initialisent avant d'exécuter l'application Flutter. /// car vous pouvez contrôler le flux d'exécution (peut-être devez-vous charger une configuration de thème, /// apiKey, langue définie par l'utilisateur ... donc chargez SettingService avant d'exécuter ApiService. /// donc GetMaterialApp () n'a pas besoin de se reconstruire et prend les valeurs directement. void initServices() async { print('starting services ...'); /// C'est ici que vous mettez get_storage, hive, shared_pref initialization. /// ou les connexions moor, ou autres choses async. await Get.putAsync(() => DbService().init()); await Get.putAsync(SettingsService()).init(); print('Tous les services ont démarré...'); } class DbService extends GetxService { Future init() async { print('$runtimeType retarde de 2 sec'); await 2.delay(); print('$runtimeType prêts!'); return this; } } class SettingsService extends GetxService { void init() async { print("$runtimeType retarde d'1 sec"); await 1.delay(); print('$runtimeType prêts!'); } } ``` La seule façon de supprimer réellement un `GetxService`, est d'utiliser`Get.reset ()`qui est comme un "Hot Reboot" de votre application. N'oubliez donc pas que si vous avez besoin d'une persistance absolue d'une instance de classe durée de vie de votre application, utilisez `GetxService`. # Changements par rapport a 2.0 1- Types Rx: | Avant | Après | | ------- | ---------- | | StringX | `RxString` | | IntX | `RxInt` | | MapX | `RxMap` | | ListX | `RxList` | | NumX | `RxNum` | | DoubleX | `RxDouble` | RxController et GetBuilder ont maintenant fusionné, vous n'avez plus besoin de mémoriser le contrôleur que vous souhaitez utiliser, utilisez simplement GetxController, cela fonctionnera pour une gestion simple de l'état et également pour la réactivité. 2- NamedRoutes Avant: ```dart GetMaterialApp( namedRoutes: { '/': GetRoute(page: Home()), } ) ``` Maintenant: ```dart GetMaterialApp( getPages: [ GetPage(name: '/', page: () => Home()), ] ) ``` Pourquoi ce changement? Souvent, il peut être nécessaire de décider quelle page sera affichée à partir d'un paramètre, ou d'un 'login token', l'approche précédente était inflexible, car elle ne le permettait pas. L'insertion de la page dans une fonction a considérablement réduit la consommation de RAM, puisque les routes ne seront pas allouées en mémoire depuis le démarrage de l'application, et cela a également permis de faire ce type d'approche: ```dart GetStorage box = GetStorage(); GetMaterialApp( getPages: [ GetPage(name: '/', page:(){ return box.hasData('token') ? Home() : Login(); }) ] ) ``` # Pourquoi Getx? 1- Plusieurs fois après une mise à jour de Flutter, plusieurs de vos packages seront invalides. Parfois, des erreurs de compilation se produisent, des erreurs apparaissent souvent pour lesquelles il n'y a toujours pas de réponses, et le développeur doit savoir d'où vient l'erreur, suivre l'erreur, puis seulement essayer d'ouvrir un problème dans le référentiel correspondant et voir son problème résolu. Get centralise les principales ressources pour le développement (gestion des états, des dépendances et des routes), vous permettant d'ajouter un package unique à votre pubspec et de commencer à travailler. Après une mise à jour Flutter, la seule chose à faire est de mettre à jour la dépendance Get et de vous mettre au travail. Get résout également les problèmes de compatibilité. Combien de fois une version d'un package n'est pas compatible avec la version d'un autre, parce que l'une utilise une dépendance dans une version et l'autre dans une autre version? Ce n'est pas non plus un problème avec Get, car tout est dans le même package et est entièrement compatible. 2- Flutter est facile, Flutter est incroyable, mais Flutter a encore quelques règles standard qui peuvent être indésirables pour la plupart des développeurs, comme `Navigator.of (context) .push (context, builder [...]`. Get simplifie le développement. Au lieu de écrire 8 lignes de code pour simplement appeler une route, vous pouvez simplement le faire: `Get.to (Home ())` et vous avez terminé, vous passerez à la page suivante. Les URL Web dynamiques sont une chose vraiment pénible à voir avec Flutter actuellement, et cela avec GetX est stupidement simple. La gestion des états dans Flutter et la gestion des dépendances sont également quelque chose qui génère beaucoup de discussions, car il y a des centaines de modèles dans la pub. Mais rien n'est aussi simple que d'ajouter un ".obs" à la fin de votre variable, et placez votre widget dans un Obx, et c'est tout, toutes les mises à jour de cette variable seront automatiquement mises à jour à l'écran. 3- Facilité sans vous soucier des performances. Les performances de Flutter sont déjà étonnantes, mais imaginez que vous utilisez un gestionnaire d'état et un localisateur pour distribuer vos classes blocs / stores / contrôleurs / etc. Vous devrez appeler manuellement l'exclusion de cette dépendance lorsque vous n'en avez pas besoin. Mais avez-vous déjà pensé à simplement utiliser votre «contrôleur`, et quand il n'était plus utilisé par personne, il serait simplement supprimé de la mémoire? C'est ce que fait GetX. Avec SmartManagement, tout ce qui n'est pas utilisé est supprimé de la mémoire et vous ne devriez pas avoir à vous soucier d'autre chose que de la programmation. Vous serez assuré de consommer le minimum de ressources nécessaires, sans même avoir créé de logique pour cela. 4- Découplage réel. Vous avez peut-être entendu le concept "séparer la vue de la business logic". Ce n'est pas une particularité de BLoC, MVC, MVVM, et tout autre standard sur le marché a ce concept. Cependant, ce concept peut souvent être atténué dans Flutter en raison de l'utilisation de `context`. Si vous avez besoin de contexte pour trouver un InheritedWidget, vous en avez besoin dans la vue, ou passez le `context` par paramètre. Je trouve particulièrement cette solution très moche, et pour travailler en équipe, nous serons toujours dépendants de la 'business logic' de View. Getx n'est pas orthodoxe avec l'approche standard, et même s'il n'interdit pas complètement l'utilisation de StatefulWidgets, InitState, etc., il a toujours une approche similaire qui peut être plus propre. Les contrôleurs ont des cycles de vie, et lorsque vous devez faire une requête APIREST par exemple, vous ne dépendez de rien de la vue. Vous pouvez utiliser onInit pour lancer l'appel http et lorsque les données arrivent, les variables sont remplies. Comme GetX est totalement réactif (vraiment, et fonctionne sous streams), une fois les éléments remplis, tous les widgets qui utilisent cette variable seront automatiquement mis à jour dans la vue. Cela permet aux personnes ayant une expertise de l'interface utilisateur de travailler uniquement avec des widgets et de ne pas avoir à envoyer quoi que ce soit à la business logic autre que des événements utilisateur (comme cliquer sur un bouton), tandis que les personnes travaillant avec la business logic seront libres de créer et de tester la Business logic séparément. # Communite ## Chaines communautaires GetX a une communauté très active et utile. Si vous avez des questions, ou souhaitez obtenir de l'aide concernant l'utilisation de ce framework, veuillez rejoindre nos canaux communautaires, votre question sera répondue plus rapidement, et ce sera l'endroit le plus approprié. Ce référentiel est exclusif pour l'ouverture des issues Github et la demande de ressources, mais n'hésitez pas à faire partie de la communauté GetX. | **Slack** | **Discord** | **Telegram** | | :-------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------- | | [![Get sur Slack](https://img.shields.io/badge/slack-join-orange.svg)](https://communityinviter.com/apps/getxworkspace/getx) | [![Discord Shield](https://img.shields.io/discord/722900883784073290.svg?logo=discord)](https://discord.com/invite/9Hpt99N) | [![Telegram](https://img.shields.io/badge/chat-on%20Telegram-blue.svg)](https://t.me/joinchat/PhdbJRmsZNpAqSLJL6bH7g) | ## Comment contribuer _Voulez-vous contribuer au projet? Nous serons fiers de vous mettre en avant comme l'un de nos collaborateurs. Voici quelques points sur lesquels vous pouvez contribuer et améliorer encore Get (et Flutter)._ - Aider à traduire les 'Readme's dans d'autres langues. - Ajout de documentation au readme (beaucoup de fonctions de Get n'ont pas encore été documentées). - Rédiger des articles ou réaliser des vidéos pour apprendre à utiliser Get (ils seront insérés dans le Readme et à l'avenir dans notre Wiki). - Offrir des PRs pour code / tests. - Ajouter de nouvelles fonctions. Toute contribution est bienvenue! ## Articles et videos - [Dynamic Themes in 3 lines using GetX™](https://medium.com/swlh/flutter-dynamic-themes-in-3-lines-c3b375f292e3) - Tutorial by [Rod Brown](https://github.com/RodBr). - [Complete GetX™ Navigation](https://www.youtube.com/watch?v=RaqPIoJSTtI) - Route management video by Amateur Coder. - [Complete GetX State Management](https://www.youtube.com/watch?v=CNpXbeI_slw) - State management video by Amateur Coder. - [GetX™ Other Features](https://youtu.be/ttQtlX_Q0eU) - Utils, storage, bindings and other features video by Amateur Coder. - [Firestore User with GetX | Todo App](https://www.youtube.com/watch?v=BiV0DcXgk58) - Video by Amateur Coder. - [Firebase Auth with GetX | Todo App](https://www.youtube.com/watch?v=-H-T_BSgfOE) - Video by Amateur Coder. - [The Flutter GetX™ Ecosystem ~ State Management](https://medium.com/flutter-community/the-flutter-getx-ecosystem-state-management-881c7235511d) - State management by [Aachman Garg](https://github.com/imaachman). - [The Flutter GetX™ Ecosystem ~ Dependency Injection](https://medium.com/flutter-community/the-flutter-getx-ecosystem-dependency-injection-8e763d0ec6b9) - Dependency Injection by [Aachman Garg](https://github.com/imaachman). - [GetX, the all-in-one Flutter package](https://www.youtube.com/watch?v=IYQgtu9TM74) - A brief tutorial covering State Management and Navigation by Thad Carnevalli. - [Build a To-do List App from scratch using Flutter and GetX](https://www.youtube.com/watch?v=EcnqFasHf18) - UI + State Management + Storage video by Thad Carnevalli. - [GetX Flutter Firebase Auth Example](https://medium.com/@jeffmcmorris/getx-flutter-firebase-auth-example-b383c1dd1de2) - Article by Jeff McMorris. - [Flutter State Management with GetX – Complete App](https://www.appwithflutter.com/flutter-state-management-with-getx/) - by App With Flutter. - [Flutter Routing with Animation using Get Package](https://www.appwithflutter.com/flutter-routing-using-get-package/) - by App With Flutter. ================================================ FILE: README-hi.md ================================================ ![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/get.png) [![pub package](https://img.shields.io/pub/v/get.svg?label=get&color=blue)](https://pub.dev/packages/get) [![popularity](https://badges.bar/get/popularity)](https://pub.dev/packages/sentry/score) [![likes](https://badges.bar/get/likes)](https://pub.dev/packages/get/score) [![pub points](https://badges.bar/get/pub%20points)](https://pub.dev/packages/get/score) ![building](https://github.com/jonataslaw/get/workflows/build/badge.svg) [![style: effective dart](https://img.shields.io/badge/style-effective_dart-40c4ff.svg)](https://pub.dev/packages/effective_dart) [![Discord Shield](https://img.shields.io/discord/722900883784073290.svg?logo=discord)](https://discord.com/invite/9Hpt99N) [![Get on Slack](https://img.shields.io/badge/slack-join-orange.svg)](https://communityinviter.com/apps/getxworkspace/getx) [![Telegram](https://img.shields.io/badge/chat-on%20Telegram-blue.svg)](https://t.me/joinchat/PhdbJRmsZNpAqSLJL6bH7g) Awesome Flutter Buy Me A Coffee ![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/getx.png)
**Languages:** [![English](https://img.shields.io/badge/Language-English-blueviolet?style=for-the-badge)](README.md) [![Vietnamese](https://img.shields.io/badge/Language-Vietnamese-blueviolet?style=for-the-badge)](README-vi.md) [![Indonesian](https://img.shields.io/badge/Language-Indonesian-blueviolet?style=for-the-badge)](README.id-ID.md) [![Urdu](https://img.shields.io/badge/Language-Urdu-blueviolet?style=for-the-badge)](README.ur-PK.md) [![Chinese](https://img.shields.io/badge/Language-Chinese-blueviolet?style=for-the-badge)](README.zh-cn.md) [![Portuguese](https://img.shields.io/badge/Language-Portuguese-blueviolet?style=for-the-badge)](README.pt-br.md) [![Spanish](https://img.shields.io/badge/Language-Spanish-blueviolet?style=for-the-badge)](README-es.md) [![Russian](https://img.shields.io/badge/Language-Russian-blueviolet?style=for-the-badge)](README.ru.md) [![Polish](https://img.shields.io/badge/Language-Polish-blueviolet?style=for-the-badge)](README.pl.md) [![Korean](https://img.shields.io/badge/Language-Korean-blueviolet?style=for-the-badge)](README.ko-kr.md) [![French](https://img.shields.io/badge/Language-French-blueviolet?style=for-the-badge)](README-fr.md) [![Japanese](https://img.shields.io/badge/Language-Japanese-blueviolet?style=for-the-badge)](README.ja-JP.md) [![Turkish](https://img.shields.io/badge/Language-Turkish-blueviolet?style=for-the-badge)](README.tr-TR.md) [![Hindi](https://img.shields.io/badge/Language-Hindi-blueviolet?style=for-the-badge)](README-hi.md)
- [Get के बारे में](#about-get) - [इंस्टॉलिंग](#installing) - [Get के साथ काउंटर ऐप](#counter-app-with-getx) - [तीन सिद्धांत](#the-three-pillars) - [स्टेट मैनेजमेंट](#state-management) - [रिएक्टिव स्टेट मैनेजर ](#reactive-state-manager) - [स्टेट मैनेजमेंट के बारे में और जाने](#more-details-about-state-management) - [रूट मैनेजमेंट ](#route-management) - [रूट मैनेजमेंट के बारे में और जाने](#more-details-about-route-management) - [डिपेंडेंसी मैनेजमेंट](#dependency-management) - [डिपेंडेंसी मैनेजमेंट के बारे में और जाने](#more-details-about-dependency-management) - [मदद करने वाले फीचर्स](#utils) - [इंटरनॅशनलिनाइज़ेशन](#internationalization) - [अनुवाद](#translations) - [अनुवादों का उपयोग करना](#using-translations) - [क्षेत्र के अनुसार पसंद](#locales) - [लोकेल बदलें](#change-locale) - [सिस्टम लोकेलस ](#system-locale) - [थीम बदलें](#change-theme) - [GetConnect](#getconnect) - [डिफ़ॉल्ट कॉन्फ़िगरेशन ](#default-configuration) - [कस्टम कॉन्फ़िगरेशन](#custom-configuration) - [GetPage Middleware](#getpage-middleware) - [वरीयता](#priority) - [रीडायरेक्ट](#redirect) - [पेज कॉल होने पर](#onpagecalled) - [बाइंडिंग शुरू होने पर](#onbindingsstart) - [पेज बिल्ड स्टार्ट पर](#onpagebuildstart) - [पेज पूरा बन ने पर](#onpagebuilt) - [पेज डिस्पोसे होने पर](#onpagedispose) - [अन्य उन्नत एपीआई](#other-advanced-apis) - [वैकल्पिक वैश्विक सेटिंग्स और मैन्युअल कॉन्फ़िगरेशन](#optional-global-settings-and-manual-configurations) - [लोकल स्टेट विद्गेट्स ](#local-state-widgets) - [वैल्यू बिल्डर](#valuebuilder) - [ObxValue](#obxvalue) - [उपयोगी सलाह](#useful-tips) - [GetView](#getview) - [GetResponsiveView](#getresponsiveview) - [इसका उपयोग कैसे करना है](#how-to-use-it) - [GetWidget](#getwidget) - [GetxService](#getxservice) - [2.0 से बड़े बदलाव](#breaking-changes-from-20) - [क्यों GetX?](#why-getx) - [समुदाय](#community) - [सामुदायिक चैनल](#community-channels) - [कैसे योगदान करें](#how-to-contribute) - [लेख और वीडियो](#articles-and-videos) # Get के बारे में - GetX, Flutter के लिए एक अतिरिक्त हल्का और शक्तिशाली समाधान है। यह स्टेट मैनेजमेंट, डिपेंडेंसी इंजेक्शन और नेविगेशन को जल्दी और व्यावहारिक रूप से जोड़ता है। - GetX के 3 बुनियादी सिद्धांत हैं। इसका मतलब है कि पुस्तकालय में सभी संसाधनों के लिए ये प्राथमिकताएं हैं: **उत्पादकता, प्रदर्शन और संगठन।** - **प्रदर्शन**: GetX प्रदर्शन और संसाधनों की न्यूनतम खपत पर केंद्रित है। GetX स्ट्रीम या चेंज नोटिफ़ायर का उपयोग नहीं करता है। - **उत्पादकता**: GetX एक आसान और सुखद सिंटैक्स का उपयोग करता है। कोई फर्क नहीं पड़ता कि आप क्या करना चाहते हैं, GetX के साथ हमेशा एक आसान तरीका होता है। यह विकास के घंटों को बचाएगा और अधिकतम प्रदर्शन प्रदान करेगा जो आपका एप्लिकेशन प्रदान कर सकता है। आम तौर पर, डेवलपर को स्मृति से नियंत्रकों को हटाने के बारे में चिंतित होना चाहिए। GetX के साथ यह आवश्यक नहीं है क्योंकि संसाधनों को स्मृति से हटा दिया जाता है जब वे डिफ़ॉल्ट रूप से उपयोग नहीं किए जाते हैं। यदि आप इसे स्मृति में रखना चाहते हैं, तो आपको अपनी निर्भरता में स्पष्ट रूप से **"permanent: true"** घोषित करना होगा। इस तरह, समय बचाने के अलावा, आपको स्मृति पर अनावश्यक निर्भरता होने का जोखिम कम होता है। डिपेंडेंसी लोडिंग भी डिफ़ॉल्ट रूप से आलसी है। - **संगठन**: गेटएक्स व्यू, प्रेजेंटेशन लॉजिक, बिजनेस लॉजिक, डिपेंडेंसी इंजेक्शन और नेविगेशन को पूरी तरह से अलग करने की अनुमति देता है। आपको मार्गों के बीच नेविगेट करने के लिए संदर्भ की आवश्यकता नहीं है, इसलिए आप इसके लिए विजेट ट्री (विज़ुअलाइज़ेशन) पर निर्भर नहीं हैं। आपको विरासत में मिले विजेट के माध्यम से अपने नियंत्रकों/ब्लॉकों तक पहुँचने के लिए संदर्भ की आवश्यकता नहीं है, इसलिए आप अपने प्रस्तुति तर्क और व्यावसायिक तर्क को अपनी विज़ुअलाइज़ेशन परत से पूरी तरह से अलग कर सकते हैं। आपको मल्टीप्रोवाइडर्स के माध्यम से अपने विजेट ट्री में अपने कंट्रोलर/मॉडल/ब्लॉक क्लास को इंजेक्ट करने की आवश्यकता नहीं है। इसके लिए, GetX अपने स्वयं के निर्भरता इंजेक्शन सुविधा का उपयोग करता है, DI को अपने दृष्टिकोण से पूरी तरह से अलग करता है। **GetX** के साथ आप जानते हैं कि डिफ़ॉल्ट रूप से क्लीन कोड वाले अपने एप्लिकेशन की प्रत्येक सुविधा को कहां खोजना है। रखरखाव को आसान बनाने के अलावा, यह मॉड्यूल के साझाकरण को कुछ ऐसा बनाता है जो तब तक फ़्लटर में अकल्पनीय था, कुछ पूरी तरह से संभव था। **BLoC** फ़्लटर में कोड व्यवस्थित करने के लिए एक प्रारंभिक बिंदु था, यह व्यावसायिक तर्क को विज़ुअलाइज़ेशन से अलग करता है। GetX इसका एक स्वाभाविक विकास है, न केवल व्यावसायिक तर्क बल्कि प्रस्तुति तर्क को अलग करना। निर्भरता और मार्गों के बोनस इंजेक्शन को भी अलग कर दिया गया है, और डेटा स्तर इससे बाहर है। आप जानते हैं कि सब कुछ कहाँ है, और यह सब एक हैलो वर्ल्ड बनाने की तुलना में आसान तरीके से है। Flutter SDK के साथ उच्च-प्रदर्शन अनुप्रयोगों के निर्माण के लिए गेटएक्स सबसे आसान, व्यावहारिक और स्केलेबल तरीका है। इसके चारों ओर एक बड़ा पारिस्थितिकी तंत्र है जो पूरी तरह से एक साथ काम करता है, यह शुरुआती लोगों के लिए आसान है, और यह विशेषज्ञों के लिए सटीक है। यह सुरक्षित, स्थिर, अप-टू-डेट है, और अंतर्निहित एपीआई की एक विशाल श्रृंखला प्रदान करता है जो डिफ़ॉल्ट फ़्लटर एसडीके में मौजूद नहीं हैं। - **GetX** फूला हुआ नहीं है। इसमें कई विशेषताएं हैं जो आपको बिना किसी चिंता के प्रोग्रामिंग शुरू करने की अनुमति देती हैं, लेकिन इनमें से प्रत्येक सुविधा अलग-अलग कंटेनरों में होती है और केवल उपयोग के बाद ही शुरू होती है। यदि आप केवल राज्य प्रबंधन का उपयोग करते हैं, तो केवल राज्य प्रबंधन संकलित किया जाएगा। यदि आप केवल मार्गों का उपयोग करते हैं, तो राज्य प्रबंधन से कुछ भी संकलित नहीं किया जाएगा। - GetX में एक विशाल पारिस्थितिकी तंत्र, एक बड़ा समुदाय, बड़ी संख्या में सहयोगी हैं, और जब तक फ़्लटर मौजूद है, तब तक इसे बनाए रखा जाएगा। GetX भी **Android, iOS, Web, Mac, Linux, Windows** और आपके Server पर समान Code के साथ चलने में सक्षम है। **आपके Backend पर Frontend पर बने आपके कोड का पूरी तरह से पुन: उपयोग करना संभव है [Get Server](https://github.com/jonataslaw/get_server)**. **इसके अलावा, संपूर्ण विकास प्रक्रिया पूरी तरह से स्वचालित हो सकती है, दोनों सर्वर पर और फ्रंट एंड पर [Get CLI](https://github.com/jonataslaw/get_cli)**. **इसके अलावा, आपकी उत्पादकता को और बढ़ाने के लिए, हमारे पास है **[VSCODE के लिए एक्सटेंशन](https://marketplace.visualstudio.com/items?itemName=get-snippets.get-snippets) and the [Android Studio और Intellij के लिए एक्सटेंशन](https://plugins.jetbrains.com/plugin/14975-getx-snippets)** # इंस्टॉलिंग अपनी ```pubspec.yaml``` फ़ाइल में Get को ऐड करे ```yaml dependencies: ... get: ``` Get को अन फाइल्स में Import करे जहां आप Get को इस्तेमाल करेंगे: ```dart import 'package:get/get.dart'; ``` # GetX के साथ काउंटर ऐप Flutter पर नए Project पर Default रूप से बनाए गए "काउंटर" प्रोजेक्ट में 100 से अधिक लाइनें (टिप्पणियों के साथ) हैं। Get की शक्ति दिखाने के लिए, मैं प्रदर्शित करूंगा कि प्रत्येक क्लिक के साथ राज्य को बदलने वाला "काउंटर" कैसे बनाया जाए, Navigation किया जाए और State Management किया जाए, सभी एक संगठित तरीके से, व्यावसायिक तर्क को दृश्य से अलग करते हुए, केवल में टिप्पणियों सहित 26 लाइन कोड। - चरण 1: अपने **MaterialApp()** से पहले “Get” जोड़ें, इसे **GetMaterialApp()** में बदल दें ```dart void main() => runApp(GetMaterialApp(home: Home())); ``` - **सूचना**: यह Flutter के MaterialApp को संशोधित नहीं करता है, GetMaterialApp एक संशोधित MaterialApp नहीं है, यह सिर्फ एक पूर्व-कॉन्फ़िगर विजेट है, जिसमें एक बच्चे के रूप में डिफ़ॉल्ट MaterialApp है। आप इसे मैन्युअल रूप से कॉन्फ़िगर कर सकते हैं, लेकिन यह निश्चित रूप से आवश्यक नहीं है। GetMaterialApp मार्ग बनाएगा, उन्हें इंजेक्ट करेगा, अनुवाद इंजेक्ट करेगा, मार्ग नेविगेशन के लिए आपको जो कुछ भी चाहिए उसे इंजेक्ट करेगा। यदि आप केवल राज्य प्रबंधन या निर्भरता प्रबंधन के लिए गेट का उपयोग करते हैं, तो GetMaterialApp का उपयोग करना आवश्यक नहीं है। GetMaterialApp मार्गों, स्नैकबार, अंतर्राष्ट्रीयकरण, बॉटमशीट, संवाद, और मार्गों से संबंधित उच्च-स्तरीय एपिस और संदर्भ की अनुपस्थिति के लिए आवश्यक है। - **सूचना²**: यह चरण केवल तभी आवश्यक है जब आप Navigation (`Get.to()`, `Get.back()` इत्यादि) का उपयोग करने वाले हों। यदि आप इसका उपयोग नहीं करने जा रहे हैं तो चरण 1 करने की आवश्यकता नहीं है। - चरण 2: अपना व्यावसायिक तर्क वर्ग बनाएं और उसके अंदर सभी चर, विधियों और नियंत्रकों को रखें। आप किसी भी Variable Ko ".obs" का प्रयोग करके Observable बना सकते है ```dart class Controller extends GetxController{ var count = 0.obs; increment() => count++; } ``` - चरण 3: अपना दृश्य बनाएं, StatelessWidget का उपयोग करें और कुछ RAM सहेजें, Get के साथ आपको StatefulWidget का उपयोग करने की आवश्यकता नहीं है। ```dart class Home extends StatelessWidget { @override Widget build(context) { // Get.put() का उपयोग करके अपनी Class के सभी Children के लिए उपलब्ध कराने के लिए तत्काल करें। final Controller c = Get.put(Controller()); return Scaffold( // जब भी गिनती बदली जाए तो Text() को अपडेट करने के लिए Obx(()=> का उपयोग करें। appBar: AppBar(title: Obx(() => Text("Clicks: ${c.count}"))), // 8 लाइन Navigator.push() को एक साधारण Get.to() से बदलें । आपको "context" की आवश्यकता नहीं है । body: Center(child: ElevatedButton( child: Text("Go to Other"), onPressed: () => Get.to(Other()))), floatingActionButton: FloatingActionButton(child: Icon(Icons.add), onPressed: c.increment)); } } class Other extends StatelessWidget { // आप किसी अन्य Page द्वारा उपयोग किए जा रहे Controller को खोजने के लिए Get से पूछ सकते हैं और आपको उस पर Redirect कर सकते हैं। final Controller c = Get.find(); @override Widget build(context){ // बदले हुए count Variable को Access करें return Scaffold(body: Center(child: Text("${c.count}"))); } } ``` **परिणाम**: ![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/counter-app-gif.gif) यह एक सरल Project है लेकिन यह पहले ही स्पष्ट कर देती है कि Get कितना शक्तिशाली है। जैसे-जैसे आपकी परियोजना बढ़ती है, यह अंतर और अधिक महत्वपूर्ण होता जाएगा। Get को टीमों के साथ काम करने के लिए डिज़ाइन किया गया था, लेकिन यह व्यक्तिगत डेवलपर के काम को आसान बनाता है। अपनी समय सीमा में सुधार करें, Performance को खोए बिना समय पर सब कुछ वितरित करें। Get हर किसी के लिए नहीं है, लेकिन अगर आपने उस वाक्यांश के साथ पहचान की है, तो Get आपके लिए है! # तीन सिद्धांत ## State management Get के दो अलग-अलग State Manager हैं: Simple State Builder (हम इसे GetBuilder कहते हैं) और Reactive State Manager (GetX/Obx) ### Reactive State Manager Reactive Programming कई लोगों को अलग-थलग कर सकती है क्योंकि इसे जटिल कहा जाता है। GetX Reactive Programming को काफी सरल बना देता है: - आपको StreamControllers बनाने की आवश्यकता नहीं होगी। - आपको प्रत्येक Variable के लिए StreamBuilder बनाने की आवश्यकता नहीं होगी - आपको प्रत्येक Class के लिए एक State बनाने की आवश्यकता नहीं होगी। - आपको Initial Value के लिए एक Get बनाने की आवश्यकता नहीं होगी। - आपको Code Generators का उपयोग करने की आवश्यकता नहीं होगी **Get** के साथ **Reactive Programming**, **"setState()"** का उपयोग करने जितना आसान है। आइए कल्पना करें कि आपके पास एक name Variable है और चाहते हैं कि हर बार जब आप इसे बदलते हैं, तो इसका उपयोग करने वाले सभी Widgets बदल जाएँ । यह आपका count Variable है ```dart var name = 'Adison Masih'; ``` इसे Observable बनाने के लिए, आपको बस इसके अंत में ".obs" जोड़ना होगा: ```dart var name = 'Adison Masih'.obs; ``` अगर आप आप उस Value को दिखाना चाहते हैं और जब भी Value बदलती हैं तो स्क्रीन को करना चाहते हैं, तो यह करें: ```dart Obx(() => Text("${controller.name}")); ``` बस इतना ही। यह _इत्ना_ आसान है। ### State Management के बारे में अधिक जानकारी राज्य प्रबंधन की अधिक गहन व्याख्या [यहां](./documentation/en_US/state_management.md) देखें । वहां आपको अधिक उदाहरण और **Simple State Manager** और **Reactive State Manager** के बीच का अंतर दिखाई देगा आपको GetX की शक्ति का अच्छा अंदाजा हो जाएगा। ## Route प्रबंधन यदि आप बिना **context** के **Routes/Snackbars/Dialogs/Bottomsheets** का उपयोग करने जा रहे हैं, तो GetX आपके लिए भी उत्कृष्ट है, बस इसे देखें: अपने **"MaterialApp()"** से पहले **“Get”** जोड़ें, इसे **"GetMaterialApp()"** में बदल दें ```dart GetMaterialApp( // यह पहले MaterialApp था home: MyHome(), ) ``` एक नई स्क्रीन पर Navigate करें: ```dart Get.to(NextScreen()); ``` नाम के साथ नई स्क्रीन पर Navigate करें। **Named Routes** पर अधिक विवरण [यहां](./documentation/en_US/route_management.md#navigation-with-named-routes) देखें। ```dart Get.toNamed('/details'); ``` Snackbar, Dialog तथा Bottomsheets या ऐसी किसी भी चीज़ को बंद करने के लिए आप आम तौर पर ```Navigator.pop(context);``` के साथ बंद करते हैं; ```dart Get.back(); ``` अगली Screen पर जाने के लिए और पिछली Screen पर वापस जाने का कोई विकल्प नहीं है (SplashScreens, Login Screens आदि में उपयोग के लिए) ```dart Get.off(NextScreen()); ``` अगली Screen पर जाने और पिछले सभी Routes को रद्द करने के लिए (Shopping Carts, Polls और Tests में उपयोगी) ```dart Get.offAll(NextScreen()); ``` ध्यान दिया कि आपको इनमें से कोई भी काम करने के लिए **"context"** का उपयोग करने की आवश्यकता नहीं है? **"Get Route Management"** का उपयोग करने का यह सबसे बड़ा लाभ है। इसके साथ, आप इन सभी **"Methods"** को अपने **"Controller Class"** से बिना किसी चिंता के Execute कर सकते हैं। ### Route Management के बारे में अधिक जानकारी **"Named Routes"** के साथ काम करें और अपने Routes पर **"Lower-Level Control"** भी प्राप्त करें! [यहां](./documentation/en_US/route_management.md) गहन दस्तावेज है ================================================ FILE: README-ne.md ================================================ ![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/get.png) [![pub package](https://img.shields.io/pub/v/get.svg?label=get&color=blue)](https://pub.dev/packages/get) [![popularity](https://img.shields.io/pub/popularity/get?logo=dart)](https://pub.dev/packages/get/score) [![likes](https://img.shields.io/pub/likes/get?logo=dart)](https://pub.dev/packages/get/score) [![pub points](https://img.shields.io/pub/points/sentry?logo=dart)](https://pub.dev/packages/get/score) ![building](https://github.com/jonataslaw/get/workflows/build/badge.svg) [![style: effective dart](https://img.shields.io/badge/style-effective_dart-40c4ff.svg)](https://pub.dev/packages/effective_dart) [![Discord Shield](https://img.shields.io/discord/722900883784073290.svg?logo=discord)](https://discord.com/invite/9Hpt99N) [![Get on Slack](https://img.shields.io/badge/slack-join-orange.svg)](https://communityinviter.com/apps/getxworkspace/getx) [![Telegram](https://img.shields.io/badge/chat-on%20Telegram-blue.svg)](https://t.me/joinchat/PhdbJRmsZNpAqSLJL6bH7g) Awesome Flutter Buy Me A Coffee ![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/getx.png)
**Languages:** [![English](https://img.shields.io/badge/Language-English-blueviolet?style=for-the-badge)](README.md) [![Vietnamese](https://img.shields.io/badge/Language-Vietnamese-blueviolet?style=for-the-badge)](README-vi.md) [![Indonesian](https://img.shields.io/badge/Language-Indonesian-blueviolet?style=for-the-badge)](README.id-ID.md) [![Urdu](https://img.shields.io/badge/Language-Urdu-blueviolet?style=for-the-badge)](README.ur-PK.md) [![Chinese](https://img.shields.io/badge/Language-Chinese-blueviolet?style=for-the-badge)](README.zh-cn.md) [![Portuguese](https://img.shields.io/badge/Language-Portuguese-blueviolet?style=for-the-badge)](README.pt-br.md) [![Spanish](https://img.shields.io/badge/Language-Spanish-blueviolet?style=for-the-badge)](README-es.md) [![Russian](https://img.shields.io/badge/Language-Russian-blueviolet?style=for-the-badge)](README.ru.md) [![Polish](https://img.shields.io/badge/Language-Polish-blueviolet?style=for-the-badge)](README.pl.md) [![Korean](https://img.shields.io/badge/Language-Korean-blueviolet?style=for-the-badge)](README.ko-kr.md) [![French](https://img.shields.io/badge/Language-French-blueviolet?style=for-the-badge)](README-fr.md) [![Japanese](https://img.shields.io/badge/Language-Japanese-blueviolet?style=for-the-badge)](README.ja-JP.md) [![Hindi](https://img.shields.io/badge/Language-Hindi-blueviolet?style=for-the-badge)](README-hi.md) [![Bangla](https://img.shields.io/badge/Language-Bangla-blueviolet?style=for-the-badge)](README-bn.md) [![Nepali](https://img.shields.io/badge/Language-Nepali-blueviolet?style=for-the-badge)](README-ne.md)
- [Get को बारेमा](#about-get) - [इन्स्टल गर्ने तरिका](#installing) - [GetX सहित काउन्टर एप](#counter-app-with-getx) - [तीन मुख्य आधार](#the-three-pillars) - [स्टेट व्यवस्थापन](#state-management) - [रिएक्टिभ स्टेट म्यानेजर](#reactive-state-manager) - [स्टेट व्यवस्थापनबारे थप विवरण](#more-details-about-state-management) - [रुट व्यवस्थापन](#route-management) - [रुट व्यवस्थापनबारे थप विवरण](#more-details-about-route-management) - [डिपेन्डेन्सी व्यवस्थापन](#dependency-management) - [डिपेन्डेन्सी व्यवस्थापनबारे थप विवरण](#more-details-about-dependency-management) - [युटिल्स (Utils)](#utils) - [अन्तर्राष्ट्रियकरण](#internationalization) - [अनुवादहरू](#translations) - [अनुवाद प्रयोग गर्ने तरिका](#using-translations) - [लोकेलहरू (Locales)](#locales) - [लोकेल परिवर्तन गर्ने](#change-locale) - [सिस्टम लोकेल](#system-locale) - [थिम परिवर्तन](#change-theme) - [GetConnect](#getconnect) - [डिफल्ट कन्फिगरेसन](#default-configuration) - [कस्टम कन्फिगरेसन](#custom-configuration) - [GetPage मिडलवेयर](#getpage-middleware) - [प्राथमिकता](#priority) - [रिडाइरेक्ट (Redirect)](#redirect) - [onPageCalled](#onpagecalled) - [OnBindingsStart](#onbindingsstart) - [OnPageBuildStart](#onpagebuildstart) - [OnPageBuilt](#onpagebuilt) - [OnPageDispose](#onpagedispose) - [अन्य उन्नत API हरू](#other-advanced-apis) - [वैकल्पिक ग्लोबल सेटिङ र म्यानुअल कन्फिगरेसन](#optional-global-settings-and-manual-configurations) - [लोकल स्टेट विजेटहरू](#local-state-widgets) - [ValueBuilder](#valuebuilder) - [ObxValue](#obxvalue) - [उपयोगी टिप्स](#useful-tips) - [GetView](#getview) - [GetResponsiveView](#getresponsiveview) - [यसलाई कसरी प्रयोग गर्ने](#how-to-use-it) - [GetWidget](#getwidget) - [GetxService](#getxservice) - [2.0 बाट भएका ब्रेकिङ परिवर्तनहरू](#breaking-changes-from-20) - [किन Getx?](#why-getx) - [कम्युनिटी](#community) - [कम्युनिटी च्यानलहरू](#community-channels) - [कसरी योगदान गर्ने](#how-to-contribute) - [लेख र भिडियोहरू](#articles-and-videos) # Get को बारेमा - GetX Flutter को लागि एक अतिरिक्त-हल्का र शक्तिशाली समाधान हो। यसले उच्च प्रदर्शनको स्टेट व्यवस्थापन, बौद्धिक डिपेन्डेन्सी इन्जेक्शन, र रुट व्यवस्थापनलाई छिटो र व्यावहारिक रूपमा संयोजन गर्दछ। - GetX का ३ आधारभूत सिद्धान्तहरू छन्। यसको अर्थ पुस्तकालयका सबै स्रोतहरूको लागि यी प्राथमिकताहरू हुन्: **PRODUCTIVITY (उत्पादकता), PERFORMANCE (प्रदर्शन) र ORGANIZATION (संगठन)।** - **PERFORMANCE (प्रदर्शन):** GetX प्रदर्शन र स्रोतहरूको न्यूनतम खपतमा केन्द्रित छ। GetX ले Streams वा ChangeNotifier प्रयोग गर्दैन। - **PRODUCTIVITY (उत्पादकता):** GetX ले सजिलो र रमाइलो सिन्ट्याक्स प्रयोग गर्दछ। तपाईं जे गर्न चाहनुहुन्छ, GetX सँग सधैं सजिलो तरिका हुन्छ। यसले विकासका घण्टाहरू बचत गर्नेछ र तपाईंको अनुप्रयोगले दिन सक्ने अधिकतम प्रदर्शन प्रदान गर्नेछ। सामान्यतया, विकासकर्ताले मेमोरीबाट Controller हरू हटाउने बारे चिन्ता गर्नुपर्छ। GetX सँग यो आवश्यक छैन किनकि स्रोतहरू डिफल्ट रूपमा प्रयोग नभएपछि मेमोरीबाट हटाइन्छन्। यदि तपाईं यसलाई मेमोरीमा राख्न चाहनुहुन्छ भने, तपाईंले आफ्नो डिपेन्डेन्सीमा "permanent: true" स्पष्ट रूपमा घोषणा गर्नुपर्छ। त्यसरी, समय बचत गर्नुको साथै, तपाईं मेमोरीमा अनावश्यक डिपेन्डेन्सीहरू हुने जोखिममा कम हुनुहुन्छ। डिपेन्डेन्सी लोडिङ पनि डिफल्ट रूपमा लेजी (lazy) हुन्छ। - **ORGANIZATION (संगठन):** GetX ले View, प्रिजेन्टेसन लजिक, बिजनेस लजिक, डिपेन्डेन्सी इन्जेक्शन, र नेभिगेसनको पूर्ण डिकपलिंग (decoupling) गर्न अनुमति दिन्छ। रुटहरू बीच नेभिगेट गर्न तपाईंलाई context को आवश्यकता पर्दैन, त्यसैले तपाईं यसको लागि widget tree (visualization) मा निर्भर हुनुहुन्न। तपाईंलाई inheritedWidget मार्फत आफ्ना controllers/blocs पहुँच गर्न context आवश्यकता पर्दैन, त्यसैले तपाईंले आफ्नो प्रिजेन्टेसन लजिक र बिजनेस लजिकलाई आफ्नो भिजुअलाइजेसन लेयरबाट पूर्ण रूपमा अलग गर्नुहुन्छ। तपाईंले `MultiProvider` हरू मार्फत आफ्नो widget tree मा Controllers/Models/Blocs कक्षाहरू इन्जेक्ट गर्नुपर्दैन। यसको लागि, GetX ले आफ्नो डिपेन्डेन्सी इन्जेक्शन सुविधा प्रयोग गर्दछ, DI लाई यसको view बाट पूर्ण रूपमा अलग गर्दछ। GetX सँग तपाईं आफ्नो एप्लिकेसनको हरेक फिचर कहाँ छ भन्ने कुरा सजिलै थाहा पाउनुहुन्छ, र डिफल्ट रूपमा कोड सफा रहन्छ। यसले मर्मतसम्भार (maintenance) सजिलो बनाउनुका साथै, Flutter मा पहिले असम्भवजस्तै लाग्ने मोड्युल साझेदारीलाई पनि पूर्ण रूपमा सम्भव बनाउँछ। Flutter मा कोड व्यवस्थित गर्ने सुरुआती बिन्दु BLoC थियो, जसले business logic लाई visualization बाट अलग गर्‍यो। GetX यसको प्राकृतिक विकास हो—यसले business logic मात्र होइन, presentation logic पनि अलग गर्छ। थप रूपमा, dependency injection र routes पनि decoupled छन्, र data layer पनि यीबाट छुट्टै रहन्छ। तपाईंलाई सबै कुरा कहाँ छ भन्ने स्पष्ट हुन्छ, र यो सबै "hello world" बनाउने भन्दा पनि सजिलो तरिकाले गर्न सकिन्छ। GetX, Flutter SDK प्रयोग गरेर उच्च-प्रदर्शन एपहरू बनाउने सबैभन्दा सजिलो, व्यवहारिक, र scalable तरिका हो। यसको वरिपरि ठूलो ecosystem छ जुन उत्कृष्ट रूपमा सँगै काम गर्छ; यो सुरु गर्नेहरूका लागि सजिलो छ र अनुभवी विकासकर्ताहरूका लागि पनि सटीक छ। यो सुरक्षित, स्थिर, अद्यावधिक, र default Flutter SDK मा नभएका धेरै built-in API हरूसहित आउँछ। - GetX अनावश्यक रूपमा भारी (bloated) छैन। यसमा धेरै सुविधाहरू छन् जसले तपाईंलाई कुनै चिन्ता बिना विकास सुरु गर्न मद्दत गर्छ, तर प्रत्येक सुविधा छुट्टाछुट्टै container मा हुन्छ र प्रयोग गरेपछि मात्रै सुरु हुन्छ। यदि तपाईंले State Management मात्र प्रयोग गर्नुभयो भने State Management मात्रै compile हुन्छ। यदि तपाईंले routes मात्रै प्रयोग गर्नुभयो भने state management सम्बन्धी कोड compile हुँदैन। - GetX को विशाल ecosystem, ठूलो समुदाय, धेरै सहयोगीहरू छन्, र Flutter रहुञ्जेल यसको मर्मतसम्भार जारी रहनेछ। GetX एउटै कोडबाट Android, iOS, Web, Mac, Linux, Windows र server मा पनि चल्न सक्षम छ। **[Get Server](https://github.com/jonataslaw/get_server) प्रयोग गरेर frontend मा लेखेको कोड backend मा पनि पूर्ण रूपमा पुन: प्रयोग गर्न सकिन्छ।** **थप रूपमा, [Get CLI](https://github.com/jonataslaw/get_cli) प्रयोग गरेर server र frontend दुवैमा सम्पूर्ण विकास प्रक्रिया पूर्ण रूपमा automate गर्न सकिन्छ।** **अझ बढी उत्पादकता बढाउनका लागि, हामीसँग [VSCode को extension](https://marketplace.visualstudio.com/items?itemName=get-snippets.get-snippets) र [Android Studio/Intellij को extension](https://plugins.jetbrains.com/plugin/14975-getx-snippets) पनि उपलब्ध छन्।** # इन्स्टल गर्ने तरिका तपाईंको `pubspec.yaml` फाइलमा Get थप्नुहोस्: ```yaml dependencies: get: ``` जहाँ प्रयोग हुन्छ, ती फाइलहरूमा get इम्पोर्ट गर्नुहोस्: ```dart import 'package:get/get.dart'; ``` # GetX सहित काउन्टर एप Flutter मा नयाँ प्रोजेक्ट बनाउँदा डिफल्ट आउने "counter" प्रोजेक्टमा (comments सहित) 100 भन्दा बढी लाइन हुन्छन्। Get कति शक्तिशाली छ देखाउन, म प्रत्येक क्लिकमा state परिवर्तन हुने, पेजहरू बीच स्विच हुने, र स्क्रिनहरूबीच state साझा हुने "counter" कसरी बनाउने भनेर देखाउँछु—यो सबै व्यवस्थित तरिकाले, business logic लाई view बाट अलग गरेर, comments सहित जम्मा 26 लाइन कोडमा। - चरण १: आफ्नो `MaterialApp` अगाडि `Get` थपेर यसलाई `GetMaterialApp` बनाउनुहोस् ```dart void main() => runApp(GetMaterialApp(home: Home())); ``` - नोट: यसले Flutter को `MaterialApp` लाई परिवर्तन गर्दैन। `GetMaterialApp` परिवर्तन गरिएको `MaterialApp` होइन; यो केवल pre-configured Widget हो, जसको child को रूपमा default `MaterialApp` हुन्छ। तपाईंले यसलाई manually configure गर्न सक्नुहुन्छ, तर सामान्यतया आवश्यक पर्दैन। `GetMaterialApp` ले routes बनाउँछ, तिनीहरू inject गर्छ, translations inject गर्छ, र route navigation का लागि चाहिने सबै कुरा तयार पार्छ। यदि तपाईं Get लाई state management वा dependency management का लागि मात्र प्रयोग गर्दै हुनुहुन्छ भने `GetMaterialApp` आवश्यक छैन। `GetMaterialApp` routes, snackbars, internationalization, bottomSheets, dialogs, र context बिना प्रयोग हुने high-level APIs का लागि आवश्यक हुन्छ। - नोट²: यो चरण route management (`Get.to()`, `Get.back()` आदि) प्रयोग गर्ने हो भने मात्र आवश्यक हुन्छ। यदि प्रयोग गर्नुहुन्न भने चरण १ आवश्यक छैन। - चरण २: आफ्नो business logic class बनाउनुहोस् र त्यसभित्र सबै variables, methods, र controllers राख्नुहोस्। साधारण `.obs` प्रयोग गरेर कुनै पनि variable लाई observable बनाउन सकिन्छ। ```dart class Controller extends GetxController{ var count = 0.obs; increment() => count++; } ``` - चरण ३: आफ्नो View बनाउनुहोस्, `StatelessWidget` प्रयोग गर्नुहोस् र केही RAM बचत गर्नुहोस्। Get प्रयोग गर्दा `StatefulWidget` को आवश्यकता धेरै अवस्थामा नपर्न सक्छ। ```dart class Home extends StatelessWidget { @override Widget build(context) { // आफ्नो class लाई Get.put() प्रयोग गरेर instantiate गर्नुहोस्, // ताकि त्यो त्यहाँका सबै "child" routes मा उपलब्ध होस्। final Controller c = Get.put(Controller()); return Scaffold( // count परिवर्तन हुँदा Text() अपडेट गर्न Obx(() => ...) प्रयोग गर्नुहोस्। appBar: AppBar(title: Obx(() => Text("क्लिक: ${c.count}"))), // 8 लाइनको Navigator.push को सट्टा साधारण Get.to() प्रयोग गर्नुहोस्। context चाहिँदैन। body: Center(child: ElevatedButton( child: Text("अर्को पेजमा जानुहोस्"), onPressed: () => Get.to(Other()))), floatingActionButton: FloatingActionButton(child: Icon(Icons.add), onPressed: c.increment)); } } class Other extends StatelessWidget { // अर्को पेजले प्रयोग गरिरहेको Controller लाई Get.find() मार्फत पाउन सकिन्छ। final Controller c = Get.find(); @override Widget build(context){ // अपडेट भएको count variable प्रयोग गर्नुहोस् return Scaffold(body: Center(child: Text("${c.count}"))); } } ``` नतिजा: ![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/counter-app-gif.gif) यो सरल प्रोजेक्ट हो, तर यसले Get कति शक्तिशाली छ भन्ने कुरा स्पष्ट देखाउँछ। तपाईंको प्रोजेक्ट बढ्दै जाँदा यो फरक अझ स्पष्ट देखिन्छ। Get टिमसँग काम गर्न डिजाइन गरिएको हो, तर यसले एकल डेभलपरको काम पनि सरल बनाउँछ। आफ्नो deadline सुधार्नुहोस्, performance नघटाई काम समयमै डेलिभर गर्नुहोस्। Get सबैका लागि नहुन सक्छ, तर यदि तपाईंलाई यो कुरा उपयुक्त लाग्छ भने Get तपाईंका लागि हो! # तीन मुख्य आधार ## स्टेट व्यवस्थापन Get मा दुई फरक स्टेट म्यानेजर छन्: simple state manager (यसलाई हामी GetBuilder भन्छौं) र reactive state manager (GetX/Obx)। ### रिएक्टिभ स्टेट म्यानेजर रिएक्टिभ प्रोग्रामिङ जटिल छ भन्ने धारणा भएकाले धेरैलाई यो अप्ठ्यारो लाग्न सक्छ। तर GetX ले reactive programming लाई धेरै सरल बनाउँछ: - तपाईंले StreamController बनाउनुपर्दैन। - प्रत्येक variable को लागि StreamBuilder बनाउनुपर्दैन। - प्रत्येक state का लागि छुट्टै class बनाउनुपर्दैन। - initial value का लागि get बनाउनुपर्दैन। - code generators प्रयोग गर्नुपर्दैन। Get सँग reactive programming गर्नु `setState` प्रयोग गरे जत्तिकै सजिलो हुन्छ। मानौं तपाईंसँग `name` variable छ र तपाईं चाहनुहुन्छ कि यसलाई परिवर्तन गर्दा यसलाई प्रयोग गर्ने सबै widgets स्वतः अपडेट होउन्। यो तपाईंको `name` variable हो: ```dart var name = 'Jonatas Borges'; ``` यसलाई observable बनाउन, अन्त्यमा `.obs` थप्नुहोस्: ```dart var name = 'Jonatas Borges'.obs; ``` UI मा त्यो value देखाउन र value परिवर्तन हुँदा स्क्रिन अपडेट गर्न, यसरी गर्नुहोस्: ```dart Obx(() => Text("${controller.name}")); ``` यत्ति हो। यति सजिलो छ। ### स्टेट व्यवस्थापनबारे थप विवरण **स्टेट व्यवस्थापनको अझ विस्तृत व्याख्या [यहाँ](./documentation/en_US/state_management.md) हेर्नुहोस्। त्यहाँ तपाईंले थप उदाहरणहरू र simple state manager तथा reactive state manager बीचको फरक पनि देख्नुहुनेछ।** यसले तपाईंलाई GetX को शक्ति राम्रोसँग बुझ्न मद्दत गर्छ। ## रुट व्यवस्थापन यदि तपाईं context बिना routes/snackbars/dialogs/bottomsheets प्रयोग गर्न चाहनुहुन्छ भने GetX तपाईंका लागि उत्कृष्ट विकल्प हो: आफ्नो `MaterialApp` अगाडि `Get` थपेर यसलाई `GetMaterialApp` बनाउनुहोस्: ```dart GetMaterialApp( // पहिले: MaterialApp( home: MyHome(), ) ``` नयाँ स्क्रिनमा जान: ```dart Get.to(NextScreen()); ``` नाम (named route) प्रयोग गरेर नयाँ स्क्रिनमा जान। Named routes बारे थप विवरण [यहाँ](./documentation/en_US/route_management.md#navigation-with-named-routes) हेर्नुहोस्। ```dart Get.toNamed('/details'); ``` snackbar, dialog, bottomsheet, वा सामान्यतया `Navigator.pop(context)` ले बन्द गर्ने कुनै पनि चीज बन्द गर्न: ```dart Get.back(); ``` अर्को स्क्रिनमा जान र अघिल्लो स्क्रिनमा फर्किने विकल्प हटाउन (SplashScreen, login screens आदि लागि): ```dart Get.off(NextScreen()); ``` अर्को स्क्रिनमा जान र सबै अघिल्ला routes हटाउन (shopping carts, polls, tests मा उपयोगी): ```dart Get.offAll(NextScreen()); ``` हेर्नुभयो? यी कुनै पनि काम गर्न तपाईंलाई context चाहिएन। यही नै Get route management को सबैभन्दा ठूलो फाइदामध्ये एक हो। यसरी तपाईं यी सबै methods आफ्नो controller class भित्रैबाट सजिलै चलाउन सक्नुहुन्छ। ### रुट व्यवस्थापनबारे थप विवरण **Get ले named routes सँग काम गर्छ र तपाईंलाई routes माथि low-level control पनि दिन्छ! यसको विस्तृत documentation [यहाँ](./documentation/en_US/route_management.md) उपलब्ध छ।** ## डिपेन्डेन्सी व्यवस्थापन Get सँग सरल र शक्तिशाली dependency manager छ, जसले Provider context वा inheritedWidget बिना, केवल १ लाइन कोडमै तपाईंको Bloc वा Controller जस्तै class प्राप्त गर्न दिन्छ: ```dart Controller controller = Get.put(Controller()); // Controller controller = Controller(); को सट्टा ``` - नोट: यदि तपाईं Get को State Manager प्रयोग गर्दै हुनुहुन्छ भने bindings API मा ध्यान दिनुहोस्; यसले तपाईंको view र controller जोड्न सजिलो बनाउँछ। तपाईंले प्रयोग गरिरहेको class भित्र instantiate गर्नुको सट्टा, तपाईं class लाई Get instance भित्र instantiate गर्नुहुन्छ, जसले यसलाई पुरै App मा उपलब्ध बनाउँछ। त्यसपछि तपाईं आफ्नो controller (वा Bloc class) सामान्य रूपमा प्रयोग गर्न सक्नुहुन्छ। **टिप:** Get को dependency management, package का अन्य भागहरूबाट decoupled छ। त्यसैले तपाईंको app पहिले नै कुनै पनि state manager प्रयोग गरिरहेको छ भने पनि सबै कुरा फेरि लेख्नुपर्दैन; यो dependency injection सिधै प्रयोग गर्न सकिन्छ। ```dart controller.fetchApi(); ``` मानौं तपाईं धेरै routes हुँदै अघि बढ्नुभयो र controller मा पहिलेको data फेरि चाहियो। सामान्यतया state manager सँग Provider वा Get_it चाहिन्थ्यो, हैन? Get सँग त्यस्तो चाहिँदैन। तपाईंले Get लाई controller "find" गर्न भन्नु मात्र पर्छ; अतिरिक्त dependency आवश्यक पर्दैन: ```dart Controller controller = Get.find(); // हो, यो जादू जस्तो देखिन्छ—Get ले सही controller फेला पारेर तपाईंलाई दिन्छ। // लाखौं controllers instantiate भए पनि Get ले सही controller नै दिन्छ। ``` त्यसपछि तपाईं पहिले प्राप्त भएको controller data फेरि प्रयोग गर्न सक्नुहुन्छ: ```dart Text(controller.textFromApi); ``` ### डिपेन्डेन्सी व्यवस्थापनबारे थप विवरण **डिपेन्डेन्सी व्यवस्थापनको विस्तृत व्याख्या [यहाँ](./documentation/en_US/dependency_management.md) हेर्नुहोस्।** # युटिल्स ## अन्तर्राष्ट्रियकरण ### अनुवादहरू अनुवादहरू साधारण key-value dictionary map को रूपमा राखिन्छन्। custom translations थप्न class बनाएर `Translations` लाई extend गर्नुहोस्। ```dart import 'package:get/get.dart'; class Messages extends Translations { @override Map> get keys => { 'en_US': { 'hello': 'Hello World', }, 'de_DE': { 'hello': 'Hallo Welt', } }; } ``` #### अनुवाद प्रयोग गर्ने तरिका निर्दिष्ट key को अन्त्यमा `.tr` थप्नुहोस्। त्यसपछि `Get.locale` र `Get.fallbackLocale` को हालको मान अनुसार अनुवाद हुन्छ। ```dart Text('title'.tr); ``` #### एकवचन र बहुवचनसहित अनुवाद प्रयोग गर्ने तरिका ```dart var products = []; Text('singularKey'.trPlural('pluralKey', products.length, Args)); ``` #### parameters सहित अनुवाद प्रयोग गर्ने तरिका ```dart import 'package:get/get.dart'; Map> get keys => { 'en_US': { 'logged_in': '@name को रूपमा @email इमेलसहित लगइन गरिएको छ', }, 'es_ES': { 'logged_in': '@name को रूपमा @email इमेलसहित लगइन गरिएको छ', } }; Text('logged_in'.trParams({ 'name': 'Jhon', 'email': 'jhon@example.com' })); ``` ### लोकेलहरू locale र translations सेट गर्न `GetMaterialApp` मा parameters दिनुहोस्। ```dart return GetMaterialApp( translations: Messages(), // तपाईंका अनुवादहरू locale: Locale('en', 'US'), // अनुवाद यही locale मा देखाइन्छ fallbackLocale: Locale('en', 'UK'), // अमान्य locale चयन भए fallback locale प्रयोग हुन्छ ); ``` #### लोकेल परिवर्तन गर्ने locale अपडेट गर्न `Get.updateLocale(locale)` चलाउनुहोस्। त्यसपछि अनुवादले स्वतः नयाँ locale प्रयोग गर्छ। ```dart var locale = Locale('en', 'US'); Get.updateLocale(locale); ``` #### सिस्टम लोकेल system locale पढ्न `Get.deviceLocale` प्रयोग गर्न सकिन्छ। ```dart return GetMaterialApp( locale: Get.deviceLocale, ); ``` ## थिम परिवर्तन अपडेट गर्न `GetMaterialApp` भन्दा माथिल्लो स्तरको widget प्रयोग नगर्नुहोस्। यसले duplicate keys समस्या ल्याउन सक्छ। थिम बदल्न मात्र `ThemeProvider` widget बनाउने पुरानो शैली धेरैले प्रयोग गर्छन्, तर **GetX™** मा यो आवश्यक छैन। तपाईं custom theme बनाएर कुनै boilerplate बिना `Get.changeTheme` भित्र सिधै प्रयोग गर्न सक्नुहुन्छ: ```dart Get.changeTheme(ThemeData.light()); ``` यदि `onTap` मा Theme बदल्ने बटन बनाउनु छ भने, तपाईं **GetX™** का दुई API संयोजन गर्न सक्नुहुन्छ: - dark `Theme` प्रयोगमा छ कि छैन जाँच्ने API - र `Theme` परिवर्तन गर्ने API, जसलाई `onPressed` मा राख्न सकिन्छ: ```dart Get.changeTheme(Get.isDarkMode? ThemeData.light(): ThemeData.dark()); ``` `.darkmode` सक्रिय हुँदा यो _light theme_ मा स्विच हुन्छ, र _light theme_ सक्रिय हुँदा _dark theme_ मा बदलिन्छ। ## GetConnect GetConnect भनेको backend र frontend बीच http वा websocket मार्फत संवाद गर्ने सजिलो तरिका हो। ### डिफल्ट कन्फिगरेसन तपाईं GetConnect लाई extend गरेर GET/POST/PUT/DELETE/SOCKET methods प्रयोग गरी आफ्नो REST API वा websocket सँग सजिलै संवाद गर्न सक्नुहुन्छ। ```dart class UserProvider extends GetConnect { // GET अनुरोध Future getUser(int id) => get('http://youapi/users/$id'); // POST अनुरोध Future postUser(Map data) => post('http://youapi/users', body: data); // फाइलसहित POST अनुरोध Future> postCases(List image) { final form = FormData({ 'file': MultipartFile(image, filename: 'avatar.png'), 'otherFile': MultipartFile(image, filename: 'cover.png'), }); return post('http://youapi/users/upload', form); } GetSocket userMessages() { return socket('https://yourapi/users/socket'); } } ``` ### कस्टम कन्फिगरेसन GetConnect धेरै नै customizable छ। तपाईं base URL, response modifiers, request modifiers, authenticator, र authenticate गर्ने प्रयास संख्या सेट गर्न सक्नुहुन्छ। साथै standard decoder सेट गरेर कुनै अतिरिक्त configuration बिना सबै requests लाई तपाईंका Models मा रूपान्तरण गर्न सकिन्छ। ```dart class HomeProvider extends GetConnect { @override void onInit() { // सबै requests jsonDecode हुँदै CasesModel.fromJson मा जान्छन् httpClient.defaultDecoder = CasesModel.fromJson; httpClient.baseUrl = 'https://api.covid19api.com'; // baseUrl = 'https://api.covid19api.com'; // यसले baseUrl सेट गर्छ // यदि [httpClient] instance बिना प्रयोग भयो भने Http र websocket दुवैका लागि // यसले सबै requests का headers मा 'apikey' थप्छ httpClient.addRequestModifier((request) { request.headers['apikey'] = '12345678'; return request; }); // server ले "Brazil" को data पठाए पनि, // response deliver हुनुअघि नै हटाइने भएकाले // त्यो data प्रयोगकर्तालाई देखिँदैन httpClient.addResponseModifier((request, response) { CasesModel model = response.body; if (model.countries.contains('Brazil')) { model.countries.remove('Brazilll'); } }); httpClient.addAuthenticator((request) async { final response = await get("http://yourapi/token"); final token = response.body['token']; // header सेट गर्ने request.headers['Authorization'] = "$token"; return request; }); // यदि HttpStatus.unauthorized आयो भने // authenticator 3 पटकसम्म call हुन्छ httpClient.maxAuthRetries = 3; } @override Future> getCases(String path) => get(path); } ``` ## GetPage मिडलवेयर अब GetPage मा नयाँ property छ जसले GetMiddleware को list लिन्छ र तिनलाई निर्दिष्ट क्रम अनुसार चलाउँछ। **नोट**: GetPage मा middleware भएमा, त्यस page का सबै child pages मा पनि ती middleware स्वतः लागू हुन्छन्। ### प्राथमिकता middlewares चल्ने क्रम GetMiddleware भित्रको priority बाट सेट गर्न सकिन्छ। ```dart final middlewares = [ GetMiddleware(priority: 2), GetMiddleware(priority: 5), GetMiddleware(priority: 4), GetMiddleware(priority: -8), ]; ``` यी middlewares **-8 => 2 => 4 => 5** क्रममै चल्छन्। ### रिडाइरेक्ट यो function call गरिएको route को page खोजिँदै गर्दा चल्छ। यसले redirect का लागि `RouteSettings` return गर्छ। `null` फर्काए redirect हुँदैन। ```dart RouteSettings redirect(String route) { final authService = Get.find(); return authService.authed.value ? null : RouteSettings(name: '/login') } ``` ### onPageCalled यो function page call भएपछि, कुनै object create हुनुअघि चल्छ। यसले page का केही गुण परिवर्तन गर्न वा नयाँ page return गर्न मद्दत गर्छ। ```dart GetPage onPageCalled(GetPage page) { final authService = Get.find(); return page.copyWith(title: 'Welcome ${authService.UserName}'); } ``` ### OnBindingsStart यो function Bindings initialize हुनुअघि चल्छ। यहाँबाट तपाईं यस page का Bindings परिवर्तन गर्न सक्नुहुन्छ। ```dart List onBindingsStart(List bindings) { final authService = Get.find(); if (authService.isAdmin) { bindings.add(AdminBinding()); } return bindings; } ``` ### OnPageBuildStart यो function Bindings initialize भएलगत्तै चल्छ। यहाँ bindings तयार भएपछि र page widget बन्नुअघि केही काम गर्न सकिन्छ। ```dart GetPageBuilder onPageBuildStart(GetPageBuilder page) { print('bindings तयार छन्'); return page; } ``` ### OnPageBuilt यो function `GetPage.page` call भएलगत्तै चल्छ र त्यस function को result (देखाइने widget) दिन्छ। ### OnPageDispose यो function page सँग सम्बन्धित सबै objects (Controllers, views, ...) dispose भएपछि तुरुन्त चल्छ। ## अन्य उन्नत API हरू ```dart // हालको स्क्रिनबाट वर्तमान args पाउने Get.arguments // अघिल्लो route को नाम पाउने Get.previousRoute // raw route मा पहुँच दिने, जस्तै rawRoute.isFirst() Get.rawRoute // GetObserver बाट Routing API मा पहुँच Get.routing // snackbar खुला छ कि छैन जाँच्ने Get.isSnackbarOpen // dialog खुला छ कि छैन जाँच्ने Get.isDialogOpen // bottomsheet खुला छ कि छैन जाँच्ने Get.isBottomSheetOpen // एउटा route हटाउने Get.removeRoute() // predicate true नआएसम्म बारम्बार back जाने Get.until() // अर्को route मा जाने र predicate true नआएसम्म अघिल्ला routes हटाउने Get.offUntil() // अर्को named route मा जाने र predicate true नआएसम्म अघिल्ला routes हटाउने Get.offNamedUntil() // app कुन platform मा चलिरहेको छ जाँच्ने GetPlatform.isAndroid GetPlatform.isIOS GetPlatform.isMacOS GetPlatform.isWindows GetPlatform.isLinux GetPlatform.isFuchsia // device को प्रकार जाँच्ने GetPlatform.isMobile GetPlatform.isDesktop // web मा सबै platforms स्वतन्त्र रूपमा समर्थित छन्! // तपाईं browser भित्र चलिरहेको छ कि छैन थाहा पाउन सक्नुहुन्छ // Windows, iOS, OSX, Android आदि मा पनि GetPlatform.isWeb // MediaQuery.of(context).size.height को equivalent, // तर immutable Get.height Get.width // Navigator को current context दिन्छ Get.context // तपाईंको code को जहाँबाट पनि foreground snackbar/dialog/bottomsheet को context दिन्छ Get.contextOverlay // नोट: तलका methods, context मा extensions हुन्। // UI को जहाँ context छ, त्यहीँ यी प्रयोग गर्न सकिन्छ // changeable height/width चाहिएको छ भने (जस्तै Desktop वा browser window resize हुँदा) // context प्रयोग गर्नुपर्छ context.width context.height // स्क्रिनको आधा, एक-तिहाइ आदि जस्ता आकार सजिलै परिभाषित गर्न मद्दत गर्छ // responsive app का लागि उपयोगी // param dividedBy (double) optional - default: 1 // param reducedBy (double) optional - default: 0 context.heightTransformer() context.widthTransformer() /// MediaQuery.sizeOf(context) जस्तै context.mediaQuerySize() /// MediaQuery.paddingOf(context) जस्तै context.mediaQueryPadding() /// MediaQuery.viewPaddingOf(context) जस्तै context.mediaQueryViewPadding() /// MediaQuery.viewInsetsOf(context) जस्तै context.mediaQueryViewInsets() /// MediaQuery.orientationOf(context) जस्तै context.orientation() /// device landscape mode मा छ कि छैन जाँच्ने context.isLandscape() /// device portrait mode मा छ कि छैन जाँच्ने context.isPortrait() /// MediaQuery.devicePixelRatioOf(context) जस्तै context.devicePixelRatio() /// MediaQuery.textScaleFactorOf(context) जस्तै context.textScaleFactor() /// स्क्रिनको shortestSide पाउने context.mediaQueryShortestSide() /// width 800 भन्दा ठूलो भए true context.showNavbar() /// shortestSide 600 भन्दा सानो भए true context.isPhone() /// shortestSide 600 भन्दा ठूलो भए true context.isSmallTablet() /// shortestSide 720 भन्दा ठूलो भए true context.isLargeTablet() /// हालको device tablet भए true context.isTablet() /// screen size अनुसार value return गर्छ /// निम्न अवस्थाका लागि value दिन सकिन्छ: /// watch: shortestSide 300 भन्दा सानो /// mobile: shortestSide 600 भन्दा सानो /// tablet: shortestSide 1200 भन्दा सानो /// desktop: width 1200 भन्दा ठूलो context.responsiveValue() ``` ### वैकल्पिक ग्लोबल सेटिङ र म्यानुअल कन्फिगरेसन `GetMaterialApp` ले धेरैजसो कुरा स्वचालित रूपमा configure गर्छ, तर तपाईं Get लाई manually पनि configure गर्न सक्नुहुन्छ। ```dart MaterialApp( navigatorKey: Get.key, navigatorObservers: [GetObserver()], ); ``` तपाईं `GetObserver` भित्र आफ्नै Middleware पनि प्रयोग गर्न सक्नुहुन्छ, यसले अरू व्यवहारमा असर गर्दैन। ```dart MaterialApp( navigatorKey: Get.key, navigatorObservers: [ GetObserver(MiddleWare.observer) // यहाँ ], ); ``` तपाईं `Get` का लागि _Global Settings_ बनाउन सक्नुहुन्छ। कुनै route push गर्नु अघि code मा `Get.config` थप्नुहोस्। वा सिधै `GetMaterialApp` भित्र पनि सेट गर्न सकिन्छ। ```dart GetMaterialApp( enableLog: true, defaultTransition: Transition.fade, opaqueRoute: Get.isOpaqueRouteDefault, popGesture: Get.isPopGestureEnable, transitionDuration: Get.defaultDurationTransition, defaultGlobalState: Get.defaultGlobalState, ); Get.config( enableLog = true, defaultPopGesture = true, defaultTransition = Transitions.cupertino ) ``` तपाईं चाहनुहुन्छ भने `Get` का सबै logging messages लाई redirect गर्न सक्नुहुन्छ। आफ्नो मनपर्ने logging package प्रयोग गरेर त्यहीं logs capture गर्न: ```dart GetMaterialApp( enableLog: true, logWriterCallback: localLogWriter, ); void localLogWriter(String text, {bool isError = false}) { // message लाई यहाँ आफ्नो मनपर्ने logging package मा पठाउनुहोस् // नोट: enableLog: false भए पनि logs यो callback मा पठाइन्छन् // चाहनुहुन्छ भने GetConfig.isLogEnable मार्फत flag जाँच्न सक्नुहुन्छ } ``` ### लोकल स्टेट विजेटहरू यी Widgets ले तपाईंलाई एउटै value व्यवस्थापन गर्न र state लाई local/ephemeral रूपमा राख्न मद्दत गर्छ। Reactive र Simple दुवै प्रकार उपलब्ध छन्। उदाहरणका लागि, `TextField` मा obscureText toggle गर्न, custom Expandable Panel बनाउन, वा `Scaffold` को body परिवर्तन गर्दै `BottomNavigationBar` को current index बदल्न सकिन्छ। #### ValueBuilder `StatefulWidget` को सरल रूप, जसले updated value लिने `.setState` callback सँग काम गर्छ। ```dart ValueBuilder( initialValue: false, builder: (value, updateFn) => Switch( value: value, onChanged: updateFn, // उही signature! चाहनुहुन्छ भने ( newValue ) => updateFn( newValue ) पनि प्रयोग गर्न सकिन्छ ), // builder method बाहिर केही call गर्नुपरेमा onUpdate: (value) => print("मान अपडेट भयो: $value"), onDispose: () => print("विजेट हटाइयो"), ), ``` #### ObxValue [`ValueBuilder`](#valuebuilder) जस्तै, तर यो Reactive version हो। तपाईं Rx instance पास गर्नुहुन्छ (`.obs` याद छ?) र यो स्वतः अपडेट हुन्छ... शानदार छैन र? ```dart ObxValue((data) => Switch( value: data.value, onChanged: data, // Rx मा _callable_ function हुन्छ! चाहनुहुन्छ भने (flag) => data.value = flag पनि प्रयोग गर्न सकिन्छ ), false.obs, ), ``` ## उपयोगी टिप्स `.obs` observables (जसलाई _Rx_ Types पनि भनिन्छ) मा धेरै प्रकारका internal methods र operators हुन्छन्। > `.obs` भएको property नै वास्तविक value हो भनेर _सोच्नु_ सामान्य कुरा हो... तर यो भ्रममा नपर्नुहोस्! > हामी variable को Type declaration धेरै पटक छोड्छौं, किनकि Dart compiler पर्याप्त स्मार्ट छ, र code > सफा देखिन्छ, तर: ```dart var message = 'Hello world'.obs; print( 'सन्देश "$message" को Type ${message.runtimeType} हो'); ``` `message` ले वास्तविक String value _print_ गरे पनि यसको Type **RxString** नै हुन्छ! त्यसैले, `message.substring( 0, 4 )` सिधै गर्न मिल्दैन। तपाईंले _observable_ भित्रको वास्तविक `value` पहुँच गर्नुपर्छ: सबैभन्दा धेरै प्रयोग हुने तरिका `.value` हो, तर तपाईंले यो पनि प्रयोग गर्न सक्नुहुन्छ... ```dart final name = 'GetX'.obs; // value अहिलेको भन्दा फरक भए मात्र stream "update" हुन्छ name.value = 'Hey'; // सबै Rx properties "callable" हुन्छन् र नयाँ value फर्काउँछन् // तर यो तरिकाले `null` स्वीकार्दैन, UI rebuild हुँदैन name('Hello'); // getter जस्तै काम गर्छ, 'Hello' print हुन्छ name() ; /// numbers: final count = 0.obs; // num primitives का सबै non-mutable operations प्रयोग गर्न सकिन्छ! count + 1; // ध्यान दिनुहोस्! `count` final नभई var हुँदा मात्र यो मान्य हुन्छ count += 1; // values सँग compare पनि गर्न सकिन्छ: count > 2; /// booleans: final flag = false.obs; // value लाई true/false बीच switch गर्छ flag.toggle(); /// all types: // `value` लाई null बनाउँछ flag.nil(); // सबै toString(), toJson() operations `value` मा forward हुन्छन् print( count ); // RxInt को भित्री `toString()` call हुन्छ final abc = [0,1,2].obs; // value लाई JSON array मा बदल्छ, RxList print हुन्छ // सबै Rx types मा Json support हुन्छ! print('json: ${jsonEncode(abc)}, type: ${abc.runtimeType}'); // RxMap, RxList र RxSet विशेष Rx types हुन्, जसले आफ्नै native types extend गर्छन् // तर reactive हुँदाहुँदै पनि List जस्तै सामान्य रूपमा काम गर्न सकिन्छ! abc.add(12); // list मा 12 थप्छ र stream UPDATE गर्छ abc[3]; // List जस्तै index 3 पढ्छ // equality Rx र value दुवैसँग काम गर्छ, तर hashCode चाहिँ सधैं value बाट लिइन्छ final number = 12.obs; print( number == 12 ); // prints > true /// Custom Rx Models: // toJson(), toString() child मा defer हुन्छन्, त्यसैले तपाईं override गरेर observable लाई direct print() गर्न सक्नुहुन्छ class User { String name, last; int age; User({this.name, this.last, this.age}); @override String toString() => '$name $last, $age वर्ष'; } final user = User(name: 'John', last: 'Doe', age: 33).obs; // `user` "reactive" हो, तर भित्रका properties भने reactive छैनन्! // त्यसैले भित्रको कुनै variable परिवर्तन गर्‍यौं भने... user.value.name = 'Roi'; // widget rebuild हुँदैन // `Rx` लाई user भित्रको परिवर्तन स्वतः थाहा हुँदैन // त्यसैले custom class मा परिवर्तन manually "notify" गर्नुपर्छ user.refresh(); // वा `update()` method प्रयोग गर्न सकिन्छ! user.update((value){ value.name='Roi'; }); print( user ); ``` ## StateMixin `UI` state व्यवस्थापन गर्ने अर्को तरिका `StateMixin` प्रयोग गर्नु हो। यसलाई लागू गर्न, `with` प्रयोग गरेर controller मा `StateMixin` थप्नुहोस्, जसले T model स्वीकार्छ। ```dart class Controller extends GetController with StateMixin{} ``` `change()` method ले चाहिएको बेला State परिवर्तन गर्छ। data र status यसरी पास गर्नुहोस्: ```dart change(data, status: RxStatus.success()); ``` RxStatus मा यी status उपलब्ध छन्: ```dart RxStatus.loading(); RxStatus.success(); RxStatus.empty(); RxStatus.error('message'); ``` यसलाई UI मा देखाउन: ```dart class OtherClass extends GetView { @override Widget build(BuildContext context) { return Scaffold( body: controller.obx( (state)=>Text(state.name), // यहाँ तपाईं custom loading indicator राख्न सक्नुहुन्छ, // default रूपमा Center(child:CircularProgressIndicator()) हुन्छ onLoading: CustomLoadingIndicator(), onEmpty: Text('डाटा फेला परेन'), // यहाँ पनि आफ्नो error widget राख्न सकिन्छ, // default रूपमा Center(child:Text(error)) हुन्छ onError: (error)=>Text(error), ), ); } ``` #### GetView यो Widget धेरै सरल तर निकै उपयोगी छ! यो `const Stateless` Widget हो, जसमा registered `Controller` का लागि `controller` getter हुन्छ—बस त्यत्ति। ```dart class AwesomeController extends GetController { final String title = 'मेरो उत्कृष्ट View'; } // controller register गर्दा प्रयोग गरेको `Type` सधैं पास गर्न सम्झनुहोस्! class AwesomeView extends GetView { @override Widget build(BuildContext context) { return Container( padding: EdgeInsets.all(20), child: Text(controller.title), // `controller.something` सिधै call गर्नुहोस् ); } } ``` #### GetResponsiveView responsive view बनाउन यो widget extend गर्नुहोस्। यस widget मा `screen` property हुन्छ जसमा screen को size र type सम्बन्धी सबै जानकारी हुन्छ। ##### कसरी प्रयोग गर्ने यसलाई build गर्ने दुई तरिका छन्। - `builder` method प्रयोग गरेर build हुने widget return गर्ने। - `desktop`, `tablet`, `phone`, `watch` methods प्रयोग गर्ने। screen type जुन method सँग मिल्छ, त्यही method build हुन्छ। जस्तै screen [ScreenType.Tablet] भए `tablet` method चल्छ, आदि। **नोट:** यो तरिका प्रयोग गर्दा `alwaysUseBuilder` property लाई `false` राख्नुहोस्। `settings` property प्रयोग गरेर screen types का width limit सेट गर्न सकिन्छ। ![example](https://github.com/SchabanBo/get_page_example/blob/master/docs/Example.gif?raw=true) यो screen को कोड [code](https://github.com/SchabanBo/get_page_example/blob/master/lib/pages/responsive_example/responsive_view.dart) #### GetWidget धेरै मानिसलाई यो Widget बारे थाहा हुँदैन, वा यसको प्रयोगबारे भ्रम हुन्छ। यसको use case कमै हुन्छ, तर निकै specific छ: यसले Controller लाई `cache` गर्छ। यो _cache_ गर्ने भएकाले, यो `const Stateless` हुन सक्दैन। > त्यसो भए Controller लाई "cache" कहिले गर्नुपर्छ? यदि तपाईं **GetX** को अर्को कम प्रयोग हुने feature `Get.create()` प्रयोग गर्नुहुन्छ भने। `Get.create(()=>Controller())` ले `Get.find()` तपाईंले कल गरेको प्रत्येक पटक नयाँ `Controller` बनाउँछ, यहीँ `GetWidget` सबैभन्दा उपयोगी हुन्छ... उदाहरणका लागि, Todo items को list राख्न। त्यसैले widget "rebuilt" भए पनि उही controller instance कायम रहन्छ। #### GetxService यो class `GetxController` जस्तै हो, र यसको lifecycle (`onInit()`, `onReady()`, `onClose()`) पनि उस्तै हुन्छ। तर यसभित्र आफ्नै "logic" हुँदैन। यसले **GetX** Dependency Injection system लाई यो subclass मेमोरीबाट **हटाउन नमिल्ने** हो भनेर मात्र सूचित गर्छ। त्यसैले `Get.find()` बाट आफ्ना "Services" सधैं reachable र active राख्न यो निकै उपयोगी छ। जस्तै: `ApiService`, `StorageService`, `CacheService`. ```dart Future main() async { await initServices(); /// SERVICES INITIALIZATION पर्खनुहोस्। runApp(SomeApp()); } /// Flutter app चलाउनु अघि Services initialize गर्नु राम्रो अभ्यास हो। /// यसले execution flow नियन्त्रण गर्न मद्दत गर्छ (जस्तै Theme configuration, /// apiKey, User को language आदि लोड गर्नुपरेमा... त्यसैले ApiService अघि SettingService लोड गर्नुहोस्। /// यसरी GetMaterialApp() लाई rebuild गर्न नपरी values सिधै लिन सक्छ। void initServices() async { print('services सुरु हुँदैछन् ...'); /// यहीँ get_storage, hive, shared_pref initialization राखिन्छ। /// वा moor connection, वा अन्य कुनै async initialization। await Get.putAsync(() => DbService().init()); await Get.putAsync(SettingsService()).init(); print('सबै services सुरु भए...'); } class DbService extends GetxService { Future init() async { print('$runtimeType 2 सेकेन्ड ढिलाइ'); await 2.delay(); print('$runtimeType तयार!'); return this; } } class SettingsService extends GetxService { void init() async { print('$runtimeType 1 सेकेन्ड ढिलाइ'); await 1.delay(); print('$runtimeType तयार!'); } } ``` `GetxService` लाई वास्तवमै delete गर्ने एक मात्र तरिका `Get.reset()` हो, जुन app को "Hot Reboot" जस्तै हो। त्यसैले app को सम्पूर्ण जीवनकालभरि class instance निरन्तर चाहिन्छ भने `GetxService` प्रयोग गर्नुहोस्। ### परीक्षणहरू तपाईं आफ्ना controllers लाई अन्य class जस्तै test गर्न सक्नुहुन्छ, lifecycle सहित: ```dart class Controller extends GetxController { @override void onInit() { super.onInit(); // मानलाई name2 मा परिवर्तन गर्ने name.value = 'name2'; } @override void onClose() { name.value = ''; super.onClose(); } final name = 'name1'.obs; void changeName() => name.value = 'name3'; } void main() { test(''' reactive variable "name" को state, सबै lifecycle मा test गर्ने''', () { /// lifecycle बिना पनि controller test गर्न सकिन्छ, /// तर GetX dependency injection प्रयोग गर्दै हुनुहुन्छ भने /// यो सिफारिस गरिँदैन final controller = Controller(); expect(controller.name.value, 'name1'); /// GetX dependency injection प्रयोग गर्दा, तपाईं सबै test गर्न सक्नुहुन्छ, /// प्रत्येक lifecycle पछि app को state समेत। Get.put(controller); // onInit was called expect(controller.name.value, 'name2'); /// functions test गर्ने controller.changeName(); expect(controller.name.value, 'name3'); /// onClose was called Get.delete(); expect(controller.name.value, ''); }); } ``` #### सुझावहरू ##### Mockito वा mocktail यदि तपाईंले आफ्नो GetxController/GetxService mock गर्नुपर्छ भने, GetxController लाई extend गरी Mock सँग mixin गर्नुहोस्, यसरी: ```dart class NotificationServiceMock extends GetxService with Mock implements NotificationService {} ``` ##### Get.reset() प्रयोग गर्ने यदि widget वा test groups परीक्षण गर्दै हुनुहुन्छ भने, अघिल्लो test का settings reset गर्न test अन्त्यमा वा `tearDown` मा `Get.reset` प्रयोग गर्नुहोस्। ##### Get.testMode यदि controllers भित्र navigation प्रयोग गर्दै हुनुहुन्छ भने, `main` को सुरुमा `Get.testMode = true` राख्नुहोस्। # 2.0 बाट भएका ब्रेकिङ परिवर्तनहरू 1- Rx types: | Before | After | | ------- | ---------- | | StringX | `RxString` | | IntX | `RxInt` | | MapX | `RxMap` | | ListX | `RxList` | | NumX | `RxNum` | | DoubleX | `RxDouble` | RxController र GetBuilder अब merge भएका छन्। अब कुन controller प्रयोग गर्ने भनेर छुट्टै सम्झिरहनुपर्दैन—`GetxController` मात्र प्रयोग गरे पुग्छ; यो simple state management र reactive दुवैमा काम गर्छ। 2- Named Routes पहिले: ```dart GetMaterialApp( namedRoutes: { '/': GetRoute(page: Home()), } ) ``` अब: ```dart GetMaterialApp( getPages: [ GetPage(name: '/', page: () => Home()), ] ) ``` यो परिवर्तन किन? धेरै अवस्थामा parameter वा login token का आधारमा कुन page देखाउने भन्ने निर्णय गर्नुपर्छ, तर पुरानो तरिका यसका लागि लचिलो थिएन। page लाई function भित्र राख्दा RAM खपत उल्लेखनीय रूपमा घट्यो, किनकि app सुरुहुँदा नै सबै routes मेमोरीमा allocate हुँदैनन्। साथै यसले यस्तो approach सम्भव बनायो: ```dart GetStorage box = GetStorage(); GetMaterialApp( getPages: [ GetPage(name: '/', page:(){ return box.hasData('token') ? Home() : Login(); }) ] ) ``` # किन Getx? 1- Flutter update पछि धेरै package हरू टुट्ने अवस्था धेरै पटक आउँछ। कहिलेकाहीँ compilation errors आउँछन्, जसको समाधान तुरुन्त उपलब्ध हुँदैन। विकासकर्ताले error को स्रोत पत्ता लगाउनुपर्छ, trace गर्नुपर्छ, त्यसपछि मात्र सम्बन्धित repository मा issue खोल्नुपर्छ। Get ले विकासका मुख्य स्रोतहरू (state, dependency र route management) एउटै ठाउँमा ल्याउँछ, जसले pubspec मा एउटै package थपेर काम सुरु गर्न दिन्छ। Flutter update पछि सामान्यतया Get dependency update गरे पुग्छ। Get ले compatibility समस्या पनि कम गर्छ। एउटा package को version अर्कोसँग नमिल्ने समस्या पनि धेरै घट्छ, किनकि सबै कुरा एउटै package भित्र पूर्ण रूपमा compatible हुन्छ। 2- Flutter सजिलो र उत्कृष्ट छ, तर अझै केही boilerplate चाहिन्छ जुन धेरै विकासकर्तालाई अनावश्यक लाग्न सक्छ, जस्तै `Navigator.of(context).push (context, builder [...])`। Get ले विकासलाई सरल बनाउँछ। route call गर्न 8 लाइन लेख्नुको सट्टा `Get.to(Home())` लेखेर सीधै अर्को page मा जान सकिन्छ। Flutter मा dynamic web URLs बनाउन अहिले पनि झन्झटिलो छ, तर GetX सँग यो धेरै सरल छ। Flutter मा state management र dependency management का लागि pub मा सयौं patterns छन्, जसले चर्चा बढाउँछ। तर variable को अन्त्यमा `.obs` थपेर widget लाई `Obx` भित्र राख्नु जत्तिकै सजिलो तरिका विरलै छ—त्यसपछि त्यो variable का सबै updates स्क्रिनमा स्वतः देखिन्छन्। 3- performance को चिन्ता बिना सजिलो विकास। Flutter को performance पहिले नै राम्रो छ, तर मानौं तपाईं state manager र locator प्रयोग गरेर blocs/stores/controllers आदि बाँडिरहनु भएको छ—अब नचाहिँदा dependency manually हटाउनुपर्छ। GetX मा भने controller प्रयोग गरिरहँदा ठीकै, र कसैले प्रयोग नगरेपछि त्यो स्वतः memory बाट हट्छ। यही काम GetX गर्छ। SmartManagement सँग प्रयोग नभएका सबै चीजहरू memory बाट हट्छन्, र तपाईंले programming बाहेक अरू धेरै चिन्ता लिनुपर्दैन। यसरी कुनै अतिरिक्त logic नलेखी पनि न्यूनतम स्रोत प्रयोग सुनिश्चित हुन्छ। 4- वास्तविक decoupling। तपाईंले "view र business logic अलग राख्ने" अवधारणा पक्कै सुन्नुभएको छ। यो BLoC, MVC, MVVM जस्ता patterns मा सामान्य अवधारणा हो। तर Flutter मा context प्रयोगका कारण यो व्यवहारमा कमजोर पर्न सक्छ। यदि InheritedWidget फेला पार्न context चाहियो भने context view मा राख्नुपर्छ वा parameter बाट पास गर्नुपर्छ। team मा काम गर्दा यसले View र business logic बीच अनावश्यक निर्भरता बढाउँछ। Getx ले केही unorthodox तर सफा approach दिन्छ। यसले StatefulWidget, InitState आदि पूर्ण रूपमा निषेध गर्दैन, तर प्रायः सफा विकल्प दिन्छ। Controllers का lifecycle हुन्छन्, र API REST call जस्ता कामका लागि view मा निर्भर हुनुपर्दैन। `onInit` बाट http call सुरु गर्न सकिन्छ, data आएपछि variables populate हुन्छन्। GetX पूर्ण रूपमा reactive भएकाले (streams मा काम गर्छ), data भरिएपछि त्यसलाई प्रयोग गर्ने widgets view मा स्वतः update हुन्छन्। यसले UI मा काम गर्ने व्यक्तिलाई widgets मै केन्द्रित हुन दिन्छ, र business logic टोलीलाई छुट्टै business logic बनाउन तथा test गर्न स्वतन्त्रता दिन्छ। यो library निरन्तर update हुँदै नयाँ features थपिरहनेछ। निःसंकोच PR पठाएर योगदान गर्नुहोस्। # कम्युनिटी ## कम्युनिटी च्यानलहरू GetX को समुदाय अत्यन्त सक्रिय र सहयोगी छ। तपाईंलाई प्रश्न छ वा यो framework प्रयोगमा सहयोग चाहिन्छ भने, हाम्रो community channels मा जोडिनुहोस्—त्यहाँ छिटो र उचित उत्तर पाउनुहुन्छ। यो repository मुख्यतः issues खोल्न र features अनुरोध गर्नका लागि हो, तर GetX Community को हिस्सा बन्न निःसंकोच आउनुहोस्। | **Slack** | **Discord** | **Telegram** | | :-------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------- | | [![Get on Slack](https://img.shields.io/badge/slack-join-orange.svg)](https://communityinviter.com/apps/getxworkspace/getx) | [![Discord Shield](https://img.shields.io/discord/722900883784073290.svg?logo=discord)](https://discord.com/invite/9Hpt99N) | [![Telegram](https://img.shields.io/badge/chat-on%20Telegram-blue.svg)](https://t.me/joinchat/PhdbJRmsZNpAqSLJL6bH7g) | ## योगदान कसरी गर्ने _प्रोजेक्टमा योगदान गर्न चाहनुहुन्छ? हामी तपाईंलाई हाम्रो सहयोगीको रूपमा देखाउन पाउँदा खुसी हुनेछौं। तपाईंले Get (र Flutter) लाई अझ राम्रो बनाउन योगदान गर्न सक्ने केही क्षेत्रहरू:_ - README लाई अन्य भाषामा अनुवाद गर्न सहयोग गर्ने। - README मा documentation थप्ने (Get का धेरै functions अझै documented छैनन्)। - Get प्रयोग सिकाउने लेख वा भिडियो बनाउने (यी README र भविष्यमा Wiki मा थपिनेछन्)। - code/tests का लागि PRs दिने। - नयाँ functions थप्ने। कुनै पनि योगदान स्वागतयोग्य छ! ## लेख र भिडियोहरू - [Flutter Getx EcoSystem package for arabic people](https://www.youtube.com/playlist?list=PLV1fXIAyjeuZ6M8m56zajMUwu4uE3-SL0) - [Pesa Coder](https://github.com/UsamaElgendy) को ट्युटोरियल। - [Dynamic Themes in 3 lines using GetX™](https://medium.com/swlh/flutter-dynamic-themes-in-3-lines-c3b375f292e3) - [Rod Brown](https://github.com/RodBr) को ट्युटोरियल। - [Complete GetX™ Navigation](https://www.youtube.com/watch?v=RaqPIoJSTtI) - Amateur Coder द्वारा Route management भिडियो। - [Complete GetX State Management](https://www.youtube.com/watch?v=CNpXbeI_slw) - Amateur Coder द्वारा State management भिडियो। - [GetX™ Other Features](https://youtu.be/ttQtlX_Q0eU) - Amateur Coder द्वारा Utils, storage, bindings र अन्य features सम्बन्धी भिडियो। - [Firestore User with GetX | Todo App](https://www.youtube.com/watch?v=BiV0DcXgk58) - Amateur Coder द्वारा भिडियो। - [Firebase Auth with GetX | Todo App](https://www.youtube.com/watch?v=-H-T_BSgfOE) - Amateur Coder द्वारा भिडियो। - [The Flutter GetX™ Ecosystem ~ State Management](https://medium.com/flutter-community/the-flutter-getx-ecosystem-state-management-881c7235511d) - [Aachman Garg](https://github.com/imaachman) द्वारा State management लेख। - [The Flutter GetX™ Ecosystem ~ Dependency Injection](https://medium.com/flutter-community/the-flutter-getx-ecosystem-dependency-injection-8e763d0ec6b9) - [Aachman Garg](https://github.com/imaachman) द्वारा Dependency Injection लेख। - [GetX, the all-in-one Flutter package](https://www.youtube.com/watch?v=IYQgtu9TM74) - Thad Carnevalli द्वारा State Management र Navigation समेटिएको छोटो ट्युटोरियल। - [Build a To-do List App from scratch using Flutter and GetX](https://www.youtube.com/watch?v=EcnqFasHf18) - Thad Carnevalli द्वारा UI + State Management + Storage भिडियो। - [GetX Flutter Firebase Auth Example](https://medium.com/@jeffmcmorris/getx-flutter-firebase-auth-example-b383c1dd1de2) - Jeff McMorris द्वारा लेख। - [Flutter State Management with GetX – Complete App](https://www.appwithflutter.com/flutter-state-management-with-getx/) - App With Flutter द्वारा। - [Flutter Routing with Animation using Get Package](https://www.appwithflutter.com/flutter-routing-using-get-package/) - App With Flutter द्वारा। - [A minimal example on dartpad](https://dartpad.dev/2b3d0d6f9d4e312c5fdbefc414c1727e?) - [Roi Peker](https://github.com/roipeker) द्वारा। - [GetConnect: The best way to perform API operations in Flutter with Get.](https://absyz.com/getconnect-the-best-way-to-perform-api-operations-in-flutter-with-getx/) - [MD Sarfaraj](https://github.com/socialmad) द्वारा। - [How To Create an App with GetX Architect in Flutter with Get CLI](https://www.youtube.com/watch?v=7mb4qBA7kTk&t=1380s) - [MD Sarfaraj](https://github.com/socialmad) द्वारा। ================================================ FILE: README-vi.md ================================================ ![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/get.png) **Ngôn ngữ: Tiếng Việt (file này), [English](README.md), [Indonesian](README.id-ID.md), [Urdu](README.ur-PK.md), [Chinese](README.zh-cn.md), [Brazilian Portuguese](README.pt-br.md), [Spanish](README-es.md), [Russian](README.ru.md), [Polish](README.pl.md), [Korean](README.ko-kr.md), [French](README-fr.md).** [![pub package](https://img.shields.io/pub/v/get.svg?label=get&color=blue)](https://pub.dev/packages/get) [![popularity](https://badges.bar/get/popularity)](https://pub.dev/packages/sentry/score) [![likes](https://badges.bar/get/likes)](https://pub.dev/packages/get/score) [![pub points](https://badges.bar/get/pub%20points)](https://pub.dev/packages/get/score) ![building](https://github.com/jonataslaw/get/workflows/build/badge.svg) [![style: effective dart](https://img.shields.io/badge/style-effective_dart-40c4ff.svg)](https://pub.dev/packages/effective_dart) [![Discord Shield](https://img.shields.io/discord/722900883784073290.svg?logo=discord)](https://discord.com/invite/9Hpt99N) [![Get on Slack](https://img.shields.io/badge/slack-join-orange.svg)](https://communityinviter.com/apps/getxworkspace/getx) [![Telegram](https://img.shields.io/badge/chat-on%20Telegram-blue.svg)](https://t.me/joinchat/PhdbJRmsZNpAqSLJL6bH7g) Awesome Flutter Buy Me A Coffee ![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/getx.png) - [Về GetX](#về-getx) - [Cài Đặt](#cài-đặt) - [Counter App với GetX](#counter-app-với-getx) - [Tam Trụ](#tam-trụ) - [Quản lý State](#quản-lý-state) - [Quản lý Reactive State](#quản-lý-reactive-state) - [Thêm thông tin về quản lý state](#thêm-thông-tin-về-quản-lý-state) - [Quản lý route](#quản-lý-route) - [Thêm thông tin về quản lý route](#thêm-thông-tin-về-quản-lý-route) - [Quản lý dependency](#quản-lý-dependency) - [Thêm thông tin về quản lý dependency](#thêm-thông-tin-về-quản-lý-dependency) - [Utils](#utils) - [Internationalization](#internationalization) - [Dịch thuật](#dịch-thuật) - [Sử dụng bản dịch thuật](#sử-dụng-bản-dịch-thuật) - [Locales](#locales) - [Đổi locale](#đổi-locale) - [System locale](#system-locale) - [Đổi Theme](#đổi-theme) - [GetConnect](#getconnect) - [Cấu hình mặc định](#cấu-hình-mặc-định) - [Cấu hình tùy chỉnh](#cấu-hình-tùy-chỉnh) - [GetPage Middleware](#getpage-middleware) - [Ưu tiên](#ưu-tiên) - [Chuyển hướng](#chuyển-hướng) - [onPageCalled](#onpagecalled) - [OnBindingsStart](#onbindingsstart) - [OnPageBuildStart](#onpagebuildstart) - [OnPageBuilt](#onpagebuilt) - [OnPageDispose](#onpagedispose) - [APIs nâng cao khác](#apis-nâng-cao-khác) - [Cấu hình thủ công và cài đặt chung tuỳ chọn](#cấu-hình-thủ-công-và-cài-đặt-chung-tuỳ-chọn) - [Local State Widgets](#local-state-widgets) - [ValueBuilder](#valuebuilder) - [ObxValue](#obxvalue) - [Mẹo hữu ích](#mẹo-hữu-ích) - [GetView](#getview) - [GetResponsiveView](#getresponsiveview) - [Hướng dẫn sử dụng trước khi dùng](#hướng-dẫn-sử-dụng-trước-khi-dùng) - [GetWidget](#getwidget) - [GetxService](#getxservice) - [Thay đổi đột phá 2.0](#thay-đổi-đột-phá-2.0) - [Tại sao lại dùng GetX](#tại-sao-lại-dùng-getx) - [Cộng đồng](#cộng-đồng) - [Kênh Cộng đồng](#kênh-cộng-đồng) - [Cách cống hiến](#cách-cống-hiến) - [Các bài báo và video](#các-bài-báo-và-video) # Về GetX - GetX hướng tới sự nhỏ gọn và giải pháp tối ưu cho Flutter với tốc độ ưu việt trong quản lý state, nạp dependency thông minh, và quản lý route nhanh chóng và thực tế. - GetX hướng tới 3 tham vọng chính, nghĩa là tất cả các tài nguyên của thư viện sẽ dành cho những điểm ưu tiên sau: **NĂNG SUẤT, HIỆU SUẤT VÀ TỔ CHỨC.** - **HIỆU SUẤT:** GetX tập trung vào hiệu suất và mức tiêu thụ tài nguyên tối thiểu, do đó nó không sử dụng Streams hoặc ChangeNotifier. - **NĂNG SUẤT:** GetX sử dụng một cú pháp dễ dàng và dễ thở. Bất kể bạn muốn làm gì, luôn có một cách dễ dàng hơn với GetX. Nó sẽ tiết kiệm hàng giờ phát triển và sẽ cung cấp hiệu suất tối đa mà ứng dụng của bạn có thể mang lại. Nói chung, nhà phát triển nên quan tâm đến việc xóa những controller ra khỏi bộ nhớ. Với GetX, mặc định các tài nguyên sẽ TỰ ĐỘNG xóa khỏi bộ nhớ khi không dùng nữa. Nếu bạn muốn giữ nó trong bộ nhớ, bạn phải khai báo rõ ràng "permanent: true" trong phần dependency của mình. Từ đó, bạn sẽ tiết kiệm thời gian và giảm rủi ro khi phụ thuộc vào bộ nhớ. Theo mặc định, tính năng tải dependency cũng lười biếng. - **TỔ CHỨC:** GetX cho phép tách toàn bộ View, presentation logic, business logic, nạp dependencies và điều hướng. Bạn không cần "context" để điều hướng giữa các route, vì vậy bạn sẽ độc lập trong sơ đồ widget (trực quan hóa). Bạn không cần "context" để truy cập Controller / Blocs của mình thông qua một InheritedWidget, vì vậy bạn hoàn toàn tách rời presentation logic và business logic ra khỏi lớp trực quan của mình. Bạn không cần phải đưa các Controller / Models / Blocs vào sơ đồ widget của mình thông qua `MultiProvider`, vì GetX sử dụng tính năng nạp dependency của riêng nó, tách hoàn toàn DI khỏi chế độ xem của nó. Với GetX, bạn biết nơi tìm từng tính năng ứng dụng của mình, với cơ chế clean code theo mặc định. Ngoài việc giúp bảo trì dễ dàng, GetX giúp việc chia sẻ các mô-đun trở thành khả thi trong Flutter. BLoC là điểm khởi đầu để tổ chức code trong Flutter, nó tách biệt business logic ra khỏi lớp trực quan hóa (visualization). GetX nảy sinh từ điều này, không chỉ tách biệt presentation logic mà còn cả business logic. Nạp dependency bổ sung và route cũng được tách ra và lớp dữ liệu cũng biến mất. Bạn sẽ biết mọi thứ ở đâu và sẽ hình dung tất cả những điều này dễ hơn cả xây dựng chương trình "Hello World". GetX là cách dễ nhất, thiết thực và có thể mở rộng để xây dựng các ứng dụng hiệu suất cao với Flutter SDK. GetX chứa đựng một hệ sinh thái rộng lớn xung quanh nó hoạt động hoàn hảo cùng nhau, rất dễ dàng cho người mới bắt đầu và nó chính xác cho các chuyên gia. Nó an toàn, ổn định, luôn cập nhật và cung cấp một loạt các API được tích hợp sẵn mà không có trong Flutter SDK mặc định. - GetX không cồng kềnh và có vô số tính năng cho phép bạn bắt đầu lập trình mà không cần lo lắng về bất cứ điều gì. Đặc biệt, nó cho phép mỗi tính năng này nằm trong các vùng chứa riêng biệt và chỉ được bắt đầu sau khi sử dụng. Nếu bạn chỉ sử dụng phần quản lý state của GetX thì sẽ chỉ có quản lý state được sử dụng. Nếu bạn chỉ sử dụng route, thì GetX không biên dịch phần quản lý state. - GetX có một hệ sinh thái khổng lồ, một cộng đồng lớn, một số lượng lớn cộng tác viên và sẽ được duy trì miễn là Flutter còn tồn tại. GetX có khả năng chạy cùng một mã (code) trên Android, iOS, Web, Mac, Linux, Windows và trên máy chủ của bạn. **Bạn hoàn toàn có thể sử dụng lại mã của mình trên frontend qua backend với [Get Server](https://github.com/jonataslaw/get_server)**. **Ngoài ra, toàn bộ quá trình phát triển có thể hoàn toàn tự động, cả trên máy chủ và frontend với [Get CLI](https://github.com/jonataslaw/get_cli)**. **Ngoài ra, nhằm tăng thêm năng suất của bạn, chúng tôi hỗ trợ [tiện ích trên VSCode](https://marketplace.visualstudio.com/items?itemName=get-snippets.get-snippets) và [tiện ích cho Android Studio/Intellij](https://plugins.jetbrains.com/plugin/14975-getx-snippets)** # Cài Đặt Thêm Get vào file pubspec.yaml: ```yaml dependencies: get: ``` Import get vào file cần sử dụng: ```dart import 'package:get/get.dart'; ``` # Counter App với GetX Dự án "counter" được tạo theo mặc định trên dự án mới trên Flutter có hơn 100 dòng (có comments). Để thể hiện sức mạnh của Get, tôi sẽ trình bày cách tạo "counter" thay đổi trạng thái với mỗi lần nhấp, chuyển đổi giữa các trang và chia sẻ trạng thái giữa các màn hình, tất cả đều theo cách có tổ chức, tách biệt logic nghiệp vụ khỏi chế độ xem, CHỈ VỚI 26 DÒNG! - Bước 1: Thêm "Get" trước MaterialApp, nó sẽ thành GetMaterialApp ```dart void main() => runApp(GetMaterialApp(home: Home())); ``` - Chú ý: điều này không sửa đổi MaterialApp của Flutter, GetMaterialApp không phải là MaterialApp được sửa đổi, nó chỉ là một Widget được tạo trước với MaterialApp mặc định là child. Bạn có thể cấu hình điều này theo cách thủ công, nhưng nó chắc chắn là không cần thiết. GetMaterialApp sẽ tạo các route, đưa chúng vào, đưa bản dịch, đưa mọi thứ bạn cần để điều hướng route. Nếu bạn chỉ sử dụng Get để quản lý trạng thái hoặc quản lý phụ thuộc, thì không cần thiết phải sử dụng GetMaterialApp. Tóm lại, GetMaterialApp chỉ cần thiết cho các route, snacksbar, internationalization, bottomSheets, Dialog và các APIs cấp cao liên quan đến route và không có "context". - Chú ý²: Một lần nữa, bước này chỉ cần thiết nếu bạn sử dụng quản lý route (`Get.to ()`, `Get.back ()`, v.v.). Nếu bạn không sử dụng nó thì không cần thực hiện bước 1 - Bước 2: Tạo lớp business logic của bạn và đặt tất cả các biến (variables), hàm (function) và controller bên trong nó. Bạn có thể làm cho bất kỳ biến nào có thể quan sát được đơn giản bằng cách sử dụng ".obs". ```dart class Controller extends GetxController{ var count = 0.obs; increment() => count++; } ``` - Bước 3: Tạo widget của bạn, sử dụng StatelessWidget và tiết kiệm RAM, với Get, bạn có thể không cần sử dụng StatefulWidget nữa. ```dart class Home extends StatelessWidget { @override Widget build(context) { // Instantiate your class using Get.put() to make it available for all "child" routes there. final Controller c = Get.put(Controller()); return Scaffold( // Use Obx(()=> to update Text() whenever count is changed. appBar: AppBar(title: Obx(() => Text("Clicks: ${c.count}"))), // Replace the 8 lines Navigator.push by a simple Get.to(). You don't need context body: Center(child: ElevatedButton( child: Text("Go to Other"), onPressed: () => Get.to(Other()))), floatingActionButton: FloatingActionButton(child: Icon(Icons.add), onPressed: c.increment)); } } class Other extends StatelessWidget { // You can ask Get to find a Controller that is being used by another page and redirect you to it. final Controller c = Get.find(); @override Widget build(context){ // Access the updated count variable return Scaffold(body: Center(child: Text("${c.count}"))); } } ``` Kết quả: ![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/counter-app-gif.gif) Đây là một dự án đơn giản nhưng nó đã cho thấy rõ sức mạnh của Get. Khi dự án của bạn phát triển, sự khác biệt này sẽ trở nên đáng kể hơn. Get được thiết kế để làm việc với các nhóm, nhưng nó làm cho công việc của một nhà phát triển cá nhân trở nên đơn giản. Cải thiện thời gian, giao mọi thứ đúng hạn mà không làm giảm hiệu suất. Get không dành cho tất cả mọi người, nhưng nếu bạn đã xác định gắn với Get, Get sẽ "get" bạn! # Tam Trụ ## Quản lý State Get có 2 cách quản lý trạng thái (state managers) khác nhau : quản lý trạng thái đơn giản (chúng ta gọi nó là GetBuilder) và quản lý trạng thái phản ứng (the reactive state manager) (GetX/Obx). ### Quản lý Reactive State Lập trình phản ứng (reactive programming) có thể khiến nhiều người xa lánh vì nó được cho là phức tạp. GetX biến lập trình phản ứng thành một thứ khá đơn giản: - Bạn sẽ không cần tạo StreamControllers. - Bạn sẽ không cần tạo StreamBuilder cho mỗi biến. - Bạn sẽ không cần tạo một lớp (class) cho mỗi trạng thái. - Bạn sẽ không cần tạo get cho một giá trị ban đầu. - Bạn sẽ không cần sử dụng trình tạo mã. Lập trình phản ứng với Get dễ dàng như sử dụng setState. Hãy tưởng tượng rằng bạn có một biến tên và muốn rằng mỗi khi bạn thay đổi nó, tất cả các widget sử dụng nó sẽ được tự động thay đổi. Đây là biến đếm của bạn: ```dart var name = 'Jonatas Borges'; ``` Để nó có thể được lắng nghe, bạn chỉ cần thêm ".obs" ở cuối: ```dart var name = 'Jonatas Borges'.obs; ``` Và trong UI, khi bạn muốn hiển thị giá trị đó và cập nhật màn hình bất cứ khi nào giá trị thay đổi, chỉ cần thực hiện điều này: ```dart Obx(() => Text("${controller.name}")); ``` Thế thôi. Chỉ là _thế_ thôi người ơi~. ### Thêm thông tin về Quản lý state **Xem thông tin cụ thể tại [đây](./documentation/en_US/state_management.md). Tại đó, bạn có thể tham khảo ví dụ và so sánh sự khác nhau giữa quản lý state cơ bản và quản lý state reactive** Bạn sẽ hình dung được sức mạnh của GetX. ## Quản lý route Nếu bạn chỉ sử dụng routes/snackbars/dialogs/bottomsheets không có context, GetX là lựa chọn số 2 trừ 1, nhìn đây: Thêm "Get" trước MaterialApp, nó sẽ biến thành GetMaterialApp ```dart GetMaterialApp( // Before: MaterialApp( home: MyHome(), ) ``` Di chuyển tới màn hình mới: ```dart Get.to(NextScreen()); ``` Di chuyển tới màn hình mới theo tên. Xem thêm tại [đây](./documentation/en_US/route_management.md#navigation-with-named-routes) ```dart Get.toNamed('/details'); ``` Để đóng snackbars, dialogs, bottomsheets, hay bất kì thứ gì, bạn có thể xài cái này để thay Navigator.pop(context); ```dart Get.back(); ``` Đi đến màn hình kế tiếp và bỏ luôn màn hình cũ (thường dùng cho màn hình giới thiệu, màn hình đăng nhập, etc.) ```dart Get.off(NextScreen()); ``` Đi đến màn hình kế tiếp và đóng tất cả các routes (hữu dụng cho shopping cart, polls, và tests) ```dart Get.offAll(NextScreen()); ``` Bạn có thấy nãy giờ chúng ta không sử dụng từ khóa "context"? Đây chính là thang điểm 9 + 1 của quản lý route ở Get. Với điểm mạnh trên, bạn có thể thao tác bất cứ đâu, kể cả trong controller class. ### Thêm thông tin về quản lý route **Get hoạt động được với named routes và cũng cung cấp cách điều khiển ở cấp thấp (lower-level control) cho routes của bạn! Tài liệu chi tiết tại [đây](./documentation/en_US/route_management.md)** ## Quản lý dependency Get hỗ trợ tính năng giúp bạn lấy class như Bloc hoặc Controller chỉ với 1 dòng, không cần Provider context hay InheritedWidget: ```dart Controller controller = Get.put(Controller()); // Rather Controller controller = Controller(); ``` - Chú ý: Nếu bạn dùng Get's State Manager, hãy chú ý đến việc bindings API, có thể giúp dễ dàng kết nối view đến controller. Thay vì khởi tạo class của bạn trong class bạn đang sử dụng, bạn đang khởi tạo nó trong phiên bản Get, điều này sẽ làm cho nó có sẵn trên toàn bộ Ứng dụng của bạn. Vì vậy, bạn có thể sử dụng bộ điều khiển (hoặc Bloc) của mình một cách bình thường **Mẹo:** quản lý dependency của Get được tách ra khỏi các phần khác của package, vì vậy, ví dụ: nếu ứng dụng của bạn đã sử dụng 1 trình quản lý trạng thái (bất kỳ cái nào, không quan trọng), bạn không cần phải viết lại tất cả, bạn có thể sử dụng nạp dependency của Get vô lo ```dart controller.fetchApi(); ``` Hãy tưởng tượng rằng bạn đã điều hướng qua nhiều route và bạn cần dữ liệu bị còn sót trong controller của mình, bạn sẽ cần một trình quản lý dependency kết hợp với Provider hoặc Get_it, đúng không? Với Get, sử dụng Get để "find" cho controller, bạn sẽ hoàn toàn độc lập: ```dart Controller controller = Get.find(); //Yes, it looks like Magic, Get will find your controller, and will deliver it to you. You can have 1 million controllers instantiated, Get will always give you the right controller. ``` Và sau đó, bạn sẽ có thể khôi phục dữ liệu controller của mình đã lấy được ở đó: ```dart Text(controller.textFromApi); ``` ### Thêm thông tin về quản lý dependency **Xem thêm tại [đây](./documentation/en_US/dependency_management.md)** # Utils ## Internationalization ### Dịch thuật Các bản dịch được lưu giữ như một bản đồ từ điển (dictionary map) key-value đơn giản. Để thêm các bản dịch tùy chỉnh, hãy tạo một class và kế thừa (extend) từ `Translation`. ```dart import 'package:get/get.dart'; class Messages extends Translations { @override Map> get keys => { 'en_US': { 'hello': 'Hello World', }, 'de_DE': { 'hello': 'Hallo Welt', } }; } ``` #### Sử dụng bản dịch thuật Chỉ cần thêm `.tr` vào key được chỉ định và nó sẽ được dịch, sử dụng giá trị hiện tại của` Get.locale` và `Get.fallbackLocale`. ```dart Text('title'.tr); ``` #### Sử dụng bản dịch thuật với số ít và số nhiều ```dart var products = []; Text('singularKey'.trPlural('pluralKey', products.length, Args)); ``` #### Sử dụng bản dịch với tham số (parameters) ```dart import 'package:get/get.dart'; Map> get keys => { 'en_US': { 'logged_in': 'logged in as @name with email @email', }, 'es_ES': { 'logged_in': 'iniciado sesión como @name con e-mail @email', } }; Text('logged_in'.trParams({ 'name': 'Jhon', 'email': 'jhon@example.com' })); ``` ### Locales Chuyển các tham số (parameters) cho `GetMaterialApp` để xác định ngôn ngữ và bản dịch. ```dart return GetMaterialApp( translations: Messages(), // your translations locale: Locale('en', 'US'), // translations will be displayed in that locale fallbackLocale: Locale('en', 'UK'), // specify the fallback locale in case an invalid locale is selected. ); ``` #### Đổi locale Gọi `Get.updateLocale (locale)` 'để cập nhật ngôn ngữ. Các bản dịch sau đó sẽ tự động sử dụng ngôn ngữ mới. ```dart var locale = Locale('en', 'US'); Get.updateLocale(locale); ``` #### System locale Để đọc system locale, sử dụng `Get.deviceLocale`. ```dart return GetMaterialApp( locale: Get.deviceLocale, ); ``` ## Đổi chủ đề (Theme) Vui lòng không sử dụng bất kỳ Widget con nào cấp cao hơn `GetMaterialApp` để cập nhật nó. Điều này có thể kích hoạt các key trùng lặp. Rất nhiều người đã quen với cách tiếp cận thời tiền sử là tạo tiện ích "ThemeProvider" chỉ để thay đổi chủ đề ứng dụng của bạn và điều này KHÔNG cần thiết với ** GetX ™ **. Bạn có thể tạo chủ đề tùy chỉnh của mình và chỉ cần thêm nó vào trong `Get.changeTheme` mà không cần bất kỳ điều gì khác: ```dart Get.changeTheme(ThemeData.light()); ``` Nếu bạn muốn tạo một cái gì đó giống như một nút thay đổi Theme với `onTap`, bạn có thể kết hợp hai API ** GetX ™ ** cho điều đó: - Api kiểm tra xem `Theme` tối có đang được sử dụng hay không. - Và `Theme` sẽ thay đổi API, bạn sử dụng với `onPressed`: ```dart Get.changeTheme(Get.isDarkMode? ThemeData.light(): ThemeData.dark()); ``` Khi bật `.darkmode`, nó sẽ chuyển _light theme_, và khi bật _light theme_ , nó sẽ chuyển _dark theme_. ## GetConnect GetConnect tạo giao thức tới http hoặc websockets ### Cấu hình mặc định Đơn giản, bạn có thể kế thừa (extend) từ GetConnect và sử dụng các phương thức GET/POST/PUT/DELETE/SOCKET khi giao tiếp với Rest API hoặc websockets. ```dart class UserProvider extends GetConnect { // Get request Future getUser(int id) => get('http://youapi/users/$id'); // Post request Future postUser(Map data) => post('http://youapi/users', body: data); // Post request with File Future> postCases(List image) { final form = FormData({ 'file': MultipartFile(image, filename: 'avatar.png'), 'otherFile': MultipartFile(image, filename: 'cover.png'), }); return post('http://youapi/users/upload', form); } GetSocket userMessages() { return socket('https://yourapi/users/socket'); } } ``` ### Cấu hình tùy chỉnh GetConnect có khả năng tùy chỉnh cao Bạn có thể xác định Url chính như answers, modifiers như request, xác địng authenticator và thậm chí số lần thử mà nó sẽ cố gắng authenticate, ngoài việc cung cấp khả năng xác định bộ giải mã chuẩn sẽ chuyển đổi tất cả các request của bạn thành Model mà không cần bất kỳ cấu hình bổ sung nào. ```dart class HomeProvider extends GetConnect { @override void onInit() { // All request will pass to jsonEncode so CasesModel.fromJson() httpClient.defaultDecoder = CasesModel.fromJson; httpClient.baseUrl = 'https://api.covid19api.com'; // baseUrl = 'https://api.covid19api.com'; // It define baseUrl to // Http and websockets if used with no [httpClient] instance // It's will attach 'apikey' property on header from all requests httpClient.addRequestModifier((request) { request.headers['apikey'] = '12345678'; return request; }); // Even if the server sends data from the country "Brazil", // it will never be displayed to users, because you remove // that data from the response, even before the response is delivered httpClient.addResponseModifier((request, response) { CasesModel model = response.body; if (model.countries.contains('Brazil')) { model.countries.remove('Brazilll'); } }); httpClient.addAuthenticator((request) async { final response = await get("http://yourapi/token"); final token = response.body['token']; // Set the header request.headers['Authorization'] = "$token"; return request; }); //Autenticator will be called 3 times if HttpStatus is //HttpStatus.unauthorized httpClient.maxAuthRetries = 3; } @override Future> getCases(String path) => get(path); } ``` ## GetPage Middleware GetPage hiện có thuộc tính mới lấy danh sách GetMiddleWare và chạy chúng theo thứ tự cụ thể. **Chú ý**: Khi GetPage có Middleware (phần trung gian), tất cả các children của trang này sẽ tự động có cùng middlewares. ### Ưu tiên Thứ tự ưu tiên của Middlewares có thể đặt như sau trong GetMiddleware. ```dart final middlewares = [ GetMiddleware(priority: 2), GetMiddleware(priority: 5), GetMiddleware(priority: 4), GetMiddleware(priority: -8), ]; ``` và chúng sẽ chạy như thế này **-8 => 2 => 4 => 5** ### Chuyển hướng Khi bạn muốn tìm kiếm trang của route được gọi, function (hàm) sẽ khởi động. Kết quả là phải có RouteSettings để chuyển hướng đến. Hoặc cung cấp cho nó null và chuyển hướng không xảy ra. ```dart RouteSettings redirect(String route) { final authService = Get.find(); return authService.authed.value ? null : RouteSettings(name: '/login') } ``` ### onPageCalled Khi bạn gọi một trang trước mọi thứ được tạo, hàm này sẽ khởi động và bạn có thể sử dụng nó để thay đổi điều gì đó về trang hoặc tạo cho nó một trang mới ```dart GetPage onPageCalled(GetPage page) { final authService = Get.find(); return page.copyWith(title: 'Welcome ${authService.UserName}'); } ``` ### OnBindingsStart Hàm này sẽ khởi động ngay trước khi Bindings diễn ra và bạn có thể thay đổi Bindings cho trang này. ```dart List onBindingsStart(List bindings) { final authService = Get.find(); if (authService.isAdmin) { bindings.add(AdminBinding()); } return bindings; } ``` ### OnPageBuildStart Hàm này sẽ khởi động ngay sau khi Bindings diễn ra. Ở đây, bạn có thể làm thứ gì đó sau khi bạn tạo Bindings và trước khi tạo trang widget. ```dart GetPageBuilder onPageBuildStart(GetPageBuilder page) { print('bindings are ready'); return page; } ``` ### OnPageBuilt Hàm này sẽ khởi động ngay sau khi GetPage.page được gọi và sẽ cho bạn kết quả của hàm và lấy widget được hiển thị. ### OnPageDispose Function này sẽ khởi động ngay sau khi hủy bỏ tất cả các đối tượng liên quan (Controller, views, ...) của trang. ## APIs nâng cao khác ```dart // give the current args from currentScreen Get.arguments // give name of previous route Get.previousRoute // give the raw route to access for example, rawRoute.isFirst() Get.rawRoute // give access to Routing API from GetObserver Get.routing // check if snackbar is open Get.isSnackbarOpen // check if dialog is open Get.isDialogOpen // check if bottomsheet is open Get.isBottomSheetOpen // remove one route. Get.removeRoute() // back repeatedly until the predicate returns true. Get.until() // go to next route and remove all the previous routes until the predicate returns true. Get.offUntil() // go to next named route and remove all the previous routes until the predicate returns true. Get.offNamedUntil() //Check in what platform the app is running GetPlatform.isAndroid GetPlatform.isIOS GetPlatform.isMacOS GetPlatform.isWindows GetPlatform.isLinux GetPlatform.isFuchsia //Check the device type GetPlatform.isMobile GetPlatform.isDesktop //All platforms are supported independently in web! //You can tell if you are running inside a browser //on Windows, iOS, OSX, Android, etc. GetPlatform.isWeb // Equivalent to : MediaQuery.of(context).size.height, // but immutable. Get.height Get.width // Gives the current context of the Navigator. Get.context // Gives the context of the snackbar/dialog/bottomsheet in the foreground, anywhere in your code. Get.contextOverlay // Chí ú: Nếu bạn dùng cái này, nhớ đặtt. thànhnce you // have access to context in any place of your UI, you can use it anywhere in the UI code // If you need a changeable height/width (like Desktop or browser windows that can be scaled) you will need to use context. context.width context.height // Gives you the power to define half the screen, a third of it and so on. // Useful for responsive applications. // param dividedBy (double) optional - default: 1 // param reducedBy (double) optional - default: 0 context.heightTransformer() context.widthTransformer() /// Similar to MediaQuery.of(context).size context.mediaQuerySize() /// Similar to MediaQuery.of(context).padding context.mediaQueryPadding() /// Similar to MediaQuery.of(context).viewPadding context.mediaQueryViewPadding() /// Similar to MediaQuery.of(context).viewInsets; context.mediaQueryViewInsets() /// Similar to MediaQuery.of(context).orientation; context.orientation() /// Check if device is on landscape mode context.isLandscape() /// Check if device is on portrait mode context.isPortrait() /// Similar to MediaQuery.of(context).devicePixelRatio; context.devicePixelRatio() /// Similar to MediaQuery.of(context).textScaleFactor; context.textScaleFactor() /// Get the shortestSide from screen context.mediaQueryShortestSide() /// True if width be larger than 800 context.showNavbar() /// True if the shortestSide is smaller than 600p context.isPhone() /// True if the shortestSide is largest than 600p context.isSmallTablet() /// True if the shortestSide is largest than 720p context.isLargeTablet() /// True if the current device is Tablet context.isTablet() /// Returns a value according to the screen size /// can give value for: /// watch: if the shortestSide is smaller than 300 /// mobile: if the shortestSide is smaller than 600 /// tablet: if the shortestSide is smaller than 1200 /// desktop: if width is largest than 1200 context.responsiveValue() ``` ### Cấu hình thủ công và cài đặt chung tuỳ chọn GetMaterialApp configures everything for you, but if you want to configure Get manually. ```dart MaterialApp( navigatorKey: Get.key, navigatorObservers: [GetObserver()], ); ``` Bạn cũng sẽ có thể dùng Middleware của riêng bạn trong `GetObserver`, điều này không ảnh hưởng những thứ khác. ```dart MaterialApp( navigatorKey: Get.key, navigatorObservers: [ GetObserver(MiddleWare.observer) // Here ], ); ``` Bạn có thể tạo _Global Settings_ cho `Get`. Chỉ cần thêm `Get.config` vào code của bạn trước khi đẩy (push) bất cứ route nào. Hoặc làm nó trực tiếp trong `GetMaterialApp` của bạn. ```dart GetMaterialApp( enableLog: true, defaultTransition: Transition.fade, opaqueRoute: Get.isOpaqueRouteDefault, popGesture: Get.isPopGestureEnable, transitionDuration: Get.defaultDurationTransition, defaultGlobalState: Get.defaultGlobalState, ); Get.config( enableLog = true, defaultPopGesture = true, defaultTransition = Transitions.cupertino ) ``` Bạn có thể tự chọn chuyển hướng tất cả logging messages từ `Get`. Nếu bạn muốn sử dụng logging package ưa thích của riêng bạn, và muốn chụp lại những logs đó: ```dart GetMaterialApp( enableLog: true, logWriterCallback: localLogWriter, ); void localLogWriter(String text, {bool isError = false}) { // pass the message to your favourite logging package here // please note that even if enableLog: false log messages will be pushed in this callback // you get check the flag if you want through GetConfig.isLogEnable } ``` ### Local State Widgets Các Widget này cho phép bạn quản lý một giá trị duy nhất và giữ trạng thái tạm thời và cục bộ. Chúng ta có các hướng đi cho Reactive và Simple. Ví dụ: bạn có thể sử dụng chúng để chuyển đổi văn bản tối nghĩa trong một `TextField`, có thể tạo một widget Expandable Panel tùy chỉnh hoặc có thể sửa đổi chỉ mục hiện tại trong `BottomNavigationBar` trong khi thay đổi nội dung bên trong một `Scaffold`. #### ValueBuilder Đơn giản hóa của `StatefulWidget` hoạt động với lệnh gọi lại` .setState` nhận giá trị cập nhật. ```dart ValueBuilder( initialValue: false, builder: (value, updateFn) => Switch( value: value, onChanged: updateFn, // same signature! you could use ( newValue ) => updateFn( newValue ) ), // if you need to call something outside the builder method. onUpdate: (value) => print("Value updated: $value"), onDispose: () => print("Widget unmounted"), ), ``` #### ObxValue Tương tự như [`ValueBuilder`](#valuebuilder), nhưng đây là phiên bản Reactive, bạn kèm một lệnh Rx (nhớ cái .obs không?) và nó cập nhật tự động ... hay chưa? ```dart ObxValue((data) => Switch( value: data.value, onChanged: data, // Rx has a _callable_ function! You could use (flag) => data.value = flag, ), false.obs, ), ``` ## Mẹo hữu ích `.obs` observable (variable có thể quan sát được) (còn được gọi là loại _Rx_) có nhiều phương thức và toán tử bên trong. > Is very common to _believe_ that a property with `.obs` **IS** the actual value... but make no mistake! > We avoid the Type declaration of the variable, because Dart's compiler is smart enough, and the code > looks cleaner, but: ```dart var message = 'Xin Chào'.obs; print( 'Message "$message" has Type ${message.runtimeType}'); ``` Ngay cả khi `message` _prints_ giá trị String, thì kiểu của nó lại là ** RxString **! Vì vậy, bạn không thể thực hiện `message.substring (0, 4) '. Bạn phải truy cập vào `value`thực bên trong _observable_: Cách được sử dụng nhiều nhất là`.value`, nhưng, bạn có biết rằng bạn cũng có thể sử dụng ... ```dart final name = 'GetX'.obs; // only "updates" the stream, if the value is different from the current one. name.value = 'Hey'; // All Rx properties are "callable" and returns the new value. // but this approach does not accepts `null`, the UI will not rebuild. name('Hello'); // is like a getter, prints 'Hello'. name() ; /// numbers: final count = 0.obs; // You can use all non mutable operations from num primitives! count + 1; // Watch out! this is only valid if `count` is not final, but var count += 1; // You can also compare against values: count > 2; /// booleans: final flag = false.obs; // switches the value between true/false flag.toggle(); /// all types: // Sets the `value` to null. flag.nil(); // All toString(), toJson() operations are passed down to the `value` print( count ); // calls `toString()` inside for RxInt final abc = [0,1,2].obs; // Converts the value to a json Array, prints RxList // Json is supported by all Rx types! print('json: ${jsonEncode(abc)}, type: ${abc.runtimeType}'); // RxMap, RxList and RxSet are special Rx types, that extends their native types. // but you can work with a List as a regular list, although is reactive! abc.add(12); // pushes 12 to the list, and UPDATES the stream. abc[3]; // like Lists, reads the index 3. // equality works with the Rx and the value, but hashCode is always taken from the value final number = 12.obs; print( number == 12 ); // prints > true /// Custom Rx Models: // toJson(), toString() are deferred to the child, so you can implement override on them, and print() the observable directly. class User { String name, last; int age; User({this.name, this.last, this.age}); @override String toString() => '$name $last, $age years old'; } final user = User(name: 'Khang', last: 'Huỳnh', age: 33).obs; // `user` is "reactive", but the properties inside ARE NOT! // So, if we change some variable inside of it... user.value.name = 'Kaiser'; // The widget will not rebuild!, // `Rx` don't have any clue when you change something inside user. // So, for custom classes, we need to manually "notify" the change. user.refresh(); // or we can use the `update()` method! user.update((value){ value.name='Kaiser'; }); print( user ); ``` ## StateMixin Một cách khác để xử lý trạng thái `UI` của bạn là sử dụng`StateMixin `. Để triển khai nó, hãy sử dụng dấu `with` để thêm`StateMixin ` vào bộ điều khiển (controller) của bạn cho phép tích hợp kèm mô hình T. ```dart class Controller extends GetController with StateMixin{} ``` Phương thức `change()` thay đổi trạng thái bất cứ khi nào chúng ta muốn. Chỉ cần chuyển dữ liệu và trạng thái theo cách này: ```dart change(data, status: RxStatus.success()); ``` RxStatus cho phép những trang thái này: ```dart RxStatus.loading(); RxStatus.success(); RxStatus.empty(); RxStatus.error('message'); ``` Để biểu hiện nó trên UI, sử dụng: ```dart class OtherClass extends GetView { @override Widget build(BuildContext context) { return Scaffold( body: controller.obx( (state)=>Text(state.name), // here you can put your custom loading indicator, but // by default would be Center(child:CircularProgressIndicator()) onLoading: CustomLoadingIndicator(), onEmpty: Text('No data found'), // here also you can set your own error widget, but by // default will be an Center(child:Text(error)) onError: (error)=>Text(error), ), ); } ``` #### GetView Widget này là bảo bối của GetX, rất đơn giản, nhưng rất hữu ích! Là một Widget `const Stateless` có getter` controller` cho một `Controller` đã đăng ký, chỉ vậy thôi người ơi~. ```dart class AwesomeController extends GetController { final String title = 'My Awesome View'; } // ALWAYS remember to pass the `Type` you used to register your controller! class AwesomeView extends GetView { @override Widget build(BuildContext context) { return Container( padding: EdgeInsets.all(20), child: Text(controller.title), // just call `controller.something` ); } } ``` #### GetResponsiveView Mở rộng tiện ích này để xây dựng chế độ responsive. Widget này chứa thuộc tính `screen` có tất cả thông tin về kích thước và loại màn hình. ##### Hướng dẫn sử dụng trước khi dùng Bạn có hai lựa chọn để xây dựng nó. - với phương thức `builder` bạn trả về tiện ích con để xây dựng. - với các phương thức `desktop`,` tablet`, `phone`,` watch`. cụ thể, các phương thức này sẽ tạo các loại màn hình khớp với ngữ cảnh khi màn hình là [ScreenType.Tablet] thì phương thức `tablet` sẽ được tạo ra và cứ như vậy. **Chú ý:** Nếu bạn dùng cái này, nhớ đặt `alwaysUseBuilder` thành `false` Với thuộc tính `settings` bạn có thể đặt chiều dài tối thiểu cho các loại màn hình. ![example](https://github.com/SchabanBo/get_page_example/blob/master/docs/Example.gif?raw=true) Code to this screen [code](https://github.com/SchabanBo/get_page_example/blob/master/lib/pages/responsive_example/responsive_view.dart) #### GetWidget Hầu hết mọi người không biết gì về Widget này, hoặc hoàn toàn nhầm lẫn về cách sử dụng nó. Trường hợp sử dụng rất hiếm, nhưng rất cụ thể: Nó `caches` một Bộ điều khiển. Bởi vì _cache_ không thể là một `const Stateless`. > Vậy khi nào mình cần cache bộ điều khiển (controller)? Nếu sử dụng, bạn sẽ dùng cái này **GetX**: `Get.create()`. `Get.create(()=>Controller())` sẽ tạo một `Controller` với mỗi lần gọi `Get.find()`, Đó là nơi mà `GetWidget` tỏa sáng ... chẳng hạn như bạn có thể sử dụng nó, để giữ một danh sách các mục Todo. Vì vậy, nếu widget được "xây dựng lại", nó sẽ giữ nguyên phiên bản controller. #### GetxService Class này giống như một `GetxController`, nó chia sẻ cùng một vòng đời (`onInit ()`,`onReady ()`,`onClose ()`). Nhưng không có "logic" bên trong của nó. Nó chỉ thông báo cho ** GetX ** Hệ thống Nạp Dependency rằng class con này ** không thể ** bị xóa khỏi bộ nhớ. Vì vậy, rất hữu ích để giữ cho "Service" của bạn luôn có thể truy cập và hoạt động với `Get.find ()`. Giống: `ApiService`,` StorageService`, `CacheService`. ```dart Future main() async { await initServices(); /// AWAIT SERVICES INITIALIZATION. runApp(SomeApp()); } /// Is a smart move to make your Services intiialize before you run the Flutter app. /// as you can control the execution flow (maybe you need to load some Theme configuration, /// apiKey, language defined by the User... so load SettingService before running ApiService. /// so GetMaterialApp() doesnt have to rebuild, and takes the values directly. void initServices() async { print('starting services ...'); /// Here is where you put get_storage, hive, shared_pref initialization. /// or moor connection, or whatever that's async. await Get.putAsync(() => DbService().init()); await Get.putAsync(SettingsService()).init(); print('All services started...'); } class DbService extends GetxService { Future init() async { print('$runtimeType delays 2 sec'); await 2.delay(); print('$runtimeType ready!'); return this; } } class SettingsService extends GetxService { void init() async { print('$runtimeType delays 1 sec'); await 1.delay(); print('$runtimeType ready!'); } } ``` Cách duy nhất để thực sự xóa một `GetxService`, là với `Get.reset ()` giống như cách thức "Khởi động nóng" ứng dụng của bạn. Vì vậy, hãy nhớ rằng, nếu bạn cần sự tồn tại tuyệt đối của một class trong vòng đời tồn tại của nó trong ứng dụng của bạn, hãy sử dụng `GetxService`. # Thay đổi đột phá 2.0 1- Rx types: | Before | After | | ------- | ---------- | | StringX | `RxString` | | IntX | `RxInt` | | MapX | `RxMap` | | ListX | `RxList` | | NumX | `RxNum` | | DoubleX | `RxDouble` | RxController và GetBuilder bây giờ đã hợp nhất, bạn không cần phải ghi nhớ bộ điều khiển nào bạn muốn sử dụng, chỉ cần sử dụng GetxController, nó sẽ hoạt động để quản lý trạng thái đơn giản và reactive. 2- NamedRoutes Before: ```dart GetMaterialApp( namedRoutes: { '/': GetRoute(page: Home()), } ) ``` Now: ```dart GetMaterialApp( getPages: [ GetPage(name: '/', page: () => Home()), ] ) ``` Tại sao lại thay đổi? Thông thường, có thể cần phải quyết định trang nào sẽ được hiển thị từ một tham số hoặc mã thông báo đăng nhập, cách tiếp cận trước đây không linh hoạt, vì nó không cho phép điều này. Việc chèn trang vào một hàm đã làm giảm đáng kể mức tiêu thụ RAM, vì các tuyến sẽ không được cấp phát trong bộ nhớ kể từ khi ứng dụng được khởi động và nó cũng cho phép thực hiện kiểu tiếp cận này: ```dart GetStorage box = GetStorage(); GetMaterialApp( getPages: [ GetPage(name: '/', page:(){ return box.hasData('token') ? Home() : Login(); }) ] ) ``` # Tại sao lại dùng GetX 1- Nhiều lần sau khi cập nhật Flutter, nhiều package của bạn sẽ bị hỏng. Đôi khi lỗi biên dịch code xảy ra, lỗi thường xuất hiện mà vẫn không có câu trả lời; và, chúng ta, nhà phát triển, cần biết lỗi đến từ đâu, theo dõi lỗi, chỉ sau đó cố gắng mở một vấn đề trong kho lưu trữ tương ứng và xem vấn đề của nó đã được giải quyết. Tập trung các tài nguyên chính để phát triển (Quản lý state, dependency và route), cho phép bạn thêm một gói duy nhất vào pubspec của mình và bắt đầu hoạt động. Sau khi cập nhật Flutter, điều duy nhất bạn cần làm là cập nhật Get dependency và bắt đầu làm việc. Get cũng giải quyết các vấn đề tương thích. Có bao nhiêu lần một phiên bản của một gói không tương thích với phiên bản của gói khác, vì một gói sử dụng phần phụ thuộc trong một phiên bản và gói kia trong phiên bản khác? Đây cũng không phải là vấn đề đáng lo ngại khi sử dụng Get, vì mọi thứ đều nằm trong cùng một gói và hoàn toàn tương thích. 2- Flutter dễ học, Flutter siêu phàm, nhưng Flutter vẫn có một số bản soạn sẵn có thể không mong muốn đối với hầu hết các nhà phát triển, chẳng hạn như `Navigator.of (context) .push (context, builder [...] '. Get đơn giản hóa việc phát triển. Thay vì viết 8 dòng mã chỉ để gọi một tuyến đường, bạn chỉ cần làm điều đó: `` Get.to (Home ()) 'và bạn đã hoàn tất, bạn sẽ chuyển sang trang tiếp theo. Các url web động là một điều thực sự khó khăn để làm với Flutter hiện tại và điều đó với GetX đơn giản đến đần độn :). Quản lý trạng thái trong Flutter và quản lý các phần phụ thuộc cũng là điều tạo ra nhiều cuộc thảo luận, vì có hàng trăm mẫu trong pub (Dart package website). Nhưng không có gì dễ dàng bằng việc thêm ".obs" ở cuối biến của bạn, và đặt tiện ích của bạn bên trong Obx, và thế là xong, tất cả các cập nhật cho biến đó sẽ được tự động cập nhật trên màn hình. 3- Dễ dàng mà không phải lo lắng về hiệu suất. Hiệu suất của Flutter đã đáng kinh ngạc rồi, nhưng hãy tưởng tượng rằng bạn sử dụng trình quản lý state và trình định vị để phân phối các Blocs / stores / controllers / v.v. của bạn. Bạn sẽ phải gọi thủ công loại trừ sự phụ thuộc khi bạn không cần đến chúng. Nhưng bạn đã bao giờ nghĩ chỉ cần sử dụng bộ điều khiển của mình và khi nó không còn được ai sử dụng nữa, nó sẽ đơn giản được xóa khỏi bộ nhớ? Đó là những gì GetX làm. Với SmartManagement, mọi thứ không được sử dụng sẽ được xóa khỏi bộ nhớ và bạn không phải lo lắng về bất cứ điều gì ngoài lập trình. Bạn sẽ được đảm bảo rằng bạn đang sử dụng các nguồn tài nguyên cần thiết tối thiểu mà thậm chí không cần tạo ra một logic nào cho việc này. 4- Tách khỏi thực tế. Bạn có thể đã nghe đến khái niệm "tách khung nhìn (view) khỏi business logic". Đây không phải là đặc thù của BLoC, MVC, MVVM và bất kỳ tiêu chuẩn nào khác trên thị trường đều có khái niệm này. Tuy nhiên, khái niệm này thường có thể được giảm thiểu trong Flutter do việc sử dụng ngữ cảnh (context). Nếu bạn cần ngữ cảnh để tìm một InheritedWidget, bạn cần nó trong dạng xem hoặc chuyển ngữ cảnh theo tham số. Tôi đặc biệt thấy giải pháp này rất chán đời; hơn nữa, để làm việc theo nhóm, chúng tôi sẽ luôn phụ thuộc vào business logic của View. GetX không chính thống với cách tiếp cận tiêu chuẩn và mặc dù nó không cấm hoàn toàn việc sử dụng StatefulWidgets, InitState, v.v., nhưng nó luôn có một cách tiếp cận tương tự có thể rõ ràng hơn. Controller có vòng đời và khi bạn cần thực hiện yêu cầu APIREST chẳng hạn, bạn độc lập với View. Bạn có thể sử dụng onInit để bắt đầu cuộc gọi http và khi dữ liệu đến, các biến sẽ được điền. Vì GetX hoạt động hoàn toàn reactive (đó là sự thực và hoạt động theo luồng), khi các mục được lấp đầy, tất cả tiện ích con sử dụng biến đó sẽ được cập nhật tự động trong View. Điều này cho phép những người có chuyên môn về UI chỉ làm việc với các widget và không phải gửi bất kỳ thứ gì đến logic nghiệp vụ ngoài các sự kiện của người dùng (như nhấp vào nút), trong khi những người làm việc với logic nghiệp vụ sẽ được tự do tạo và kiểm tra logic nghiệp vụ riêng. Thư viện này sẽ luôn được cập nhật và triển khai các tính năng mới. Hãy thoải mái đưa ra các bài PR và đóng góp cho chúng. # Cộng đồng ## Kênh Cộng đồng GetX có một cộng đồng rất tích cực và hữu ích. Nếu bạn có thắc mắc hoặc muốn được hỗ trợ về việc sử dụng khuôn khổ này, vui lòng tham gia các kênh cộng đồng của chúng tôi, câu hỏi của bạn sẽ được trả lời nhanh hơn và đó sẽ là nơi phù hợp nhất. Kho lưu trữ này dành riêng cho các vấn đề mở và yêu cầu tài nguyên, nhưng hãy thoải mái trở thành một phần của Cộng đồng GetX. | **Slack** | **Discord** | **Telegram** | | :-------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------- | | [![Get on Slack](https://img.shields.io/badge/slack-join-orange.svg)](https://communityinviter.com/apps/getxworkspace/getx) | [![Discord Shield](https://img.shields.io/discord/722900883784073290.svg?logo=discord)](https://discord.com/invite/9Hpt99N) | [![Telegram](https://img.shields.io/badge/chat-on%20Telegram-blue.svg)](https://t.me/joinchat/PhdbJRmsZNpAqSLJL6bH7g) | ## Cách cống hiến _Bạn muốn đóng góp cho dự án? Chúng tôi sẽ tự hào giới thiệu bạn với tư cách là một trong những cộng tác viên của chúng tôi. Dưới đây là một số điểm mà bạn có thể đóng góp và làm cho Get (và Flutter) tốt hơn nữa._ - Giúp dịch readme sang các ngôn ngữ khác. - Thêm tài liệu vào readme (rất nhiều chức năng của Get chưa được tài liệu hóa). - Viết bài hoặc làm video dạy cách sử dụng Get (chúng sẽ được chèn vào Readme và trong tương lai trong Wiki của chúng tôi). - Đưa ra các PR cho mã / bài kiểm tra. - Bao gồm các chức năng mới. Mọi đóng góp đều được hoan nghênh! ## Các bài báo và video - [Flutter GetX EcoSystem package for arabic people](https://www.youtube.com/playlist?list=PLV1fXIAyjeuZ6M8m56zajMUwu4uE3-SL0) - Hướng dẫn bởi [Pesa Coder](https://github.com/UsamaElgendy). - [Dynamic Themes in 3 lines using GetX™](https://medium.com/swlh/flutter-dynamic-themes-in-3-lines-c3b375f292e3) - Hướng dẫn bởi [Rod Brown](https://github.com/RodBr). - [Complete GetX™ Navigation](https://www.youtube.com/watch?v=RaqPIoJSTtI) - Quản lý route bởi Amateur Coder. - [Complete GetX State Management](https://www.youtube.com/watch?v=CNpXbeI_slw) - Quản lý State video by Amateur Coder. - [GetX™ Other Features](https://youtu.be/ttQtlX_Q0eU) - Utils, storage, bindings and other features video by Amateur Coder. - [Firestore User with GetX | Todo App](https://www.youtube.com/watch?v=BiV0DcXgk58) - Video by Amateur Coder. - [Firebase Auth with GetX | Todo App](https://www.youtube.com/watch?v=-H-T_BSgfOE) - Video by Amateur Coder. - [The Flutter GetX™ Ecosystem ~ State Management](https://medium.com/flutter-community/the-flutter-getx-ecosystem-state-management-881c7235511d) - Quản lý State by [Aachman Garg](https://github.com/imaachman). - [The Flutter GetX™ Ecosystem ~ Dependency Injection](https://medium.com/flutter-community/the-flutter-getx-ecosystem-dependency-injection-8e763d0ec6b9) - Nạp dependency by [Aachman Garg](https://github.com/imaachman). - [GetX, the all-in-one Flutter package](https://www.youtube.com/watch?v=IYQgtu9TM74) - Giới thiệu sơ lược về quản lý State and Navigation by Thad Carnevalli. - [Build a To-do List App from scratch using Flutter and GetX](https://www.youtube.com/watch?v=EcnqFasHf18) - UI + State Management + Storage video by Thad Carnevalli. - [GetX Flutter Firebase Auth Example](https://medium.com/@jeffmcmorris/getx-flutter-firebase-auth-example-b383c1dd1de2) - Article by Jeff McMorris. - [Flutter State Management with GetX – Complete App](https://www.appwithflutter.com/flutter-state-management-with-getx/) - by App With Flutter. - [Flutter Routing with Animation using Get Package](https://www.appwithflutter.com/flutter-routing-using-get-package/) - by App With Flutter. - [A minimal example on dartpad](https://dartpad.dev/2b3d0d6f9d4e312c5fdbefc414c1727e?) - by [Roi Peker](https://github.com/roipeker) ================================================ FILE: README.id-ID.md ================================================ ![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/get.png) **Bahasa: Indonesia (file ini), [Inggris](README.md), [Orang Vietnam](README-vi.md), [Urdu](README.ur-PK.md), [China](README.zh-cn.md), [Portugis (Brazil)](README.pt-br.md), [Spanyol](README-es.md), [Russia](README.ru.md), [Polandia](README.pl.md), [Korea](README.ko-kr.md), [French](README-fr.md)** [![pub package](https://img.shields.io/pub/v/get.svg?label=get&color=blue)](https://pub.dev/packages/get) [![popularity](https://badges.bar/get/popularity)](https://pub.dev/packages/sentry/score) [![likes](https://badges.bar/get/likes)](https://pub.dev/packages/get/score) [![pub points](https://badges.bar/get/pub%20points)](https://pub.dev/packages/get/score) ![building](https://github.com/jonataslaw/get/workflows/build/badge.svg) [![style: effective dart](https://img.shields.io/badge/style-effective_dart-40c4ff.svg)](https://pub.dev/packages/effective_dart) [![Discord Shield](https://img.shields.io/discord/722900883784073290.svg?logo=discord)](https://discord.com/invite/9Hpt99N) [![Get on Slack](https://img.shields.io/badge/slack-join-orange.svg)](https://communityinviter.com/apps/getxworkspace/getx) [![Telegram](https://img.shields.io/badge/chat-on%20Telegram-blue.svg)](https://t.me/joinchat/PhdbJRmsZNpAqSLJL6bH7g) Awesome Flutter Buy Me A Coffee ![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/getx.png) - [Tentang Get](#tentang-get) - [Instalasi](#instalasi) - [Aplikasi Counter menggunakan GetX](#aplikasi-counter-menggunakan-getx) - [Tiga Pilar](#tiga-pilar) - [State management](#state-management) - [Reactive State Manager](#reactive-state-manager) - [Detail lebih lanjut mengenai state management](#detail-lebih-lanjut-mengenai-state-management) - [Route management](#route-management) - [Detail lebih lanjut mengenai route management](#detail-lebih-lanjut-mengenai-route-management) - [Dependency management](#dependency-management) - [Detail lebih lanjut mengenai dependency management](#detail-lebih-lanjut-mengenai-dependency-management) - [Utilitas](#utilitas) - [Internasionalisasi](#internasionalisasi) - [Translasi](#translasi) - [Menggunakan Translasi](#menggunakan-translasi) - [Lokalisasi](#lokalisasi) - [Mengubah Lokal](#mengubah-lokal) - [Lokal Sistem](#lokal-sistem) - [Mengubah Tema](#mengubah-tema) - [GetConnect](#getconnect) - [Konfigurasi Default](#konfigurasi-default) - [Konfigurasi Kustom](#konfigurasi-kustom) - [GetPage Middleware](#getpage-middleware) - [Prioritas](#prioritas) - [Redirect](#redirect) - [OnPageCalled](#onpagecalled) - [OnBindingsStart](#onbindingsstart) - [OnPageBuildStart](#onpagebuildstart) - [OnPageBuilt](#onpagebuilt) - [OnPageDispose](#onpagedispose) - [API Lanjutan Lainnya](#api-lanjutan-lainnya) - [Pengaturan Global Opsional dan Konfigurasi Manual](#pengaturan-global-opsional-dan-konfigurasi-manual) - [Local State Widgets](#local-state-widgets) - [ValueBuilder](#valuebuilder) - [ObxValue](#obxvalue) - [Tips berguna](#tips-berguna) - [GetView](#getview) - [GetResponsiveView](#getresponsiveview) - [Cara pakai](#cara-pakai) - [GetWidget](#getwidget) - [GetxService](#getxservice) - [Breaking change dari 2.0](#breaking-change-dari-20) - [Mengapa Getx?](#mengapa-getx) - [Komunitas](#komunitas) - [Channel Komunitas](#kanal-komunitas) - [Cara berkontribusi](#cara-berkontribusi) - [Artikel dan Video](#artikel-dan-video) # Tentang Get - GetX adalah solusi ekstra-ringan dan powerful untuk Flutter. Ini mengkombinasikan state management dengan performa tinggi, injeksi dependensi yang cerdas, dan route management secara singkat dan praktis. - GetX memiliki 3 prinsip dasar, yang menjadi prioritas untuk semua resource yang ada di dalamnya: **PRODUKTIFITAS, PERFORMA DAN ORGANISASI** - **PERFORMA:** GetX fokus pada performa dan konsumsi resource minimum. GetX tidak menggunakan Stream atau ChangeNotifier. - **PRODUKTIFITAS:** GetX menggunakan sintaks yang mudah dan nyaman. Tidak peduli apa yang akan anda lakukan, akan selalu ada cara yang lebih mudah dengan GetX. Ini akan menghemat waktu development, dan meng-ekstrak performa maksimum pada aplikasi anda. Umumnya, developer akan selalu berhubungan dengan penghapusan controller dari memori. Dengan GetX, ini tidak diperlukan, karena resource akan dihapus dari memori secara default ketika tidak digunakan. Jika anda ingin menyimpannnya kedalam memori, anda harus secara eksplisit mendeklarasikan "permanent: true" pada dependensi anda. Dengan begitu, selain menghemat waktu, anda juga mengurangi resiko memiliki dependensi yang tidak diperlukan dalam memori. Pemuatan dependensi juga bersifat "lazy" secara default. - **ORGANISASI:** GetX memungkinkan pemisahan View, Presentation Logic, Business Logic, Dependency Injection, dan Navigasi. Anda tidak perlu konteks untuk berpindah antar halaman. Jadi, anda tidak lagi bergantung pada widget tree (visualisasi) untuk hal ini. Anda tidak perlu konteks untuk mengakses controller/bloc melalui InheritedWidget. Dengan ini, anda benar benar memisahkan presentation logic dan business logic dari lapisan visual. Anda tidak perlu menginjeksi kelas Controller/Model/Bloc kedalam widget tree melalui multiprovider, untuk hal ini GetX menggunakan fitur dependency injection nya sendiri, memisahkan DI dari View secara total. Dengan GetX, anda tahu dimana harus mencari setiap fitur dalam aplikasi anda, memiliki kode yang bersih secara default. Ini selain untuk memfasilitasi maintenance, membuat pembagian modul, sesuatu yang hingga saat itu di Flutter tidak terpikirkan, sesuatu yang sangat mungkin. BLoC adalah permulaan awal dalam meng-organisir kode di Flutter, ini memisahkan business logic dari visualisasi. GetX adalah evolusi natural dari ini, tidak hanya memisahkan business logic, tapi juga presentation logic. Injeksi dependensi dan route juga dipisahkan sebagai bonus, dan lapisan data benar-benar terpisah secara menyeluruh. Anda tahu dimana semuanya berada, dan segalanya dengan cara yang lebih mudah daripada membuat sebuah hello world. GetX adalah cara termudah, praktis, dan scalable untuk membangun aplikasi dengan performa tinggi menggunakan Flutter SDK, dengan ekosistem besar di sekelilingnya yang bekerjasama secara sempurna, mudah dipahami untuk pemula, dan akurat untuk ahli. Aman, stabil, up-to-date, dan menawarkan banyak cakupan build-in API yang tidak tersedia di dalam default Flutter SDK. - GetX tidak "bloated". Dirinya memiliki banyak fitur yang memungkinkan anda memulai programming tanpa mengkhawatirkan apapun, namun setiap fiturnya terletak didalam kontainer terpisah, dan hanya dimulai setelah digunakan. Jika anda hanya menggunakan State Management, hanya State Management yang akan di-compile. Jika anda hanya menggunakan routes, state management tidak akan di-compile. - GetX memiliki ekosistem yang besar, komunitas yang juga besar, banyak kolaborator, dan akan di maintenance selama Flutter ada. GetX juga mampu berjalan dengan kode yang sama di Android, iOS, Web, Mac, Linux, Windows, dan server anda. **Juga memungkinkan untuk me-reuse kode yang dibuat di frontend ke backend dengan [Get Server](https://github.com/jonataslaw/get_server)**. **Selain itu, seluruh proses development bisa di automasi secara menyeluruh, untuk keduanya (server dan frontend) menggunakan [Get CLI](https://github.com/jonataslaw/get_cli)**. **Selain itu, untuk lebih meningkatkan produktifitas anda, kami memiliki [ekstensi untuk VSCode](https://marketplace.visualstudio.com/items?itemName=get-snippets.get-snippets) dan [ekstensi untuk Android Studio/Intellij](https://plugins.jetbrains.com/plugin/14975-getx-snippets)** # Instalasi Tambahkan Get kedalam file `pubspec.yaml` anda: ```yaml dependencies: get: ``` Import get didalam file dimana get akan digunakan: ```dart import 'package:get/get.dart'; ``` # Aplikasi Counter menggunakan GetX Proyek "counter" yang dibuat secara default ketika membuat proyek Flutter memiliki lebih dari 100 baris (termasuk comment). Untuk menunjukkan kekuatan Get, kami akan mendemonstrasikan bagaimana cara membuat "counter" yang mengubah state setiap klik, berpindah, dan berbagi state antar halaman, semua dalam cara yang terorganisir, memisahkan business logic dari view, dalam HANYA 26 BARIS KODE TERMASUK COMMENT. - Langkah 1: Tambahkan "Get" sebelum MaterialApp, mengubahnya menjadi GetMaterialApp ```dart void main() => runApp(GetMaterialApp(home: Home())); ``` - Catatan: ini tidak mengubah MaterialApp bawaan Flutter, GetMaterialApp bukan sebuah MaterialApp yang dimodifikasi, itu hanyalah sebuah Widget yang telah dikonfigurasi sebelumnya, yang mana memiliki default MaterialApp sebagai child. Anda bisa mengkonfigurasinya secara manual, namun hal itu benar-benar tidak diperlukan. GetMaterialApp akan membuat route, menginjeksinya, menginjeksi translasi/terjemahan, dan semua yang anda butuhkan untuk navigasi route. Jika anda hanya menggunakan Get untuk manajemen state atau manajemen dependensi, tidak perlu menggunakan GetMaterialApp. GetMaterialApp diperlukan untuk route, snackbar, internasionalisasi/terjemahan, bottomSheet, dialog, dan high-level API yang berhubungan dengan route dan ketiadaan konteks. - Catatan²: Langkah ini hanya diperlukan jika anda akan menggunakan manajemen route (`Get.to()`, `Get.back()` dan seterusnya). Jika anda tidak menggunakannya, langkah 1 tidak diperlukan. - Langkah 2: Buat file baru untuk business logic dan taruh semua variabel, metode, dan kontroler didalamnya. Anda bisa membuat variabel apapun menjadi "observable" menggunakan notasi tambahan ".obs". ```dart class Controller extends GetxController{ var count = 0.obs; increment() => count++; } ``` - Langkah 3: Buat file baru untuk View, gunakan StatelessWidget dan hemat penggunaan RAM, dengan Get, anda mungkin tidak perlu lagi menggunakan StatefulWidget. ```dart class Home extends StatelessWidget { @override Widget build(context) { // Instansiasi kelas anda menggunakan Get.put() untuk membuatnya tersedia untuk seluruh "child" route dibawahnya. final Controller c = Get.put(Controller()); return Scaffold( // Gunakan Obx(() => ...) untuk mengupdate Text() ketika `count` berubah. appBar: AppBar(title: Obx(() => Text("Clicks: ${c.count}"))), // Ganti 8 baris Navigator.push menggunan Get.to() agar lebih sederhana. Anda tidak perlu `context`. body: Center(child: ElevatedButton( child: Text("Go to Other"), onPressed: () => Get.to(Other()))), floatingActionButton: FloatingActionButton(child: Icon(Icons.add), onPressed: c.increment)); } } class Other extends StatelessWidget { // Anda bisa meminta Get untuk menemukan kontroler yang digunakan di halaman lain dan redirect ke halaman itu. final Controller c = Get.find(); @override Widget build(context){ // Akses variabel `count` yang telah di update. return Scaffold(body: Center(child: Text("${c.count}"))); } } ``` Hasil: ![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/counter-app-gif.gif) Ini adalah proyek sederhana, namun sudah membuatnya terlihat jelas betapa powerful kemampuan yang dimiliki Get. Sepanjang proyek anda berkembang, perbedaan ini akan menjadi lebih signifikan. Get di desain untuk bekerja dalam tim, namun juga memudahkan pekerjaan untuk developer perseorangan dan membuatnya menjadi lebih sederhana. Tingkatkan deadline anda, antarkan semuanya tanpa kehilangan performa. Get bukan untuk semua orang, namun jika anda tersinggung dengan frasa tersebut, Get cocok untukmu! # Tiga Pilar ## State management Get memiliki dua state manager berbeda: Simple state manager (kami menyebutnya GetBuilder) dan Reactive state manager (GetX/Obx) ### Reactive State Manager Reactive programming bisa meng-alienasi banya orang karena katanya, sulit dimengerti. GetX mengubah reactive programming menjadi sesuatu yang cukup sederhana: - Anda tidak perlu membuat StreamController. - Anda tidak perlu membuat StreamBuilder untuk setiap variabel. - Anda tidak perlu membuat kelas untuk setiap state. - Anda tidak perlu membuat get untuk sebuah value awal (initial value). - Anda tidak perlu menggunakan generator kode. Reactive programming dengan Get semudah menggunakan setState. Bayangkan anda memiliki variabel nama, dan setiap kali anda mengubahnya, semua widget yang menggunakannya akan berubah secara otomatis. Ini variabel count anda: ```dart var name = 'Jonatas Borges'; ``` Untuk membuatnya "observable", anda hanya perlu menambahkan ".obs" di belakangnya: ```dart var name = 'Jonatas Borges'.obs; ``` Dan didalam UI, ketika anda ingin menampilkan value dan update tampilan ketika value itu berubah, cukup lakukan ini: ```dart Obx(() => Text("${controller.name}")); ``` Selesai! _Sesederhana_ itu. ### Detail lebih lanjut mengenai state management **Baca penjelasan lebih lanjut tentang state management [disini](./documentation/id_ID/state_management.md). Disana anda akan melihat contoh lebih banyak dan juga perbedaan diantara simple state manager dengan reactive state manager** Anda akan mendapatkan pemahaman yang baik tentang kekuatan dari GetX. ## Route management Jika anda ingin menggunakan routes/snackbars/dialogs/bottomsheets tanpa context, GetX luar biasa cocok untuk anda, lihat ini: Tambahkan "Get" sebelum MaterialApp, mengubahnya menjadi GetMaterialApp ```dart GetMaterialApp( // Sebelumnya: MaterialApp( home: MyHome(), ) ``` Pindah ke halaman baru: ```dart Get.to(NextScreen()); ``` Pindah ke halaman baru menggunakan nama. Baca detail lebih lanjut tentang penamaan route [disini](./documentation/id_ID/route_management.md#navigation-with-named-routes) ```dart Get.toNamed('/details'); ``` Untuk menutup snackbar, dialog, bottomsheet, atau apapun yang normalnya anda tutup menggunakan Navigator.pop(context); ```dart Get.back(); ``` Untuk pergi ke halaman baru dan mencegah user kembali ke halaman sebelumnya (biasanya digunakan untuk SplashScreen, LoginScreen, dsb). ```dart Get.off(NextScreen()); ``` Untuk pergi ke halaman baru dan batalkan navigasi sebelumnya (berguna untuk shopping cart, polls, dan test). ```dart Get.offAll(NextScreen()); ``` Sadarkah bahwa anda tidak menggunakan context sama sekali untuk hal tersebut? Itu adalah keuntungan terbesar dalam menggunakan Get route management. Dengan ini, anda bisa mengeksekusi semua metode dari controller, tanpa ragu. ### Detail lebih lanjut mengenai route management **Get bekerja dengan named route dan juga menawarkan kontrol dengan level yang lebih rendah untuk navigasimu! Dokumentasinya ada [disini](./documentation/id_ID/route_management.md)** ## Dependency management Get memiliki dependency manager sederhana dan powerful yang memungkinkan anda mendapatkan kelas yang setara dengan Bloc atau Controller hanya dengan 1 baris kode, tanpa Provider context, tanpa inheritedWidget: ```dart Controller controller = Get.put(Controller()); ``` - Catatan: Jika anda menggunakan State Manager milik Get, harap untuk lebih memperhatikan [Bindings](./documentation/id_ID/dependency_management.md#bindings) api, yang mana akan membuat pengkoneksian View terhadap Controller jadi lebih mudah. Daripada menginstansiasi kelas anda didalam kelas yang anda gunakan, cukup lakukan hal itu di dalam Get instance, ini akan membuatnya tersedia di semua tempat di Aplikasimu. Jadi anda bisa menggunakan controller (atau class Bloc) secara normal. **Tips:** Dependency Management Get terpisah dari bagian lain dari package, jadi jika sebagai contoh aplikasi anda sudah menggunakan state manager (tidak peduli apapun itu), anda tidak perlu menulis ulang sama sekali, anda bisa menggunakan dependency injection tanpa masalah. ```dart controller.fetchApi(); ``` Bayangkan anda bernavigasi melewati route yang sangat banyak, dan anda membutuhkan data yang tertinggal didalam controller jauh di belakang route sebelumnya, anda akan butuh state manager dikombinasikan dengan Provider atau Get_it, benar kan? Tidak dengan Get. Anda hanya perlu meminta Get untuk "menemukan" controllernya, anda tidak perlu dependensi tambahan: ```dart Controller controller = Get.find(); // Ya, terlihat seperti Sulap, Get akan menemukan controller anda, dan akan mengantarkannya ke lokasi anda. // Anda bisa memiliki 1 juta controller terinisialisasi, Get akan selalu memberimu controller yang tepat. ``` Dan setelahnya anda bisa memperoleh data yang tertinggal sebelumnya: ```dart Text(controller.textFromApi); ``` ### Detail lebih lanjut mengenai dependency management **Baca penjelasan lebih lanjut tentang dependency management [disini](./documentation/id_ID/dependency_management.md)** # Utilitas ## Internasionalisasi ### Translasi Translasi disimpan sebagai key-value map sederhana. Untuk menambahkan translasi kustom, buat sebuah kelas dan extend `Translations`. ```dart import 'package:get/get.dart'; class Messages extends Translations { @override Map> get keys => { 'en_US': { 'hello': 'Hello World', }, 'id_ID': { 'hello': 'Halo Dunia', } }; } ``` #### Menggunakan Translasi Cukup tambahkan `.tr` setelah key yang disebutkan dan value nya akan diterjemahkan, menggunakan value awal dari `Get.locale` dan `Get.fallbackLocale`. ```dart Text('title'.tr); ``` ### Lokalisasi Berikan parameter ke `GetMaterialApp` untuk mendefinisikan lokal dan translasi. ```dart return GetMaterialApp( translations: Messages(), // gunakan translasi yang anda buat locale: Locale('id', 'ID'), // translasi akan ditampilkan di lokal ini fallbackLocale: Locale('en', 'US'), // berikan lokal penumpu untuk berjaga-jaga jika lokal yang tidak valid dipilih ); ``` #### Mengubah Lokal Panggil `Get.updateLocale(locale)` untuk memperbarui lokal. Setelahnya, translasi akan menggunakan lokal baru. ```dart var locale = Locale('en', 'US'); Get.updateLocale(locale); ``` #### Lokal Sistem Untuk membaca lokal sistem, anda bisa menggunakan `Get.deviceLocale`. ```dart return GetMaterialApp( locale: Get.deviceLocale, ); ``` ## Mengubah Tema Harap untuk tidak menggunakan widget dengan level lebih tinggi daripada `GetMaterialApp` untuk memperbaruinya. Ini akan menyebabkan "duplicate keys". Banyak orang terbiasa menggunakan cara lama untuk membuat sebuah "ThemeProvider" widget hanya untuk mengubah tema aplikasi anda, dan ini tentu saja TIDAK diperlukan dengan **GetX™**. Anda bisa membuat tema kustom anda sendiri dan cukup menambahkannya kedalam `Get.changeTheme` tanpa boilerplate: ```dart Get.changeTheme(ThemeData.light()); ``` Jika anda ingin membuat sesuatu seperti tombol yang mengubah Tema ketika `onPressed`, anda bisa mengkombinasikan dua **GetX™** API: - API yang melakukan pengecekan terhadap tema gelap `Get.isDarkMode`. - Dan API pengubah tema `Get.changeTheme`, anda cukup meletakannya didalam `onPressed`: ```dart Get.changeTheme(Get.isDarkMode? ThemeData.light(): ThemeData.dark()); ``` Ketika `.darkmode` aktif, ini akan mengubah aplikasi anda ke _light theme_, dan sebaliknya, jika _light theme_ sedang aktif, ini akan mengubah aplikasi anda ke _dark theme_. ## GetConnect GetConnect adalah cara mudah untuk berkomunikasi dari backend ke frontend menggunakan http atau websocket. ### Konfigurasi Default Anda bisa secara sederhana meng-extend GetConnect dan menggunakan GET/POST/PUT/DELETE/SOCKET untuk berkomunikasi dengan REST API atau Websocket anda. ```dart class UserProvider extends GetConnect { // Get request Future getUser(int id) => get('http://youapi/users/$id'); // Post request Future postUser(Map data) => post('http://youapi/users', body: data); // Post request dengan File Future> postCases(List image) { final form = FormData({ 'file': MultipartFile(image, filename: 'avatar.png'), 'otherFile': MultipartFile(image, filename: 'cover.png'), }); return post('http://youapi/users/upload', form); } GetSocket userMessages() { return socket('https://yourapi/users/socket'); } } ``` ### Konfigurasi Kustom GetConnect sangat bisa di disesuaikan, anda bisa mendefinisikan base URL, Response Modifier, Request Modifier, Authenticator, dan bahkan jumlah percobaan akses ulang (retry) yang mana akan mencoba meng-autentikasi dirinya sendiri, sebagai tambahan, anda juga bisa mendefinisikan dekoder standar yang akan mengubah seluruh request kedalam Model anda tanpa konfigurasi tambahan. ```dart class HomeProvider extends GetConnect { @override void onInit() { // Semua request akan melewati jsonEncode, jadi, CasesModel.fromJson() httpClient.defaultDecoder = CasesModel.fromJson; httpClient.baseUrl = 'https://api.covid19api.com'; // baseUrl = 'https://api.covid19api.com'; // Ini akan men-setting baseUrl ke // Http dan websocket jika digunakan tanpa [httpClient] // Ini akan mengaitkan properti 'apikey' kedalam header dari semua request. httpClient.addRequestModifier((request) { request.headers['apikey'] = '12345678'; return request; }); // Bahkan jika server mengirim data dari negara "Brazil" // itu tidak akan pernah ditampilkan ke user, karena anda menghapus // data tersebut sebelum response, bahkan sebelum response diantarkan. httpClient.addResponseModifier((request, response) { CasesModel model = response.body; if (model.countries.contains('Brazil')) { model.countries.remove('Brazilll'); } }); httpClient.addAuthenticator((request) async { final response = await get("http://yourapi/token"); final token = response.body['token']; // Sesuaikan header request.headers['Authorization'] = "$token"; return request; }); // Authenticator akan dipanggil 3 kali jika // HttpStatus == HttpStatus.unauthorized httpClient.maxAuthRetries = 3; } } @override Future> getCases(String path) => get(path); } ``` ## GetPage Middleware GetPage sekarang memiliki properti baru yang menerima list GetMiddleware dan menjalankannya dalam urutan spesifik. **Catatan**: Ketika GetPage memiliki middleware, seluruh child dari halaman tersebut akan secara otomatis memiliki middleware yang sama. ### Prioritas Urutan dari Middleware yang akan dijalankan bisa diatur berdasarkan prioritas didalam GetMiddleware. ```dart final middlewares = [ GetMiddleware(priority: 2), GetMiddleware(priority: 5), GetMiddleware(priority: 4), GetMiddleware(priority: -8), ]; ``` middleware diatas akan dijalankan dengan urutan sebagai berikut **-8 => 2 => 4 => 5** ### Redirect Fungsi ini akan terpanggil ketika halaman dari route yang dipanggil sedang dicari. RouteSettings diperlukan untuk mengatur tujuan dari fungsi redirect. Atau berikan null jika tidak ingin ada redirect. ```dart RouteSettings redirect(String route) { final authService = Get.find(); return authService.authed.value ? null : RouteSettings(name: '/login') } ``` ### OnPageCalled Fungsi ini akan terpanggil ketika halaman yang dituju dipanggil sebelum apapun dibuat, anda bisa menggunakannya untuk mengubah sesuatu tentang halaman tersebut atau berikan halaman baru. ```dart GetPage onPageCalled(GetPage page) { final authService = Get.find(); return page.copyWith(title: 'Welcome ${authService.UserName}'); } ``` ### OnBindingsStart Fungsi ini akan terpanggil tepat sebelum Binding ter-inisialisasi. Disini anda bisa mengubah Binding untuk halaman yang dituju. ```dart List onBindingsStart(List bindings) { final authService = Get.find(); if (authService.isAdmin) { bindings.add(AdminBinding()); } return bindings; } ``` ### OnPageBuildStart Fungsi ini akan terpanggil tepat setelah Binding ter-inisialisasi. Disini anda bisa melakukan sesuatu sebelum halaman yang dituju dibuat. ```dart GetPageBuilder onPageBuildStart(GetPageBuilder page) { print('bindings are ready'); return page; } ``` ### OnPageBuilt Fungsi ini akan terpanggil tepat setelah fungsi `GetPage.page` terpanggil dan akan memberikan anda hasil dari fungsinya. Dan mengambil widget yang akan ditampilkan. ### OnPageDispose Fungsi ini akan terpanggil tepat setelah semua objek yang berhubungan (Controller, Views, ...) ter-dispose dari halaman. ## API Lanjutan Lainnya ```dart // memberikan argument dari halaman yang sedang ditampilkan Get.arguments // memberikan nama dari route sebelumnya Get.previousRoute // memberikan akses raw route, contoh: rawRoute.isFirst() Get.rawRoute // memberikan akses terhadap Routing API dari GetObserver Get.routing // cek apakah snackbar sedang tampil Get.isSnackbarOpen // cek apakah dialog sedang tampil Get.isDialogOpen // cek apakah bottomsheet sedang tampil Get.isBottomSheetOpen // hapus satu route Get.removeRoute() // kembali berturut-turut hingga predikat mereturn nilai true. Get.until() // pergi ke halaman selanjutnya dan hapus semua route sebelumnya hingga predikat mereturn nilai true. Get.offUntil() // pergi ke halaman selanjutnya menggunakan nama dan hapus semua route sebelumnya hingga predikat mereturn nilai true. Get.offNamedUntil() // Cek di platform apa aplikasi sedang berjalan GetPlatform.isAndroid GetPlatform.isIOS GetPlatform.isMacOS GetPlatform.isWindows GetPlatform.isLinux GetPlatform.isFuchsia // Cek tipe perangkat GetPlatform.isMobile GetPlatform.isDesktop // Semua platform didukung secara independen di web! // Anda bisa mengetahui apakah anda menjalankannya didalam browser // di Windows, iOS, OSX, Android, dsb. GetPlatform.isWeb // Sama dengan : MediaQuery.of(context).size.height, // tapi immutable. Get.height Get.width // Memberikan konteks saat ini dari sebuah Navigator Get.context // Memberikan konteks dari snackbar/dialog/bottomsheet di Gives the latar depan, dimanapun di kode anda Get.contextOverlay // Catatan: metode berikut adalah sebuah perluasan konteks. Berhubung anda // memiliki akses terhadap konteks dimanapun di UI anda, anda bisa menggunakannya dimanapun di kode UI // Jika anda memerlukan height/width yang bisa dirubah (seperti Desktop atau browser yang bisa di sesuaikan) anda akan memerlukan konteks context.width context.height // Memberikan anda kemampuan untuk mendefinisikan separuh layar, sepertiga, dan seterusnya. // Berguna untuk aplikasi responsive. // param dibagi dengan (double) optional - default: 1 // param dikurangi dengan (double) optional - default: 0 context.heightTransformer() context.widthTransformer() /// Mirip seperti MediaQuery.of(context).size context.mediaQuerySize() /// Mirip seperti MediaQuery.of(context).padding context.mediaQueryPadding() /// Mirip seperti MediaQuery.of(context).viewPadding context.mediaQueryViewPadding() /// Mirip seperti MediaQuery.of(context).viewInsets; context.mediaQueryViewInsets() /// Mirip seperti MediaQuery.of(context).orientation; context.orientation() /// Cek apakah perangkat sedang dalam mode lansekap context.isLandscape() /// Cek apakah perangkat sedang dalam mode portrait context.isPortrait() /// Mirip seperti MediaQuery.of(context).devicePixelRatio; context.devicePixelRatio() /// Mirip seperti MediaQuery.of(context).textScaleFactor; context.textScaleFactor() /// Dapatkan shortestSide dari layar context.mediaQueryShortestSide() /// True jika layar lebih besar dari 800 context.showNavbar() /// True jika shortestSide kurang dari 600p context.isPhone() /// True jika shortestSide lebih besar dari 600p context.isSmallTablet() /// True jika shortestSide lebih besar dari 720p context.isLargeTablet() /// True jika perangkat adalah sebuah Tablet context.isTablet() /// Memberikan sebuah value berdasarkan ukuran layar /// dapat memberi value untuk: /// watch: jika shortestSide lebih kecil dari 300 /// mobile: jika shortestSide lebih kecil dari 600 /// tablet: jika shortestSide lebih kecil dari 1200 /// desktop: jika lebar lebih besar dari 1200 context.responsiveValue() ``` ### Pengaturan Global Opsional dan Konfigurasi Manual GetMaterialApp mengkonfigurasi semuanya untuk anda, namun jika anda ingin mengkonfigurasi Get secara manual. ```dart MaterialApp( navigatorKey: Get.key, navigatorObservers: [GetObserver()], ); ``` Anda juga bisa menggunakan Middleware anda sendiri melalui `GetObserver`, ini tidak akan mempengaruhi apapun. ```dart MaterialApp( navigatorKey: Get.key, navigatorObservers: [ GetObserver(MiddleWare.observer) // Disini ], ); ``` Anda bisa membuat _Pengaturan Global_ untuk `Get`. Cukup tambahkan `Get.config` kedalam kode anda sebelum berpindah ke route manapun. Atau lakukan secara langsung di `GetMaterialApp` ```dart GetMaterialApp( enableLog: true, defaultTransition: Transition.fade, opaqueRoute: Get.isOpaqueRouteDefault, popGesture: Get.isPopGestureEnable, transitionDuration: Get.defaultDurationTransition, defaultGlobalState: Get.defaultGlobalState, ); Get.config( enableLog = true, defaultPopGesture = true, defaultTransition = Transitions.cupertino ) ``` Anda bisa secara opsional me-redirect seluruh pesan logging dari `Get`. Jika anda ingin menggunakan logging buatan anda sendiri, logging package favorit, dan ingin meng-capture lognya: ```dart GetMaterialApp( enableLog: true, logWriterCallback: localLogWriter, ); void localLogWriter(String text, {bool isError = false}) { // oper message ke logging package favorit anda disini // harap dicatat bahwa meskipun enableLog: false, pesan log akan di-push dalam callback ini, // anda dapat memeriksa flag-nya jika anda mau melalui GetConfig.isLogEnable } ``` ### Local State Widgets Widget ini memungkinkan anda untuk mengelola satu nilai, dan menjaga state emphemeral dan lokal. Kita memiliki rasa untuk Reactive dan Simple. Contohnya, anda mungkin menggunakannya untuk men-toggle obscureText di sebuah `TextField`, mungkin membuat Expandable Panel kustom, atau mungkin memodifikasi index saat ini dalam `BottomNavigationBar` sembari mengganti konten dari body didalam `Scaffold` #### ValueBuilder Sebuah simplifikasi dari `StatefulWidget` yang berfungsi dengan sebuah callback `.setState` yang menerima value yang telah diperbarui. ```dart ValueBuilder( initialValue: false, builder: (value, updateFn) => Switch( value: value, onChanged: updateFn, // signaturenya sama! anda bisa menggunakan ( newValue ) => updateFn( newValue ) ), // jika anda perlu memanggil sesuatu diluar builder method. onUpdate: (value) => print("Value updated: $value"), onDispose: () => print("Widget unmounted"), ), ``` #### ObxValue Mirip seperti [`ValueBuilder`](#valuebuilder), tapi ini versi Reactive nya, anda bisa menaruh Rx instance (ingat .obs?) dan akan ter-update secara otomatis... keren kan? ```dart ObxValue((data) => Switch( value: data.value, onChanged: data, // Rx memiliki sebuah _callable_ function! Anda bisa menggunakan (flag) => data.value = flag, ), false.obs, ), ``` ## Tips berguna `.obs`ervables (juga dikenal sebagai _Rx_ Types) memiliki beragam metode dan operator internal. > Sangat umum untuk _percaya_ bahwa sebuah properti dengan `.obs` **ADALAH** nilai aktual... jangan salah! > Kami menghindari Type declaration dari sebuah variabel, karena compiler Dart cukup pintar, dan kode nya > terlihat lebih bersih, tapi: ```dart var message = 'Hello world'.obs; print( 'Message "$message" has Type ${message.runtimeType}'); ``` Meskipun `message` _mengeluarkan output_ nilai String aktual, tipenya adalah **RxString**! Jadi, anda tidak bisa melakukan `message.substring( 0, 4 )`. Anda perlu mengakses `value` aslinya didalam _observable_: Cara yang paling "sering digunakan" adalah `.value`, tapi, tahukah anda bahwa anda juga bisa menggunakan... ```dart final name = 'GetX'.obs; // hanya "memperbarui" stream, jika nilainya berbeda dari sebelumnya. name.value = 'Hey'; // Seluruh properti Rx "bisa dipanggil" dan akan mereturn nilai baru. // tapi cara ini tidak menerima `null`, UI-nya tidak akan rebuild. name('Hello'); // ini seperti getter, mengeluarkan output 'Hello'. name(); /// angka: final count = 0.obs; // Anda bisa menggunakan semua operasi non-mutable dari primitif num! count + 1; // Hati hati! ini hanya valid jika `count` tidak final, melainkan var count += 1; // Anda juga bisa melakukan komparasi antar nilai: count > 2; /// boolean: final flag = false.obs; // bertukar nilai antara true/false flag.toggle(); /// semua tipe: // Atur `value` menjadi null. flag.nil(); // Semua operasi toString(), toJson() dikirimkan ke `value` print( count ); // memanggil `toString()` didalamnya untuk RxInt final abc = [0,1,2].obs; // Mengkonversi nilai dari Array json, mengeluarkan output RxList // Json didukung oleh semua tipe Rx! print('json: ${jsonEncode(abc)}, type: ${abc.runtimeType}'); // RxMap, RxList dan RxSet adalah tipe Rx spesial, mereka meng-extends native type masing-masing. // tapi anda bisa bekerja menggunakan List sebagai list biasa, meskipun reactive! abc.add(12); // memasukkan 12 kedalam list, dan MEMPERBARUI stream. abc[3]; // seperti List, membaca index ke 3. // persamaan berfungsi dengan Rx dan value nya, namun hashCode nya selalu diambil dari value final number = 12.obs; print( number == 12 ); // mengeluarkan output: true /// Model Rx Kustom: // toJson(), toString() ditangguhkan ke child, jadi anda bisa mengimplementasi override pada mereka dan print() observable nya secara langsung class User { String name, last; int age; User({this.name, this.last, this.age}); @override String toString() => '$name $last, $age years old'; } final user = User(name: 'John', last: 'Doe', age: 33).obs; // `user` memang "reaktif", tapi properti didalamnya TIDAK REAKTIF! // Jadi, jika kita mengubah variabel didalamnya... user.value.name = 'Roi'; // Widget tidak akan rebuild!, // `Rx` tidak mengetahui apapun ketika anda mengubah sesuatu didalam user. // Jadi, untuk kelas kustom, kita perlu secara manual "memberi tahu" perubahannya. user.refresh(); // atau kita bisa menggunakan `update()` method! user.update((value){ value.name='Roi'; }); print( user ); ``` #### GetView Saya menyukai Widget ini, sangat simpel dan berguna! Adalah sebuah `const Stateless` Widget yang memiliki getter `controller` untuk `Controller` yang terdaftar, itu saja. ```dart class AwesomeController extends GetxController { final String title = 'My Awesome View'; } // SELALU ingat untuk memberikan `Type` yang anda gunakan untuk mendaftarkan controller anda! class AwesomeView extends GetView { @override Widget build(BuildContext context) { return Container( padding: EdgeInsets.all(20), child: Text(controller.title), // cukup panggil `controller.something` ); } } ``` #### GetResponsiveView Extend widget ini untuk membuat responsive view. widget ini mengandung properti `screen` yang memiliki semua informasi tentang ukuran layar dan tipenya. ##### Cara pakai Anda memiliki dua opsi untuk mem-buildnya. - dengan `builder` method yang anda return ke widget yang akan di-build. - dengan metode `desktop`, `tablet`,`phone`, `watch`. method spesifik akan dibuat ketika tipe layar cocok dengan method. ketika layarnya adalah [ScreenType.Tablet] maka method `tablet` akan di eksekusi dan seterusnya. **Catatan:** Jika anda menggunakan metode ini, mohon atur properti `alwaysUseBuilder` ke `false` Dengan properti `settings` anda bisa mengatur batasan lebar untuk tipe layar. ![example](https://github.com/SchabanBo/get_page_example/blob/master/docs/Example.gif?raw=true) Code to this screen [code](https://github.com/SchabanBo/get_page_example/blob/master/lib/pages/responsive_example/responsive_view.dart) #### GetWidget Kebanyakan orang tidak tahu untuk apa Widget ini, atau benar benar membingungkan penggunaannya. Kasus penggunaannya sangat langka, namun sangat spesifik: Melakukan `cache` terhadap Controller. Karena _cache_, tidak bisa dijadikan `const Stateless`. > Lalu, kapan anda harus men-"cache" sebuah Controller? Jika anda menggunakan, fitur "tidak terlalu umum" dari **GetX**: `Get.create()`. `Get.create(()=>Controller())` akan men-generate `Controller` baru setiap kali anda memanggil `Get.find()`, Itulah dimana `GetWidget` bercahaya... karena anda bisa menggunakannya, sebagai contoh, untuk menyimpan list dari sebuah Todo item. Jadi, jika widget ter-"rebuild", dia akan meyimpan controller yang sama. #### GetxService Kelas ini mirip seperti `GetxController`, dia berbagi lifecycle ( `onInit()`, `onReady()`, `onClose()`). Tetapi tidak memiliki "logic" didalamnya. Dia hanya memberi tahu Sistem Dependency Injection **GetX**, bahwa subclass ini **TIDAK BISA** dihapus dari memori. Jadi ini sangat berguna untuk memastikan "Service" anda selalu dapat dijangkau dan aktif dengan `Get.find()`. Seperti: `ApiService`, `StorageService`, `CacheService`. ```dart Future main() async { await initServices(); /// AWAIT SERVICES INITIALIZATION. runApp(SomeApp()); } /// Adalah gerakan yang cerdas untuk membuat Service anda menginisialisasi sebelum anda menjalankan aplikasi Flutter /// seperti anda bisa mengontrol flow eksekusi (mungkin anda perlu memuat beberapa konfigurasi tema, /// apiKey, bahasa yang ditentukan oleh user...) jadi, load SettingSerice sebelum menjalankan ApiService. /// supaya GetMaterialApp() tidak perlu rebuild, dan mengambil nilainya secara langsung. void initServices() async { print('starting services ...'); /// Disini adalah dimana anda meletakkan get_storage, hive, inisialisasi shared_pref. /// atau koneksi moor, atau apapun yang sifatnya async. await Get.putAsync(() => DbService().init()); await Get.putAsync(SettingsService()).init(); print('All services started...'); } class DbService extends GetxService { Future init() async { print('$runtimeType delays 2 sec'); await 2.delay(); print('$runtimeType ready!'); return this; } } class SettingsService extends GetxService { void init() async { print('$runtimeType delays 1 sec'); await 1.delay(); print('$runtimeType ready!'); } } ``` The only way to actually delete a `GetxService`, is with `Get.reset()` which is like a "Hot Reboot" of your app. So remember, if you need absolute persistence of a class instance during the lifetime of your app, use `GetxService`. Satu-satunya cara untuk benar benar menghapus sebuah `GetxService`, adalah dengan `Get.reset()` dimana ini seperti "Hot Reboot" dari aplikasi anda. Jadi ingat, jika anda butuh persistensi absolut dari sebuah instance kelas selama masa hidup aplikasi anda, gunakan `GetxService`. # Breaking change dari 2.0 1- Tipe Rx: | Sebelum | Sesudah | | ------- | ---------- | | StringX | `RxString` | | IntX | `RxInt` | | MapX | `RxMap` | | ListX | `RxList` | | NumX | `RxNum` | | DoubleX | `RxDouble` | RxController dan GetBuilder sekarang digabungkan, anda tidak lagi perlu mengingat kontroler mana yang ingin anda gunakan, cukup gunakan GetxController, ini akan bekerja untuk simple state management dan reactive juga. 2- NamedRoutes Sebelumnya: ```dart GetMaterialApp( namedRoutes: { '/': GetRoute(page: Home()), } ) ``` Sekarang: ```dart GetMaterialApp( getPages: [ GetPage(name: '/', page: () => Home()), ] ) ``` Mengapa berubah? Seringkali, mungkin diperlukan untuk memutuskan halaman mana yang akan ditampilkan melalui sebuah parameter, atau login token, cara sebelumnya sangat tidak fleksibel dan tidak memungkinkan untuk melakukan hal ini. Memasukkan data kedalam fungsi mengurangi konsumsi RAM secara signifikan, mengingat route tidak akan di alokasikan ke memori sejak aplikasi dimulai, dan ini memungkinkan kita melakukan ini: ```dart GetStorage box = GetStorage(); GetMaterialApp( getPages: [ GetPage(name: '/', page:(){ return box.hasData('token') ? Home() : Login(); }) ] ) ``` # Mengapa Getx? 1- Seringkali setelah Flutter update, banyak package anda yang akan berhenti bekerja. Terkadang compilation error terjadi, error yang sering muncul dan belum ada jawabannya, dan developer perlu mengetahui dimana errornya berasal, mencari errornya, lalu kemudian mencoba membuka sebuah isu di repository yang bersangkutan, dan melihat apakah problemnya terselesaikan. Get memusatkan resource utama untuk development (State, dependency dan route management), memungkinkan anda untuk menambahkan satu package kedalam pubspec, dan mulai bekerja. Setelah Flutter update, satu-satunya hal yang anda perlu lakukan adalah memperbarui dependensi Get, dan kembali bekerja. Get juga menyelesaikan isu kompatibilitas. Berapa kali sebuah versi dari sebuah package tidak kompatibel dengan versi lainnya, karena yang satu menggunakan sebuah dependensi dalam satu versi, dan yang lain menggunakan versi lainnya? Ini juga bukan sebuah masalah menggunakan Get, yang mana semua berada di package yang sama dan kompatibel secara penuh. 2- Flutter mudah digunakan, Flutter luar biasa, tetapi Flutter masih memiliki beberapa boilerplate yang mungkin tidak diinginkan untuk kebanyakan developer, seperti `Navigator.of(context).push(context builder [...]`. Get menyederhanakan proses development. Daripada menulis 8 baris kode hanya untuk memanggil route, anda bisa menggunakan: `Get.to(Home())` dan selesai, anda akan pergi ke halaman selanjutnya. URL web dinamis adalah hal yang sangat menyakitkan untuk dilakukan dengan Flutter saat ini, dan dengan GetX sangat sederhana. Mengelola state di Flutter, dan megelola dependensi juga suatu hal yang menghasilkan banyak diskusi, dengan ratusan jenis pattern di pub. Tetapi tidak ada yang semudah menambahkan ".obs" di akhir variabel anda, dan meletakkan widget didalam Obx, dan selesai, semua update terhadap variabel tersebut akan secara otomatis terupdate di layar. 3- Meringankan tanpa mengkhawatirkan performa. Performa Flutter sudah luar biasa, tetapi bayangkan anda menggunakan state manager, dan sebuah locator untuk mendistribusikan bloc/store/controller dsb, kelas. Anda perlu secara manual memanggil pengecualian terhadap dependensi ketika anda tidak membutuhkannya. Namun apakah anda pernah terfikirkan ketika simpelnya, anda menggunakan controller, dan tidak lagi digunakan oleh siapapun, akan dihapus dari memori? Itu yang GetX lakukan. Dengan SmartManagement, semua yang tidak digunakan akan dihapus dari memori, dan anda tidak perlu khawatir tentang apapun selain programming. Anda akan terjamin bahwa anda mengkonsumsi resource minimum yang diperlukan, bahkan tanpa harus membuat logic untuk hal ini. 4- Actual decoupling. Anda mungkin pernah mendengar konsep "pisahkan view dari business logic". Ini bukanlah sebuah keanehan dari BLoC, MVC, MVVM, dan standard lainnya dalam market yang memiliki konsep ini. Namun, konsep ini terkadang termitigasi di Flutter karena penggunaan konteks. Jika anda memerlukan konteks untuk menemukan InheritedWidget, anda membutuhkannya di view, atau mengirim konteks melalui parameter. Saya menemukan bahwa solusi ini sangat jelek, dan untuk bekerja dalam tim kami harus selalu memiliki sebuah ketergantungan pada business logic di dalam view. GetX adalah cara yang tidak lazim dengan metode standard, dan sementara itu tidak benar-benar secara penuh melarang penggunaan StatefulWidgets, InitState, dsb., ini selalu memiliki metode yang mirip dan bisa lebih bersih. Controller memiliki life cycle, dan ketika anda perlu membuat APIREST request sebagai contoh, anda tidak bergantung pada apapun didalam view. Anda bisa menggunakan onInit untuk menginisiasi pemanggilan http dan ketika datanya sampai, variabel akan dipopulasikan. GetX juga secara penuh reaktif (serius, dan bekerja dibawah stream), sekali items terisi, semua widget yang menggunakan variabel itu akan secara otomatis diperbarui didalam view. Ini memungkinkan orang orang dengan keahlian di bagian UI untuk bekerja hanya dengan widget, dan tidak perlu mengirim apapun ke business logic selain user event (seperti meng-klik sebuah tombol), sementara orang yang bekerja dengan business logic akan bebas membuat dan melakukan test terhadap business logic secara terpisah. Library ini akan terus diperbarui dan mengimplementasikan fitur baru. Jangan ragu untuk menawarkan PR dan berkontribusi ke mereka. # Komunitas ## Channel Komunitas GetX memiliki komunitas yang sangat aktif dan membantu. Jika anda memiliki pertanyaan, atau membutuhkan bantuan mengenai penggunaan framework ini, bergabunglah dengan kanal komunitas kami, pertanyaan anda akan dijawab lebih cepat, dan akan menjadi tempat yang paling cocok. Repositori ini eksklusif untuk pembukaan isu dan permintaan resource, tapi jangan ragu untuk menjadi bagian dari Komunitas GetX. | **Slack** | **Discord** | **Telegram** | | :-------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------- | | [![Get on Slack](https://img.shields.io/badge/slack-join-orange.svg)](https://communityinviter.com/apps/getxworkspace/getx) | [![Discord Shield](https://img.shields.io/discord/722900883784073290.svg?logo=discord)](https://discord.com/invite/9Hpt99N) | [![Telegram](https://img.shields.io/badge/chat-on%20Telegram-blue.svg)](https://t.me/joinchat/PhdbJRmsZNpAqSLJL6bH7g) | ## Cara berkontribusi _Ingin berkontribusi kedalam proyek? Kami akan sangat bangga untuk menyorot anda sebagai salah satu dari kolaborator kami. Ini adalah beberapa point dimana anda bisa berkontribusi dan membuat Get (dan Flutter) jadi lebih baik_ - Membantu menerjemahkan readme ke dalam bahasa lain. - Menambahkan dokumentasi ke dalam readme (banyak fungsi dari Get yang masih belum terdokumentasi). - Menulis artikel atau membuat video mengajarkan tentang penggunaan Get (akan dimasukkan kedalam readme dan Wiki kami di masa yang akan datang). - Menawarkan PR untuk kode/test. - Menambahkan fungsi baru. Kontribusi dalam bentuk apapun dipersilahkan! ## Artikel dan Video - [Dynamic Themes in 3 lines using GetX™](https://medium.com/swlh/flutter-dynamic-themes-in-3-lines-c3b375f292e3) - Tutorial oleh [Rod Brown](https://github.com/RodBr). - [Complete GetX™ Navigation](https://www.youtube.com/watch?v=RaqPIoJSTtI) - Route management video oleh Amateur Coder. - [Complete GetX State Management](https://www.youtube.com/watch?v=CNpXbeI_slw) - State management video oleh Amateur Coder. - [GetX™ Other Features](https://youtu.be/ttQtlX_Q0eU) - Utils, storage, bindings and other features video oleh Amateur Coder. - [Firestore User with GetX | Todo App](https://www.youtube.com/watch?v=BiV0DcXgk58) - Video oleh Amateur Coder. - [Firebase Auth with GetX | Todo App](https://www.youtube.com/watch?v=-H-T_BSgfOE) - Video oleh Amateur Coder. - [The Flutter GetX™ Ecosystem ~ State Management](https://medium.com/flutter-community/the-flutter-getx-ecosystem-state-management-881c7235511d) - State management oleh [Aachman Garg](https://github.com/imaachman). - [The Flutter GetX™ Ecosystem ~ Dependency Injection](https://medium.com/flutter-community/the-flutter-getx-ecosystem-dependency-injection-8e763d0ec6b9) - Dependency Injection oleh [Aachman Garg](https://github.com/imaachman). - [GetX, the all-in-one Flutter package](https://www.youtube.com/watch?v=IYQgtu9TM74) - A brief tutorial covering State Management and Navigation oleh Thad Carnevalli. - [Build a To-do List App from scratch using Flutter and GetX](https://www.youtube.com/watch?v=EcnqFasHf18) - UI + State Management + Storage video oleh Thad Carnevalli. - [GetX Flutter Firebase Auth Example](https://medium.com/@jeffmcmorris/getx-flutter-firebase-auth-example-b383c1dd1de2) - Article oleh Jeff McMorris. - [Flutter State Management with GetX – Complete App](https://www.appwithflutter.com/flutter-state-management-with-getx/) - oleh App With Flutter. - [Flutter Routing with Animation using Get Package](https://www.appwithflutter.com/flutter-routing-using-get-package/) - oleh App With Flutter. ================================================ FILE: README.ja-JP.md ================================================ ![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/get.png) [![pub package](https://img.shields.io/pub/v/get.svg?label=get&color=blue)](https://pub.dev/packages/get) [![popularity](https://badges.bar/get/popularity)](https://pub.dev/packages/sentry/score) [![likes](https://badges.bar/get/likes)](https://pub.dev/packages/get/score) [![pub points](https://badges.bar/get/pub%20points)](https://pub.dev/packages/get/score) ![building](https://github.com/jonataslaw/get/workflows/build/badge.svg) [![style: effective dart](https://img.shields.io/badge/style-effective_dart-40c4ff.svg)](https://pub.dev/packages/effective_dart) [![Discord Shield](https://img.shields.io/discord/722900883784073290.svg?logo=discord)](https://discord.com/invite/9Hpt99N) [![Get on Slack](https://img.shields.io/badge/slack-join-orange.svg)](https://communityinviter.com/apps/getxworkspace/getx) [![Telegram](https://img.shields.io/badge/chat-on%20Telegram-blue.svg)](https://t.me/joinchat/PhdbJRmsZNpAqSLJL6bH7g) Awesome Flutter Buy Me A Coffee ![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/getx.png)
**言語** [![英語](https://img.shields.io/badge/Language-English-blueviolet?style=for-the-badge)](README.md) [![ベトナム語](https://img.shields.io/badge/Language-Vietnamese-blueviolet?style=for-the-badge)](README-vi.md) [![インドネシア語](https://img.shields.io/badge/Language-Indonesian-blueviolet?style=for-the-badge)](README.id-ID.md) [![ウルドゥー語](https://img.shields.io/badge/Language-Urdu-blueviolet?style=for-the-badge)](README.ur-PK.md) [![中国語](https://img.shields.io/badge/Language-Chinese-blueviolet?style=for-the-badge)](README.zh-cn.md) [![ポルトガル語](https://img.shields.io/badge/Language-Portuguese-blueviolet?style=for-the-badge)](README.pt-br.md) [![スペイン語](https://img.shields.io/badge/Language-Spanish-blueviolet?style=for-the-badge)](README-es.md) [![ロシア語](https://img.shields.io/badge/Language-Russian-blueviolet?style=for-the-badge)](README.ru.md) [![ポーランド語](https://img.shields.io/badge/Language-Polish-blueviolet?style=for-the-badge)](README.pl.md) [![韓国語](https://img.shields.io/badge/Language-Korean-blueviolet?style=for-the-badge)](README.ko-kr.md) [![フランス語](https://img.shields.io/badge/Language-French-blueviolet?style=for-the-badge)](README-fr.md) [![日本語](https://img.shields.io/badge/Language-Japanese-blueviolet?style=for-the-badge)](README-ja.md)
- [Getとは](#Getとは) - [インストール方法](#インストール方法) - [GetXによるカウンターアプリ](#GetXによるカウンターアプリ) - [三本柱](#三本柱) - [状態管理](#状態管理) - [リアクティブな状態管理](#リアクティブな状態管理) - [状態管理に関する詳細ドキュメント](#状態管理に関する詳細ドキュメント) - [Route管理](#Route管理) - [Route管理に関する詳細ドキュメント](#Route管理に関する詳細ドキュメント) - [依存オブジェクト管理](#依存オブジェクト管理) - [依存オブジェクト管理に関する詳細ドキュメント](#依存オブジェクト管理に関する詳細ドキュメント) - [ユーティリティ](#ユーティリティ) - [多言語対応](#多言語対応) - [翻訳ファイル](#翻訳ファイル) - [翻訳ファイルの利用](#翻訳ファイルの利用) - [ロケール](#ロケール) - [ロケールの変更](#ロケールの変更) - [システムのロケールを読み込む](#システムのロケールを読み込む) - [Themeの変更](#Themeの変更) - [GetConnect](#getconnect) - [デフォルト設定](#デフォルト設定) - [カスタム設定](#カスタム設定) - [GetPageにミドルウェアを設定](#GetPageにミドルウェアを設定) - [実行優先度](#実行優先度) - [redirect](#redirect) - [onPageCalled](#onpagecalled) - [onBindingsStart](#onbindingsstart) - [onPageBuildStart](#onpagebuildstart) - [onPageBuilt](#onpagebuilt) - [onPageDispose](#onpagedispose) - [その他API](#その他API) - [オプションのグローバル設定と手動設定](#オプションのグローバル設定と手動設定) - [ローカルステートWidget](#ローカルステートWidget) - [ValueBuilder](#valuebuilder) - [ObxValue](#obxvalue) - [お役立ちTIPS](#お役立ちTIPS) - [StateMixin](#statemixin) - [GetView](#getview) - [GetResponsiveView](#getresponsiveview) - [使い方](#使い方]) - [GetWidget](#getwidget) - [GetxService](#getxservice) - [テストの実行](#テストの実行) - [mockitoやmocktailを使う場合](#mockitoやmocktailを使う場合) - [Get.reset()](#Get.reset()) - [Get.testMode](#Get.testMode) - [バージョン2.0からの破壊的変更](#バージョン2.0からの破壊的変更) - [なぜGetXなのか](#なぜGetXなのか) - [コミュニティ](#コミュニティ) - [コミュニティチャンネル](#コミュニティチャンネル) - [コントリビュート方法](#コントリビュート方法) - [GetXに関する記事と動画](#GetXに関する記事と動画) # Getとは - GetXはFlutterのための超軽量でパワフルなソリューションです。高パフォーマンスな状態管理機能、インテリジェントな依存オブジェクト管理機能、そしてRoute管理機能の三本柱を軽量かつ実用的な形で組み合わせています。 - GetXは3つの基本原則を念頭に開発されています。 **【生産性、パフォーマンス、コードの分離性】** これらはライブラリ内のすべてのリソースに優先適用されている原則です。 - **パフォーマンス:** GetXは高いパフォーマンスと最小限のリソース消費を目標にしています。GetXはでは Stream および ChangeNotifier を利用しなくて済みます。 - **生産性:** GetXはシンプルで使い心地のいいシンタックスを採用しています。あなたの実現したい機能がどんなものであれ、GetXを使えばより簡単に実現できる方法が見つかるでしょう。開発にかかる時間を短縮し、あなたのアプリケーションのパフォーマンスを最大限引き出してくれます。 開発者はメモリリソースの管理に気を配るのが常です。しかしGetXでは、リソースが使用されていないときはメモリから削除されるのがデフォルト動作のため、過度に気にかける必要はありません。(逆にメモリに残しておきたい場合は、依存オブジェクトをインスタンス化するメソッドを使う際に「permanent: true」と宣言してください)これにより時間が節約できますし、不要な依存オブジェクトがメモリ上に残るリスクも少なくなります。メモリへの読み込みについてもデフォルトは遅延読み込みであり、使用するときに初めてメモリ上に読み込まれます。 - **コードの分離性:** GetXを使うと、ビュー、プレゼンテーションロジック、ビジネスロジック、依存オブジェクトの注入、およびナビゲーション周りのコードを書き分けやすくなります。Routeのナビゲーションにはcontextを必要としないため、Widgetツリーに依存することはありません。ロジックについてもInheritedWidget経由でController/BLoCにアクセスする際のcontextは必要ありません。プレゼンテーションロジックとビジネスロジックをUIクラスから完全に切り離すことができます。また、Controller/モデル/BLoCのクラスを、`MultiProvider`を使ってWidgetツリーに注入する必要もありません。GetXでは独自の依存オブジェクト注入機能を使用し、ビュークラスからビューとは無関係なコードをなくすことができるのです。 GetXを使うことでアプリケーションの各機能がどこにあるのかがわかりやすくなり、自然と見やすいコードになります。メンテナンスが容易になるだけでなく、それまでのFlutterでは考えられなかったモジュール共有が簡単に実現できるようになりました。 BLoCはこの分野におけるFlutterの出発点と言えるものでしたが、GetXはこれを正統進化させており、ビジネスロジックのみならずプレゼンテーションロジックも分離することができます。そのほかデータレイヤーはもちろん、依存オブジェクトやRouteの注入に関するコードも。どこに何が配置されているのか全体の見通しがしやすくなり、Hello Worldを表示させるかのように簡単にアプリの機能を利用できるようになるでしょう。 Flutterアプリを作るならGetXは最も簡単で実用的、かつスケーラブルなソリューションです。強力なエコシステムも存在があるため、初心者にはわかりやすさ、プロには正確性を提供することができます。そしてFlutter SDKにはない幅広い種類のAPIを提供し、セキュアで安定的な環境を構築します。 - GetXは肥大化したライブラリではありません。何も気にせずすぐに開発を始められるよう多数の機能を標準で備えていますが、それぞれの機能は個別にコンテナに入っており、使用してはじめて起動します。状態管理機能しか利用していない場合はその機能だけがコンパイルされます。Route管理機能だけを利用していれば、状態管理機能がコンパイルされることはありません。 - GetXには巨大なエコシステム、コミュニティ、コラボレーターの存在があるため、Flutterが存在する限りメンテナンスされ続けます。またGetXもFlutterと同様にAndroid、iOS、Web、Mac、Linux、Windows、そしてあなたのサーバー上で、単一のコードから実行することができます。 **[Get Server](https://github.com/jonataslaw/get_server)を使うことで、フロントエンドで作成したコードをバックエンドで再利用することが可能です。** **さらに、[Get CLI](https://github.com/jonataslaw/get_cli)を使えば、サーバー側でもフロントエンド側でも開発プロセス全体を自動化することができます。** **また、生産性をさらに高めるためのツールとして、[VSCode用の拡張機能](https://marketplace.visualstudio.com/items?itemName=get-snippets.get-snippets) と [Android Studio/Intellij用の拡張機能](https://plugins.jetbrains.com/plugin/14975-getx-snippets)があります。** # インストール方法 Getパッケージを pubspec.yaml に追加します: ```yaml dependencies: get: ``` 使用するときはこのようにインポートしてください: ```dart import 'package:get/get.dart'; ``` # GetXによるカウンターアプリ Flutterで新規プロジェクトを作成する際に表示されるカウンターアプリは、コメントを含めると100行以上あります。Getの実力を示すため、このカウンターアプリを可読性を重視した形で、コメントを含めてわずか26行のコードで作成する方法を紹介します。 - ステップ1: MaterialAppの前に「Get」を足して、GetMaterialAppにします。 ```dart void main() => runApp(GetMaterialApp(home: Home())); ``` - 注1: GetMaterialAppはFlutterのMaterialAppに手を加えたものではありません。MaterialAppをchildに持ち、諸々の追加設定をしてくれるWidgetに過ぎません。この設定は手動でも可能ですが、その必要はありません。GetMaterialAppは、Routeの作成・注入、言語翻訳の注入など、ナビゲーションに必要なものをすべて注入してくれます。Getを状態管理や依存オブジェクト管理に限定して使用する場合は、GetMaterialAppを使用する必要はありません。GetMaterialAppは、Route、SnackBar、多言語対応、BottomSheet、Dialog、contextなしの高レベルAPIを利用する場合に必要です。 - 注2: このステップは、Route管理機能(`Get.to()`や`Get.back()`など)を使用しない場合は、必要ありません。 - ステップ2: ビジネスロジッククラスを作成し、そこに必要な変数、メソッド、コントローラをすべて配置します。 変数に ".obs" を付け足すことで、その変数の値の変化を監視することが可能になります。 ```dart class Controller extends GetxController{ var count = 0.obs; increment() => count++; } ``` - ステップ3: ビューを作成します。StatelessWidgetを使用することでRAMが節約できます。GetではStatefulWidgetを使用する必要がなくなるかもしれません。 ```dart class Home extends StatelessWidget { @override Widget build(context) { // Get.put()を使ってクラスをインスタンス化することですべての子Routeで利用できるようになります。 final Controller c = Get.put(Controller()); return Scaffold( // countが変わるたびにTextを更新するにはObx(()=>)を使ってください。 appBar: AppBar(title: Obx(() => Text("Clicks: ${c.count}"))), // 8行使っていたNavigator.pushの代わりに短い Get.to()を使ってください。context不要です。 body: Center(child: ElevatedButton( child: Text("Go to Other"), onPressed: () => Get.to(Other()))), floatingActionButton: FloatingActionButton(child: Icon(Icons.add), onPressed: c.increment)); } } class Other extends StatelessWidget { // 他のページで使われているコントローラーを見つけてきてくれます。 final Controller c = Get.find(); @override Widget build(context){ // 最新のcount変数の値にアクセス return Scaffold(body: Center(child: Text("${c.count}"))); } } ``` Result: ![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/counter-app-gif.gif) これはシンプルな例ですが、すでにGetがいかに強力であるかがわかると思います。プロジェクトが大きければ大きいほど、この差はもっと開くでしょう。 Getはチームでの作業を想定して設計されていますが、個人開発者の仕事もシンプルにしてくれます。 パフォーマンスを落とさず納期までにすべて納品。Getはすべての人に向いているわけではありませんが、このフレーズにぴんと来た人には確実に向いています! # 三本柱 ## 状態管理 Getの状態管理には、非リアクティブ(GetBuilder)と、リアクティブ(GetX/Obx)の2つのアプローチがあります。 ### リアクティブな状態管理 リアクティブプログラミングは複雑であると言われ、多くの人に敬遠されています。GetXは、リアクティブプログラミングをシンプルなものに変えます: * StreamControllerを作る必要はありません。 * 変数ごとにStreamBuilderをセットする必要はありません。 * 状態ごとにクラスを作る必要はありません。 * 初期値のためにgetを準備する必要はありません。 - コードの自動生成をする必要がありません。 GetにおけるリアクティブプログラミングはsetStateと同じように簡単です。 例えば、名前の変数があって、それを変更するたびに、その名前を使っているすべてのWidgetを自動で更新したい場合。 ```dart var name = 'Jonatas Borges'; ``` このnameをObservable(監視可能)にするには, ".obs"を値の末尾に付けるだけです。 ```dart var name = 'Jonatas Borges'.obs; ``` UIでその値を表示し、値が変わるたびに内容を更新したい場合は次のようにします。 ```dart Obx(() => Text("${controller.name}")); ``` 以上です。こんなに簡単なんですよ。 ### 状態管理に関する詳細ドキュメント **状態管理に関するより詳細な説明を知りたい方は[こちらの日本語ドキュメント](./documentation/ja_JP/state_management.md)をご覧ください。多くの事例や、非リアクティブな状態管理とリアクティブな状態管理の違いについても説明されています。** GetXパワーがもたらす利点をより理解していただけると思います。 ## Route管理 GetXはcontextなしでRoute/SnackBar/Dialog/BottomSheetを使用することができます。具体的に見ていきましょう。 いつものMaterialAppの前に「Get」を付け足して、GetMaterialAppにしましょう。 ```dart GetMaterialApp( // MaterialApp の前に Get home: MyHome(), ) ``` 新しいRouteに画面遷移するにはこのシンタックス。 ```dart Get.to(NextScreen()); ``` 名前付きRouteに画面遷移するにはこのシンタックス。名前付きRouteの詳細は[こちらの日本語ドキュメント](./documentation/ja_JP/route_management.md#navigation-with-named-routes) ```dart Get.toNamed('/details'); ``` SnackBar、Dialog、BottomSheetなど、Navigator.pop(context)で閉じられるRouteはこれで閉じます。 ```dart Get.back(); ``` 次の画面に移動した後、前の画面に戻れないようにする場合(スプラッシュスクリーンやログイン画面など)はこちら。 ```dart Get.off(NextScreen()); ``` 次の画面に進み、前のRouteをすべてキャンセルする場合(ショッピングカート、アンケート、テストなど)はこちら。 ```dart Get.offAll(NextScreen()); ``` 以上、contextを一度も使わなかったことに気付きましたか?これがGetでRoute管理を行う最大のメリットのひとつです。contextを使わないので、たとえばcontrollerクラスの中でも、これらのメソッドを実行することができます。 ### Route管理に関する詳細ドキュメント **Getは名前付きRouteでも動作し、Routeの下位レベルの制御も可能です。詳細なドキュメントは[こちらの日本語ドキュメント](./documentation/ja_JP/route_management.md)にあります。** ## 依存オブジェクト管理 Getにはシンプルで強力な依存オブジェクト注入機能があります。わずか1行のコードで、Provider contextやinheritedWidgetも使わず、BLoCやControllerのようなクラスのインスタンスを取得することができます。 ```dart Controller controller = Get.put(Controller()); // controller = Controller() とする代わりに ``` - 注: Getの状態管理機能を使用している場合は、Bindings APIにもご注目を。BindingsはビューとControllerを結びつけるのをより便利にしてくれます。 一つのクラスの中でControllerクラスをインスタンス化するのではなく、Getインスタンスの中でインスタンス化することで、アプリ全体でControllerが利用できるようになります。 **ヒント:** Getの依存オブジェクト注入機能の部分は、パッケージ全体の中でも他の部分と切り離されているので、たとえば、あなたのアプリがすでに状態管理機能を一部で使用していたとしても、それらを書き直す必要はなく、この依存オブジェクト注入機能をそのまま使用することができます。 ```dart controller.fetchApi(); ``` 色々なRouteを行き来した後に、あるControllerクラスのデータにアクセスする必要が生じたとしましょう。ProviderやGet_itなら再びそのクラスに依存オブジェクトを注入する必要がありますよね?Getの場合は違います。Getでは「find」と依頼するだけで、追加の依存オブジェクトの注入は必要ありません。 ```dart Controller controller = Get.find(); //マジックみたいですね。Getは正しいcontrollerをきちんと探してきてくれますよ。100万のcontrollerのインスタンスがあっても、Getは必ず正しいcontrollerを探し当てます。 ``` そして、findで取得したコントローラーのデータをこのように呼び出すことができます。 ```dart Text(controller.textFromApi); ``` ### 依存オブジェクト管理に関する詳細ドキュメント **依存オブジェクト管理に関するより詳細な説明は[こちらの日本語ドキュメント](./documentation/ja_JP/dependency_management.md)をご覧ください。** # ユーティリティ ## 多言語対応 ### 翻訳ファイル 翻訳ファイルはシンプルなキーと値のMapとして保持されます。 翻訳を追加するには、クラスを作成して `Translations` を継承します。 ```dart import 'package:get/get.dart'; class Messages extends Translations { @override Map> get keys => { 'en_US': { 'hello': 'Hello World', }, 'de_DE': { 'hello': 'Hallo Welt', } }; } ``` #### 翻訳ファイルの利用 指定されたキーに `.tr` (translateのtr)を追加するだけで、`Get.locale` と `Get.fallbackLocale` の現在の値をに沿って適切な言語に翻訳されます。 ```dart Text('title'.tr); ``` #### 単数系と複数形に対応 ```dart var products = []; Text('singularKey'.trPlural('pluralKey', products.length, Args)); ``` #### パラメーターに対応 ```dart import 'package:get/get.dart'; Map> get keys => { 'en_US': { 'logged_in': 'logged in as @name with email @email', }, 'es_ES': { 'logged_in': 'iniciado sesión como @name con e-mail @email', } }; Text('logged_in'.trParams({ 'name': 'Jhon', 'email': 'jhon@example.com' })); ``` ### ロケール ロケールと翻訳を定義するため、`GetMaterialApp`にパラメータを渡します。 ```dart return GetMaterialApp( translations: Messages(), // Translationsを継承したクラスのインスタンス locale: Locale('en', 'US'), // このロケール設定に沿って翻訳が表示される fallbackLocale: Locale('en', 'UK'), // 無効なロケールだったときのフォールバックを指定 ); ``` #### ロケールの変更 ロケールを変更するには、`Get.updateLocale(locale)`を呼び出します。翻訳は新しいロケールに沿ってなされます。 ```dart var locale = Locale('en', 'US'); Get.updateLocale(locale); ``` #### システムのロケールを読み込む システムのロケールを読み込むには、`Get.deviceLocale`を使用します。 ```dart return GetMaterialApp( locale: Get.deviceLocale, ); ``` ## Themeの変更 `GetMaterialApp`より上位のWidgetを使ってThemeを変更しないでください。Keyの重複を引き起こす可能性があります。アプリのThemeを変更するためには「ThemeProvider」Widgetを作成するという前時代的なアプローチが採られることが多いですが、**GetX™**ではこのようなことは必要ありません。 カスタムのThemeDataを作成したら、それを`Get.changeTheme`内に追加するだけです。 ```dart Get.changeTheme(ThemeData.light()); ``` もし、`onTap`でThemeを変更するボタンを作りたいのであれば、以下の2つの**GetX™** APIを組み合わせることができます。 - Dark Theme が使われているかどうかをチェックするAPI - Theme を変えるAPI(ボタンの`onPressed`の中に設置できます) ```dart Get.changeTheme(Get.isDarkMode? ThemeData.light(): ThemeData.dark()); ``` Darkモードが有効であれば、_light theme_に切り替わり、Lightモードが有効なら、_dark theme_に切り替わります。 ## GetConnect GetConnect は、http または websocket を使用してバックエンドとフロントエンド間の通信を行う機能です。 ### デフォルト設定 GetConnectを拡張することで、GET/POST/PUT/DELETE/SOCKETメソッドを使用して、Rest APIやウェブソケットと通信することができます。 ```dart class UserProvider extends GetConnect { // Get リクエスト Future getUser(int id) => get('http://youapi/users/$id'); // Post リクエスト Future postUser(Map data) => post('http://youapi/users', body: data); // File付き Post リクエスト Future> postCases(List image) { final form = FormData({ 'file': MultipartFile(image, filename: 'avatar.png'), 'otherFile': MultipartFile(image, filename: 'cover.png'), }); return post('http://youapi/users/upload', form); } GetSocket userMessages() { return socket('https://yourapi/users/socket'); } } ``` ### カスタム設定 GetConnect は高度なカスタマイズが可能です。ベースUrlの定義はもちろん、リクエストヘッダーを足したり、レスポンスボディに変更を加えたり、認証情報を追加したり、認証回数の制限を設けたりすることができるほか、リクエストをModelに変換するデコーダを定義することもできます。 ```dart class HomeProvider extends GetConnect { @override void onInit() { // デフォルトデコーダーをセット httpClient.defaultDecoder = CasesModel.fromJson; httpClient.baseUrl = 'https://api.covid19api.com'; // baseUrlをセット // リクエストヘッダーに 'apikey' プロパティを付け足しています。 httpClient.addRequestModifier((request) { request.headers['apikey'] = '12345678'; return request; }); // サーバーが"Brazil"を含むデータを送ってきてもユーザーに表示されることはありません。 // レスポンスがUIレイヤーに届けられる前にデータが取り除かれているからです。 httpClient.addResponseModifier((request, response) { CasesModel model = response.body; if (model.countries.contains('Brazil')) { model.countries.remove('Brazilll'); } }); httpClient.addAuthenticator((request) async { final response = await get("http://yourapi/token"); final token = response.body['token']; // ヘッダーをセット request.headers['Authorization'] = "$token"; return request; }); // HttpStatus が HttpStatus.unauthorized である限り、 // 3回まで認証が試みられます。 httpClient.maxAuthRetries = 3; } } @override Future> getCases(String path) => get(path); } ``` ## GetPageにミドルウェアを設定 GetPageに新しいプロパティが追加され、GetMiddleWareのListを設定することができるようになりました。GetMiddleWareは設定した任意の順序で実行されます。 **注**: GetPageにミドルウェアを設定すると、そのページの子ページはすべて同じミドルウェアを自動的に持つことになります。 ### 実行優先度 GetMiddlewareに設定したpriority(優先度)の若い順にミドルウェアが実行されます。 ```dart final middlewares = [ GetMiddleware(priority: 2), GetMiddleware(priority: 5), GetMiddleware(priority: 4), GetMiddleware(priority: -8), ]; ``` この場合の実行順序は **-8 => 2 => 4 => 5** ### redirect redirect関数は、Routeを呼び出してページが検索されると実行されます。リダイレクト先のRouteSettingsが戻り値となります。もしくはnullを与えれば、リダイレクトは行われません。 ```dart RouteSettings redirect(String route) { final authService = Get.find(); return authService.authed.value ? null : RouteSettings(name: '/login') } ``` ### onPageCalled onPageCalled関数は、ページが呼び出された直後に実行されます。 この関数を使ってページの内容を変更したり、新しいページを作成したりすることができます。 ```dart GetPage onPageCalled(GetPage page) { final authService = Get.find(); return page.copyWith(title: 'Welcome ${authService.UserName}'); } ``` ### onBindingsStart onBindingsStart関数は、Bindingsが初期化される直前に実行されます。 たとえば、ページのBindingsを変更することもできます。 ```dart List onBindingsStart(List bindings) { final authService = Get.find(); if (authService.isAdmin) { bindings.add(AdminBinding()); } return bindings; } ``` ### onPageBuildStart onPageBuildStart関数は、Bindingsが初期化された直後、ページWidgetが作成される前に実行されます。 ```dart GetPageBuilder onPageBuildStart(GetPageBuilder page) { print('bindings are ready'); return page; } ``` ### onPageBuilt onPageBuilt関数は、GetPage.page(ページのビルダー)が呼び出された直後に実行され、表示されるWidgetを結果として受け取ることができます。 ### onPageDispose onPageDispose関数は、ページに関するすべてのオブジェクト(Controller、ビューなど)が破棄された直後に実行されます。 ## その他API ```dart // 現在の画面に渡されているargs(引数)を取得 Get.arguments // 直前のRouteの名前("/" など)を取得 Get.previousRoute // 現在のRouteオブジェクトを取得 Get.rawRoute // GetObserverからRoutingを取得 Get.routing // SnackBarが開いているかチェック Get.isSnackbarOpen // Dialogが開いているかチェック Get.isDialogOpen // BottomSheetが開いているかチェック Get.isBottomSheetOpen // Routeを削除 Get.removeRoute() // 引数のRoutePredicateがtrueを返すまで画面を戻る Get.until() // 引数で指定したRouteに進み、RoutePredicateがtrueを返すまで画面を戻る Get.offUntil() // 引数で指定した名前付きRouteに進み、RoutePredicateがtrueを返すまで画面を戻る Get.offNamedUntil() // アプリがどのプラットフォームで実行されているかのチェック GetPlatform.isAndroid GetPlatform.isIOS GetPlatform.isMacOS GetPlatform.isWindows GetPlatform.isLinux GetPlatform.isFuchsia // アプリがどのデバイスで実行されているかのチェック GetPlatform.isMobile GetPlatform.isDesktop // プラットフォームとデバイスのチェックは独立 // 同じOSでもウェブで実行されているのか、ネイティブで実行されているのか区別 GetPlatform.isWeb // MediaQuery.of(context).size.height と同じ // ただしimmutable Get.height Get.width // Navigatorの現在のcontextを取得 Get.context // SnackBar/Dialog/BottomSheet などフォアグラウンドのcontextを取得 Get.overlayContext // 注: 以降のメソッドはcontextの拡張メソッドです。 // contextと同じくUIのどこからでもアクセスできます。 // ウィンドウサイズの変更などに合わせて変わる height/width を取得 context.width context.height // 画面の半分のサイズ,1/3のサイズなどを取得 // レスポンシブなデザインの場合に便利 // オプションのパラメーター dividedBy で割る数を指定 // オプションのパラメーター reducedBy でパーセンテージを指定 context.heightTransformer() context.widthTransformer() /// MediaQuery.of(context).size とほぼ同じ context.mediaQuerySize() /// MediaQuery.of(context).padding とほぼ同じ context.mediaQueryPadding() /// MediaQuery.of(context).viewPadding とほぼ同じ context.mediaQueryViewPadding() /// MediaQuery.of(context).viewInsets とほぼ同じ context.mediaQueryViewInsets() /// MediaQuery.of(context).orientation とほぼ同じ context.orientation() /// デバイスがランドスケープ(横長)モードかどうかチェック context.isLandscape() /// デバイスがポートレート(縦長)モードかどうかチェック context.isPortrait() /// MediaQuery.of(context).devicePixelRatio とほぼ同じ context.devicePixelRatio() /// MediaQuery.of(context).textScaleFactor とほぼ同じ context.textScaleFactor() /// 画面の短辺の長さを取得 context.mediaQueryShortestSide() /// 画面の横幅が800より大きい場合にtrueを返す context.showNavbar() /// 画面の短辺が600より小さい場合にtrueを返す context.isPhone() /// 画面の短辺が600より小さい場合にtrueを返す context.isSmallTablet() /// 画面の短辺が720より大きい場合にtrueを返す context.isLargeTablet() /// デバイスがタブレットの場合にtrueを返す context.isTablet() /// 画面サイズに合わせて value を返す /// たとえば: /// 短辺が300より小さい → watchパラメーターの値を返す /// 短辺が600より小さい → mobileパラメーターの値を返す /// 短辺が1200より小さい → tabletパラメーターの値を返す /// 横幅が1200より大きい → desktopパラメーターの値を返す context.responsiveValue() ``` ### オプションのグローバル設定と手動設定 GetMaterialApp はすべてあなたの代わりに設定してくれますが、手動で設定を施したい場合は MaterialApp の navigatorKey と navigatorObservers の値を指定してください。 ```dart MaterialApp( navigatorKey: Get.key, navigatorObservers: [GetObserver()], ); ``` `GetObserver`内で独自のミドルウェアを使用することもできます。これは他に影響を及ぼすことはありません。 ```dart MaterialApp( navigatorKey: Get.key, navigatorObservers: [ GetObserver(MiddleWare.observer) // ここ ], ); ``` `Get` クラスに_グローバル設定_を施すことができます。Routeをプッシュする前のコードに `Get.config` を追加するだけです。もしくは、`GetMaterialApp` 内で直接設定することもできます。 ```dart GetMaterialApp( enableLog: true, defaultTransition: Transition.fade, opaqueRoute: Get.isOpaqueRouteDefault, popGesture: Get.isPopGestureEnable, transitionDuration: Get.defaultDurationTransition, defaultGlobalState: Get.defaultGlobalState, ); Get.config( enableLog = true, defaultPopGesture = true, defaultTransition = Transitions.cupertino ) ``` オプションで、すべてのログメッセージを `Get` からリダイレクトさせることができます。 お好みのロギングパッケージを使ってログを取得したい場合はこのようにしてください。 ```dart GetMaterialApp( enableLog: true, logWriterCallback: localLogWriter, ); void localLogWriter(String text, {bool isError = false}) { // ここでお好みのロギングパッケージにメッセージを渡してください // enableLog: false にしても、ログメッセージはこのコールバックでプッシュされる点ご注意を // ログが有効かどうかのチェックは Get.isLogEnable で可能 } ``` ### ローカルステートWidget ローカルステートWidgetは、1つの変数の状態を一時的かつローカルに管理したい場合に便利です。 シンプルなValueBuilderとリアクティブなObxValueの2種類があります。 たとえば、`TextField` Widgetの obscureText プロパティを切り替えたり、折りたたみ可能なパネルをカスタムで作成したり、`BottomNavigation` の現在のインデックス値を変更して内容を変更したりといった用途に最適です。 #### ValueBuilder setStateでお馴染みの `StatefulWidget` をシンプルにしたビルダーWidgetです。 ```dart ValueBuilder( initialValue: false, builder: (value, updateFn) => Switch( value: value, onChanged: updateFn, // ( newValue ) => updateFn( newValue ) も可 ), // builderメソッドの外で何か実行する場合 onUpdate: (value) => print("Value updated: $value"), onDispose: () => print("Widget unmounted"), ), ``` #### ObxValue [`ValueBuilder`](#valuebuilder)に似ていますが、これはそのリアクティブバージョンです。Rxインスタンス(.obsを付けたときに戻る値です)を渡すと自動で更新されます。すごいでしょ? ```dart ObxValue((data) => Switch( value: data.value, onChanged: data, // Rxには_呼び出し可能な_関数が備わっているのでこれだけでOK // (flag) => data.value = flag も可能 ), false.obs, ), ``` ## お役立ちTIPS `.obs`が付いた型(_Rx_型とも呼ばれる)には、さまざまな内部メソッドや演算子が用意されています。 > `.obs`が付いたプロパティが **実際の値** だと信じてしまいがちですが...間違えないように! > 我々がcontrollerにおける変数の型宣言を省略してvarとしているのはDartのコンパイラが賢い上に、 > そのほうがコードがすっきる見えるからですが… ```dart var message = 'Hello world'.obs; print( 'Message "$message" has Type ${message.runtimeType}'); ``` `message`を _print_ することで実際の文字列が取り出されはしますが、型は **RxString** です! そのため `message.substring( 0, 4 )` などといったことはできません。 Stringのメソッドにアクセスするには _observable_ の中にある実際の値 `value` にアクセスします。 アクセスには `.value`を使うのが通常ですが、他の方法もあるのでご紹介します。 ```dart final name = 'GetX'.obs; // 新しい値が現在のものと異なる場合のみ Stream が更新されます。 name.value = 'Hey'; // すべてのRxプロパティは「呼び出し可能」で、valueを返してくれます。 // ただし `null` は受付不可。nullの場合はUIが更新されない。 name('Hello'); // これはgetterみたいなものです。'Hello' を返します。 name() ; /// num型の場合 final count = 0.obs; // num型の非破壊的な演算子はすべて使えます。 count + 1; // 注意! この場合は`count`がfinalなら有効ではないです。varなら有効。 count += 1; // 比較演算子も使用可能 count > 2; /// bool型の場合 final flag = false.obs; // true/false を入れ替えることができます。 flag.toggle(); /// すべての型 // `value` を null にセット。 flag.nil(); // toString(), toJson() などの操作はすべて `value` が対象になります。 print( count ); // RxIntの `toString()` が呼び出されて数字がprintされる。 final abc = [0,1,2].obs; // json配列に変換した値と、'RxList' がprintされます。 // JsonはすべてのRx型でサポートされています! print('json: ${jsonEncode(abc)}, type: ${abc.runtimeType}'); // RxMap、RxList、RxSetはそれぞれの元の型を拡張した特別なRx型です。 // たとえばRxListは通常のListとして扱うことができる上にリアクティブです。 abc.add(12); // 12をListにプッシュし、Streamを更新してくれます。 abc[3]; // Listと同様にインデックス番号3の値を取得してくれます。 // 等価演算子はRx型と元の型でも動作しますが、.hashCode は常にvalueから取得します。 final number = 12.obs; print( number == 12 ); // true /// カスタムのRxモデル // toJson()やtoString()をモデルクラスに設定すれば、.obsからでもprintされるように実装可能。 class User { String name, last; int age; User({this.name, this.last, this.age}); @override String toString() => '$name $last, $age years old'; } final user = User(name: 'John', last: 'Doe', age: 33).obs; // `user` 自体はリアクティブですが、その中のプロパティはリアクティブではありません。 // そのため、このようにプロパティの値を変更してもWidgetは更新されません。 user.value.name = 'Roi'; // `Rx` には自ら変更を検知する手段がないからです。 // そのため、カスタムクラスの場合はこのようにWidgetに変更を知らせる必要があります。 user.refresh(); // もしくは `update()` メソッドを使用してください。 user.update((value){ value.name='Roi'; }); print( user ); ``` #### StateMixin `UI`の状態を管理するもう一つの手法として、`StateMixin`を利用する方法があります。 controllerクラスに`with`を使って`StateMixin`を追加することで実装可能です。 ``` dart class Controller extends GetController with StateMixin{} ``` `change()`メソッドにより好きなタイミングで状態を変更することができます。 このようにデータと状態を渡すだけです。 ```dart change(data, status: RxStatus.success()); ``` RxStatus には以下のステータスが存在します。 ``` dart RxStatus.loading(); RxStatus.success(); RxStatus.empty(); RxStatus.error('message'); ``` ステータスごとにUIを設定するにはこのようにします。 ```dart class OtherClass extends GetView { @override Widget build(BuildContext context) { return Scaffold( body: controller.obx( (state)=>Text(state.name), // ローディング中はカスタムのインジケーターの設定も可能ですが、 // デフォルトで Center(child:CircularProgressIndicator()) となります。 onLoading: CustomLoadingIndicator(), onEmpty: Text('No data found'), // 同様にエラーWidgetはカスタム可能ですが、 // デフォルトは Center(child:Text(error)) です。 onError: (error)=>Text(error), ), ); } ``` #### GetView このWidgetは私のお気に入りです。とてもシンプルで扱いやすいですよ! このWidgetを一言で表現すると、「controllerをgetterに持つ `const` な StatelessWidget」です。 ```dart class AwesomeController extends GetController { final String title = 'My Awesome View'; } // controllerの `型` を渡すのを忘れずに! class AwesomeView extends GetView { @override Widget build(BuildContext context) { return Container( padding: EdgeInsets.all(20), child: Text(controller.title), // `controller.なんとか` でアクセス ); } } ``` #### GetResponsiveView GetViewをレスポンシブデザインに対応させたい場合はこのWidgetを継承してください。 画面サイズやデバイスタイプなどの情報を持つ `screen` プロパティを保持しています。 ##### 使い方 Widgetをビルドする方法は2つあります。 - `builder` メソッドを使う。 - `desktop`, `tablet`, `phone`, `watch` メソッドを使う。 画面サイズ、デバイスタイプに応じたWidgetがビルドされます。 たとえば画面が [ScreenType.Tablet] なら `tablet` メソッドが実行されます。 **注:** `alwaysUseBuilder` プロパティをfalseにする必要があります。 `settings` プロパティでブレイクポイントを設定することもできます。 ![例](https://github.com/SchabanBo/get_page_example/blob/master/docs/Example.gif?raw=true) この画面のコード [コード](https://github.com/SchabanBo/get_page_example/blob/master/lib/pages/responsive_example/responsive_view.dart) #### GetWidget このWidgetはあまり知られておらず、使用するケースは稀です。 GetViewとの違いは、Controllerを`キャッシュ`してくれる点です。 このキャッシュがあるため `const` にはできません。 > それでは一体いつControllerをキャッシュする必要があるのかって? それは **GetX** のこれまた使う機会の少ない `Get.create()` を使うときです。 `Get.create(()=>Controller())` は `Get.find()` を実行するたびに 新しいControllerインスタンスを生成します。 そこで `GetWidget` の出番です。たとえば、Todoアイテムのリスト内容を保持したいとき。 Widgetが更新されてもアイテムはControllerのキャッシュを参照してくれます。 #### GetxService このクラスは `GetxController` に似ており、同様のライフサイクル(`onInit()`, `onReady()`, `onClose()`)を共有しますが、そこに「ロジック」はありません。**GetX**の依存オブジェクト注入システムに、このサブクラスがメモリから **削除できない** ということを知らせるだけです。 そのため `Get.find()` で `ApiService`, `StorageService`, `CacheService` のようなサービス系クラスにいつでもアクセスできるようにしておくと非常に便利です。 ```dart Future main() async { await initServices(); /// サービスクラスの初期化をawait runApp(SomeApp()); } /// Flutterアプリ実行前にサービスクラスを初期化してフローをコントロールするのは賢いやり方です。 /// たとえば GetMaterialAppを更新する必要がないようにUser別の /// Theme、apiKey、言語設定などをApiサービス実行前にロードしたり。 void initServices() async { print('starting services ...'); /// get_storage, hive, shared_pref の初期化はここで行います。 /// あるいは moor の connection など非同期のメソッドならなんでも。 await Get.putAsync(() => DbService().init()); await Get.putAsync(SettingsService()).init(); print('All services started...'); } class DbService extends GetxService { Future init() async { print('$runtimeType delays 2 sec'); await 2.delay(); print('$runtimeType ready!'); return this; } } class SettingsService extends GetxService { void init() async { print('$runtimeType delays 1 sec'); await 1.delay(); print('$runtimeType ready!'); } } ``` `GetxService` を破棄する唯一の方法は `Get.reset()` メソッドを使うことです。 これはアプリにおける「ホットリブート」のようなものです。あるクラスのインスタンスを ライフサイクルの間ずっと残しておきたい場合は `GetxService` を使うというのを覚えておいてください。 ## テストの実行 Controllerのライフサイクル含め、他のクラスと同様にテストを実行することができます。 ```dart class Controller extends GetxController { @override void onInit() { super.onInit(); // 値を name2 に変更 name.value = 'name2'; } @override void onClose() { name.value = ''; super.onClose(); } final name = 'name1'.obs; void changeName() => name.value = 'name3'; } void main() { test(''' Test the state of the reactive variable "name" across all of its lifecycles''', () { /// ライフサイクルごとのテストは必ずしも行う必要はありませんが、 /// GetXの依存オブジェクト注入機能を使用しているのであれば実行をおすすめします。 final controller = Controller(); expect(controller.name.value, 'name1'); /// このようにライフサイクル経過ごとの状態をテスト可能です。 Get.put(controller); // onInit が実行される expect(controller.name.value, 'name2'); /// 関数もテストしましょう controller.changeName(); expect(controller.name.value, 'name3'); /// onClose が実行される Get.delete(); expect(controller.name.value, ''); }); } ``` #### mockitoやmocktailを使う場合 GetxController/GetxService をモックする場合 Mock をミックスインしてください。 ```dart class NotificationServiceMock extends GetxService with Mock implements NotificationService {} ``` #### Get.reset() WidgetやGroupのテスト時に、テストの最後かtearDownの中で Get.reset() を実行することで設定をリセットすることができます。 #### Get.testMode Controllerの中でナビゲーションを使用している場合は、`Get.testMode = true`をmainの開始で実行してください。 # バージョン2.0からの破壊的変更 1- Rx型の名称 | 変更前 | 変更後 | | ------- | ---------- | | StringX | `RxString` | | IntX | `RxInt` | | MapX | `RxMap` | | ListX | `RxList` | | NumX | `RxNum` | | DoubleX | `RxDouble` | RxControllerとGetBuilderが統合され、Controllerにどれを使うか覚えておく必要がなくなりました。GetxControllerを使うだけで、リアクティブと非リアクティブな状態管理の両方に対応できるようになりました。 2- 名前付きRoute 変更前: ```dart GetMaterialApp( namedRoutes: { '/': GetRoute(page: Home()), } ) ``` 変更後: ```dart GetMaterialApp( getPages: [ GetPage(name: '/', page: () => Home()), ] ) ``` 変更の効果: ページ表示にはパラメータやログイントークンを起点にする方法もありますが、以前のアプローチではこれができず、柔軟性に欠けていました。 ページを関数から取得するよう変更したことで、このようなアプローチを可能にし、アプリ起動直後にRouteがメモリに割り当てられることもないため、RAMの消費量を大幅に削減することもできました。 ```dart GetStorage box = GetStorage(); GetMaterialApp( getPages: [ GetPage(name: '/', page:(){ return box.hasData('token') ? Home() : Login(); }) ] ) ``` # なぜGetXなのか 1- Flutterのアップデートが重なると、依存パッケージがうまく動かなくなることがあります。コンパイルエラーを起こしたり、その時点で解決方法がないようなエラーが発生したり。開発者はそのエラーを追跡し、該当リポジトリにissueを提起し、問題が解決されるのを待つ必要があります。Getは開発に必要な主要リソース(状態管理、依存オブジェクト管理、Route管理)を一元化し、Pubspecにパッケージを1つ追加するだけでコーディングを開始することができます。Flutterがアップデートしたときに必要なことは、Getも併せてアップデートすることだけです。それですぐに作業を再開できます。またGetはパッケージ間の互換性の問題も解消します。互いに依存するパッケージAの最新バージョンとBの最新バージョンの間に互換性がない、ということが何度あったでしょうか。Getを使えばすべてが同じパッケージ内にあるため、互換性の心配はありません。 2- Flutterは手軽で素晴らしいフレームワークですが、`Navigator.of(context).push (context, builder [...]`のように、ほとんどの開発者にとって不要な定型文が一部にあります。Getを使えばそのような定型文を簡素化できます。Routeを呼ぶためだけに8行のコードを書く代わりに、`Get.to(Home())`を実行すれば、次の画面に遷移することができるのです。またウェブURLを動的なものにすることは現在Flutterでは本当に骨の折れる作業ですが、GetXを使えば非常に簡単です。そしてFlutterにおける状態管理と依存オブジェクト管理については、たくさんのパターンやパッケージがあるので多くの議論を生んでいます。しかしGetXのアプローチは大変シンプルです。これは一例ですが、変数の最後に「.obs」を追加して「Obx()」の中にWidgetを配置するだけで、その変数の状態変化が自動でWidgetに反映されます。 3- GetXではパフォーマンスのことをあまり気にせず開発ができます。Flutterのパフォーマンスはそれだけで素晴らしいものですが、状態管理と併せて BLoC / データストア / Controller などを届けるためのサービスロケーターを使用することを想像してみてください。そのインスタンスが必要ないときはリソースを解放するイベントを明示的に呼び出さなければなりません。そんなとき、使用されなくなったら自動でメモリから削除してくれればいいのに、と考えたことはありませんか?それを実現してくれるのがGetXです。SmartManagement機能により未使用のリソースはすべて自動でメモリから破棄されるので、本来の開発作業に集中することができます。メモリ管理のためのロジックを作らなくても、常に必要最小限のリソースを使っていることが保証されるのです。 4- コードのデカップリング(分離)がしやすい。「ビューをビジネスロジックから分離する」というコンセプトを聞いたことがあるかもしれません。これはなにもBLoC、MVC、MVVMに限ったことではなく、どのアーキテクチャパターンもこのコンセプトが考え方の基本にあると言っていいでしょう。しかし、Flutterではcontextの使用によりこのコンセプトが薄まってしまいがちです。 InheritedWidgetを参照するためにcontextが必要なとき、ビューの中でそれを使用するか、引数としてcontextを渡しますよね?私はこの方法は美しくないと感じます。常にビュー内のビジネスロジックに依存しなければならないのは、特にチームで仕事をする場面においては不便だと感じます。GetXによるアプローチでは、StatefulWidgetやinitStateなどの使用を禁止しているわけではありませんが、それらよりもずっとスッキリ書けるようになっています。Controller自体にライフサイクルがあるため、たとえばREST APIのリクエストを行うときも、ビューの中の何かに依存するということがありません。Controllerのライフサイクルの一つである onInit を使用してhttpを呼び出し、データが到着すると変数にセットされます。GetXはリアクティブな変数を扱うことができるので、インスタンス変数が変わりし次第、その変数に依存するWidgetがすべて自動更新されます。これによりUIの担当者はWidgetの見た目に注力することができ、ボタンクリックなどのユーザーイベント以外のものをビジネスロジックに渡す必要がなくなります。その一方でビジネスロジックの担当者はビジネスロジックだけに集中し、個別のテストを簡単に行うことができます。 GetXライブラリは今後も更新され続け、新しい機能を実装していきます。気軽にプルリクエストを出していただき、ライブラリの成長に貢献していただけるとうれしいです。 # コミュニティ ## コミュニティチャンネル GetXコミュニティは非常に活発で有益な情報であふれています。ご質問がある場合や、このフレームワークの使用に関して支援が必要な場合は、ぜひコミュニティチャンネルにご参加ください。このリポジトリは、issueの提起およびリクエスト専用ですが、気軽にコミュニティにご参加いただければ幸いです。 | **Slack** | **Discord** | **Telegram** | | :-------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------- | | [![Get on Slack](https://img.shields.io/badge/slack-join-orange.svg)](https://communityinviter.com/apps/getxworkspace/getx) | [![Discord Shield](https://img.shields.io/discord/722900883784073290.svg?logo=discord)](https://discord.com/invite/9Hpt99N) | [![Telegram](https://img.shields.io/badge/chat-on%20Telegram-blue.svg)](https://t.me/joinchat/PhdbJRmsZNpAqSLJL6bH7g) | ## コントリビュート方法 _GetXプロジェクトに貢献してみませんか?あなたをコントリビューターの一人としてご紹介できるのを楽しみにしています。GetおよびFlutterをより良いものにするためのコントリビュート例をご紹介します。_ - Readmeの多言語対応。 - Readmeの追加ドキュメント執筆 (ドキュメントで触れられていない機能がまだまだたくさんあります)。 - Getの使い方を紹介する記事やビデオの作成(Readmeに掲載させていただきます。将来的にWikiができればそこにも掲載予定)。 - コードやテストのプルリクエスト。 - 新機能の提案。 どのような形の貢献であれ歓迎しますので、ぜひコミュニティにご参加ください! ## GetXに関する記事と動画 - [Flutter Getx EcoSystem package for arabic people](https://www.youtube.com/playlist?list=PLV1fXIAyjeuZ6M8m56zajMUwu4uE3-SL0) - Tutorial by [Pesa Coder](https://github.com/UsamaElgendy). - [Dynamic Themes in 3 lines using GetX™](https://medium.com/swlh/flutter-dynamic-themes-in-3-lines-c3b375f292e3) - Tutorial by [Rod Brown](https://github.com/RodBr). - [Complete GetX™ Navigation](https://www.youtube.com/watch?v=RaqPIoJSTtI) - Route management video by Amateur Coder. - [Complete GetX State Management](https://www.youtube.com/watch?v=CNpXbeI_slw) - State management video by Amateur Coder. - [GetX™ Other Features](https://youtu.be/ttQtlX_Q0eU) - Utils, storage, bindings and other features video by Amateur Coder. - [Firestore User with GetX | Todo App](https://www.youtube.com/watch?v=BiV0DcXgk58) - Video by Amateur Coder. - [Firebase Auth with GetX | Todo App](https://www.youtube.com/watch?v=-H-T_BSgfOE) - Video by Amateur Coder. - [The Flutter GetX™ Ecosystem ~ State Management](https://medium.com/flutter-community/the-flutter-getx-ecosystem-state-management-881c7235511d) - State management by [Aachman Garg](https://github.com/imaachman). - [The Flutter GetX™ Ecosystem ~ Dependency Injection](https://medium.com/flutter-community/the-flutter-getx-ecosystem-dependency-injection-8e763d0ec6b9) - Dependency Injection by [Aachman Garg](https://github.com/imaachman). - [GetX, the all-in-one Flutter package](https://www.youtube.com/watch?v=IYQgtu9TM74) - A brief tutorial covering State Management and Navigation by Thad Carnevalli. - [Build a To-do List App from scratch using Flutter and GetX](https://www.youtube.com/watch?v=EcnqFasHf18) - UI + State Management + Storage video by Thad Carnevalli. - [GetX Flutter Firebase Auth Example](https://medium.com/@jeffmcmorris/getx-flutter-firebase-auth-example-b383c1dd1de2) - Article by Jeff McMorris. - [Flutter State Management with GetX – Complete App](https://www.appwithflutter.com/flutter-state-management-with-getx/) - by App With Flutter. - [Flutter Routing with Animation using Get Package](https://www.appwithflutter.com/flutter-routing-using-get-package/) - by App With Flutter. - [A minimal example on dartpad](https://dartpad.dev/2b3d0d6f9d4e312c5fdbefc414c1727e?) - by [Roi Peker](https://github.com/roipeker) ================================================ FILE: README.ko-kr.md ================================================ ![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/get.png) [![pub package](https://img.shields.io/pub/v/get.svg?label=get&color=blue)](https://pub.dev/packages/get) [![popularity](https://badges.bar/get/popularity)](https://pub.dev/packages/sentry/score) [![likes](https://badges.bar/get/likes)](https://pub.dev/packages/get/score) [![pub points](https://badges.bar/get/pub%20points)](https://pub.dev/packages/get/score) ![building](https://github.com/jonataslaw/get/workflows/build/badge.svg) [![style: effective dart](https://img.shields.io/badge/style-effective_dart-40c4ff.svg)](https://pub.dev/packages/effective_dart) [![Discord Shield](https://img.shields.io/discord/722900883784073290.svg?logo=discord)](https://discord.com/invite/9Hpt99N) [![Get on Slack](https://img.shields.io/badge/slack-join-orange.svg)](https://communityinviter.com/apps/getxworkspace/getx) [![Telegram](https://img.shields.io/badge/chat-on%20Telegram-blue.svg)](https://t.me/joinchat/PhdbJRmsZNpAqSLJL6bH7g) Awesome Flutter Buy Me A Coffee ![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/getx.png)
**Languages:** [![영어](https://img.shields.io/badge/Language-English-blueviolet?style=for-the-badge)](README.md) [![베트남어](https://img.shields.io/badge/Language-Vietnamese-blueviolet?style=for-the-badge)](README-vi.md) [![인도네시아어](https://img.shields.io/badge/Language-Indonesian-blueviolet?style=for-the-badge)](README.id-ID.md) [![우르두어](https://img.shields.io/badge/Language-Urdu-blueviolet?style=for-the-badge)](README.ur-PK.md) [![중국어](https://img.shields.io/badge/Language-Chinese-blueviolet?style=for-the-badge)](README.zh-cn.md) [![포르투칼어](https://img.shields.io/badge/Language-Portuguese-blueviolet?style=for-the-badge)](README.pt-br.md) [![스페인어](https://img.shields.io/badge/Language-Spanish-blueviolet?style=for-the-badge)](README-es.md) [![러시아어](https://img.shields.io/badge/Language-Russian-blueviolet?style=for-the-badge)](README.ru.md) [![폴란드어](https://img.shields.io/badge/Language-Polish-blueviolet?style=for-the-badge)](README.pl.md) [![한국어](https://img.shields.io/badge/Language-Korean-blueviolet?style=for-the-badge)](README.ko-kr.md) [![프랑스어](https://img.shields.io/badge/Language-French-blueviolet?style=for-the-badge)](README-fr.md)
- [Get에 대하여](#get에-대하여) - [설치](#설치) - [GetX를 사용한 Counter 앱](#getx를-사용한-counter-앱) - [세가지 주요점](#세가지-주요점) - [상태 관리](#상태-관리) - [반응형 상태 관리자](#반응형-상태-관리자) - [상태 관리에 대한 자세한 내용](#상태-관리에-대한-자세한-내용) - [라우트 관리](#라우트-관리) - [라우트 관리에 대한 자세한 내용](#라우트-관리에-대한-자세한-내용) - [종속성 관리](#종속성-관리) - [종속성 관리에 대한 자세한 내용](#종속성-관리에-대한-자세한-내용) - [기능들](#기능들) - [국제화](#국제화) - [번역](#번역) - [번역 사용법](#번역-사용법) - [지역화](#지역화) - [지역 변경](#지역-변경) - [시스템 지역](#시스템-지역) - [테마 변경](#테마-변경) - [GetConnect](#getconnect) - [기본 구성](#기본-구성) - [커스텀 구성](#커스텀-구성) - [GetPage Middleware](#getpage-middleware) - [Priority](#priority) - [Redirect](#redirect) - [onPageCalled](#onpagecalled) - [OnBindingsStart](#onbindingsstart) - [OnPageBuildStart](#onpagebuildstart) - [OnPageBuilt](#onpagebuilt) - [OnPageDispose](#onpagedispose) - [기타 고급 API](#기타-고급-api) - [선택적 전역 설정과 수동 구성](#선택적-전역-설정과-수동-구성) - [지역 상태 위젯들](#지역-상태-위젯들) - [ValueBuilder](#valuebuilder) - [ObxValue](#obxvalue) - [유용한 팁](#유용한-팁) - [GetView](#getview) - [GetResponsiveView](#getresponsiveview) - [사용 방법](#사용-방법) - [GetWidget](#getwidget) - [GetxService](#getxservice) - [2.0의 주요 변경점](#20의-주요-변경점) - [왜 Getx인가?](#왜-getx인가) - [커뮤니티](#커뮤니티) - [커뮤니티 채널](#커뮤니티-채널) - [기여하는 방법](#기여하는-방법) - [기사 및 비디오](#기사-및-비디오) # Get에 대하여 - GetX는 Flutter를 위한 매우 가볍고 강력한 솔루션입니다. 고성능 상태 관리, 지능형 종속성 주입 및 빠르고 실용적인 라우트 관리가 결합되어 있습니다. - GetX는 라이브러리의 모든 사항에 대해서 **생산성, 성능, 조직화**의 3 가지 기본 원칙을 가지고 있습니다. - **성능:** GetX는 성능과 최소한의 리소스 소비에 중점을 둡니다. GetX는 Streams나 ChangeNotifier를 사용하지 않습니다. - **생산성:** GetX는 쉽고 친숙한 구문을 사용합니다. 원하시는 것보다 Getx에는 항상 더 쉬운 방법이 있습니다. 개발 시간을 아끼고 애플리케이션을 최대 성능으로 제공할 수 있습니다. 일반적으로 개발자는 메모리에서 컨트롤러들을 제거하는 데 관심을 가져야합니다. GetX에서는 리소스가 기본적으로 사용되지 않으면 메모리에서 제거되므로 필요하지 않습니다. 만약 메모리에 유지하려면 종속성에서 "permanent : true"를 명시적으로 선언해야합니다. 이렇게하면 시간을 절약 할 수있을뿐만 아니라 불필요한 메모리 종속성이 발생할 위험이 줄어 듭니다. 종속성은 기본적으로 lazy로 로드됩니다. - **조직화:** GetX는 화면, 프레젠테이션 로직, 비즈니스 로직, 종속성 주입 및 네비게이션을 완전히 분리 할 수 있습니다. 라우트간 전환을 하는데에 컨텍스트가 필요하지 않아 위젯 트리(시각객체)에 독립적입니다. inheritedWidget을 통해 컨트롤러/블록에 접근하는 데 컨텍스트가 필요하지 않아 시각화 계층에서 프레젠테이션 로직과 비즈니스 로직을 완전히 분리됩니다. 이 GetX는 자체 종속성 주입 기능을 사용하여 DI를 뷰에서 완전히 분리하기 때문에 다중 Provider를 통해 위젯 트리에서 컨트롤러/모델/블록으로 주입 할 필요가 없습니다. GetX를 사용하면 기본적으로 클린 코드를 가지게 되어 애플리케이션의 각 기능을 쉽게 찾을 수 있습니다. 이것은 유지 보수를 용이하게 하며 모듈의 공유가 가능하고 Flutter에서는 생각할 수 없었던 것들도 전부 가능합니다. BLoC은 Flutter에서 코드를 구성하기 위한 시작점으로 비즈니스 로직과 시각객체를 분리합니다. Getx는 비즈니스 로직 뿐만 아니라 프레젠테이션 로직을 분리하는 자연스러운 진화입니다. 추가로 종속성 주입과 라우트 또한 분리되고 데이터 계층이 모두로부터 분리됩니다. Hello World를 구현하는 것보다 더 쉽게 모든 것이 어디 있는지 알수 있습니다. Flutter SDK와 함께 GetX를 사용하면 가장 쉽고 실용적이며 확장 가능한 고성능 어플리케이션을 만들수 있습니다. 초보자에게는 쉬우며 전문가에게는 정확하고 완벽하게 동작하는 대규모 생태계가 함께합니다. 안전하고 안정적이며 업데이트되고 기본 Flutter SDK에 없는 광범위한 API 빌드를 제공합니다. - GetX는 비대하지 않습니다. 아무 걱정없이 프로그래밍을 시작할 수 있는 다양한 기능이 있지만 각 기능은 별도의 컨테이너에 있으며 사용한 후에만 시작됩니다. 만약 상태 관리만 사용하면 오직 상태 관리만 컴파일 됩니다. 라우트만 사용하는 경우 상태 관리는 컴파일되지 않습니다. - GetX는 거대한 생태계, 대규모 커뮤니티, 수많은 공동 작업자를 보유하고 있으며 Flutter가 존재하는 한 유지됩니다. GetX도 Android, iOS, 웹, Mac, Linux, Windows 및 서버에서 동일한 코드로 실행할 수 있습니다. **[Get Server](https://github.com/jonataslaw/get_server)를 사용한 백엔드에는 프런트엔드에서 만든 코드를 완전히 재사용 할 수 있습니다.** **추가로 [Get CLI](https://github.com/jonataslaw/get_cli)를 프런트엔드와 서버 양쪽에서 사용하면 전체 개발 프로세스를 자동화 할 수 있습니다.** **추가로 생산성 향상을 위해 [VSCode 확장](https://marketplace.visualstudio.com/items?itemName=get-snippets.get-snippets)과 [Android Studio/Intellij 확장](https://plugins.jetbrains.com/plugin/14975-getx-snippets)이 있습니다.** # 설치 pubspec.yaml 파일에 Get 추가: ```yaml dependencies: get: ``` 사용할 파일에 Import get: ```dart import 'package:get/get.dart'; ``` # GetX를 사용한 Counter 앱 Flutter의 새 프로젝트에서 기본적으로 생성 되는 "counter" 프로젝트는 100줄이 넘습니다 (코멘트 포함). Get의 강력함을 보여주기 위해 클릭 할 때마다 상태를 변경하고, 페이지 사이를 전환하고, 화면 사이의 상태를 공유하는 "counter"를 만드는 방법을 주석이 포함된 26줄의 코드로 보여줄 것입니다. - 1 단계: MaterialApp 에 "Get"을 추가하여 GetMaterialApp 으로 변경합니다. ```dart void main() => runApp(GetMaterialApp(home: Home())); ``` - 주석: 이는 Flutter의 MaterialApp을 변경하지 않으며 GetMaterialApp 또한 수정 된 MaterialApp이 아니고, 기본 MaterialApp을 자식으로 갖는 사전 구성된 위젯 일뿐입니다. 수동으로 구성 할 수 있지만 반드시 필요한 것은 아닙니다. GetMaterialApp은 라우트를 생성하고 추가하며, 번역을 추가하고, 라우트 탐색에 필요한 모든 것을 추가합니다. 만약 상태 관리 또는 종속성 관리에만 Get을 사용하는 경우 GetMaterialApp을 사용할 필요가 없습니다. GetMaterialApp은 라우트, 스택바, 국제화, bottomSheets, 다이얼로그 및 컨텍스트 부재와 라우트에 연관된 상위 api들에 필요합니다. - 주석²: 이 단계는 라우트 관리 (`Get.to ()`,`Get.back ()` 등)를 사용하려는 경우에만 필요합니다. 사용하지 않을 경우 1 단계를 수행 할 필요가 없습니다. - 2 단계: 비즈니스 로직 클래스를 만들고 모든 변수, 함수, 컨트롤러를 포함하십시오. ".obs"를 이용하면 간단히 모든 변수를 observable로 만들수 있습니다. ```dart class Controller extends GetxController{ var count = 0.obs; increment() => count++; } ``` - 3 단계: StatelessWidget를 이용해 View를 만들어 RAM을 아끼고 StatefulWidget은 더 이상 사용하지 않아도 됩니다. ```dart class Home extends StatelessWidget { @override Widget build(context) { // Get.put()을 사용하여 클래스를 인스턴스화하여 모든 "child'에서 사용가능하게 합니다. final Controller c = Get.put(Controller()); return Scaffold( // count가 변경 될 때마다 Obx(()=> 를 사용하여 Text()에 업데이트합니다. appBar: AppBar(title: Obx(() => Text("Clicks: ${c.count}"))), // 8줄의 Navigator.push를 간단한 Get.to()로 변경합니다. context는 필요없습니다. body: Center(child: ElevatedButton( child: Text("Go to Other"), onPressed: () => Get.to(Other()))), floatingActionButton: FloatingActionButton(child: Icon(Icons.add), onPressed: c.increment)); } } class Other extends StatelessWidget { // 다른 페이지에서 사용되는 컨트롤러를 Get으로 찾아서 redirect 할 수 있습니다. final Controller c = Get.find(); @override Widget build(context){ // 업데이트된 count 변수에 연결 return Scaffold(body: Center(child: Text("${c.count}"))); } } ``` 결론: ![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/counter-app-gif.gif) 이것은 간단한 프로젝트 이지만 Get이 얼마나 강력한지 명확히 보여줍니다. 프로젝트가 성장하면 차이점이 더 커질 것 입니다. Get은 팀단위 업무에 맞춰 디자인되었지만 개별 개발자의 작업도 단순화합니다. 마감일을 개선하고 성능의 손실 없이 재시간에 제공하십시오. Get은 모두를 위한 것은 아니지만 위의 설명에 해당사항이 있으면 당신을 위한 것입니다! # 세가지 주요점 ## 상태 관리 Get은 두가지 상태 관리자가 있습니다: 단순 상태관리자(GetBuilder라고 함)와 반응형 상태관리자(GetX/Obx) ### 반응형 상태 관리자 반응형 프로그래밍은 복잡하다고 알려져있기 때문에 많은 사람들에게 소외될 수 있습니다. GetX가 매우 단순하게 반응형 프로그래밍을 바꿉니다: - StreamControllers를 만들 필요가 없습니다. - 각 변수에 대해 StreamBuilder를 만들 필요가 없습니다. - 각각의 상태(state)를 위한 클래스를 만들 필요가 없습니다. - 초기값을 위한 get이 필요하지 않습니다. - 코드 생성기를 사용할 필요가 없습니다. Get의 반응형 프로그램은 setState를 사용하는 것 만큼 쉽습니다. 매번 변경되기를 원하고 모든 위젯에서 자동적으로 반영되는 변수가 있다고 가정해봅시다. 여기 name 변수가 있습니다: ```dart var name = 'Jonatas Borges'; ``` ".obs"만 끝에 추가하여 observable로 만듭니다: ```dart var name = 'Jonatas Borges'.obs; ``` 아래와 같이 간단히 보여주고 싶은 UI에 추가하면 값이 변경될때마다 화면에 업데이트 됩니다: ```dart Obx(() => Text("${controller.name}")); ``` 이게 다 입니다. _정말_ 간단하죠. ### 상태 관리에 대한 자세한 내용 **상태 관리에 대한 자세한 설명은 [여기](./documentation/kr_KO/state_management.md)를 보십시오. 여기에서 더 많은 예제와 단순 상태 관리자와 반응형 상태 관리자의 차이점을 볼 수 있습니다.** GetX 능력에 대한 좋은 아이디어를 얻을 수 있습니다. ## 라우트 관리 만약 라우트/스낵바/다이얼로그/bottomsheets을 context 없이 사용하려면 GetX는 훌륭한 대안입니다. 여기를 보십시오: MaterialApp 앞에 "Get"을 추가해서 GetMaterialApp으로 변경합니다. ```dart GetMaterialApp( // Before: MaterialApp( home: MyHome(), ) ``` 새로운 화면으로 이동합니다: ```dart Get.to(NextScreen()); ``` 명칭으로 새로운 화면으로 이동합니다. 명칭으로 라우트하는 더 자세한 사항은 [여기](./documentation/kr_KO/route_management.md#이름있는-라우트-탐색) 있습니다. ```dart Get.toNamed('/details'); ``` 스낵바, 다이얼로그, bottomsheets 또는 Navigator.pop(context);로 닫아야 하는 어떤것도 닫게 합니다: ```dart Get.back(); ``` 다음 화면으로 이동하고 이전 화면으로 돌아갈 필요가 없는 경우 (스플래시, 로그인화면 등..) ```dart Get.off(NextScreen()); ``` 다음 화면으로 이동하고 이전 화면들 모두 닫는 경우 (쇼핑카트, 투표, 테스트에 유용) ```dart Get.offAll(NextScreen()); ``` 이러한 작업을 수행하기 위해 컨텍스트를 사용할 필요가 없다는 것을 보셨나요? 이것이 Get 라우트 관리를 사용하는 가장 큰 장점 중 하나입니다. 이를 통해 걱정없이 컨트롤러 클래스 내에서 이러한 모든 메서드를 실행할 수 있습니다. ### 라우트 관리에 대한 자세한 내용 **Get은 명명된 라우트로 작업하고 더 쉬운 방식으로 라우트의 제어를 제공합니다! [여기](./documentation/kr_KO/route_management.md)에 더 자세한 문서가 있습니다.** ## 종속성 관리 Get은 간단하고 강력한 종속성 관리자를 가지고 있어 Bloc나 Controller와 유사한 클래스를 Provide context, inheritedWidget 없이 1줄의 코드로 끌어낼 수 있습니다: ```dart Controller controller = Get.put(Controller()); // Rather Controller controller = Controller(); ``` - 주석: Get의 상태 관리자를 사용중이면 뷰를 controller에 더 쉽게 연결할 수 있는 바인딩 api에 더 주의를 기울이십시오. 사용 중인 클래스에서 클래스를 인스턴스화하는 대신에 Get 인스턴스에서 인스턴스화하면 앱에서 해당 클래스를 사용할 수 있습니다. 그래서 controller(또는 Bloc)를 정상적으로 사용할 수 있습니다. **팁:** Get 종속성 관리는 패키지의 다른 부분과 분리되어서 예제 앱이 이미 상태 관리자(하나여도 상관없음)를 사용중이면 모두 다시 작성할 필요 없이 아무 문제 없이 종속성 주입을 사용할 수 있습니다. ```dart controller.fetchApi(); ``` 여러 경로들을 탐색했고 controller에 남아있는 데이터가 필요가 있다고 가정하십시오. Get_it이나 Provider와 조합된 상태 관리자가 필요합니다. 맞습니까? Get은 아닙니다. 다른 추가적인 종속성이 필요없이 controller를 Get의 "find"로 찾으면 됩니다: ```dart Controller controller = Get.find(); //마법처럼 Get이 controller를 찾아서 가져올 것 입니다. 백만개의 인스턴스화 contrller를 가질수 있고 Get은 올바른 controller를 항상 가져다 줄 것입니다. ``` 그리고나서 가져온 controller 데이터를 사용할 수 있습니다: ```dart Text(controller.textFromApi); ``` ### 종속성 관리에 대한 자세한 내용 **종속성 관리에 대한 더 자세한 사항은 [여기](./documentation/kr_KO/dependency_management.md)에 있습니다.** # 기능들 ## 국제화 ### 번역 번역은 간단한 key-value 맵으로 유지됩니다. 커스텀 번역을 추가하려면 `Translations`으로 확장하여 클래스를 만드세요. ```dart import 'package:get/get.dart'; class Messages extends Translations { @override Map> get keys => { 'en_US': { 'hello': 'Hello World', }, 'de_DE': { 'hello': 'Hallo Welt', } }; } ``` #### 번역 사용법 단지 `.tr`로 명시된 키만 추가하면 `Get.locale`과 `Get.fallbackLocale`의 현재값을 사용해서 번역될 것 입니다. ```dart Text('title'.tr); ``` #### 단수와 복수의 번역 사용법 ```dart var products = []; Text('singularKey'.trPlural('pluralKey', products.length, Args)); ``` #### 파라미터로 번역 사용하는 방법 ```dart import 'package:get/get.dart'; Map> get keys => { 'en_US': { 'logged_in': 'logged in as @name with email @email', }, 'es_ES': { 'logged_in': 'iniciado sesión como @name con e-mail @email', } }; Text('logged_in'.trParams({ 'name': 'Jhon', 'email': 'jhon@example.com' })); ``` ### 지역화 `GetMaterialApp`의 파라미터를 전달하여 지역과 번역어를 정의합니다. ```dart return GetMaterialApp( translations: Messages(), // 번역들 locale: Locale('en', 'US'), // 해당 지역의 번역이 표시 fallbackLocale: Locale('en', 'UK'), // 잘못된 지역이 선택된 경우 복구될 지역을 지정 ); ``` #### 지역 변경 지역을 업데이트할때 `Get.updateLocale(locale)`를 콜하십시오. 새로운 지역을 사용하여 자동적으로 번역합니다. ```dart var locale = Locale('en', 'US'); Get.updateLocale(locale); ``` #### 시스템 지역 `Get.deviceLocale`를 사용해서 시스템 지역을 읽어옵니다. ```dart return GetMaterialApp( locale: Get.deviceLocale, ); ``` ## 테마 변경 테마를 업데이트하기 위해 `GetMaterialApp` 보다 더 상위 위젯을 사용하지 말아 주십시오. 이러면 중복 키가 트리거 될 수 있습니다. 많은 사람들이 테마를 바꾸기 위해 "ThemeProvider" 위젯을 사용하고 있는데 **GetX**는 이런 방식이 필요 없습니다. 다른 표준사항은 없이 `Get.changeTheme`로 추가하고 간단하게 커스텀 테마를 만들수 있습니다: ```dart Get.changeTheme(ThemeData.light()); ``` `onTap`에 테마 변경이 있는 버튼 같은 무언가를 만들고 싶다면 두개의 **GetX™** API를 조합하여 가능합니다: - 다크`테마`를 사용중인지 확인합니다. - 그리고 `테마` 변경 API 를 `onPressed`에 넣으면 됩니다: ```dart Get.changeTheme(Get.isDarkMode? ThemeData.light(): ThemeData.dark()); ``` `.darkmode`가 활성화 될때 _light theme_ 로 바뀔것 이고 _light theme_ 가 활성화되면 _dark theme_ 로 변경될 것입니다. ## GetConnect GetConnect는 http나 websockets으로 프론트와 백엔드의 통신을 위한 쉬운 방법입니다. ### 기본 구성 GetConnect를 간단하게 확장하고 Rest API나 websockets의 GET/POST/PUT/DELETE/SOCKET 메서드를 사용할 수 있습니다. ```dart class UserProvider extends GetConnect { // Get request Future getUser(int id) => get('http://youapi/users/$id'); // Post request Future postUser(Map data) => post('http://youapi/users', body: data); // Post request with File Future> postCases(List image) { final form = FormData({ 'file': MultipartFile(image, filename: 'avatar.png'), 'otherFile': MultipartFile(image, filename: 'cover.png'), }); return post('http://youapi/users/upload', form); } GetSocket userMessages() { return socket('https://yourapi/users/socket'); } } ``` ### 커스텀 구성 GetConnect는 고도로 커스텀화 할 수 있습니다. base Url을 정의하고 응답자 및 요청을 수정하고 인증자를 정의할 수 있습니다. 그리고 인증 횟수까지 정의 할 수 있습니다. 더해서 추가 구성없이 모델로 응답을 변형시킬 수 있는 표준 디코더 정의도 가능합니다. ```dart class HomeProvider extends GetConnect { @override void onInit() { // 모든 요청은 jsonEncode로 CasesModel.fromJson()를 거칩니다. httpClient.defaultDecoder = CasesModel.fromJson; httpClient.baseUrl = 'https://api.covid19api.com'; // baseUrl = 'https://api.covid19api.com'; // [httpClient] 인스턴트 없이 사용하는경우 Http와 websockets의 baseUrl 정의 // 모든 요청의 헤더에 'apikey' 속성을 첨부합니다. httpClient.addRequestModifier((request) { request.headers['apikey'] = '12345678'; return request; }); // 서버가 "Brazil"이란 데이터를 보내더라도 // 응답이 전달되기 전에 응답의 데이터를 지우기 때문에 // 사용자에게 표시되지 않을 것입니다. httpClient.addResponseModifier((request, response) { CasesModel model = response.body; if (model.countries.contains('Brazil')) { model.countries.remove('Brazilll'); } }); httpClient.addAuthenticator((request) async { final response = await get("http://yourapi/token"); final token = response.body['token']; // 헤더 설정 request.headers['Authorization'] = "$token"; return request; }); // 인증자가 HttpStatus가 HttpStatus.unauthorized이면 // 3번 호출됩니다. httpClient.maxAuthRetries = 3; } } @override Future> getCases(String path) => get(path); } ``` ## GetPage Middleware GetPage는 GetMiddleWare의 목록을 특정 순서로 실행하는 새로운 프로퍼티를 가집니다. **주석**: GetPage가 Middleware를 가질때 페이지의 모든 하위는 같은 Middleware를 자동적으로 가지게 됩니다. ### Priority Middleware의 실행 순서는 GetMiddleware안의 priority에 따라서 설정할 수 있습니다. ```dart final middlewares = [ GetMiddleware(priority: 2), GetMiddleware(priority: 5), GetMiddleware(priority: 4), GetMiddleware(priority: -8), ]; ``` 이 Middleware는 다음 순서로 실행됩니다. **-8 => 2 => 4 => 5** ### Redirect 이 함수는 호출된 라우트의 페이지를 검색할때 호출됩니다. 리다이렉트한 결과로 RouteSettings을 사용합니다. 또는 null을 주면 리다이렉트 하지 않습니다. ```dart RouteSettings redirect(String route) { final authService = Get.find(); return authService.authed.value ? null : RouteSettings(name: '/login') } ``` ### onPageCalled 이 함수는 생성되지 않은 페이지가 호출될 때 호출됩니다. 페이지에 대한 어떤것을 변경하는데 사용하거나 새로운 페이지를 줄 수 있습니다. ```dart GetPage onPageCalled(GetPage page) { final authService = Get.find(); return page.copyWith(title: 'Welcome ${authService.UserName}'); } ``` ### OnBindingsStart 이 함수는 Bindings가 초기화되기 바로 직전에 호출됩니다. 여기에서 이 페이지를 위해 Bindings을 변경할 수 있습니다. ```dart List onBindingsStart(List bindings) { final authService = Get.find(); if (authService.isAdmin) { bindings.add(AdminBinding()); } return bindings; } ``` ### OnPageBuildStart 이 함수는 Bindings가 초기화된 직후에 호출됩니다. 여기에서 bindings를 생성한 후 페이지 위젯을 생성하기 전에 무엇이든 할 수 있습니다. ```dart GetPageBuilder onPageBuildStart(GetPageBuilder page) { print('bindings are ready'); return page; } ``` ### OnPageBuilt 이 함수는 GetPage.page 함수가 호출된 직후에 호출며 함수의 결과를 제공합니다. 그리고 표시될 위젯을 가져옵니다. ### OnPageDispose 이 함수는 페이지의 연관된 모든 오브젝트들(Controllers, views, ...)이 해제된 직후에 호출됩니다. ## 기타 고급 API ```dart // currentScreen에서 현재 인수들을 제공 Get.arguments // 이전 경로의 이름을 제공 Get.previousRoute // rawRoute.isFirst()와 같은 접근에 필요한 원시 경로를 제공 Get.rawRoute // GetObserver로 부터 Rounting API의 접근을 제공 Get.routing // snackbar가 열려 있는지 확인 Get.isSnackbarOpen // dialog가 열려 있는지 확인 Get.isDialogOpen // bottomsheet가 열려 있는지 확인 Get.isBottomSheetOpen // 1개의 경로 제거 Get.removeRoute() // 값이 true가 될때까지 반복적으로 되돌림 Get.until() // 다음 경로로 이동하고 값이 true가 될때까지 이전 모든 경로를 제거 Get.offUntil() // 명명된 다음 경로로 이동하고 값이 true가 될때까지 이전 모든 경로를 제거 Get.offNamedUntil() // 앱이 구동중인 플랫폼을 확인 GetPlatform.isAndroid GetPlatform.isIOS GetPlatform.isMacOS GetPlatform.isWindows GetPlatform.isLinux GetPlatform.isFuchsia // 장치 타입을 확인 GetPlatform.isMobile GetPlatform.isDesktop // 모든 플랫폼은 독립적으로 웹을 제공합니다! // Windows, iOS, OSX, Android 등의 // 브러우저에서 구동중이면 알 수 있습니다. GetPlatform.isWeb // MediaQuery.of(context).size.height 과 동일 // 하지만 불변함. Get.height Get.width // Navigator의 현재 context를 제공 Get.context // 코드 어디에서든지 foreground에서 snackbar/dialog/bottomsheet의 context를 제공 Get.contextOverlay // 주석: 다음 메서드는 context의 확장입니다. // UI의 모든 위치에서 컨텍스트에 액세스 할 수 있으므로 UI 코드의 어느 곳에서나 사용할 수 있습니다. // 변경되는 height/width(데스크탑이나 브라우저와 같이 늘어날 수 있는 것)가 필요하면 context를 사용해야함 context.width context.height // 화면의 절반, 1/3 등을 정의할 수 있는 기능을 제공합니다. // 반응성이 높은 앱에 유용합니다. // param dividedBy (double) optional - default: 1 // param reducedBy (double) optional - default: 0 context.heightTransformer() context.widthTransformer() /// MediaQuery.of(context).size 와 유사함 context.mediaQuerySize() /// MediaQuery.of(context).padding 와 유사함 context.mediaQueryPadding() /// MediaQuery.of(context).viewPadding 와 유사함 context.mediaQueryViewPadding() /// MediaQuery.of(context).viewInsets; 와 유사함 context.mediaQueryViewInsets() /// MediaQuery.of(context).orientation; 와 유사함 context.orientation() /// 장치의 가로 모드 확인 context.isLandscape() /// 장치의 세로 모드 확인 context.isPortrait() /// MediaQuery.of(context).devicePixelRatio; 와 유사함 context.devicePixelRatio() /// MediaQuery.of(context).textScaleFactor; 와 유사함 context.textScaleFactor() /// 화면에서 shortestSide를 제공 context.mediaQueryShortestSide() /// True if width be larger than 800 context.showNavbar() /// shortestSide가 600p 미만이면 True context.isPhone() /// shortestSide가 600p 이상이면 True context.isSmallTablet() /// shortestSide가 720p 이상이면 True context.isLargeTablet() /// 현재 장치가 Tablet이면 True context.isTablet() /// 화면 사이즈에 따라 value를 반환 /// 반환될 수 있는 값들: /// watch: shortestSide가 300 미만일 때 /// mobile: shortestSide가 600 미만일 때 /// tablet: shortestSide가 1200 미만일 때 /// desktop: shortestSide가 1200 이상일 때 context.responsiveValue() ``` ### 선택적 전역 설정과 수동 구성 GetMaterialApp은 모든 것이 구성되어 있지만 원한다면 수동으로 Get을 구성할 수 있습니다. ```dart MaterialApp( navigatorKey: Get.key, navigatorObservers: [GetObserver()], ); ``` `GetObserver`안에 Middleware를 사용할 수 있고 이로 인한 어떤 영향도 없습니다. ```dart MaterialApp( navigatorKey: Get.key, navigatorObservers: [ GetObserver(MiddleWare.observer) // Here ], ); ``` `Get`을 위한 _Global Settings_ 을 만들수 있습니다. 어떠한 라우트도 포함되기 전에 `Get.config`에 추가하십시오. 또는 `GetMaterialApp`에 직접 추가 하십시오. ```dart GetMaterialApp( enableLog: true, defaultTransition: Transition.fade, opaqueRoute: Get.isOpaqueRouteDefault, popGesture: Get.isPopGestureEnable, transitionDuration: Get.defaultDurationTransition, defaultGlobalState: Get.defaultGlobalState, ); Get.config( enableLog = true, defaultPopGesture = true, defaultTransition = Transitions.cupertino ) ``` 선택적으로 `Get`으로 모든 로그 메세지를 리다이렉트 할 수 있습니다. 만약 유명한 로그 패키지를 사용하고 싶으면 여기에서 원하는 로그가 있습니다: ```dart GetMaterialApp( enableLog: true, logWriterCallback: localLogWriter, ); void localLogWriter(String text, {bool isError = false}) { // pass the message to your favourite logging package here // please note that even if enableLog: false log messages will be pushed in this callback // you get check the flag if you want through GetConfig.isLogEnable } ``` ### 지역 상태 위젯들 이러한 위젯은 단일값을 관리하고 지역적이고 임시적인 상태를 유지합니다. 우리는 반응적이고 단순함을 위해 추가할 수 있습니다. 예를 들어 `TextField`의 obscureText의 전환으로 사용하거나 커스텀된 확장되는 패널을 만들거나 `Scaffold`의 body가 변경되는 동안 `BottomNavigationBar`의 현재 index를 수정할 수 있습니다. #### ValueBuilder 업데이트된 값을 되돌려 받는 `.setState`로 작동하는 `StatefulWidget`의 단순화 입니다. ```dart ValueBuilder( initialValue: false, builder: (value, updateFn) => Switch( value: value, onChanged: updateFn, // same signature! you could use ( newValue ) => updateFn( newValue ) ), // if you need to call something outside the builder method. onUpdate: (value) => print("Value updated: $value"), onDispose: () => print("Widget unmounted"), ), ``` #### ObxValue [`ValueBuilder`](#valuebuilder)와 비슷하지만 Rx 인스턴스(마법같은 .obs를 기억하세요)를 전달하고 자동적으로 업데이트되는 반응형 버전입니다... 놀랍지 않습니까? ```dart ObxValue((data) => Switch( value: data.value, onChanged: data, // Rx에는 호출가능한 함수가 있습니다! (flag) => data.value = flag, 가 사용가능 합니다. ), false.obs, ), ``` ## 유용한 팁 `.obs`(_Rx_ 타입이라고 알려진)는 다양한 내부 메서드와 연산자가 있습니다. > `.obs`프로퍼티가 **실제 값**이라고 _믿는_ 것은 일반적이지만 실수하지 마십시오! > 다트의 컴파일러는 충분히 똑똑하고 코드가 깔끔하기 때문에 변수의 타입 선언을 하지 않습니다. > 하지만: ```dart var message = 'Hello world'.obs; print( 'Message "$message" has Type ${message.runtimeType}'); ``` `message`가 실제 String 값을 _출력_ 하더라도 타입은 **RxString**입니다! 그래서 `message.substring( 0, 4 )`은 사용하지 못합니다. _observable(.obs)_ 안의 실제 값에 접근해야 합니다: 가장 많이 사용되는 방법은 `.value`지만 사용할 수 있었는지 알고 있었나요... ```dart final name = 'GetX'.obs; // 현재 값과 다른 값이면 stream을 업데이트만 합니다. name.value = 'Hey'; // 모든 Rx 프로퍼티가 "호출 가능"하고 새로운 값을 반환합니다. // 하지만 이 접근방식은 `null`를 허용하지 않고 UI가 재구축하지 않습니다. name('Hello'); // getter와 과 같이 'Hello'를 출력합니다. name() ; /// 숫자 타입들: final count = 0.obs; // 기존 숫자 타입으로 모든 변형 불가 작업을 사용할수 있습니다. count + 1; // 주의하세요! 아래는 `count`가 final이 아닌 경우에만 유효합니다. count += 1; // 값들을 비교할 수도 있습니다: count > 2; /// booleans: final flag = false.obs; // true/false 사이의 전환이 됩니다. flag.toggle(); /// 모든 타입들: // `값`을 null로 셋합니다. flag.nil(); // 모든 toString(), toJson() 함수들은 `값`으로 전달됩니다. print( count ); // RxInt 내부에서 `toString()`이 호출됩니다. final abc = [0,1,2].obs; // 값을 json 배열로 바꾸고 RxList를 출력합니다. // Json은 모든 Rx 타입들을 지원합니다! print('json: ${jsonEncode(abc)}, type: ${abc.runtimeType}'); // RxMap, RxList 그리고 RxSet은 그들의 native 타입들을 확장한 특별한 Rx 타입들입니다. // 반응형이긴 하지만 일반 list로서 RxList가 동작합니다! abc.add(12); // list에 12가 들어가고 stream을 업데이트합니다. abc[3]; // List와 같이 인덱스 3을 읽습니다. // 동등비교는 Rx와 값에서 동작하지만 해시코드는 항상 값으로부터 받습니다. final number = 12.obs; print( number == 12 ); // prints > true /// 커스텀 Rx 모델들: // toJson(), toString()은 child에게 지연됩니다. 그래서 이것들을 재정의 하고 직접 관찰하여 print() 할수 있습니다. class User { String name, last; int age; User({this.name, this.last, this.age}); @override String toString() => '$name $last, $age years old'; } final user = User(name: 'John', last: 'Doe', age: 33).obs; // `user`는 "반응형"이지만 내부 프로퍼티들은 아닙니다! // 그래서 만약 내부의 변수를 바꾸면... user.value.name = 'Roi'; // 위젯은 재구성 되지 않을것 입니다! // user의 내부의 무언가가 바뀌어도 `Rx`는 알 수가 않습니다. // 그래서 커스텀 클래스들은 수동으로 바뀌었다고 "알릴" 필요가 있습니다. user.refresh(); // 또는 `update()` 함수를 사용할 수 있습니다! user.update((value){ value.name='Roi'; }); print( user ); ``` ## StateMixin `UI` 상태를 처리하는 또 다른 방법은 `StateMixin` 를 사용하는 것입니다. 이를 구현하려면 `with`를 사용하여 `StateMixin`을 추가하고 T 모델을 허용하는 컨트롤러에 연결합니다. ``` dart class Controller extends GetController with StateMixin{} ``` `change()` 메소드는 우리가 원할 때마다 State를 변경합니다. 다음과 같이 데이터와 상태를 전달하면 됩니다: ```dart change(data, status: RxStatus.success()); ``` RxStatus는 다음 상태를 허용합니다: ``` dart RxStatus.loading(); RxStatus.success(); RxStatus.empty(); RxStatus.error('message'); ``` UI에서 사용하는 방법: ```dart class OtherClass extends GetView { @override Widget build(BuildContext context) { return Scaffold( body: controller.obx( (state)=>Text(state.name), // 여기에 사용자 정의 로딩 표시기를 넣을 수 있지만 // 기본값은 Center(child:CircularProgressIndicator()) 입니다 onLoading: CustomLoadingIndicator(), onEmpty: Text('No data found'), // 여기에서도 자신의 오류 위젯을 설정할 수 있지만 // 기본값은 Center(child:Text(error)) 입니다 onError: (error)=>Text(error), ), ); } ``` #### GetView 이 위젯을 사랑합니다. 매우 간단하고 유용합니다! 등록된 `Controller`인 `controller`의 getter로 가져온 `const Stateless`위젯 입니다. 이게 전부입니다. ```dart class AwesomeController extends GetxController { final String title = 'My Awesome View'; } // controller를 등록할때 사용한 `타입`을 전달하는 것을 항상 기억하세요! class AwesomeView extends GetView { @override Widget build(BuildContext context) { return Container( padding: EdgeInsets.all(20), child: Text(controller.title), // 단지 `controller.something`을 호출합니다. ); } } ``` #### GetResponsiveView ResponsiveView 를 빌드하려면 이 위젯을 확장하세요. 이 위젯에는 화면 크기 및 유형에 대한 모든 정보가 있는 화면 속성이 포함되어 있습니다. ##### 사용 방법 그것을 빌드하기 위한 두가지 옵션이 있습니다. - `builder` method 를 사용하면 빌드 할 위젯을 반환합니다. - `desktop`, `tablet`, `phone`, `watch` method를 사용하면 특정 메소드는 screen type 이 일치할때 빌드됩니다. Screen type이 [ScreenType.Tablet] 일때, tablet method 가 스며나옵니다. 참고: 만약 이 method 를 사용할 경우, `alwaysUseBuilder` 프로퍼티를 `false` 로 설정해주세요. `settings` 프로퍼티를 사용하면 screen types 를 위한 width limit 를 설정할 수 있습니다. ![example](https://github.com/SchabanBo/get_page_example/blob/master/docs/Example.gif?raw=true) Code to this screen [code](https://github.com/SchabanBo/get_page_example/blob/master/lib/pages/responsive_example/responsive_view.dart) #### GetWidget 대부분의 사람들이 이 위젯에대해 모르거나 사용법을 완전히 혼동합니다. 사용 사례는 매우 드물지만 매우 구체적입니다: Controller를 `caches`합니다. _cache_ 이기 때문에 `const Stateless`가 될 수 없습니다. > 그러면 언제 Controller를 "cache"할 필요가 있을까요? 만약 **GetX**의 기능 중 또 다른 "흔하지 않은" 기능을 사용하는 경우:`Get.create()` `Get.create(()=>Controller())`가 `Get.find()`을 호출할 때마다 새로운 `Controller`를 생성할 것 입니다. 여기서 `GetWidget`이 빛나게 됩니다... 예를 들어 Todo 리스트를 유지하려고 사용할 때 입니다. 위젯이 "재구성"될때 동일한 controller 인스턴스를 유지할 것입니다. #### GetxService 이 클래스틑 `GetxController`와 같이 동일한 생성주기(`onInit()`, `onReady()`, `onClose()`)를 공유합니다. 하지만 이안에 "로직"은 없습니다. 단지 **GetX** 종속성 주입 시스템이 하위클래스를 메모리에서 **삭제할 수 없음**을 알립니다. 그래서 `Get.find()`로 활성화하고 항상 접근하는 "서비스들"을 유지하는데 매우 유용합니다. : `ApiService`, `StorageService`, `CacheService`. ```dart Future main() async { await initServices(); /// 서비스들 초기화를 기다림. runApp(SomeApp()); } /// 플러터 앱이 실행되기 전에 서비스들을 초기화하는 현명한 방법입니다. /// 실행 흐름을 제어 할수 있으므로(테마 구성, apiKey, 사용자가 정의한 언어등을 로드해야 할 필요가 있으므로 /// ApiService의 구동전에 SettingService를 로드해야 합니다. /// 그래서 GetMaterialApp()은 재구성하지 않고 직접적으로 값을 가져옵니다. Future initServices() async { print('starting services ...'); /// 여기에서 get_storage, hive, shared_pref 초기화를 하세요. /// 또는 연결 고정 또는 비동기적인 무엇이든 하세요. await Get.putAsync(() => DbService().init()); await Get.putAsync(SettingsService()).init(); print('All services started...'); } class DbService extends GetxService { Future init() async { print('$runtimeType delays 2 sec'); await 2.delay(); print('$runtimeType ready!'); return this; } } class SettingsService extends GetxService { void init() async { print('$runtimeType delays 1 sec'); await 1.delay(); print('$runtimeType ready!'); } } ``` `GetxService`를 실질적으로 지우는 한가지 방법은 앱의 "Hot Reboot"과 같은 `Get.reset()`뿐 입니다. 따라서 앱 실행중 절대로 유지되어야 하는 클래스 인스턴스가 필요하면 `GetxService`를 사용하세요. ### 테스트 당신은 당신의 컨트롤러들을 생성주기를 포함하여 다른 어떤 클래스처럼 테스트할 수 있습니다 : ```dart class Controller extends GetxController { @override void onInit() { super.onInit(); //name2로 값 변경 name.value = 'name2'; } @override void onClose() { name.value = ''; super.onClose(); } final name = 'name1'.obs; void changeName() => name.value = 'name3'; } void main() { test(''' Test the state of the reactive variable "name" across all of its lifecycles''', () { /// 당신은 생성주기를 제외하고 컨트롤러를 테스트할 수 있습니다, /// 그러나 당신이 사용하지 않는다면 추천되지 않습니다 /// GetX 종속성 주입 final controller = Controller(); expect(controller.name.value, 'name1'); /// 당신이 그것을 사용한다면, 당신은 모든 것을 테스트할 수 있습니다, /// 각각의 생성주기 이후 어플리케이션의 상태를 포함하여. Get.put(controller); // onInit was called expect(controller.name.value, 'name2'); /// 당신의 함수를 테스트하세요 controller.changeName(); expect(controller.name.value, 'name3'); /// onClose 호출됨 Get.delete(); expect(controller.name.value, ''); }); } ``` #### 팁들 ##### Mockito 또는 mocktail 당신이 당신의 GetxController/GetxService를 모킹하려고 한다면, 당신은 GetxController를 extend 하고, Mock과 mixin 하라, 그렇게 되면 ```dart class NotificationServiceMock extends GetxService with Mock implements NotificationService {} ``` ##### Get.reset() 사용하기 당신이 위젯 또는 테스트 그룹을 테스트하고 있다면, 당신의 테스트의 마지막 또는 해제 때 당신의 이전 테스트에서 모든 설정을 리셋하기 위해 Get.rest을 사용하십시오 ##### Get.testMode 당신이 당신의 컨트롤러에서 당신의 네비게이션을 사용하고 있다면, 당신의 메인의 시작에 `Get.testMode = true` 를 사용하십시오. # 2.0의 주요 변경점 1- Rx 타입들: | 이전 | 이후 | | ------- | ---------- | | StringX | `RxString` | | IntX | `RxInt` | | MapX | `RxMap` | | ListX | `RxList` | | NumX | `RxNum` | | DoubleX | `RxDouble` | RxController와 GetBuilder는 합쳐졌습니다. 더이상 사용할 controller를 기억시킬 필요가 없습니다. GetxController를 사용하세요. 단순 및 반응형 상태관리 모두에서 잘 동작합니다. 2- 명명된 라우트 이전: ```dart GetMaterialApp( namedRoutes: { '/': GetRoute(page: Home()), } ) ``` 지금: ```dart GetMaterialApp( getPages: [ GetPage(name: '/', page: () => Home()), ] ) ``` 무엇이 달라졌습니까? 종종 매개 변수 또는 로그인 토큰에 의해 표시 할 페이지를 결정해야 할 수 있습니다. 이전 접근 방식은 이를 허용하지 않았기 때문에 유연하지 않았습니다. 페이지를 함수에 삽입하면 앱이 시작된 이후 라우트가 메모리에 할당되지 않고 이러한 유형의 접근 방식이 가능하기 때문에 RAM 소비가 크게 감소했습니다: ```dart GetStorage box = GetStorage(); GetMaterialApp( getPages: [ GetPage(name: '/', page:(){ return box.hasData('token') ? Home() : Login(); }) ] ) ``` # 왜 Getx인가? 1- 플러터가 업데이트된 이후 자주 많은 패키지가 깨졌을 것입니다. 때때로 컴파일중 에러가 발생하고 종종 이에 대해 답변을 해줄 사람이 없었을 겁니다. 그리고 개발자는 에러가 어디에서 발생했는지 추적해서 알아야합니다. 그리고 오직 리포지트리를 통해서 이슈를 오픈하고 해결책을 찾아야합니다. Get은 개발을 위한 주 리소스들(상태, 종속성, 라우트 관리)을 중앙화합니다. pubspec에 단일 패키지를 추가하고 작업을 시작 할 수 있습니다. 플러터가 업데이트 된 이후에도 Get 의존을 업데이트하면 작업할 수 있습니다. Get은 호환성 이슈도 해결합니다. 한 버전에서 종속적으로 사용하여 다른 버전에서 다른 버전을 사용할때 패키지 버전이 다른 버전과 호환되지 않는 경우가 몇 번입니까? 모든 것이 동일한 패키지에 있고 완벽하게 호환되므로 Get을 사용하면 문제가 되지 않습니다. 2- 플러터는 쉽고 놀랍지만 대다수의 개발자들이 원하지 않는 몇가지 상용구가 있습니다. `Navigator.of(context).push (context, builder [...]` 같은 것들 입니다. Get은 개발을 단순화합니다. 라우트를 위해 8줄의 코드를 작성하고 `Get.to(Home())`만 하면 다음 페이지로 갈 수 있습니다. 동적 웹 url은 현재 플러터에서 정말로 고통스러운 것이고 GetX로 하는 것은 정말 간단합니다. 플러터에서 상태와 종속성을 관리하는 것은 pub에서 수백가지의 패턴이라 많은 논의를 생산합니다. 그러나 변수 끝에 ".obs"를 추가하고 위젯을 Obx 안에 배치하는 것만큼 쉬운 것은 없습니다. 이것으로 해당 변수가 업데이트되면 화면에 자동으로 업데이트됩니다. 3- 성능에 대하여 걱정하지 않아도 됩니다. 플러터의 성능은 이미 놀랍습니다. 하지만 상태관리자를 사용하고 blocs/stores/controllers 등의 클래스들을 로케이터로 배포하는 것을 상상해보십시오. 종속성이 필요 없는 경우 종속성 제외를 수동으로 호출해야 합니다. 하지만 간단하게 controller를 사용하고 이것들을 더이상 사용하지 않을때 간단하게 메모리에서 삭제될수 있을까요? 이것이 GetX가 하는 일입니다. SmartManagement를 사용하면 사용하지 않는 모든것이 메모리에서 삭제되기 때문에 프로그래밍 말고 다른 걱정을 할 필요가 없습니다. 이러한 로직을 만들지 않고도 최소한의 리소스만 사용함을 보장합니다. 4- 실질적으로 분리됨. "비즈니스 로직으로부터 뷰를 분리"라는 개념을 들어보셨을 겁니다. 이것은 BLoC, MVC, MVVM의 특징이 아니며 이미 나와 있는 또 다른 표준 개념입니다. 그러나 이 개념은 context의 사용으로 인해 플러터에서 종종 완화됩니다. 만약 InheritedWidget을 찾기 위해 context가 필요하면 뷰나 파라미터로 context를 전달해야 합니다. 저는 특히 이 방식이 매우 별로이고 팀의 작업이 항상 뷰의 비즈니스 로직에 의존하게 됩니다. GetX는 표준 접근에서 비정통적이고 StatefulWidgets, InitState 등의 사용을 완전 배제하지 않지만 항상 깔끔하게 유사한 접근을 제공합니다. 예를 들어 Controllers의 수명주기에서 APIREST 요청이 필요할 때 뷰에 어떤 것도 의존할 필요가 없습니다. http 호출의 초기화를 onInit 에서 사용가능 하고 데이터가 도착하면 변수들이 채워집니다. GetX는 완전히 반응형이며(실제 스트림으로 동작) 아이탬중 하나가 채워지면 이 변수를 사용중인 모든 위젯이 자동적으로 화면에서 갱신됩니다. 이를 통해 UI 전문가는 위젯으로만 작업하고 사용자 이벤트(예 : 버튼 클릭) 이외의 비즈니스 로직에 아무것도 보낼 필요가 없으며 비즈니스 로직을 개발하는 사람들은 비즈니스 로직을 별도로 만들고 테스트 할 수 있습니다. 이 라이브러리는 항상 업데이트되고 새로운 기능이 포함됩니다. 자유롭게 PR을 제공하고 여기에 기여하세요. # 커뮤니티 ## 커뮤니티 채널 GetX에는 매우 활동적이고 유용한 커뮤니티가 있습니다. 이 프레임워크의 사용과 관련하여 질문이 있거나 도움이 필요한 경우 커뮤니티 채널에 가입하십시오. 질문에 대한 답변이 더 빨리 제공되며 가장 적합한 장소가 될 것입니다. 이 저장소는 이슈오픈 및 리소스 요청 전용이지만 GetX 커뮤니티의 일부에 속해있습니다. | **Slack** | **Discord** | **Telegram** | | :-------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------- | | [![Get on Slack](https://img.shields.io/badge/slack-join-orange.svg)](https://communityinviter.com/apps/getxworkspace/getx) | [![Discord Shield](https://img.shields.io/discord/722900883784073290.svg?logo=discord)](https://discord.com/invite/9Hpt99N) | [![Telegram](https://img.shields.io/badge/chat-on%20Telegram-blue.svg)](https://t.me/joinchat/PhdbJRmsZNpAqSLJL6bH7g) | ## 기여하는 방법 _프로젝트에 기여하고 싶으신가요? 우리는 귀하를 우리의 협력자 중 한 명으로 부각시켜 자랑스럽게 생각합니다. 다음은 Get(그리고 플러터)을 더욱 향상시키고 기여할 수 있는 몇 가지 사항입니다._ - readme을 다른 언어로 번역하는 데 도움이 됩니다. - readme에 문서를 추가합니다(Get의 많은 기능이 아직 문서화되지 않았습니다). - Get 사용법을 설명하는 기사를 쓰거나 비디오를 만듭니다(읽기 및 향후 위키에 삽입될 예정). - 코드/테스트에 대한 PR을 제공합니다. - 새로운 기능을 포함합니다. 어떤 기여도 환영합니다! ## 기사 및 비디오 - [Flutter Getx EcoSystem package for arabic people](https://www.youtube.com/playlist?list=PLV1fXIAyjeuZ6M8m56zajMUwu4uE3-SL0) - Tutorial by [Pesa Coder](https://github.com/UsamaElgendy). - [Dynamic Themes in 3 lines using GetX™](https://medium.com/swlh/flutter-dynamic-themes-in-3-lines-c3b375f292e3) - Tutorial by [Rod Brown](https://github.com/RodBr). - [Complete GetX™ Navigation](https://www.youtube.com/watch?v=RaqPIoJSTtI) - Route management video by Amateur Coder. - [Complete GetX State Management](https://www.youtube.com/watch?v=CNpXbeI_slw) - State management video by Amateur Coder. - [GetX™ Other Features](https://youtu.be/ttQtlX_Q0eU) - Utils, storage, bindings and other features video by Amateur Coder. - [Firestore User with GetX | Todo App](https://www.youtube.com/watch?v=BiV0DcXgk58) - Video by Amateur Coder. - [Firebase Auth with GetX | Todo App](https://www.youtube.com/watch?v=-H-T_BSgfOE) - Video by Amateur Coder. - [The Flutter GetX™ Ecosystem ~ State Management](https://medium.com/flutter-community/the-flutter-getx-ecosystem-state-management-881c7235511d) - State management by [Aachman Garg](https://github.com/imaachman). - [The Flutter GetX™ Ecosystem ~ Dependency Injection](https://medium.com/flutter-community/the-flutter-getx-ecosystem-dependency-injection-8e763d0ec6b9) - Dependency Injection by [Aachman Garg](https://github.com/imaachman). - [GetX, the all-in-one Flutter package](https://www.youtube.com/watch?v=IYQgtu9TM74) - A brief tutorial covering State Management and Navigation by Thad Carnevalli. - [Build a To-do List App from scratch using Flutter and GetX](https://www.youtube.com/watch?v=EcnqFasHf18) - UI + State Management + Storage video by Thad Carnevalli. - [GetX Flutter Firebase Auth Example](https://medium.com/@jeffmcmorris/getx-flutter-firebase-auth-example-b383c1dd1de2) - Article by Jeff McMorris. - [Flutter State Management with GetX – Complete App](https://www.appwithflutter.com/flutter-state-management-with-getx/) - by App With Flutter. - [Flutter Routing with Animation using Get Package](https://www.appwithflutter.com/flutter-routing-using-get-package/) - by App With Flutter. - [A minimal example on dartpad](https://dartpad.dev/2b3d0d6f9d4e312c5fdbefc414c1727e?) - by [Roi Peker](https://github.com/roipeker) ================================================ FILE: README.md ================================================ ![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/get.png) [![pub package](https://img.shields.io/pub/v/get.svg?label=get&color=blue)](https://pub.dev/packages/get) [![popularity](https://img.shields.io/pub/popularity/get?logo=dart)](https://pub.dev/packages/get/score) [![likes](https://img.shields.io/pub/likes/get?logo=dart)](https://pub.dev/packages/get/score) [![pub points](https://img.shields.io/pub/points/sentry?logo=dart)](https://pub.dev/packages/get/score) ![building](https://github.com/jonataslaw/get/workflows/build/badge.svg) [![style: effective dart](https://img.shields.io/badge/style-effective_dart-40c4ff.svg)](https://pub.dev/packages/effective_dart) [![Discord Shield](https://img.shields.io/discord/722900883784073290.svg?logo=discord)](https://discord.com/invite/9Hpt99N) [![Get on Slack](https://img.shields.io/badge/slack-join-orange.svg)](https://communityinviter.com/apps/getxworkspace/getx) [![Telegram](https://img.shields.io/badge/chat-on%20Telegram-blue.svg)](https://t.me/joinchat/PhdbJRmsZNpAqSLJL6bH7g) Awesome Flutter Buy Me A Coffee ![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/getx.png)
**Languages:** [![English](https://img.shields.io/badge/Language-English-blueviolet?style=for-the-badge)](README.md) [![Vietnamese](https://img.shields.io/badge/Language-Vietnamese-blueviolet?style=for-the-badge)](README-vi.md) [![Indonesian](https://img.shields.io/badge/Language-Indonesian-blueviolet?style=for-the-badge)](README.id-ID.md) [![Urdu](https://img.shields.io/badge/Language-Urdu-blueviolet?style=for-the-badge)](README.ur-PK.md) [![Chinese](https://img.shields.io/badge/Language-Chinese-blueviolet?style=for-the-badge)](README.zh-cn.md) [![Portuguese](https://img.shields.io/badge/Language-Portuguese-blueviolet?style=for-the-badge)](README.pt-br.md) [![Spanish](https://img.shields.io/badge/Language-Spanish-blueviolet?style=for-the-badge)](README-es.md) [![Russian](https://img.shields.io/badge/Language-Russian-blueviolet?style=for-the-badge)](README.ru.md) [![Polish](https://img.shields.io/badge/Language-Polish-blueviolet?style=for-the-badge)](README.pl.md) [![Korean](https://img.shields.io/badge/Language-Korean-blueviolet?style=for-the-badge)](README.ko-kr.md) [![French](https://img.shields.io/badge/Language-French-blueviolet?style=for-the-badge)](README-fr.md) [![Japanese](https://img.shields.io/badge/Language-Japanese-blueviolet?style=for-the-badge)](README.ja-JP.md) [![Hindi](https://img.shields.io/badge/Language-Hindi-blueviolet?style=for-the-badge)](README-hi.md) [![Bangla](https://img.shields.io/badge/Language-Bangla-blueviolet?style=for-the-badge)](README-bn.md) [![Nepali](https://img.shields.io/badge/Language-Nepali-blueviolet?style=for-the-badge)](README-ne.md)
- [About Get](#about-get) - [Installing](#installing) - [Counter App with GetX](#counter-app-with-getx) - [The Three pillars](#the-three-pillars) - [State management](#state-management) - [Reactive State Manager](#reactive-state-manager) - [More details about state management](#more-details-about-state-management) - [Route management](#route-management) - [More details about route management](#more-details-about-route-management) - [Dependency management](#dependency-management) - [More details about dependency management](#more-details-about-dependency-management) - [Utils](#utils) - [Internationalization](#internationalization) - [Translations](#translations) - [Using translations](#using-translations) - [Locales](#locales) - [Change locale](#change-locale) - [System locale](#system-locale) - [Change Theme](#change-theme) - [GetConnect](#getconnect) - [Default configuration](#default-configuration) - [Custom configuration](#custom-configuration) - [GetPage Middleware](#getpage-middleware) - [Priority](#priority) - [Redirect](#redirect) - [onPageCalled](#onpagecalled) - [OnBindingsStart](#onbindingsstart) - [OnPageBuildStart](#onpagebuildstart) - [OnPageBuilt](#onpagebuilt) - [OnPageDispose](#onpagedispose) - [Other Advanced APIs](#other-advanced-apis) - [Optional Global Settings and Manual configurations](#optional-global-settings-and-manual-configurations) - [Local State Widgets](#local-state-widgets) - [ValueBuilder](#valuebuilder) - [ObxValue](#obxvalue) - [Useful tips](#useful-tips) - [GetView](#getview) - [GetResponsiveView](#getresponsiveview) - [How to use it](#how-to-use-it) - [GetWidget](#getwidget) - [GetxService](#getxservice) - [Breaking changes from 2.0](#breaking-changes-from-20) - [Why Getx?](#why-getx) - [Community](#community) - [Community channels](#community-channels) - [How to contribute](#how-to-contribute) - [Articles and videos](#articles-and-videos) # About Get - GetX is an extra-light and powerful solution for Flutter. It combines high-performance state management, intelligent dependency injection, and route management quickly and practically. - GetX has 3 basic principles. This means that these are the priority for all resources in the library: **PRODUCTIVITY, PERFORMANCE AND ORGANIZATION.** - **PERFORMANCE:** GetX is focused on performance and minimum consumption of resources. GetX does not use Streams or ChangeNotifier. - **PRODUCTIVITY:** GetX uses an easy and pleasant syntax. No matter what you want to do, there is always an easier way with GetX. It will save hours of development and will provide the maximum performance your application can deliver. Generally, the developer should be concerned with removing controllers from memory. With GetX this is not necessary because resources are removed from memory when they are not used by default. If you want to keep it in memory, you must explicitly declare "permanent: true" in your dependency. That way, in addition to saving time, you are less at risk of having unnecessary dependencies on memory. Dependency loading is also lazy by default. - **ORGANIZATION:** GetX allows the total decoupling of the View, presentation logic, business logic, dependency injection, and navigation. You do not need context to navigate between routes, so you are not dependent on the widget tree (visualization) for this. You don't need context to access your controllers/blocs through an inheritedWidget, so you completely decouple your presentation logic and business logic from your visualization layer. You do not need to inject your Controllers/Models/Blocs classes into your widget tree through `MultiProvider`s. For this, GetX uses its own dependency injection feature, decoupling the DI from its view completely. With GetX you know where to find each feature of your application, having clean code by default. In addition to making maintenance easy, this makes the sharing of modules something that until then in Flutter was unthinkable, something totally possible. BLoC was a starting point for organizing code in Flutter, it separates business logic from visualization. GetX is a natural evolution of this, not only separating the business logic but the presentation logic. Bonus injection of dependencies and routes are also decoupled, and the data layer is out of it all. You know where everything is, and all of this in an easier way than building a hello world. GetX is the easiest, practical, and scalable way to build high-performance applications with the Flutter SDK. It has a large ecosystem around it that works perfectly together, it's easy for beginners, and it's accurate for experts. It is secure, stable, up-to-date, and offers a huge range of APIs built-in that are not present in the default Flutter SDK. - GetX is not bloated. It has a multitude of features that allow you to start programming without worrying about anything, but each of these features are in separate containers and are only started after use. If you only use State Management, only State Management will be compiled. If you only use routes, nothing from the state management will be compiled. - GetX has a huge ecosystem, a large community, a large number of collaborators, and will be maintained as long as the Flutter exists. GetX too is capable of running with the same code on Android, iOS, Web, Mac, Linux, Windows, and on your server. **It is possible to fully reuse your code made on the frontend on your backend with [Get Server](https://github.com/jonataslaw/get_server)**. **In addition, the entire development process can be completely automated, both on the server and on the front end with [Get CLI](https://github.com/jonataslaw/get_cli)**. **In addition, to further increase your productivity, we have the [extension to VSCode](https://marketplace.visualstudio.com/items?itemName=get-snippets.get-snippets) and the [extension to Android Studio/Intellij](https://plugins.jetbrains.com/plugin/14975-getx-snippets)** # Installing Add Get to your pubspec.yaml file: ```yaml dependencies: get: ``` Import get in files that it will be used: ```dart import 'package:get/get.dart'; ``` # Counter App with GetX The "counter" project created by default on new project on Flutter has over 100 lines (with comments). To show the power of Get, I will demonstrate how to make a "counter" changing the state with each click, switching between pages and sharing the state between screens, all in an organized way, separating the business logic from the view, in ONLY 26 LINES CODE INCLUDING COMMENTS. - Step 1: Add "Get" before your MaterialApp, turning it into GetMaterialApp ```dart void main() => runApp(GetMaterialApp(home: Home())); ``` - Note: this does not modify the MaterialApp of the Flutter, GetMaterialApp is not a modified MaterialApp, it is just a pre-configured Widget, which has the default MaterialApp as a child. You can configure this manually, but it is definitely not necessary. GetMaterialApp will create routes, inject them, inject translations, inject everything you need for route navigation. If you use Get only for state management or dependency management, it is not necessary to use GetMaterialApp. GetMaterialApp is necessary for routes, snackbars, internationalization, bottomSheets, dialogs, and high-level apis related to routes and absence of context. - Note²: This step is only necessary if you gonna use route management (`Get.to()`, `Get.back()` and so on). If you not gonna use it then it is not necessary to do step 1 - Step 2: Create your business logic class and place all variables, methods and controllers inside it. You can make any variable observable using a simple ".obs". ```dart class Controller extends GetxController{ var count = 0.obs; increment() => count++; } ``` - Step 3: Create your View, use StatelessWidget and save some RAM, with Get you may no longer need to use StatefulWidget. ```dart class Home extends StatelessWidget { @override Widget build(context) { // Instantiate your class using Get.put() to make it available for all "child" routes there. final Controller c = Get.put(Controller()); return Scaffold( // Use Obx(()=> to update Text() whenever count is changed. appBar: AppBar(title: Obx(() => Text("Clicks: ${c.count}"))), // Replace the 8 lines Navigator.push by a simple Get.to(). You don't need context body: Center(child: ElevatedButton( child: Text("Go to Other"), onPressed: () => Get.to(Other()))), floatingActionButton: FloatingActionButton(child: Icon(Icons.add), onPressed: c.increment)); } } class Other extends StatelessWidget { // You can ask Get to find a Controller that is being used by another page and redirect you to it. final Controller c = Get.find(); @override Widget build(context){ // Access the updated count variable return Scaffold(body: Center(child: Text("${c.count}"))); } } ``` Result: ![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/counter-app-gif.gif) This is a simple project but it already makes clear how powerful Get is. As your project grows, this difference will become more significant. Get was designed to work with teams, but it makes the job of an individual developer simple. Improve your deadlines, deliver everything on time without losing performance. Get is not for everyone, but if you identified with that phrase, Get is for you! # The Three pillars ## State management Get has two different state managers: the simple state manager (we'll call it GetBuilder) and the reactive state manager (GetX/Obx) ### Reactive State Manager Reactive programming can alienate many people because it is said to be complicated. GetX turns reactive programming into something quite simple: - You won't need to create StreamControllers. - You won't need to create a StreamBuilder for each variable - You will not need to create a class for each state. - You will not need to create a get for an initial value. - You will not need to use code generators Reactive programming with Get is as easy as using setState. Let's imagine that you have a name variable and want that every time you change it, all widgets that use it are automatically changed. This is your count variable: ```dart var name = 'Jonatas Borges'; ``` To make it observable, you just need to add ".obs" to the end of it: ```dart var name = 'Jonatas Borges'.obs; ``` And in the UI, when you want to show that value and update the screen whenever the values changes, simply do this: ```dart Obx(() => Text("${controller.name}")); ``` That's all. It's _that_ simple. ### More details about state management **See an more in-depth explanation of state management [here](./documentation/en_US/state_management.md). There you will see more examples and also the difference between the simple state manager and the reactive state manager** You will get a good idea of GetX power. ## Route management If you are going to use routes/snackbars/dialogs/bottomsheets without context, GetX is excellent for you too, just see it: Add "Get" before your MaterialApp, turning it into GetMaterialApp ```dart GetMaterialApp( // Before: MaterialApp( home: MyHome(), ) ``` Navigate to a new screen: ```dart Get.to(NextScreen()); ``` Navigate to new screen with name. See more details on named routes [here](./documentation/en_US/route_management.md#navigation-with-named-routes) ```dart Get.toNamed('/details'); ``` To close snackbars, dialogs, bottomsheets, or anything you would normally close with Navigator.pop(context); ```dart Get.back(); ``` To go to the next screen and no option to go back to the previous screen (for use in SplashScreens, login screens, etc.) ```dart Get.off(NextScreen()); ``` To go to the next screen and cancel all previous routes (useful in shopping carts, polls, and tests) ```dart Get.offAll(NextScreen()); ``` Noticed that you didn't have to use context to do any of these things? That's one of the biggest advantages of using Get route management. With this, you can execute all these methods from within your controller class, without worries. ### More details about route management **Get works with named routes and also offers lower-level control over your routes! There is in-depth documentation [here](./documentation/en_US/route_management.md)** ## Dependency management Get has a simple and powerful dependency manager that allows you to retrieve the same class as your Bloc or Controller with just 1 lines of code, no Provider context, no inheritedWidget: ```dart Controller controller = Get.put(Controller()); // Rather Controller controller = Controller(); ``` - Note: If you are using Get's State Manager, pay more attention to the bindings API, which will make it easier to connect your view to your controller. Instead of instantiating your class within the class you are using, you are instantiating it within the Get instance, which will make it available throughout your App. So you can use your controller (or class Bloc) normally **Tip:** Get dependency management is decoupled from other parts of the package, so if for example, your app is already using a state manager (any one, it doesn't matter), you don't need to rewrite it all, you can use this dependency injection with no problems at all ```dart controller.fetchApi(); ``` Imagine that you have navigated through numerous routes, and you need data that was left behind in your controller, you would need a state manager combined with the Provider or Get_it, correct? Not with Get. You just need to ask Get to "find" for your controller, you don't need any additional dependencies: ```dart Controller controller = Get.find(); //Yes, it looks like Magic, Get will find your controller, and will deliver it to you. You can have 1 million controllers instantiated, Get will always give you the right controller. ``` And then you will be able to recover your controller data that was obtained back there: ```dart Text(controller.textFromApi); ``` ### More details about dependency management **See a more in-depth explanation of dependency management [here](./documentation/en_US/dependency_management.md)** # Utils ## Internationalization ### Translations Translations are kept as a simple key-value dictionary map. To add custom translations, create a class and extend `Translations`. ```dart import 'package:get/get.dart'; class Messages extends Translations { @override Map> get keys => { 'en_US': { 'hello': 'Hello World', }, 'de_DE': { 'hello': 'Hallo Welt', } }; } ``` #### Using translations Just append `.tr` to the specified key and it will be translated, using the current value of `Get.locale` and `Get.fallbackLocale`. ```dart Text('title'.tr); ``` #### Using translation with singular and plural ```dart var products = []; Text('singularKey'.trPlural('pluralKey', products.length, Args)); ``` #### Using translation with parameters ```dart import 'package:get/get.dart'; Map> get keys => { 'en_US': { 'logged_in': 'logged in as @name with email @email', }, 'es_ES': { 'logged_in': 'iniciado sesión como @name con e-mail @email', } }; Text('logged_in'.trParams({ 'name': 'Jhon', 'email': 'jhon@example.com' })); ``` ### Locales Pass parameters to `GetMaterialApp` to define the locale and translations. ```dart return GetMaterialApp( translations: Messages(), // your translations locale: Locale('en', 'US'), // translations will be displayed in that locale fallbackLocale: Locale('en', 'UK'), // specify the fallback locale in case an invalid locale is selected. ); ``` #### Change locale Call `Get.updateLocale(locale)` to update the locale. Translations then automatically use the new locale. ```dart var locale = Locale('en', 'US'); Get.updateLocale(locale); ``` #### System locale To read the system locale, you could use `Get.deviceLocale`. ```dart return GetMaterialApp( locale: Get.deviceLocale, ); ``` ## Change Theme Please do not use any higher level widget than `GetMaterialApp` in order to update it. This can trigger duplicate keys. A lot of people are used to the prehistoric approach of creating a "ThemeProvider" widget just to change the theme of your app, and this is definitely NOT necessary with **GetX™**. You can create your custom theme and simply add it within `Get.changeTheme` without any boilerplate for that: ```dart Get.changeTheme(ThemeData.light()); ``` If you want to create something like a button that changes the Theme in `onTap`, you can combine two **GetX™** APIs for that: - The api that checks if the dark `Theme` is being used. - And the `Theme` Change API, you can just put this within an `onPressed`: ```dart Get.changeTheme(Get.isDarkMode? ThemeData.light(): ThemeData.dark()); ``` When `.darkmode` is activated, it will switch to the _light theme_, and when the _light theme_ becomes active, it will change to _dark theme_. ## GetConnect GetConnect is an easy way to communicate from your back to your front with http or websockets ### Default configuration You can simply extend GetConnect and use the GET/POST/PUT/DELETE/SOCKET methods to communicate with your Rest API or websockets. ```dart class UserProvider extends GetConnect { // Get request Future getUser(int id) => get('http://youapi/users/$id'); // Post request Future postUser(Map data) => post('http://youapi/users', body: data); // Post request with File Future> postCases(List image) { final form = FormData({ 'file': MultipartFile(image, filename: 'avatar.png'), 'otherFile': MultipartFile(image, filename: 'cover.png'), }); return post('http://youapi/users/upload', form); } GetSocket userMessages() { return socket('https://yourapi/users/socket'); } } ``` ### Custom configuration GetConnect is highly customizable You can define base Url, as answer modifiers, as Requests modifiers, define an authenticator, and even the number of attempts in which it will try to authenticate itself, in addition to giving the possibility to define a standard decoder that will transform all your requests into your Models without any additional configuration. ```dart class HomeProvider extends GetConnect { @override void onInit() { // All request will pass to jsonEncode so CasesModel.fromJson() httpClient.defaultDecoder = CasesModel.fromJson; httpClient.baseUrl = 'https://api.covid19api.com'; // baseUrl = 'https://api.covid19api.com'; // It define baseUrl to // Http and websockets if used with no [httpClient] instance // It's will attach 'apikey' property on header from all requests httpClient.addRequestModifier((request) { request.headers['apikey'] = '12345678'; return request; }); // Even if the server sends data from the country "Brazil", // it will never be displayed to users, because you remove // that data from the response, even before the response is delivered httpClient.addResponseModifier((request, response) { CasesModel model = response.body; if (model.countries.contains('Brazil')) { model.countries.remove('Brazilll'); } }); httpClient.addAuthenticator((request) async { final response = await get("http://yourapi/token"); final token = response.body['token']; // Set the header request.headers['Authorization'] = "$token"; return request; }); //Autenticator will be called 3 times if HttpStatus is //HttpStatus.unauthorized httpClient.maxAuthRetries = 3; } @override Future> getCases(String path) => get(path); } ``` ## GetPage Middleware The GetPage has now new property that takes a list of GetMiddleWare and run them in the specific order. **Note**: When GetPage has a Middlewares, all the children of this page will have the same middlewares automatically. ### Priority The Order of the Middlewares to run can be set by the priority in the GetMiddleware. ```dart final middlewares = [ GetMiddleware(priority: 2), GetMiddleware(priority: 5), GetMiddleware(priority: 4), GetMiddleware(priority: -8), ]; ``` those middlewares will be run in this order **-8 => 2 => 4 => 5** ### Redirect This function will be called when the page of the called route is being searched for. It takes RouteSettings as a result to redirect to. Or give it null and there will be no redirecting. ```dart RouteSettings redirect(String route) { final authService = Get.find(); return authService.authed.value ? null : RouteSettings(name: '/login') } ``` ### onPageCalled This function will be called when this Page is called before anything created you can use it to change something about the page or give it new page ```dart GetPage onPageCalled(GetPage page) { final authService = Get.find(); return page.copyWith(title: 'Welcome ${authService.UserName}'); } ``` ### OnBindingsStart This function will be called right before the Bindings are initialize. Here you can change Bindings for this page. ```dart List onBindingsStart(List bindings) { final authService = Get.find(); if (authService.isAdmin) { bindings.add(AdminBinding()); } return bindings; } ``` ### OnPageBuildStart This function will be called right after the Bindings are initialize. Here you can do something after that you created the bindings and before creating the page widget. ```dart GetPageBuilder onPageBuildStart(GetPageBuilder page) { print('bindings are ready'); return page; } ``` ### OnPageBuilt This function will be called right after the GetPage.page function is called and will give you the result of the function. and take the widget that will be showed. ### OnPageDispose This function will be called right after disposing all the related objects (Controllers, views, ...) of the page. ## Other Advanced APIs ```dart // give the current args from currentScreen Get.arguments // give name of previous route Get.previousRoute // give the raw route to access for example, rawRoute.isFirst() Get.rawRoute // give access to Routing API from GetObserver Get.routing // check if snackbar is open Get.isSnackbarOpen // check if dialog is open Get.isDialogOpen // check if bottomsheet is open Get.isBottomSheetOpen // remove one route. Get.removeRoute() // back repeatedly until the predicate returns true. Get.until() // go to next route and remove all the previous routes until the predicate returns true. Get.offUntil() // go to next named route and remove all the previous routes until the predicate returns true. Get.offNamedUntil() //Check in what platform the app is running GetPlatform.isAndroid GetPlatform.isIOS GetPlatform.isMacOS GetPlatform.isWindows GetPlatform.isLinux GetPlatform.isFuchsia //Check the device type GetPlatform.isMobile GetPlatform.isDesktop //All platforms are supported independently in web! //You can tell if you are running inside a browser //on Windows, iOS, OSX, Android, etc. GetPlatform.isWeb // Equivalent to : MediaQuery.of(context).size.height, // but immutable. Get.height Get.width // Gives the current context of the Navigator. Get.context // Gives the context of the snackbar/dialog/bottomsheet in the foreground, anywhere in your code. Get.contextOverlay // Note: the following methods are extensions on context. Since you // have access to context in any place of your UI, you can use it anywhere in the UI code // If you need a changeable height/width (like Desktop or browser windows that can be scaled) you will need to use context. context.width context.height // Gives you the power to define half the screen, a third of it and so on. // Useful for responsive applications. // param dividedBy (double) optional - default: 1 // param reducedBy (double) optional - default: 0 context.heightTransformer() context.widthTransformer() /// Similar to MediaQuery.sizeOf(context); context.mediaQuerySize() /// Similar to MediaQuery.paddingOf(context); context.mediaQueryPadding() /// Similar to MediaQuery.viewPaddingOf(context); context.mediaQueryViewPadding() /// Similar to MediaQuery.viewInsetsOf(context); context.mediaQueryViewInsets() /// Similar to MediaQuery.orientationOf(context); context.orientation() /// Check if device is on landscape mode context.isLandscape() /// Check if device is on portrait mode context.isPortrait() /// Similar to MediaQuery.devicePixelRatioOf(context); context.devicePixelRatio() /// Similar to MediaQuery.textScaleFactorOf(context); context.textScaleFactor() /// Get the shortestSide from screen context.mediaQueryShortestSide() /// True if width be larger than 800 context.showNavbar() /// True if the shortestSide is smaller than 600p context.isPhone() /// True if the shortestSide is largest than 600p context.isSmallTablet() /// True if the shortestSide is largest than 720p context.isLargeTablet() /// True if the current device is Tablet context.isTablet() /// Returns a value according to the screen size /// can give value for: /// watch: if the shortestSide is smaller than 300 /// mobile: if the shortestSide is smaller than 600 /// tablet: if the shortestSide is smaller than 1200 /// desktop: if width is largest than 1200 context.responsiveValue() ``` ### Optional Global Settings and Manual configurations GetMaterialApp configures everything for you, but if you want to configure Get manually. ```dart MaterialApp( navigatorKey: Get.key, navigatorObservers: [GetObserver()], ); ``` You will also be able to use your own Middleware within `GetObserver`, this will not influence anything. ```dart MaterialApp( navigatorKey: Get.key, navigatorObservers: [ GetObserver(MiddleWare.observer) // Here ], ); ``` You can create _Global Settings_ for `Get`. Just add `Get.config` to your code before pushing any route. Or do it directly in your `GetMaterialApp` ```dart GetMaterialApp( enableLog: true, defaultTransition: Transition.fade, opaqueRoute: Get.isOpaqueRouteDefault, popGesture: Get.isPopGestureEnable, transitionDuration: Get.defaultDurationTransition, defaultGlobalState: Get.defaultGlobalState, ); Get.config( enableLog = true, defaultPopGesture = true, defaultTransition = Transitions.cupertino ) ``` You can optionally redirect all the logging messages from `Get`. If you want to use your own, favourite logging package, and want to capture the logs there: ```dart GetMaterialApp( enableLog: true, logWriterCallback: localLogWriter, ); void localLogWriter(String text, {bool isError = false}) { // pass the message to your favourite logging package here // please note that even if enableLog: false log messages will be pushed in this callback // you get check the flag if you want through GetConfig.isLogEnable } ``` ### Local State Widgets These Widgets allows you to manage a single value, and keep the state ephemeral and locally. We have flavours for Reactive and Simple. For instance, you might use them to toggle obscureText in a `TextField`, maybe create a custom Expandable Panel, or maybe modify the current index in `BottomNavigationBar` while changing the content of the body in a `Scaffold`. #### ValueBuilder A simplification of `StatefulWidget` that works with a `.setState` callback that takes the updated value. ```dart ValueBuilder( initialValue: false, builder: (value, updateFn) => Switch( value: value, onChanged: updateFn, // same signature! you could use ( newValue ) => updateFn( newValue ) ), // if you need to call something outside the builder method. onUpdate: (value) => print("Value updated: $value"), onDispose: () => print("Widget unmounted"), ), ``` #### ObxValue Similar to [`ValueBuilder`](#valuebuilder), but this is the Reactive version, you pass a Rx instance (remember the magical .obs?) and updates automatically... isn't it awesome? ```dart ObxValue((data) => Switch( value: data.value, onChanged: data, // Rx has a _callable_ function! You could use (flag) => data.value = flag, ), false.obs, ), ``` ## Useful tips `.obs`ervables (also known as _Rx_ Types) have a wide variety of internal methods and operators. > Is very common to _believe_ that a property with `.obs` **IS** the actual value... but make no mistake! > We avoid the Type declaration of the variable, because Dart's compiler is smart enough, and the code > looks cleaner, but: ```dart var message = 'Hello world'.obs; print( 'Message "$message" has Type ${message.runtimeType}'); ``` Even if `message` _prints_ the actual String value, the Type is **RxString**! So, you can't do `message.substring( 0, 4 )`. You have to access the real `value` inside the _observable_: The most "used way" is `.value`, but, did you know that you can also use... ```dart final name = 'GetX'.obs; // only "updates" the stream, if the value is different from the current one. name.value = 'Hey'; // All Rx properties are "callable" and returns the new value. // but this approach does not accepts `null`, the UI will not rebuild. name('Hello'); // is like a getter, prints 'Hello'. name() ; /// numbers: final count = 0.obs; // You can use all non mutable operations from num primitives! count + 1; // Watch out! this is only valid if `count` is not final, but var count += 1; // You can also compare against values: count > 2; /// booleans: final flag = false.obs; // switches the value between true/false flag.toggle(); /// all types: // Sets the `value` to null. flag.nil(); // All toString(), toJson() operations are passed down to the `value` print( count ); // calls `toString()` inside for RxInt final abc = [0,1,2].obs; // Converts the value to a json Array, prints RxList // Json is supported by all Rx types! print('json: ${jsonEncode(abc)}, type: ${abc.runtimeType}'); // RxMap, RxList and RxSet are special Rx types, that extends their native types. // but you can work with a List as a regular list, although is reactive! abc.add(12); // pushes 12 to the list, and UPDATES the stream. abc[3]; // like Lists, reads the index 3. // equality works with the Rx and the value, but hashCode is always taken from the value final number = 12.obs; print( number == 12 ); // prints > true /// Custom Rx Models: // toJson(), toString() are deferred to the child, so you can implement override on them, and print() the observable directly. class User { String name, last; int age; User({this.name, this.last, this.age}); @override String toString() => '$name $last, $age years old'; } final user = User(name: 'John', last: 'Doe', age: 33).obs; // `user` is "reactive", but the properties inside ARE NOT! // So, if we change some variable inside of it... user.value.name = 'Roi'; // The widget will not rebuild!, // `Rx` don't have any clue when you change something inside user. // So, for custom classes, we need to manually "notify" the change. user.refresh(); // or we can use the `update()` method! user.update((value){ value.name='Roi'; }); print( user ); ``` ## StateMixin Another way to handle your `UI` state is use the `StateMixin` . To implement it, use the `with` to add the `StateMixin` to your controller which allows a T model. ```dart class Controller extends GetController with StateMixin{} ``` The `change()` method change the State whenever we want. Just pass the data and the status in this way: ```dart change(data, status: RxStatus.success()); ``` RxStatus allow these status: ```dart RxStatus.loading(); RxStatus.success(); RxStatus.empty(); RxStatus.error('message'); ``` To represent it in the UI, use: ```dart class OtherClass extends GetView { @override Widget build(BuildContext context) { return Scaffold( body: controller.obx( (state)=>Text(state.name), // here you can put your custom loading indicator, but // by default would be Center(child:CircularProgressIndicator()) onLoading: CustomLoadingIndicator(), onEmpty: Text('No data found'), // here also you can set your own error widget, but by // default will be an Center(child:Text(error)) onError: (error)=>Text(error), ), ); } ``` #### GetView I love this Widget, is so simple, yet, so useful! Is a `const Stateless` Widget that has a getter `controller` for a registered `Controller`, that's all. ```dart class AwesomeController extends GetController { final String title = 'My Awesome View'; } // ALWAYS remember to pass the `Type` you used to register your controller! class AwesomeView extends GetView { @override Widget build(BuildContext context) { return Container( padding: EdgeInsets.all(20), child: Text(controller.title), // just call `controller.something` ); } } ``` #### GetResponsiveView Extend this widget to build responsive view. this widget contains the `screen` property that have all information about the screen size and type. ##### How to use it You have two options to build it. - with `builder` method you return the widget to build. - with methods `desktop`, `tablet`,`phone`, `watch`. the specific method will be built when the screen type matches the method when the screen is [ScreenType.Tablet] the `tablet` method will be exuded and so on. **Note:** If you use this method please set the property `alwaysUseBuilder` to `false` With `settings` property you can set the width limit for the screen types. ![example](https://github.com/SchabanBo/get_page_example/blob/master/docs/Example.gif?raw=true) Code to this screen [code](https://github.com/SchabanBo/get_page_example/blob/master/lib/pages/responsive_example/responsive_view.dart) #### GetWidget Most people have no idea about this Widget, or totally confuse the usage of it. The use case is very rare, but very specific: It `caches` a Controller. Because of the _cache_, can't be a `const Stateless`. > So, when do you need to "cache" a Controller? If you use, another "not so common" feature of **GetX**: `Get.create()`. `Get.create(()=>Controller())` will generate a new `Controller` each time you call `Get.find()`, That's where `GetWidget` shines... as you can use it, for example, to keep a list of Todo items. So, if the widget gets "rebuilt", it will keep the same controller instance. #### GetxService This class is like a `GetxController`, it shares the same lifecycle ( `onInit()`, `onReady()`, `onClose()`). But has no "logic" inside of it. It just notifies **GetX** Dependency Injection system, that this subclass **can not** be removed from memory. So is super useful to keep your "Services" always reachable and active with `Get.find()`. Like: `ApiService`, `StorageService`, `CacheService`. ```dart Future main() async { await initServices(); /// AWAIT SERVICES INITIALIZATION. runApp(SomeApp()); } /// Is a smart move to make your Services intiialize before you run the Flutter app. /// as you can control the execution flow (maybe you need to load some Theme configuration, /// apiKey, language defined by the User... so load SettingService before running ApiService. /// so GetMaterialApp() doesnt have to rebuild, and takes the values directly. void initServices() async { print('starting services ...'); /// Here is where you put get_storage, hive, shared_pref initialization. /// or moor connection, or whatever that's async. await Get.putAsync(() => DbService().init()); await Get.putAsync(SettingsService()).init(); print('All services started...'); } class DbService extends GetxService { Future init() async { print('$runtimeType delays 2 sec'); await 2.delay(); print('$runtimeType ready!'); return this; } } class SettingsService extends GetxService { void init() async { print('$runtimeType delays 1 sec'); await 1.delay(); print('$runtimeType ready!'); } } ``` The only way to actually delete a `GetxService`, is with `Get.reset()` which is like a "Hot Reboot" of your app. So remember, if you need absolute persistence of a class instance during the lifetime of your app, use `GetxService`. ### Tests You can test your controllers like any other class, including their lifecycles: ```dart class Controller extends GetxController { @override void onInit() { super.onInit(); //Change value to name2 name.value = 'name2'; } @override void onClose() { name.value = ''; super.onClose(); } final name = 'name1'.obs; void changeName() => name.value = 'name3'; } void main() { test(''' Test the state of the reactive variable "name" across all of its lifecycles''', () { /// You can test the controller without the lifecycle, /// but it's not recommended unless you're not using /// GetX dependency injection final controller = Controller(); expect(controller.name.value, 'name1'); /// If you are using it, you can test everything, /// including the state of the application after each lifecycle. Get.put(controller); // onInit was called expect(controller.name.value, 'name2'); /// Test your functions controller.changeName(); expect(controller.name.value, 'name3'); /// onClose was called Get.delete(); expect(controller.name.value, ''); }); } ``` #### Tips ##### Mockito or mocktail If you need to mock your GetxController/GetxService, you should extend GetxController, and mixin it with Mock, that way ```dart class NotificationServiceMock extends GetxService with Mock implements NotificationService {} ``` ##### Using Get.reset() If you are testing widgets, or test groups, use Get.reset at the end of your test or in tearDown to reset all settings from your previous test. ##### Get.testMode if you are using your navigation in your controllers, use `Get.testMode = true` at the beginning of your main. # Breaking changes from 2.0 1- Rx types: | Before | After | | ------- | ---------- | | StringX | `RxString` | | IntX | `RxInt` | | MapX | `RxMap` | | ListX | `RxList` | | NumX | `RxNum` | | DoubleX | `RxDouble` | RxController and GetBuilder now have merged, you no longer need to memorize which controller you want to use, just use GetxController, it will work for simple state management and for reactive as well. 2- NamedRoutes Before: ```dart GetMaterialApp( namedRoutes: { '/': GetRoute(page: Home()), } ) ``` Now: ```dart GetMaterialApp( getPages: [ GetPage(name: '/', page: () => Home()), ] ) ``` Why this change? Often, it may be necessary to decide which page will be displayed from a parameter, or a login token, the previous approach was inflexible, as it did not allow this. Inserting the page into a function has significantly reduced the RAM consumption, since the routes will not be allocated in memory since the app was started, and it also allowed to do this type of approach: ```dart GetStorage box = GetStorage(); GetMaterialApp( getPages: [ GetPage(name: '/', page:(){ return box.hasData('token') ? Home() : Login(); }) ] ) ``` # Why Getx? 1- Many times after a Flutter update, many of your packages will break. Sometimes compilation errors happen, errors often appear that there are still no answers about, and the developer needs to know where the error came from, track the error, only then try to open an issue in the corresponding repository, and see its problem solved. Get centralizes the main resources for development (State, dependency and route management), allowing you to add a single package to your pubspec, and start working. After a Flutter update, the only thing you need to do is update the Get dependency, and get to work. Get also resolves compatibility issues. How many times a version of a package is not compatible with the version of another, because one uses a dependency in one version, and the other in another version? This is also not a concern using Get, as everything is in the same package and is fully compatible. 2- Flutter is easy, Flutter is incredible, but Flutter still has some boilerplate that may be unwanted for most developers, such as `Navigator.of(context).push (context, builder [...]`. Get simplifies development. Instead of writing 8 lines of code to just call a route, you can just do it: `Get.to(Home())` and you're done, you'll go to the next page. Dynamic web urls are a really painful thing to do with Flutter currently, and that with GetX is stupidly simple. Managing states in Flutter, and managing dependencies is also something that generates a lot of discussion, as there are hundreds of patterns in the pub. But there is nothing as easy as adding a ".obs" at the end of your variable, and place your widget inside an Obx, and that's it, all updates to that variable will be automatically updated on the screen. 3- Ease without worrying about performance. Flutter's performance is already amazing, but imagine that you use a state manager, and a locator to distribute your blocs/stores/controllers/ etc. classes. You will have to manually call the exclusion of that dependency when you don't need it. But have you ever thought of simply using your controller, and when it was no longer being used by anyone, it would simply be deleted from memory? That's what GetX does. With SmartManagement, everything that is not being used is deleted from memory, and you shouldn't have to worry about anything but programming. You will be assured that you are consuming the minimum necessary resources, without even having created a logic for this. 4- Actual decoupling. You may have heard the concept "separate the view from the business logic". This is not a peculiarity of BLoC, MVC, MVVM, and any other standard on the market has this concept. However, this concept can often be mitigated in Flutter due to the use of context. If you need context to find an InheritedWidget, you need it in the view, or pass the context by parameter. I particularly find this solution very ugly, and to work in teams we will always have a dependence on View's business logic. Getx is unorthodox with the standard approach, and while it does not completely ban the use of StatefulWidgets, InitState, etc., it always has a similar approach that can be cleaner. Controllers have life cycles, and when you need to make an APIREST request for example, you don't depend on anything in the view. You can use onInit to initiate the http call, and when the data arrives, the variables will be populated. As GetX is fully reactive (really, and works under streams), once the items are filled, all widgets that use that variable will be automatically updated in the view. This allows people with UI expertise to work only with widgets, and not have to send anything to business logic other than user events (like clicking a button), while people working with business logic will be free to create and test the business logic separately. This library will always be updated and implementing new features. Feel free to offer PRs and contribute to them. # Community ## Community channels GetX has a highly active and helpful community. If you have questions, or would like any assistance regarding the use of this framework, please join our community channels, your question will be answered more quickly, and it will be the most suitable place. This repository is exclusive for opening issues, and requesting resources, but feel free to be part of GetX Community. | **Slack** | **Discord** | **Telegram** | | :-------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------- | | [![Get on Slack](https://img.shields.io/badge/slack-join-orange.svg)](https://communityinviter.com/apps/getxworkspace/getx) | [![Discord Shield](https://img.shields.io/discord/722900883784073290.svg?logo=discord)](https://discord.com/invite/9Hpt99N) | [![Telegram](https://img.shields.io/badge/chat-on%20Telegram-blue.svg)](https://t.me/joinchat/PhdbJRmsZNpAqSLJL6bH7g) | ## How to contribute _Want to contribute to the project? We will be proud to highlight you as one of our collaborators. Here are some points where you can contribute and make Get (and Flutter) even better._ - Helping to translate the readme into other languages. - Adding documentation to the readme (a lot of Get's functions haven't been documented yet). - Write articles or make videos teaching how to use Get (they will be inserted in the Readme and in the future in our Wiki). - Offering PRs for code/tests. - Including new functions. Any contribution is welcome! ## Articles and videos - [Flutter Getx EcoSystem package for arabic people](https://www.youtube.com/playlist?list=PLV1fXIAyjeuZ6M8m56zajMUwu4uE3-SL0) - Tutorial by [Pesa Coder](https://github.com/UsamaElgendy). - [Dynamic Themes in 3 lines using GetX™](https://medium.com/swlh/flutter-dynamic-themes-in-3-lines-c3b375f292e3) - Tutorial by [Rod Brown](https://github.com/RodBr). - [Complete GetX™ Navigation](https://www.youtube.com/watch?v=RaqPIoJSTtI) - Route management video by Amateur Coder. - [Complete GetX State Management](https://www.youtube.com/watch?v=CNpXbeI_slw) - State management video by Amateur Coder. - [GetX™ Other Features](https://youtu.be/ttQtlX_Q0eU) - Utils, storage, bindings and other features video by Amateur Coder. - [Firestore User with GetX | Todo App](https://www.youtube.com/watch?v=BiV0DcXgk58) - Video by Amateur Coder. - [Firebase Auth with GetX | Todo App](https://www.youtube.com/watch?v=-H-T_BSgfOE) - Video by Amateur Coder. - [The Flutter GetX™ Ecosystem ~ State Management](https://medium.com/flutter-community/the-flutter-getx-ecosystem-state-management-881c7235511d) - State management by [Aachman Garg](https://github.com/imaachman). - [The Flutter GetX™ Ecosystem ~ Dependency Injection](https://medium.com/flutter-community/the-flutter-getx-ecosystem-dependency-injection-8e763d0ec6b9) - Dependency Injection by [Aachman Garg](https://github.com/imaachman). - [GetX, the all-in-one Flutter package](https://www.youtube.com/watch?v=IYQgtu9TM74) - A brief tutorial covering State Management and Navigation by Thad Carnevalli. - [Build a To-do List App from scratch using Flutter and GetX](https://www.youtube.com/watch?v=EcnqFasHf18) - UI + State Management + Storage video by Thad Carnevalli. - [GetX Flutter Firebase Auth Example](https://medium.com/@jeffmcmorris/getx-flutter-firebase-auth-example-b383c1dd1de2) - Article by Jeff McMorris. - [Flutter State Management with GetX – Complete App](https://www.appwithflutter.com/flutter-state-management-with-getx/) - by App With Flutter. - [Flutter Routing with Animation using Get Package](https://www.appwithflutter.com/flutter-routing-using-get-package/) - by App With Flutter. - [A minimal example on dartpad](https://dartpad.dev/2b3d0d6f9d4e312c5fdbefc414c1727e?) - by [Roi Peker](https://github.com/roipeker) - [GetConnect: The best way to perform API operations in Flutter with Get.](https://absyz.com/getconnect-the-best-way-to-perform-api-operations-in-flutter-with-getx/) - by [MD Sarfaraj](https://github.com/socialmad) - [How To Create an App with GetX Architect in Flutter with Get CLI](https://www.youtube.com/watch?v=7mb4qBA7kTk&t=1380s) - by [MD Sarfaraj](https://github.com/socialmad) ================================================ FILE: README.pl.md ================================================ ![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/get.png) *Languages: [English](README.md), [Wietnamski](README-vi.md), [Indonezyjski](README.id-ID.md), [Urdu](README.ur-PK.md), [Język chiński](README.zh-cn.md), [Brazilian Portuguese](README.pt-br.md), [Spanish](README-es.md), [Russian](README.ru.md), Polish (Jesteś tu), [Koreański](README.ko-kr.md), [French](README-fr.md)* [![pub package](https://img.shields.io/pub/v/get.svg?label=get&color=blue)](https://pub.dev/packages/get) [![popularity](https://badges.bar/get/popularity)](https://pub.dev/packages/sentry/score) [![likes](https://badges.bar/get/likes)](https://pub.dev/packages/get/score) [![pub points](https://badges.bar/get/pub%20points)](https://pub.dev/packages/get/score) ![building](https://github.com/jonataslaw/get/workflows/build/badge.svg) [![style: effective dart](https://img.shields.io/badge/style-effective_dart-40c4ff.svg)](https://pub.dev/packages/effective_dart) [![Discord Shield](https://img.shields.io/discord/722900883784073290.svg?logo=discord)](https://discord.com/invite/9Hpt99N) [![Get on Slack](https://img.shields.io/badge/slack-join-orange.svg)](https://communityinviter.com/apps/getxworkspace/getx) [![Telegram](https://img.shields.io/badge/chat-on%20Telegram-blue.svg)](https://t.me/joinchat/PhdbJRmsZNpAqSLJL6bH7g) Awesome Flutter Buy Me A Coffee ![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/getx.png) - [Kanały komunikacji i wsparcia:](#kanały-komunikacji-i-wsparcia) - [Wprowadzenie](#wprowadzenie) - [Instalacja](#instalacja) - [Counter App z GetX](#counter-app-z-getx) - [Trzy filary](#trzy-filary) - [Menadżer stanu](#menadżer-stanu) - [Reaktywny menadżer stanu](#reaktywny-menadżer-stanu) - [Bardziej szczegółowo o menadżerze stanu](#bardziej-szczegółowo-o-menadżerze-stanu) - [Video tłumaczące użycie menadżera stanu](#video-tłumaczące-użycie-menadżera-stanu) - [Zarządzanie routami](#zarządzanie-routami) - [Więcej o routach](#więcej-o-routach) - [Video tłumaczące użycie](#video-tłumaczące-użycie) - [Zarządzanie dependencies](#zarządzanie-dependencies) - [Bardziej szczegółowo o menadżerze dependencies](#bardziej-szczegółowo-o-menadżerze-dependencies) - [Jak włożyć coś od siebie](#jak-włożyć-coś-od-siebie) - [Narzędzia](#narzędzia) - [Zmiana motywu](#zmiana-motywu) - [Inne zaawansowane API](#inne-zaawansowane-api) - [Opcjonalne globalne ustawienia i manualna konfiguracja](#opcjonalne-globalne-ustawienia-i-manualna-konfiguracja) - [Video tłumaczące inne funkcjonalności GetX](#video-tłumaczące-inne-funkcjonalności-getx) - [Zmiany od 2.0](#zmiany-od-20) # Kanały komunikacji i wsparcia: [**Slack (English)**](https://communityinviter.com/apps/getxworkspace/getx) [**Discord (English and Portuguese)**](https://discord.com/invite/9Hpt99N) [**Telegram (Portuguese)**](https://t.me/joinchat/PhdbJRmsZNpAqSLJL6bH7g) # Wprowadzenie - GetX jest bardzo lekką, a zarazem potężną biblioteką do Flattera. Łączy wysoką wydajność menadżera stanu, inteligętne dodawanie dependencies i zarządzanie routami w szybki i praktyczny sposób. - GetX nie jest dla wszystkich, skupia się na jak najmniejszej konsumpcji zasobów (wydajności) ([zobacz benchmarki](https://github.com/jonataslaw/benchmarks)), używaniu łatwej skłani (produktywności) i daniu możliwości pełnego rozbicia View na z logiki biznesowej (organizacja). - GetX da Ci supermoce i zwiększy produktywność w tworzeniu projektu. Oszczędzi godziny zarówno początkującym jak i ekspertom. - Nawiguj bez podawania `context`, używaj open `dialogs`, `snackbarów` oraz `bottomsheetów` z każdego miejsca w kodzie. Zarządzaj stanami i dodawaj dependencies w prosty i praktyczny sposób! - Get jest bezpieczny, stabilny i aktualny. Oprócz tego oferuje szeroki zakres API, które nie są zawarte w standardowym frameworku. - GetX nie jest przytłaczający. Ma wiele funkcjonalności pozwalajacych na rozpoczęcie programowania bez martwienia się zupełnie nic. Wszystkie funkcjonalności są w osobnych kontenerach, które dodawane są dopiero po ich użyciu. Jeśli tylko używasz menadżera stanu, tylko on będzie kompilowany. Jeśli używasz routów, lecz nic z menadżera stanu to nie będzie on kompilowany. Możesz skompilować repozytorium benchmark i zobaczysz że używa tylko menadżera stanu. Aplikacje używajace Get są mniejsze niz inne, ponieważ wszystkie rozwiązania GetX są projektowane z myślą o lekkości i wydajności. Jest to też zasługa Flutterowego AOT, które jest niesamowite i eliminuje nieużywane zasoby jak żaden inny framework. **GetX zwiększa twoja produktywność, lecz możesz to jeszcze przyspieszyć instalując rozszerzenie [GetX extension](https://marketplace.visualstudio.com/items?itemName=get-snippets.get-snippets) do swojego VSCode**. Jeszcze nie dostępne w innych IDE. # Instalacja Dodaj Get do swojego pliku pubspec.yaml: ```yaml dependencies: get: ``` Zaimportuj Get do plików w których chcesz go użyć: ```dart import 'package:get/get.dart'; ``` # Counter App z GetX Przykładowa aplikaja tworzona domyślnie podczas kreacji nowego projektu we Flaterze ma ponad 100 lini kodu (z komentarzami). By pokazać siłę Get pokażę jak zrobić "licznik" ze zmianą stanu przy każdym kliknięciu, zmianą stron i udostępniajac stan pomiędzy ekranami. Wszystko w zorganizowany sposób dzieląc bussines logic z view w zaledwie 26 LINI KODU WŁĄCZAJĄC W TO KOMENTARZE. -Krok 1: Dodaj "Get" przed MaterialApp, zamieniając je na GetMaterialApp ```dart void main() => runApp(GetMaterialApp(home: Home())); ``` - Note: nie jest to modyfikaja MaterialApp, ponieważ GetMaterialApp nie jest zmodyfikowanym MaterialApp z Fluttera, jest tylko skonfigurowanym Widgetem mającym domyślnie MaterialApp jako dziecko. Możesz to konfigurować ręcznie, ale nie jest to konieczne. GetMaterialApp jest niezbędne dla działania routów, snackbarów, bootomsheetów, internacjonalizacji, dialogów i wysokopoziomowych api powiązanych z routami i nieobecnościa kontekstu. Nie jest to jednak wymagane do używania zarzadzania stanem i dependencies. -Krok 2: Tworzymy klasę business logic i umieszczmy w niej wszystkie zmienne, metody oraz kontrolery. Możesz zmienić zmiennaą na obserwowalną używajac prostego subfixu ".obs" ```dart class Controller extends GetxController{ var count = 0.obs; increment() => count.value++; } ``` - Krok 3: Tworzymy View. Użyj StatelessWidget oszczędzajac przy tym RAM. Z Get nie będzie Ci potrzebny StatefullWidget. ```dart class Home extends StatelessWidget { @override Widget build(context) { // Instantiate your class using Get.put() to make it available for all "child" routes there. final Controller c = Get.put(Controller()); return Scaffold( // Use Obx(()=> to update Text() whenever count is changed. appBar: AppBar(title: Obx(() => Text("Clicks: " + c.count.string))), // Replace the 8 lines Navigator.push by a simple Get.to(). You don't need context body: Center(child: ElevatedButton( child: Text("Go to Other"), onPressed: () => Get.to(Other()))), floatingActionButton: FloatingActionButton(child: Icon(Icons.add), onPressed: c.increment)); } } class Other extends StatelessWidget { // You can ask Get to find a Controller that is being used by another page and redirect you to it. final Controller c = Get.find(); @override Widget build(context){ // Access the updated count variable return Scaffold(body: Center(child: Text(c.count.string))); } } ``` Wynik: ![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/counter-app-gif.gif) Jest to prosty projekt, ale już na jego przykładzie widać potęgę Get. Wraz ze wzrostem rozmiaru aplikacji ta różnica tylko się powiększa. Get był projektowany dla pracy z zespołem, ale równie dobrze sprawdza się w indywidualnych projektach. Zawsze dotrzymuj deadlinów i dostarczaj swoje rozwiązania na czas bez straty na wydajności. Get nie jest dla wszystkich jak już wspominałem, ale jeśli identyfikujesz się z powyższym zdaniem Get jest właśnie dla Ciebie. # Trzy filary ## Menadżer stanu Obecnie istnieje kilka menadżeów dla Fluttera. Jednak większość z nich wymaga używania ChangeNotifier, po to aby zaktualizować widżety, co nie sprawdza się pod kątem wydajności w średnich i dużych aplikacach. Możesz sprawdzić w oficjalnej dokumentacji, że ChangeNotifier powinien być używany z maksimum dwoma listinerami (https://api.flutter.dev/flutter/foundation/ChangeNotifier-class.html), będąc praktycznie bezużytecznym w średnich i duzych projektach. Get nie jest ani lepszy, ani gorszy od innych menadżerów stanów, ale powinieneś rozpatrzyć te punkty jak i poniższe, aby wybrać między użyciem Get w czystej formie (Vanilla), albo używaniem go wraz z innym menadżerem. Definitywnie Get nie jest przeciwnikiem żadnego innego menadżera, ponieważ jest on mikroframeworkiem, nie tylko menadżerem stanu. Może być użyty samodzielnie, lub w koegzystencji. Get ma bardzo lekki i prosty menadżer stanu (napisany w tylko 95 lini kodu), który nie używa ChangeNotifier. Sprosta on wymaganiom szczególnie nowych we Flutterze i nie sprawi problemu nawet w dużych aplikacjach. ### Reaktywny menadżer stanu Reaktywne programowanie może odtrącać niektórych, ponieważ powszechnie jest uważane za skomplikowane. GetX zamienia to w coś prostego: - Nie musisz tworzyć StreamControllerów, - Nie musisz tworzyć StreamBuildera dla każdej zmiennej, - Nie ma potrzeby tworzenia klasy dla każdego stanu, - Nie musisz tworzyć Get dla inicjalnej zmiennej Wyobraź sobie, że masz zmienną i za każdym razem jak zmienisz ją chcesz żeby wszystkie widżety używające jej automatycznie się zmieniły Przykładowa zmienna: ```dart var name = 'Jonatas Borges'; ``` By zamienić ją na obserwowalną dodaj ".obx" na końcu: ```dart var name = 'Jonatas Borges'.obs; ``` I w UI, kiedy chcesz go zaktualizować przy modyfikacji zmiennej po prostu dodaj to: ```dart Obx (() => Text (controller.name)); ``` To wszystko. *Proste*, co nie? ### Bardziej szczegółowo o menadżerze stanu **Zobacz bardziej szczegółowe wytłumaczenie menadżera stanu [tutaj](./documentation/en_US/state_management.md). Znajdują się tam przykłady jak o różnice między prostym menadżerem stanu oraz reaktywnym** ### Video tłumaczące użycie menadżera stanu Tadas Petra nagrał o tym niezwykły film: Link: [Complete GetX State Management](https://www.youtube.com/watch?v=CNpXbeI_slw) ## Zarządzanie routami Jeśli chcesz używać routes/snackbars/dialogs/bottomsheets z GetX możesz to robić bez contextu. Zamień MaterialApp na GetMaterialApp ```dart GetMaterialApp( // Before: MaterialApp( home: MyHome(), ) ``` By nawigować do nowego ekranu: ```dart Get.to(NextScreen()); ``` By powrócić do poprzedniego ekranu ```dart Get.back(); ``` By przejść do następnego ekranu bez możliwości powrotu do poprzedniego (do zastosowania SplashScreenów, ekranów logowania itd.) ```dart Get.off(NextScreen()); ``` By przejść do następnego ekranu niszcząc poprzednie routy (użyteczne w koszykach, ankietach i testach) ```dart Get.offAll(NextScreen()); ``` By nawigować do następnego routa i otrzymać, lub uaktualnić dane zaraz po tym jak z niego wrócisz: ```dart var data = await Get.to(Payment()); ``` w innym ekranie wyślij dane z poprzedniego routa:featury ```dart Get.back(result: 'sucess'); ``` I użyj następujące np.: ```dart if(data == 'sucess') madeAnything(); ``` Zobacz, ze do żadnej z tych operacji nie potrzebowałeś contextu. Jest to jedna z głównych zalet GetX oszczędzającego na niepotrzebnej obudowie w kod i dającego możliwość używania tych metod w klasie kontrolera. ### Więcej o routach **Get używa named routes oraz oferuje niskopoziomową obsługę routów! Zobacz bardziej szczegółową dokumentacje [tutaj](./documentation/en_US/route_management.md)** ### Video tłumaczące użycie Tadas Petra nagrał o tym niezwykły film: Link: [Complete GetX Navigation](https://www.youtube.com/watch?v=RaqPIoJSTtI) ## Zarządzanie dependencies Get ma prosty, a zarazem potężny menadżer dependencies. Pozwala on na otrzymanie tych samych klas jak twoje Bloc lub Kontroler pisząc jedną linię kodu bez Provider context i inheritedWidget: ```dart Controller controller = Get.put(Controller()); // Rather Controller controller = Controller(); ``` - Note: Jeśli używasz menadżera stanu Get zwróć uwagę na binding api, które pozwoli Ci łatwiej połączyć twój widok z kontrolerem. https://github.com/jonataslaw/get **Tip:** Menadżer dependency Get jest oddzielony od innych części pakietu więc jeśli już używasz menadżera stanu(którego kolwiek, bez różnicy) nie musisz przepisywać tego wszystkiego na nowo. Możesz używać tego dodawania dependencies bez poroblemu. ```dart controller.fetchApi(); ``` Wyobraź sobie, że musisz nawigować pomiędzy wieloma routami i potrzebujesz dane z kontrolerów z poprzednich ekranów. Musiałbyś użyć menadżera stanu z dodatkiem Providera albo Get_it, prawda? Otóż nie z Fet. Musisz po prostu poprosić Get o znalezienie tego kontrolera i nie potrzebujesz przy tym dodatkowych dependencies. ```dart Controller controller = Get.find(); //Tak, to wygląda jak Magia, Get znjadzie Twój kontroler i Ci go dostarczy. Możesz mieć nawet MILION kontrolerów, a Get zawsze da Ci prawidłowy kontroler. ``` I wtedy będziesz mógł otrzymać z niego dane bez żadnego problemu ```dart Text(controller.textFromApi); ``` ### Bardziej szczegółowo o menadżerze dependencies **Zobzcz więcej w dokumentacji [tutaj](./documentation/en_US/dependency_management.md)** # Jak włożyć coś od siebie Możesz uczestniczyć w rozwoju projektu na różny sposób: - Pomagając w tłumaczeniu readme na inne języki. - Dodając dokumentację do readme (nawet połowa funkcji została jeszcze opisana). - Pisząc artykuły i nagrywając filmy pokazujące użycie biblioteki Get (będą zamieszczone w readme, a w przyszłości na naszej Wiki). - Oferując PR-y dla kodu i testów. - Dodając nowe funkcje. Każda współpraca jest mile widziana! # Narzędzia ## Zmiana motywu Nie powinno się używać innego widżetu niż GetMaterialApp by go zaktualizować. To może powodować duplikacje kluczy. Wiele osób nawykło do prehistorycznego podejścia tworzenia widżetu "ThemeProvider" tylko po to by zmienić motyw aplikacji. Z Get nie jest to wymagane. Możesz stworzyć customowy motyw i łatwo go dodać z Get.changeTheme bez niepotrzebnego kodu. ```dart Get.changeTheme(ThemeData.light()); ``` Jeśli chcesz stworzyć coś jak przycisk zmieniający motyw aplikacji na onTap, możesz połączyć dwia Get API. Api sprawdzające czy ciemny motyw jest używany i Api zajmujące się zmianą motywu. Po prostu użyj tego w onPressed: ```dart Get.changeTheme(Get.isDarkMode? ThemeData.light(): ThemeData.dark());featury ``` Kiedy ciemny motyw jest aktywny zmieni się on na jasny, w przeciwnym wypadku zmieni się na ciemny. Jeśli interesuje Cię jak zmieniać motywy podążaj za samouczkiem na Medium pokazującum zmianę motywu przy użyciu Get: - [Dynamic Themes in 3 lines using Get](https://medium.com/swlh/flutter-dynamic-themes-in-3-lines-c3b375f292e3) - Samouczek stworzony przez [Rod Brown](https://github.com/RodBr). ## Inne zaawansowane API ```dart // give the current args from currentScreen Get.arguments // give name of previous route Get.previousRoute // give the raw route to access for example, rawRoute.isFirst() Get.rawRoute // give access to Rounting API from GetObserver Get.routing // check if snackbar is open Get.isSnackbarOpen // check if dialog is open Get.isDialogOpen // check if bottomsheet is opefeaturyn Get.isBottomSheetOpen // remove one route. Get.removeRoute() // back repeatedly until the predicate returns true. Get.until() // go to next route and remove all the previous routes until the predicate returns true. Get.offUntil() // go to next named route and remove all the previous routes until the predicate returns true. Get.offNamedUntil() //Check in what platform the app is running GetPlatform.isAndroid GetPlatform.isIOS GetPlatform.isWeb // Equivalent to the method: MediaQuery.of(context).size.height, but they are immutable. Get.height Get.width // Gives the current context of navigator. Get.context // Gives the context of the snackbar/dialog/bottomsheet in the foreground anywhere in your code. Get.contextOverlay // Note: the following methods are extensions on context. Since you // have access to context in any place of your UI, you can use it anywhere in the UI code // If you need a changeable height/width (like browser windows that can be scfeaturyaled) you will need to use context. context.width context.height // gives you the power to define half the screen now, a third of it and so on. //Useful for responsive applications. // param dividedBy (double) optional - default: 1 // param reducedBy (double) optional - default: 0 context.heightTransformer() context.widthTransformer() /// similar to MediaQuery.of(context).size context.mediaQuerySize() /// similar to MediaQuery.of(context).padding context.mediaQueryPadding() /// similar to MediaQuery.of(context).viewPadding context.mediaQueryViewPadding() /// similar to MediaQuery.of(context).viewInsets; context.mediaQueryViewInsets() /// similar to MediaQuery.of(context).orientation; context.orientation() /// check if device is on landscape mode context.isLandscape() /// check if device is on portrait mode context.isPortrait() /// similar to MediaQuery.of(context).devicePixelRatio; context.devicePixelRatio() /// similar to MediaQuery.of(context).textScaleFactor; context.textScaleFactor() /// get the shortestSide from screen context.mediaQueryShortestSide() /// True if width be larger thfeaturyan 800 context.showNavbar() /// True if the shortestSide is smaller than 600p context.isPhone() /// True if the shortestSide is largest than 600p context.isSmallTablet() /// True if the shortestSide is largest than 720p context.isLargeTablet() /// True if the current device is Tablet context.isTablet() ``` ### Opcjonalne globalne ustawienia i manualna konfiguracja GetMaterialApp konfiguruje wszystko za Ciebie, ale jeśli chcesz możesz konfigurować Get manualnie. ```dart MaterialApp( navigatorKey: Get.key, navigatorObservers: [GetObserver()], ); ``` Będziesz mógł używać swojego Midware z GetObserver, nie wpływa to na nic. ```dart MaterialApp( navigatorKey: Get.key, navigatorObservers: [ GetObserver(MiddleWare.observer) // Here ], ); ``` Mozesz stworzyć globalne ustawienia dla Get. Tylko dodaj Get.config do swojego kodu przed użyciem routów, lub bezpośrednio w GetMaterialApp ```dart GetMaterialApp( enableLog: true, defaultTransition: Transition.fade, opaqueRoute: Get.isOpaqueRouteDefault, popGesture: Get.isPopGestureEnable, transitionDuration: Get.defaultDurationTransition, defaultGlobalState: Get.defaultGlobalState,https://github.com/jonataslaw/ge ); Get.config( enableLog = true, defaultPopGesture = true, defaultTransition = Transitions.cupertino ) ``` Opcjonalnie możesz przekierować wszystkie logi z Get by używać swojej ulubionej paczki i zbierać w niej logi. ```dart GetMaterialApp( enableLog: true, logWriterCallback: localLogWriter, ); void localLogWriter(String text, {bool isError = false}) { // tutaj przekaż wiadomosci do ulubionej paczki // pamiętaj że nawet jeśli "enableLog: false" logi i tak będą wysłane w tym callbacku // Musisz sprawdzić konfiguracje flag jeśli chcesz przez GetConfig.isLogEnable } ``` ## Video tłumaczące inne funkcjonalności GetX Tadas Petra nagrał niezwykły film tłumaczący powyższe zagadnienia! Link: [GetX Other Features](https://youtu.be/ttQtlX_Q0eU) # Zmiany od 2.0 1- Typy Rx: | Przed | Po | | ------- | ---------- | | StringX | `RxString` | | IntX | `RxInt` | | MapX | `RxMax` | | ListX | `RxList` | | NumX | `RxNum` | | DoubleX | `RxDouble` | RXController i GetBuilder teraz zostały połączone. Nie musisz już pamiętać którego kontrolera chcesz użyć, po prostu korzystaj z GetxController, będzie działać zarówno dla prostego jak i reaktywnego menadżera stanów. 2- NamedRoutes Wcześniej: ```dart GetMaterialApp( namedRoutes: { '/': GetRoute(page: Home()), } ) ``` Teraz: ```dart GetMaterialApp( getPages: [ GetPage(name: '/', page: () => Home()), ] ) ``` Po co ta zmiana? Często może być niezbędnym decydowanie która strona będzie wyświetlana w zależności od parametru, lub tokenu logowania. Wczesniejsze podejście było nieelastyczne, ponieważ na to nie pozwalało. Zawarcie strony w funkcji zmniejszyło sporzycie RAM-u, ze względu na niealokowanie routów od początku działania aplikacji. Pozwoliło to także na takie podejscie: ```dart GetStorage box = GetStorage(); GetMaterialApp( getPages: [ GetPage(name: '/', page:(){ return box.hasData('token') ? Home() : Login(); }) ] ) ``` ================================================ FILE: README.pt-br.md ================================================ ![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/get.png) **Idiomas: [Inglês](README.md), [Vietnamita](README-vi.md), [Indonésia](README.id-ID.md), [Urdu](README.ur-PK.md), [Chinês](README.zh-cn.md), Português (este arquivo), [Espanhol](README-es.md), [Russo](README.ru.md), [Polonês](README.pl.md), [Coreano](README.ko-kr.md), [Francês](README-fr.md)** [![pub package](https://img.shields.io/pub/v/get.svg?label=get&color=blue)](https://pub.dev/packages/get) [![popularity](https://badges.bar/get/popularity)](https://pub.dev/packages/sentry/score) [![likes](https://badges.bar/get/likes)](https://pub.dev/packages/get/score) [![pub points](https://badges.bar/get/pub%20points)](https://pub.dev/packages/get/score) ![building](https://github.com/jonataslaw/get/workflows/build/badge.svg) [![style: effective dart](https://img.shields.io/badge/style-effective_dart-40c4ff.svg)](https://pub.dev/packages/effective_dart) [![Discord Shield](https://img.shields.io/discord/722900883784073290.svg?logo=discord)](https://discord.com/invite/9Hpt99N) [![Get on Slack](https://img.shields.io/badge/slack-join-orange.svg)](https://communityinviter.com/apps/getxworkspace/getx) [![Telegram](https://img.shields.io/badge/chat-on%20Telegram-blue.svg)](https://t.me/joinchat/PhdbJRmsZNpAqSLJL6bH7g) Awesome Flutter Buy Me A Coffee ![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/getx.png)

Pedimos desculpas por qualquer parte não traduzida aqui. O GetX™ é atualizado com muita frequência e as traduções podem não vir ao mesmo tempo. Então, para manter essa documentação pelo menos com tudo que a versão em inglês tem, eu vou deixar todos os textos não-traduzidos aqui (eu considero que é melhor ele estar lá em inglês do que não estar), então se alguém quiser traduzir, seria muito útil 😁

- [Sobre Get](#sobre-get) - [Instalando](#instalando) - [App Counter usando GetX](#app-counter-usando-getx) - [Os três pilares](#os-três-pilares) - [Gerenciamento de estado](#gerenciamento-de-estado) - [Reactive state manager](#reactive-state-manager) - [Mais detalhes sobre gerenciamento de estado](#mais-detalhes-sobre-gerenciamento-de-estado) - [Explicação em video do gerenciamento de estado](#explicação-em-video-do-gerenciamento-de-estado) - [Gerenciamento de rotas](#gerenciamento-de-rotas) - [Mais detalhes sobre gerenciamento de rotas](#mais-detalhes-sobre-gerenciamento-de-rotas) - [Explicação em video do gerenciamento de rotas](#explicação-em-video-do-gerenciamento-de-rotas) - [Gerenciamento de Dependência](#gerenciamento-de-dependência) - [Mais detalhes sobre gerenciamento de dependências](#mais-detalhes-sobre-gerenciamento-de-dependências) - [Utilidades](#utilidades) - [Internacionalização](#internacionalização) - [Traduções](#traduções) - [Usando traduções](#usando-traduções) - [Localidade](#localidade) - [Alterar local](#alterar-local) - [Localidade do sistema operacional](#localidade-do-sistema-operacional) - [Mudar tema (changeTheme)](#mudar-tema-changetheme) - [GetConnect](#getconnect) - [Configuração Padrão](#configuração-padrão) - [Configuração Personalizada](#configuração-personalizada) - [GetPage Middleware](#getpage-middleware) - [Priority](#priority) - [Redirect](#redirect) - [onPageCalled](#onpagecalled) - [OnBindingsStart](#onbindingsstart) - [OnPageBuildStart](#onpagebuildstart) - [OnPageBuilt](#onpagebuilt) - [OnPageDispose](#onpagedispose) - [Outras APIs avançadas](#outras-apis-avançadas) - [Configurações Globais opcionais e configurações manuais](#configurações-globais-opcionais-e-configurações-manuais) - [Widgets de Estado Local](#widgets-de-estado-local) - [ValueBuilder](#valuebuilder) - [ObxValue](#obxvalue) - [Explicação em vídeo sobre Outras Features do GetX](#explicação-em-vídeo-sobre-outras-features-do-getx) - [Dicas Úteis](#dicas-úteis) - [GetView](#getview) - [GetWidget](#getwidget) - [GetxService](#getxservice) - [Breaking Changes da versão 2 para 3](#breaking-changes-da-versão-2-para-3) - [Tipagem Rx](#tipagem-rx) - [RxController e GetBuilder se uniram](#rxcontroller-e-getbuilder-se-uniram) - [Rotas nomeadas](#rotas-nomeadas) - [Porque essa mudança?](#porque-essa-mudança) - [Por que GetX™?](#por-que-getx) - [Comunidade](#comunidade) - [Canais da comunidade](#canais-da-comunidade) - [Como contribuir](#como-contribuir) - [Artigos e vídeos](#artigos-e-vídeos) # Sobre Get - Get é uma biblioteca poderosa e extraleve para Flutter. Ela combina um gerenciador de estado de alta performance, injeção de dependência inteligente e gerenciamento de rotas de uma forma rápida e prática. - GetX™ possui 3 princípios básicos, o que significa que esta é a prioridade para todos os recursos da biblioteca: **PRODUTIVIDADE, PERFORMANCE AND ORGANIZAÇÃO.** - **PERFOMANCE**: GetX™ é focado em desempenho e consumo mínimo de recursos. GetX não usa Streams ou ChangeNotifier. - **PRODUTIVIDADE**: GetX™ usa uma sintaxe fácil e agradável. Não importa o que você queira fazer, sempre há uma maneira mais fácil com GetX™. Isso economizará horas de desenvolvimento e extrairá o máximo de desempenho que seu aplicativo pode oferecer. Geralmente, o desenvolvedor deve se preocupar em remover os controladores da memória. Com GetX™, isso não é necessário porque, por padrão, os recursos são removidos da memória quando não são usados. Se quiser mantê-lo na memória, você deve declarar explicitamente "permanent: true" em sua dependência. Dessa forma, além de economizar tempo, você corre menos risco de ter dependências desnecessárias na memória. O carregamento da dependência também é lazy por padrão. - **ORGANIZAÇÃO**: GetX™ permite o desacoplamento total da View, lógica de apresentação, lógica de negócios, injeção de dependência e navegação. Você não precisa de contexto para navegar entre as rotas, portanto, você não depende da árvore do widget (visualização) para isso. Você não precisa de contexto para acessar seus Controllers/BLoCs por meio de um inheritedWidget, então você desacopla completamente sua lógica de apresentação e lógica de negócios de sua camada de visualização. Você não precisa injetar suas classes Controllers/Models/BLoCs em sua árvore de widgets através de multiproviders, pois GetX™ usa seu próprio recurso de injeção de dependência, desacoplando a DI de sua View completamente. Com GetX™ você sabe onde encontrar cada recurso de sua aplicação, tendo o código limpo por padrão. Isso além de facilitar a manutenção, torna o compartilhamento dos módulos, algo que até então em Flutter era impensável, em algo totalmente possível. O BLoC foi um ponto de partida para organizar o código no Flutter, ele separa a lógica de negócios da visualização. GetX™ é uma evolução natural disso, separando não apenas a lógica de negócios, mas a lógica de apresentação. O bônus da injeção de dependências e rotas também são dissociadas e a camada de dados está fora de tudo. Você sabe onde está tudo e tudo isso de uma maneira mais fácil do que construir um hello world. GetX™ é a maneira mais fácil, prática e escalonável de construir aplicativos de alto desempenho com o Flutter SDK, com um grande ecossistema em torno dele que funciona perfeitamente em conjunto, sendo fácil para iniciantes e preciso para especialistas. É seguro, estável, atualizado e oferece uma grande variedade de APIs integradas que não estão presentes no Flutter SDK padrão. - GetX™ não é inchado. Possui uma infinidade de recursos que permitem que você comece a programar sem se preocupar com nada, mas cada um desses recursos está em contêineres separados e só são iniciados após o uso. Se você usar apenas o Gerenciamento de estado, apenas o Gerenciamento de estado será compilado. Se você usar apenas rotas, nada do gerenciamento de estado será compilado. - GetX™ possui um enorme ecossistema, uma grande comunidade, um grande número de colaboradores e será mantido enquanto o Flutter existir. Getx também é capaz de rodar com o mesmo código no Android, iOS, Web, Mac, Linux, Windows e em seu servidor. **É possível reutilizar totalmente seu código feito no frontend em seu backend com [Get Server](https://github.com/jonataslaw/get_server)**. **Além disso, todo o processo de desenvolvimento pode ser totalmente automatizado, tanto no servidor quanto no front-end com **[Get CLI](https://github.com/jonataslaw/get_cli)**. **Além disso, para aumentar ainda mais sua produtividade, temos a [extensão para VSCode](https://marketplace.visualstudio.com/items?itemName=get-snippets.get-snippets) e a [extensão para Android Studio/Intellij](https://plugins.jetbrains.com/plugin/14975-getx-snippets)** # Instalando Adicione Get ao seu arquivo pubspec.yaml ```yaml dependencies: get: ``` Importe o get nos arquivos que ele for usado: ```dart import 'package:get/get.dart'; ``` # App Counter usando GetX O app 'Counter' criado por padrão no flutter com o comando `flutter create` tem mais de 100 linhas(incluindo os comentários). Para demonstrar o poder do Get, irei demonstrar como fazer o mesmo 'Counter' mudando o estado em cada toque trocando entre páginas e compartilhando o estado entre telas. Tudo de forma organizada, separando a lógica de negócio da View, COM SOMENTE 26 LINHAS INCLUINDO COMENTÁRIOS - Passo 1: Troque `MaterialApp` para `GetMaterialApp` ```dart void main() => runApp(GetMaterialApp(home: Home())); ``` - **Obs:** Isso não modifica o `MaterialApp` do Flutter, GetMaterialApp não é uma versão modificada do MaterialApp, é só um Widget pré-configurado, que tem como child o MaterialApp padrão. Você pode configurar isso manualmente, mas definitivamente não é necessário. GetMaterialApp vai criar rotas, injetá-las, injetar traduções, injetar tudo que você precisa para navegação por rotas (gerenciamento de rotas). Se você quer somente usar o gerenciador de estado ou somente o gerenciador de dependências, não é necessário usar o GetMaterialApp. Ele somente é necessário para: - Rotas - Snackbars/bottomsheets/dialogs - apis relacionadas a rotas e a ausência de `context` - Internacionalização - **Obs²:** Esse passo só é necessário se você for usar o gerenciamento de rotas (`Get.to()`, `Get.back()` e assim por diante), Se você não vai usar isso então não é necessário seguir o passo 1 - Passo 2: Cria a sua classe de regra de negócio e coloque todas as variáveis, métodos e controllers dentro dela. Você pode fazer qualquer variável observável usando um simples `.obs` ```dart class Controller extends GetxController{ var count = 0.obs; increment() => count.value++; } ``` - Passo 3: Crie sua View usando StatelessWidget, já que, usando Get, você não precisa mais usar StatefulWidgets. ```dart class Home extends StatelessWidget { @override Widget build(context) { // Instancie sua classe usando Get.put() para torná-la disponível para todas as rotas subsequentes final Controller c = Get.put(Controller()); return Scaffold( // Use Obx(()=> para atualizar Text() sempre que a contagem é alterada. appBar: AppBar(title: Obx(() => Text("Total de cliques: ${c.count}"))), // Troque o Navigator.push de 8 linhas por um simples Get.to(). Você não precisa do 'context' body: Center(child: ElevatedButton( child: Text("Ir pra Outra tela"), onPressed: () => Get.to(Outra()))), floatingActionButton: FloatingActionButton(child: Icon(Icons.add), onPressed: c.increment)); } } class Outra extends StatelessWidget { // Você pode pedir o Get para encontrar o controller que foi usado em outra página e redirecionar você pra ele. final Controller c = Get.find(); @override Widget build(context) => Scaffold(body: Center(child: Text("${c.count}"))); } ``` Resultado: ![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/counter-app-gif.gif) Esse é um projeto simples mas já deixa claro o quão poderoso o Get é. Enquanto seu projeto cresce, essa diferença se torna bem mais significante. Get foi feito para funcionar com times, mas torna o trabalho de um desenvolvedor individual simples. Melhore seus prazos, entregue tudo a tempo sem perder performance. Get não é para todos, mas se você identificar com o que foi dito acima, Get é para você! # Os três pilares ## Gerenciamento de estado GetX™ possui dois gerenciadores de estado diferentes: o gerenciador de estado simples (vamos chamá-lo de GetBuilder) e o gerenciador de estado reativo (GetX/Obx) ### Reactive state manager Programação reativa pode alienar muitas pessoas porque é dito que é complicado. GetX™ transforma a programação reativa em algo bem simples: * Você não precisa criar StreamControllers * Você não precisa criar um StreamBuilder para cada variável * Você não precisa criar uma classe para cada estado * Você não precisa criar um get para o valor inicial * Você não precisará usar geradores de código Programação reativa com o Get é tão fácil quanto usar setState. Vamos imaginar que você tenha uma variável e quer que toda vez que ela alterar, todos os widgets que a usam são automaticamente alterados. Essa é sua variável: ```dart var name = 'Jonatas Borges'; ``` Para fazer dela uma variável observável, você só precisa adicionar `.obs` no final: ```dart var name = 'Jonatas Borges'.obs; ``` E Na UI, quando quiser mostrar a variável e escutar as mudanças dela, simplesmente faça isso: ```dart Obx (() => Text (controller.name)); ``` Só isso. É *simples assim*; ### Mais detalhes sobre gerenciamento de estado **Veja uma explicação mais completa do gerenciamento de estado [aqui](./documentation/pt_BR/state_management.md). Lá terá mais exemplos e também a diferença do simple state manager do reactive state manager** ### Explicação em video do gerenciamento de estado Amateur Coder fez um vídeo ótimo sobre o gerenciamento de estado! (em inglês). Link: [Complete GetX State Management](https://www.youtube.com/watch?v=CNpXbeI_slw) Você vai ter uma boa idea do poder do GetX™ ## Gerenciamento de rotas Se você for usar routes / snackbars / dialogs / bottomsheets sem contexto, GetX™ é excelente para você também, veja: Adicione "Get" antes do seu MaterialApp, transformando-o em GetMaterialApp ```dart GetMaterialApp( // Antes: MaterialApp( home: MyHome(), ) ``` Para navegar para uma próxima tela: ```dart Get.to(ProximaTela()); ``` Para navegar para uma próxima tela com uma rota nomeada. Veja mais detalhes sobre rotas nomeadas [aqui](./documentation/pt_BR/route_management.md#navegar-com-rotas-nomeadas) ```dart Get.toNamed('/detalhes'); ``` Para fechar snackbars, dialogs, bottomsheets, ou qualquer coisa que você normalmente fecharia com o `Navigator.pop(context)` (como por exemplo fechar a View atual e voltar para a anterior): ```dart Get.back(); ``` Para ir para a próxima tela e NÃO deixar opção para voltar para a tela anterior (bom para SplashScreens, telas de login e etc.): ```dart Get.off(ProximaTela()); ``` Para ir para a próxima tela e cancelar todas as rotas anteriores (útil em telas de carrinho, votações ou testes): ```dart Get.offAll(ProximaTela()); ``` Para navegar para a próxima rota e receber ou atualizar dados assim que retornar da rota: ```dart var dados = await Get.to(Pagamento()); ``` Notou que você não precisou usar `context` para fazer nenhuma dessas coisas? Essa é uma das maiores vantagens de usar o gerenciamento de rotas do GetX™. Com isso, você pode executar todos esse métodos de dentro da classe Controller, sem preocupações. ### Mais detalhes sobre gerenciamento de rotas **GetX™ funciona com rotas nomeadas também! Veja uma explicação mais completa do gerenciamento de rotas [aqui](./documentation/pt_BR/route_management.md)** ### Explicação em video do gerenciamento de rotas Amateur Coder fez um outro vídeo excelente sobre gerenciamento de rotas! Link: [Complete Getx Navigation](https://www.youtube.com/watch?v=RaqPIoJSTtI) ## Gerenciamento de Dependência - Nota: Se você está usando o gerenciador de estado do Get, você não precisa se preocupar com isso, só leia a documentação, mas dê uma atenção a api `Bindings`, que vai fazer tudo isso automaticamente para você. Já está usando o Get e quer fazer seu projeto o melhor possível? Get tem um gerenciador de dependência simples e poderoso que permite você pegar a mesma classe que seu Bloc ou Controller com apenas uma linha de código, sem Provider context, sem inheritedWidget: ```dart Controller controller = Get.put(Controller()); // Em vez de Controller controller = Controller(); ``` Em vez de instanciar sua classe dentro da classe que você está usando, você está instanciando ele dentro da instância do Get, que vai fazer ele ficar disponível por todo o App para que então você possa usar seu controller (ou uma classe Bloc) normalmente **Dica:** O gerenciamento de dependência Get é desacoplado de outras partes do pacote, então se, por exemplo, seu aplicativo já estiver usando um gerenciador de estado (qualquer um, não importa), você não precisa reescrever tudo, você pode usar esta injeção de dependência sem problemas ```dart controller.fetchApi(); ``` Agora, imagine que você navegou por inúmeras rotas e precisa de dados que foram deixados para trás em seu controlador. Você precisaria de um gerenciador de estado combinado com o Provider ou Get_it, correto? Não com Get. Você só precisa pedir ao Get para "procurar" pelo seu controlador, você não precisa de nenhuma dependência adicional para isso: ```dart Controller controller = Get.find(); // Sim, parece Magia, o Get irá descobrir qual é seu controller e irá te entregar. // Você pode ter 1 milhão de controllers instanciados, o Get sempre te entregará o controller correto. // Apenas se lembre de Tipar seu controller, final controller = Get.find(); por exemplo, não irá funcionar. ``` E então você será capaz de recuperar os dados do seu controller que foram obtidos anteriormente: ```dart Text(controller.textFromApi); ``` Procurando por `lazyLoading` (carregar somente quando for usar)? Você pode declarar todos os seus controllers e eles só vão ser inicializados e chamados quando alguém precisar. Você pode fazer isso ```dart Get.lazyPut(()=> ApiMock()); /// ApiMock só será chamado quando alguém usar o Get.find pela primeira vez ``` ### Mais detalhes sobre gerenciamento de dependências **Veja uma explicação mais completa do gerenciamento de dependência [aqui](./documentation/pt_BR/dependency_management.md)** # Utilidades ## Internacionalização ### Traduções Nós mantemos as traduções num simples dictionary map de chave-valor. Para adicionar traduções personalizadas, crie uma classe e estenda `Translations`. ```dart import 'package:get/get.dart'; class Messages extends Translations { @override Map> get keys => { 'en_US': { 'hello': 'Hello World', }, 'de_DE': { 'hello': 'Hallo Welt', } }; } ``` #### Usando traduções Basta anexar `.tr` a chave especificada e ela será traduzida, usando o valor atual de `Get.locale` ou `Get.fallbackLocale`. ```dart Text('hello'.tr); ``` ### Localidade Passe parâmetros para `GetMaterialApp` definir a localidade e as traduções. ```dart return GetMaterialApp( translations: Messages(), // suas traduções locale: Locale('en', 'US'), // as traduções serão exibidas para esta localidade fallbackLocale: Locale('en', 'UK'), // especifica uma localidade em caso de falha na localidade definida ); ``` #### Alterar local Use `Get.updateLocale(locale)` para atualizar a localidade. As traduções usarão automaticamente a nova localidade e a UI será atualizada. ```dart var locale = Locale('en', 'US'); Get.updateLocale(locale); ``` #### Localidade do sistema operacional Para ler a localidade do sistema operacional, você pode usar `Get.deviceLocale`. ```dart import 'dart:ui' as ui; return GetMaterialApp( locale: Get.deviceLocale, ); ``` ## Mudar tema (changeTheme) Por favor não use widget acima do GetMaterialApp para atualizar o tema. Isso pode causar keys duplicadas. Várias pessoas estão acostumadas com o jeito normal de criar um Widget `ThemeProvider` só pra alterar o tema do app, mas isso definitivamente NÃO é necessário com GetX™. Você pode criar seu tema customizado e simplesmente adicionar dentro do `Get.changeTheme` sem nenhum boilerplate para isso: ```dart Get.changeTheme(ThemeData.light()) ``` Se você quer criar algo como um botão que muda o tema com o toque, você pode combinar duas APIs GetX™ pra isso: - A API que checa se o tema dark está sendo aplicado; - A API de mudar o tema e colocar isso no `onPressed:` ```dart Get.changeTheme(Get.isDarkMode ? ThemeData.light() : ThemeData.dark()) ``` Quando o modo Dark está ativado, ele vai trocar pro modo light e vice versa. Se você quiser saber mais como trocar o tema, você pode seguir esse tutorial no Medium que até ensina persistência do tema usando Get (e SharedPreferences): - [Dynamic Themes in 3 lines using Get](https://medium.com/swlh/flutter-dynamic-themes-in-3-lines-c3b375f292e3) - Tutorial by [Rod Brown](https://github.com/RodBr). ## GetConnect GetConnect é uma maneira fácil de se comunicar de trás para a frente com http ou websockets ### Configuração Padrão Você pode simplesmente estender GetConnect e usar os métodos GET/POST/PUT/DELETE/SOCKET para se comunicar com sua API Rest ou websockets. ```dart class UserProvider extends GetConnect { // Requisição Get Future getUser(int id) => get('http://youapi/users/$id'); // Requisição Post Future postUser(Map data) => post('http://youapi/users', body: data); // Requisição Post com Arquivo Future> postCases(List image) { final form = FormData({ 'file': MultipartFile(image, filename: 'avatar.png'), 'otherFile': MultipartFile(image, filename: 'cover.png'), }); return post('http://youapi/users/upload', form); } GetSocket userMessages() { return socket('https://yourapi/users/socket'); } } ``` ### Configuração Personalizada GetConnect é altamente personalizável, você pode definir uma base Url, modificadores de resposta, modificadores de Requests, definir um autenticador e até o número de tentativas em que tentará se autenticar, além de dar a possibilidade de definir um decodificador padrão que irá transformar todas as suas solicitações em seus modelos sem qualquer configuração adicional. ```dart class HomeProvider extends GetConnect { @override void onInit() { // Todas as requisições passam por jsonEncode e então por CasesModel.fromJson() httpClient.defaultDecoder = CasesModel.fromJson; httpClient.baseUrl = 'https://api.covid19api.com'; // baseUrl = 'https://api.covid19api.com'; // Define baseUrl para // Http e websockets se usado sem uma instância [httpClient] // Anexa a propriedade 'apikey' no cabeçalho de todas as requisições httpClient.addRequestModifier((request) { request.headers['apikey'] = '12345678'; return request; }); // Mesmo que o servidor envie dados do país "Brasil", // eles nunca serão exibidos para os usuários, porque você removeu // os dados da resposta, mesmo antes de a resposta ser entregue httpClient.addResponseModifier((request, response) { CasesModel model = response.body; if (model.countries.contains('Brazil')) { model.countries.remove('Brazil'); } }); httpClient.addAuthenticator((request) async { final response = await get("http://yourapi/token"); final token = response.body['token']; // Configurando um cabeçalho request.headers['Authorization'] = "$token"; return request; }); // O autenticador será chamado 3 vezes se HttpStatus for // HttpStatus.unauthorized httpClient.maxAuthRetries = 3; } } @override Future> getCases(String path) => get(path); } ``` ## GetPage Middleware O GetPage agora tem uma nova propriedade que recebe uma lista de GetMiddleWare e executa cada item na ordem específica. **Nota**: Quando GetPage tem Middlewares, todos os filhos desta página terão os mesmos middlewares automaticamente. ### Priority A ordem dos middlewares a serem executados pode ser definida pela prioridade no GetMiddleware. ```dart final middlewares = [ GetMiddleware(priority: 2), GetMiddleware(priority: 5), GetMiddleware(priority: 4), GetMiddleware(priority: -8), ]; ``` Esses middlewares serão executados nesta ordem: **-8 => 2 => 4 => 5** ### Redirect Esta função será chamada quando a página da rota chamada estiver sendo pesquisada. RouteSettings se torna o resultado do redirecionamento. Ou retorne nulo e não haverá redirecionamento. ```dart RouteSettings redirect(String route) { final authService = Get.find(); return authService.authed.value ? null : RouteSettings(name: '/login') } ``` ### onPageCalled Esta função será chamada quando uma página for chamada, antes de qualquer coisa ser criada e você pode usá-la para mudar algo sobre a página ou dar-lhe uma nova página ```dart GetPage onPageCalled(GetPage page) { final authService = Get.find(); return page.copyWith(title: 'Welcome ${authService.UserName}'); } ``` ### OnBindingsStart Esta função será chamada logo antes da inicialização dos Bindings. Aqui você pode alterar as ligações desta página. ```dart List onBindingsStart(List bindings) { final authService = Get.find(); if (authService.isAdmin) { bindings.add(AdminBinding()); } return bindings; } ``` ### OnPageBuildStart Esta função será chamada logo após a inicialização dos Bindings. Aqui você pode fazer algo depois de criar as ligações e antes de criar o widget da página. ```dart GetPageBuilder onPageBuildStart(GetPageBuilder page) { print('bindings are ready'); return page; } ``` ### OnPageBuilt Esta função será chamada logo após a função GetPage.page ser chamada e fornecerá o resultado da função e obtém o widget que será mostrado. ### OnPageDispose Esta função será chamada logo após descartar todos os objetos relacionados (controladores, visualizações, ...) da página. ## Outras APIs avançadas ```dart // fornece os arguments da tela atual Get.arguments // fornece o nome da rota anterior Get.previousRoute // fornece a rota bruta para acessar por exemplo, rawRoute.isFirst() Get.rawRoute // fornece acesso a API de rotas de dentro do GetObserver Get.routing // checa se o snackbar está aberto Get.isSnackbarOpen // checa se o dialog está aberto Get.isDialogOpen // checa se o bottomsheet está aberto Get.isBottomSheetOpen // remove uma rota. Get.removeRoute() // volta repetidamente até o predicate retorne true. Get.until() // vá para a próxima rota e remove todas as rotas //anteriores até que o predicate retorne true. Get.offUntil() // vá para a próxima rota nomeada e remove todas as //rotas anteriores até que o predicate retorne true. Get.offNamedUntil() // Verifica em que plataforma o app está sendo executado // (Esse método é completamente compatível com o FlutterWeb, // diferente do método do framework "Platform.isAndroid") GetPlatform.isAndroid GetPlatform.isIOS GetPlatform.isMacOS GetPlatform.isWindows GetPlatform.isLinux GetPlatform.isFuchsia // Verifica o tipo de dispositivo GetPlatform.isMobile GetPlatform.isDesktop // Todas as plataformas são suportadas de forma independente na web! // Você pode saber se está executando dentro de um navegador // no Windows, iOS, OSX, Android, etc. GetPlatform.isWeb // Equivalente ao método: MediaQuery.of(context).size.width ou height, mas é imutável. // Significa que não irá atualizar mesmo que o tamanho da tela mude (como em navegadores ou app desktop) Get.height Get.width // fornece o context da tela em qualquer lugar do seu código. Get.context // fornece o context de snackbar/dialog/bottomsheet em qualquer lugar do seu código. Get.contextOverlay // Obs: os métodos a seguir são extensions do context. Já que se // tem acesso ao context em qualquer lugar do código da UI, você pode usar lá // Se você precisa de um width/height adaptável (como em navegadores em que a janela pode ser redimensionada) // você precisa usar 'context' context.width context.height // Dá a você agora o poder de definir metade da tela, um terço da dela e assim por diante. // Útil para aplicativos responsivos. // param dividedBy (double) opcional - default: 1 // param reducedBy (double) opcional - default: 0 context.heightTransformer() context.widthTransformer() /// similar a MediaQuery.of(context).size context.mediaQuerySize() /// similar a MediaQuery.of(this).padding context.mediaQueryPadding() /// similar a MediaQuery.of(this).viewPadding context.mediaQueryViewPadding() /// similar a MediaQuery.of(this).viewInsets; context.mediaQueryViewInsets() /// similar a MediaQuery.of(this).orientation; context.orientation() /// verifica se o dispositivo está no modo paisagem context.isLandscape() /// verifica se o dispositivo está no modo retrato context.isPortrait() /// similar a MediaQuery.of(this).devicePixelRatio; context.devicePixelRatio() /// similar a MediaQuery.of(this).textScaleFactor; context.textScaleFactor() /// obtém a menor dimensão (largura ou altura) da tela context.mediaQueryShortestSide() /// retorna True se a largura da tela for maior que 800px context.showNavbar() /// retorna True se a menor dimensão (largura ou altura) da tela for menor que 600px context.isPhone() /// retorna True se a menor dimensão (largura ou altura) da tela for maior ou igual a 600px context.isSmallTablet() /// retorna True se a menor dimensão (largura ou altura) da tela for maior ou igual a 720px context.isLargeTablet() /// retorna True se o dispositivo é um Tablet context.isTablet() /// Retorna um valor de acordo com o tamanho da tela /// Os valores possíveis são: /// swatch: se a menor dimensão (largura ou altura) da tela for menor que 300px /// mobile: se a menor dimensão (largura ou altura) da tela for menor que 600px /// tablet: se a menor dimensão (largura ou altura) da tela for menor que 1200px /// desktop: se a largura da tela é maior ou iguial a 1200px context.responsiveValue() ``` ### Configurações Globais opcionais e configurações manuais GetMaterialApp configura tudo para você, mas se quiser configurar Get manualmente, você pode. ```dart MaterialApp( navigatorKey: Get.key, navigatorObservers: [GetObserver()], ); ``` Você também será capaz de usar seu próprio Middleware dentro do GetObserver, isso não irá influenciar em nada. ```dart MaterialApp( navigatorKey: Get.key, navigatorObservers: [ GetObserver(MiddleWare.observer) // Aqui ], ); ``` Você pode criar Configurações Globais para o Get. Apenas adicione `Get.config` ao seu código antes de usar qualquer rota ou faça diretamente no seu GetMaterialApp ```dart GetMaterialApp( enableLog: true, defaultTransition: Transition.fade, opaqueRoute: Get.isOpaqueRouteDefault, popGesture: Get.isPopGestureEnable, transitionDuration: Get.defaultDurationTransition, defaultGlobalState: Get.defaultGlobalState, ); Get.config( enableLog = true, defaultPopGesture = true, defaultTransition = Transitions.cupertino ) ``` É possível redirecionar todas as mensagens de log do GetX™. Útil quando se tem um package de logging e vc quer que ele lide com todos os logs ```dart GetMaterialApp( enableLog: true, logWriterCallback: localLogWriter, ); void localLogWriter(String text, {bool isError = false}) { // passage a mensagem para seu package de logging favorito aqui // Obs: mesmo que as mensagens de log estejam desativadas // com o comando "enableLog: false", as mensagens ainda vão passar por aqui // Você precisa checar essa config manualmente aqui se quiser respeitá-la } ``` ### Widgets de Estado Local Esses Widgets permitem que você gerencie um único valor e mantenha o estado efêmero e localmente. Temos versões para Reativo e Simples. Por exemplo, você pode usá-los para alternar obscureText em um `TextField`, talvez criar um painel expansível personalizado ou talvez modificar o índice atual em um `BottomNavigationBar` enquanto altera o conteúdo do corpo em um `Scaffold`. #### ValueBuilder Uma simplificação de `StatefulWidget` que funciona com um callback de `setState` que passa o valor atualizado. ```dart ValueBuilder( initialValue: false, builder: (value, updateFn) => Switch( value: value, onChanged: updateFn, // mesma assinatura! Você poderia usar ( newValue ) => updateFn( newValue ) ), // se você precisa chamar algo fora do método builder. onUpdate: (value) => print("Valor atualizado: $value"), onDispose: () => print("Widget desmontado"), ), ``` #### ObxValue Similar a ValueBuilder, mas esta é a versão Reativa, você passa uma instância Rx (lembra do .obs mágico?) e atualiza automaticamente... não é incrível? ```dart ObxValue( (data) => Switch( value: data.value, onChanged: data, // Rx tem uma função _callable_! Você poderia usar (flag) => data.value = flag, ), false.obs, ), ``` ### Explicação em vídeo sobre Outras Features do GetX Amateur Coder fez um vídeo incrível sobre utils, storage, bindings e outras features! Link: [GetX Other Features](https://youtu.be/ttQtlX_Q0eU) ## Dicas Úteis `.obs`ervables (também conhecidos como _Rx_ Types) possuem uma grande variedade de métodos e operadores internos. > É muito comum acreditar que uma propriedade com `.obs` **É** o valor real... mas não se engane! > Evitamos a declaração de tipo da variável, porque o compilador do Dart é inteligente o suficiente e o código > parece mais limpo, mas: ```dart var message = 'Hello world'.obs; print( 'Message "$message" é do tipo ${message.runtimeType}'); ``` Mesmo que `message` _imprima_ o valor da string, seu tipo é **RxString**! Então, você não pode fazer `message.substring( 0, 4 )`. Você tem que acessar o `valor` real dentro do _observable_: A "maneira" mais usada é utilizando `.value`, mas, você sabia que também pode usar: ```dart final name = 'GetX'.obs; // apenas "atualiza" o stream, se o valor for diferente do atual. name.value = 'Hey'; // Todas as propriedades Rx são "chamáveis" e retorna o novo valor. // mas esta abordagem não aceita `null`, a UI não será reconstruída name('Hello'); // é como um getter, imprime 'Hello' name() ; /// números: final count = 0.obs; // Você pode usar todas as operações não mutáveis ​​de um num! count + 1; // Cuidado! isso só é válido se `count` não for final, mas var count += 1; // Você também pode comparar com os valores: count > 2; /// booleans: final flag = false.obs; // mude o valor entre true/false flag.toggle(); /// todos os tipos: // Defina `value` como null. flag.nil(); // Todas as operações toString() e toJson() são passada para `value` print( count ); // chama `toString()` de RxInt final abc = [0,1,2].obs; // Converte o valor em um Array json, imprime RxList // Json é suportado por todos os Rx types! print('json: ${jsonEncode(abc)}, type: ${abc.runtimeType}'); // RxMap, RxList e RxSet são Rx types especiais, que estendem seus tipos nativos. // mas você pode trabalhar com uma lista como uma lista normal, embora seja reativa! abc.add(12); // Coloca 12 na lista, e ATUALIZA o stream. abc[3]; // como uma lista lê o índice 3. // a igualdade funciona com o Rx e o value do observável, mas o hashCode é sempre obtido do value final number = 12.obs; print( number == 12 ); // prints > true /// Rx Models personalizados: // toJson(), toString() são transferidos para o filho, para que você possa implementar // override neles e imprimir o observável diretamente. class User { String name, last; int age; User({this.name, this.last, this.age}); @override String toString() => '$name $last, $age years old'; } final user = User(name: 'John', last: 'Doe', age: 33).obs; // `user` é "reativo", mas as propriedades dentro NÃO SÃO! // Então, se mudarmos alguma variável dentro dele: user.value.name = 'Roi'; // O widget não vai reconstruir!, // `Rx` não tem nenhuma notificação quando você muda algo dentro do usuário. // Portanto, para classes personalizadas, precisamos "notificar" manualmente a mudança. user.refresh(); // ou podemos usar o método `update()`! user.update((value){ value.name='Roi'; }); print( user ); // Resultado (toString): Roi Doe, 33 years old ``` #### GetView Eu amo este Widget, é tão simples, mas tão útil! É um Widget `const Stateless` que tem um getter `controller` registrado para Controller, só isso. ```dart class AwesomeController extends GetxController { final String title = 'My Awesome View'; } // SEMPRE lembre de passar o `Type` que você usou para registrar seu controlador! class AwesomeView extends GetView { @override Widget build(BuildContext context) { return Container( padding: EdgeInsets.all(20), child: Text(controller.title ), // apenas chame `controller.something` ); } } ``` #### GetWidget A maioria das pessoas não tem ideia sobre este widget, ou confunde totalmente o uso dele. O caso de uso é muito raro, mas muito específico: Ele armazena em `cache` um Controller. Por causa do _cache_, não pode ser um `const Stateless`. > Então, quando você precisa armazenar em "cache" um Controller? Se você usar, uma outra característica "não tão comum" de **GetX™**: `Get.create()`. `Get.create(()=>Controller())` irá gerar um novo `Controller` cada vez que você chamar `Get.find()`, É aí que `GetWidget` brilha... já que você pode usá-lo, por exemplo, para manter uma lista de itens Todo. Portanto, se o widget for "reconstruído", ele manterá a mesma instância do controlador. #### GetxService Esta classe é como um `GetxController`, ele compartilha o mesmo ciclo de vida ( `onInit()`, `onReady()`, `onClose()`). Mas não tem "lógica" dentro dele. Ele apenas notifica o sistema de injeção de dependência do GetX™ de que esta subclasse **não pode** ser removida da memória. Portanto, é muito útil manter seus "Services" sempre acessíveis e ativos com `Get.find()`. Como: `ApiService`, `StorageService`, `CacheService`. ```dart Future main() async { await initServices(); /// Aguarda a inicialização dos Services. runApp(SomeApp()); } /// É uma jogada inteligente para inicializar seus services antes de executar o aplicativo Flutter, /// já que você pode controlar o fluxo de execução (talvez você precise carregar alguma configuração de tema, /// apiKey, linguagem definida pelo usuário ... então carregue SettingService antes de executar ApiService. /// então GetMaterialApp() não precisa reconstruir e obtém os valores diretamente. void initServices() async { print('iniciando serviços...'); /// Aqui é onde você coloca a inicialização de get_storage, hive, shared_pref. /// ou checa a conexão, ou o que quer que seja assíncrono. await Get.putAsync(() => DbService().init()); await Get.putAsync(SettingsService()).init(); print('Todos os serviços iniciados.'); } class DbService extends GetxService { Future init() async { print('$runtimeType delays 2 sec'); await 2.delay(); print('$runtimeType ready!'); return this; } } class SettingsService extends GetxService { void init() async { print('$runtimeType delays 1 sec'); await 1.delay(); print('$runtimeType ready!'); } } ``` A única maneira de realmente excluir um `GetxService`, é com o `Get.reset()`, que é como uma "hot restart" do seu aplicativo. Portanto, lembre-se, se você precisar de persistência absoluta de uma instância de classe durante o ciclo de vida de seu aplicativo, use GetxService. # Breaking Changes da versão 2 para 3 ## Tipagem Rx | Antes | Depois | | ------- | ---------- | | StringX | `RxString` | | IntX | `RxInt` | | MapX | `RxMap` | | ListX | `RxList` | | NumX | `RxNum` | | DoubleX | `RxDouble` | ## RxController e GetBuilder se uniram RxController e GetBuilder agora viraram um só, você não precisa mais memorizar qual controller quer usar, apenas coloque `GetxController`, vai funcionar para os dois gerenciamento de estados ```dart //Gerenciador de estado simples class Controller extends GetXController { String nome = ''; void atualizarNome(String novoNome) { nome = novoNome; update() } } ``` ```dart class Controller extends GetXController { final nome = ''.obs; // não precisa de um método direto pra atualizar o nome // só usar o nome.value } ``` ## Rotas nomeadas Antes: ```dart GetMaterialApp( namedRoutes: { '/': GetRoute(page: Home()), } ) ``` Agora: ```dart GetMaterialApp( getPages: [ GetPage(name: '/', page: () => Home()), ] ) ``` ### Porque essa mudança? Frequentemente, pode ser necessário decidir qual pagina vai ser mostrada ao usuário a partir de um parâmetro, como um token de login. A forma abordada anteriormente não era flexível, já que não permitia isso. Inserir a página numa função reduziu significativamente o consumo de RAM, já que as rotas não são alocadas na memória no momento que o app é iniciado e também permite fazer esse tipo de abordagem: ```dart GetStorage box = GetStorage(); GetMaterialApp( getPages: [ GetPage(name: '/', page:(){ return box.hasData('token') ? Home() : Login(); }) ] ) ``` # Por que GetX™? 1- Muitas vezes após uma atualização do Flutter, muitos dos seus packages irão quebrar. As vezes acontecem erros de compilação, muitas vezes aparecem erros que ainda não existem respostas sobre e o desenvolvedor necessita saber de onde o erro veio, rastreá-lo, para só então tentar abrir uma issue no repositório correspondente e ver seu problema resolvido. Get centraliza os principais recursos para o desenvolvimento (Gerência de estado, de dependências e de rotas), permitindo você adicionar um único package em seu pubspec e começar a trabalhar. Após uma atualização do Flutter, a única coisa que você precisa fazer é atualizar a dependencia do Get e começar a trabalhar. Get também resolve problemas de compatibilidade. Quantas vezes uma versão de um package não é compatível com a versão de outro, porque um utiliza uma dependência em uma versão e o outro em outra versão? Essa também não é uma preocupação usando Get, já que tudo está no mesmo package e é totalmente compatível. 2- Flutter é fácil, Flutter é incrível, mas Flutter ainda tem algum boilerplate que pode ser indesejado para maioria dos desenvolvedores, como o Navigator.of(context).push(context, builder[...]. Get simplifica o desenvolvimento. Em vez de escrever 8 linhas de código para apenas chamar uma rota, você pode simplesmente fazer: Get.to(Home()) e pronto, você irá para a próxima página. Urls dinâmicas da web é algo realmente doloroso de fazer com o Flutter atualmente e isso com o GetX™ é estupidamente simples. Gerenciar estados no Flutter e gerenciar dependências também é algo que gera muita discussão, por haver centenas de padrões na pub. Mas não há nada que seja tão fácil quanto adicionar um ".obs" no final de sua variável, colocar o seu widget dentro de um Obx e pronto, todas atualizações daquela variável serão automaticamente atualizadas na tela. 3- Facilidade sem se preocupar com desempenho. O desempenho do Flutter já é incrível, mas imagine que você use um gerenciador de estados e um locator para distribuir suas classes blocs/stores/controllers/ etc. Você deverá chamar manualmente a exclusão daquela dependência quando não precisar dela. Mas já pensou em simplesmente usar seu controlador e quando ele não tivesse mais sendo usado por ninguém, ele simplesmente fosse excluído da memória? É isso que GetX™ faz. Com o SmartManagement, tudo que não está sendo usado é excluído da memória e você não deve se preocupar em nada além de programar. Você terá garantia que está consumindo o mínimo de recursos necessários, sem ao menos ter criado uma lógica para isso. 4- Desacoplamento real. Você já deve ter ouvido o conceito "separar a view da lógica de negócios". Isso não é uma peculiaridade do BLoC, MVC ou MVVM, qualquer outro padrão existente no mercado tem esse conceito. No entanto, muitas vezes esse conceito pode ser mitigado no Flutter por conta do uso do context. Se você precisa de context para localizar um InheritedWidget, você precisa disso na view ou passar o context por parâmetro. Eu particularmente acho essa solução muito feia e para trabalhar em equipes teremos sempre uma dependência da lógica de negócios da View. GetX™ é pouco ortodoxo com a abordagem padrão e apesar de não proibir totalmente o uso de StatefulWidgets, InitState e etc, ele tem sempre uma abordagem similar que pode ser mais limpa. Os controllers tem ciclos de vida e quando você precisa fazer uma solicitação APIREST por exemplo, você não depende de nada da view. Você pode usar onInit para iniciar a chamada http e quando os dados chegarem, as variáveis serão preenchidas. Como GetX™ é totalmente reativo (de verdade e trabalha sob streams), assim que os itens forem preenchidos, automaticamente será atualizado na view todos os widgets que usam aquela variável. Isso permite que as pessoas especialistas em UI trabalhem apenas com widgets e não precisem enviar nada para a lógica de negócio além de eventos do usuário (como clicar em um botão), enquanto as pessoas que trabalham com a lógica de negócio ficarão livres para criá-la e testá-la separadamente. # Comunidade ## Canais da comunidade GetX™ tem uma comunidade altamente ativa e útil. Se você tiver dúvidas, ou quiser alguma ajuda com relação ao uso deste framework, por favor entre em nossos canais da comunidade, sua dúvida será respondida mais rapidamente, e será o lugar mais adequado. Este repositório é exclusivo para abertura de issues e solicitação de recursos, mas fique à vontade para fazer parte da Comunidade GetX™. | **Slack (Inglês)** | **Discord (Inglês e Português)** | **Telegram (Português)** | | :-------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------- | | [![Get on Slack](https://img.shields.io/badge/slack-join-orange.svg)](https://communityinviter.com/apps/getxworkspace/getx) | [![Discord Shield](https://img.shields.io/discord/722900883784073290.svg?logo=discord)](https://discord.com/invite/9Hpt99N) | [![Telegram](https://img.shields.io/badge/chat-on%20Telegram-blue.svg)](https://t.me/joinchat/PhdbJRmsZNpAqSLJL6bH7g) | ## Como contribuir _Quer contribuir com o projeto? Teremos o orgulho de destacá-lo como um de nossos colaboradores. Aqui estão alguns pontos onde você pode contribuir e tornar o Get (e Flutter) ainda melhor._ - Ajudando a traduzir o readme para outros idiomas. - Adicionando documentação ao readme (muitas funções do Get ainda não foram documentadas). - Escreva artigos ou faça vídeos ensinando como usar o Get (eles serão inseridos no Readme e futuramente em nosso Wiki). - Fazendo PRs para código/testes. - Incluindo novas funções. Qualquer contribuição é bem-vinda! ## Artigos e vídeos - [Dynamic Themes in 3 lines using GetX™](https://medium.com/swlh/flutter-dynamic-themes-in-3-lines-c3b375f292e3) - Tutorial by [Rod Brown](https://github.com/RodBr). (inglês) - [Complete GetX™ Navigation](https://www.youtube.com/watch?v=RaqPIoJSTtI) - Route management video by Amateur Coder. (inglês) - [Complete GetX™ State Management](https://www.youtube.com/watch?v=CNpXbeI_slw) - State management video by Amateur Coder. (inglês) - [GetX™ Other Features](https://youtu.be/ttQtlX_Q0eU) - Utils, storage, bindings and other features video by Amateur Coder. (inglês) - [Firestore User with GetX™ | Todo App](https://www.youtube.com/watch?v=BiV0DcXgk58) - Video by Amateur Coder. (inglês) - [Firebase Auth with GetX™ | Todo App](https://www.youtube.com/watch?v=-H-T_BSgfOE) - Video by Amateur Coder. (inglês) - [The Flutter GetX™ Ecosystem ~ State Management](https://medium.com/flutter-community/the-flutter-getx-ecosystem-state-management-881c7235511d) - State management by [Aachman Garg](https://github.com/imaachman). (inglês) - [The Flutter GetX™ Ecosystem ~ Dependency Injection](https://medium.com/flutter-community/the-flutter-getx-ecosystem-dependency-injection-8e763d0ec6b9) - Dependency Injection by [Aachman Garg](https://github.com/imaachman). (inglês) - [GetX™, the all-in-one Flutter package](https://www.youtube.com/watch?v=IYQgtu9TM74) - A brief tutorial covering State Management and Navigation by Thad Carnevalli. (inglês) - [Build a To-do List App from scratch using Flutter and GetX™](https://www.youtube.com/watch?v=EcnqFasHf18) - UI + State Management + Storage video by Thad Carnevalli. (inglês) - [GetX™ Flutter Firebase Auth Example](https://medium.com/@jeffmcmorris/getx-flutter-firebase-auth-example-b383c1dd1de2) - Article by Jeff McMorris. (inglês) - [Flutter State Management with GetX – Complete App](https://www.appwithflutter.com/flutter-state-management-with-getx/) - by App With Flutter. (inglês) - [Flutter Routing with Animation using Get Package](https://www.appwithflutter.com/flutter-routing-using-get-package/) - by App With Flutter. (inglês) ================================================ FILE: README.ru.md ================================================ ![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/get.png) _Языки: Русский (этот файл), [вьетнамский](README-vi.md), [индонезийский](README.id-ID.md), [урду](README.ur-PK.md), [Английский](README.md), [Китайский](README.zh-cn.md), [Бразильский Португальский](README.pt-br.md), [Испанский](README-es.md), [Польский](README.pl.md), [Kорейский](README.ko-kr.md), [French](README-fr.md)._ [![pub package](https://img.shields.io/pub/v/get.svg?label=get&color=blue)](https://pub.dev/packages/get) [![popularity](https://badges.bar/get/popularity)](https://pub.dev/packages/sentry/score) [![likes](https://badges.bar/get/likes)](https://pub.dev/packages/get/score) [![pub points](https://badges.bar/get/pub%20points)](https://pub.dev/packages/get/score) ![building](https://github.com/jonataslaw/get/workflows/build/badge.svg) [![style: effective dart](https://img.shields.io/badge/style-effective_dart-40c4ff.svg)](https://pub.dev/packages/effective_dart) [![Discord Shield](https://img.shields.io/discord/722900883784073290.svg?logo=discord)](https://discord.com/invite/9Hpt99N) [![Get on Slack](https://img.shields.io/badge/slack-join-orange.svg)](https://communityinviter.com/apps/getxworkspace/getx) [![Telegram](https://img.shields.io/badge/chat-on%20Telegram-blue.svg)](https://t.me/joinchat/PhdbJRmsZNpAqSLJL6bH7g) Awesome Flutter Buy Me A Coffee ![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/getx.png) - [Про Get](#про-get) - [Установка](#установка) - [Приложение "Счётчик" с GetX](#приложение-счётчик-с-getx) - [Три столпа](#три-столпа) - [Управление состоянием](#управление-состоянием) - [Реактивное управление состоянием](#реактивное-управление-состоянием) - [Подробнее об управлении состоянием](#подробнее-об-управлении-состоянием) - [Управление маршрутами](#управление-маршрутами) - [Подробнее об управлении маршрутами](#подробнее-об-управлении-маршрутами) - [Внедрение зависимостей](#внедрение-зависимостей) - [Подробнее о внедрении зависимостей](#подробнее-о-внедрении-зависимостей) - [Утилиты](#утилиты) - [Интернационализация](#интернационализация) - [Переводы](#переводы) - [Использование переводов](#использование-переводов) - [Локализация](#локализация) - [Изменение локализации](#изменение-локализации) - [Системная локализация](#системная-локализация) - [Изменение темы](#изменение-темы) - [Другие API](#другие-api) - [Дополнительные глобальные настройки и ручные настройки](#дополнительные-глобальные-настройки-и-ручные-настройки) - [Локальные виджеты состояния](#локальные-виджеты-состояния) - [ValueBuilder](#valuebuilder) - [ObxValue](#obxvalue) - [Полезные советы](#полезные-советы) - [GetView](#getview) - [GetWidget](#getwidget) - [GetxService](#getxservice) - [Критические изменения по сравнению с версией 2.0](#критические-изменения-по-сравнению-с-версией-20) - [Почему Getx?](#почему-getx) - [Сообщества](#сообщества) - [Каналы сообщества](#каналы-сообщества) - [Как внести свой вклад](#как-внести-свой-вклад) - [Статьи и видео](#статьи-и-видео) # Про Get - GetX - это сверхлегкое и мощное решение для Flutter. Оно совмещает в себе высокопроизводительное управление состоянием, интеллектуальное внедрение зависимостей, управление маршрутами быстрым и практичным способом. - GetX имеет 3 базовых принципа, являющихся приоритетом для всех ресурсов в библиотеке - **Производительность:** GetX сфокусирован на производительности и минимальном потреблении ресурсов. Бенчмарки почти всегда не имеют значения в реальном мире, но, если Вам угодно, здесь ([бенчмарки](https://github.com/jonataslaw/benchmarks)) есть индикаторы потребления, где GetX работает лучше, чем другие подходы к управлению состоянием. Разница небольшая, но демонстрирует нашу заботу о ресурсах. - **Продуктивность:** GetX использует простой и приятный синтаксис. Не имеет значения, что вы хотите сделать, всегда есть более легкий способ с GetX. Это сэкономит часы разработки и обеспечит максимальную производительность, которую может обеспечить ваше приложение. - **Организация:** GetX позволяет полностью разделить представление, логику представления, бизнес-логику, внедрение зависимостей и навигацию. Вам не нужен контекст для навигации между маршрутами, поэтому вы не зависите от дерева виджетов. Вам не нужен контекст для доступа к вашим контроллерам / блокам через наследуемый виджет, поэтому вы полностью отделяете логику представления и бизнес-логику от уровня визуализации. Вам не нужно внедрять классы Controllers / Models / Blocs в дерево виджетов через мультипровайдеры, поскольку GetX использует собственную функцию внедрения зависимостей, полностью отделяя DI от его представления. С GetX вы знаете, где найти каждую функцию вашего приложения, имея чистый код по умолчанию. Это, помимо упрощения обслуживания, делает возможным совместное использование модулей, что до того момента во Flutter было немыслимо. BLoC был отправной точкой для организации кода во Flutter, он отделяет бизнес-логику от визуализации. Getx является естественным развитием этого, разделяя не только бизнес-логику, но и логику представления. Дополнительное внедрение зависимостей и маршрутов также разделено, и уровень данных не учитывается. Вы знаете, где все находится, и это проще, чем написать "Hello World". GetX - это самый простой, практичный и масштабируемый способ создания высокопроизводительных приложений с помощью Flutter SDK с большой экосистемой вокруг него, которая отлично работает, прост для новичков и точен для экспертов. Он безопасен, стабилен, актуален и предлагает огромный набор встроенных API, которых нет в Flutter SDK по умолчанию. - GetX не раздут. Он имеет множество функций, которые позволяют вам начать программировать, ни о чем не беспокоясь, но каждая из этих функций находится в отдельных контейнерах и запускается только после использования. Если вы используете только управление состоянием, то будет скомпилировано только управление состоянием. Если вы используете маршрутизацию, то ничего из управления состоянием не будет скомпилировано. Вы можете воспользоваться репозиторием бенчмарка, и вы увидите, что используя только управление состоянием Get, приложение, которое скомпилировано с помощью Get, имеет меньший размер, чем приложения использующие другие пакеты для управления состоянием, потому что всё, что не используется, не будет скомпилировано в Ваш код. Таким образом каждое решение GetX было спроектировано, чтобы быть сверхлёгким. Также в этом есть и заслуга Flutter, который умеет устранять неиспользуемые ресурсы, как ни один другой фреймворк. - Getx имеет огромную экосистему, способную работать с одним и тем же кодом на Android, iOS, в Интернете, Mac, Linux, Windows и на вашем сервере. **С помощью [Get Server](https://github.com/jonataslaw/get_server) ваш код, созданный на веб-интерфейсе, можно повторно использовать на вашем сервере.** **Кроме того, весь процесс разработки может быть полностью автоматизирован как на сервере, так и во внешнем интерфейсе с помощью [Get CLI](https://github.com/jonataslaw/get_cli)**. **Кроме того, для дальнейшего повышения вашей продуктивности у нас есть [расширение для VSCode](https://marketplace.visualstudio.com/items?itemName=get-snippets.get-snippets) и [расширение для Android Studio / Intellij](https://plugins.jetbrains.com/plugin/14975-getx-snippets).** # Установка Добавьте Get в файл pubspec.yaml: ```yaml dependencies: get: ``` Импортируйте Get в файлы, в которых планируете его использовать: ```dart import 'package:get/get.dart'; ``` # Приложение "Счётчик" с GetX Проект "Счётчик", созданный по умолчанию для нового проекта на Flutter, имеет более 100 строк (с комментариями). Чтобы показать возможности Get, я продемонстрирую, как сделать "Счётчик", изменяющий состояние при каждом клике, переключении между страницами и передаче состояния между экранами. Всё это вместе с разделением бизнес логики от представления занимает ВСЕГО ЛИШЬ 26 СТРОК КОДА, ВКЛЮЧАЯ КОММЕНТАРИИ. - Шаг 1: Добавьте "Get" перед вашим MaterialApp, превращая его в GetMaterialApp ```dart void main() => runApp(GetMaterialApp(home: Home())); ``` - Примечание: это не изменяет MaterialApp, GetMaterialApp не является модифицированным MaterialApp, это просто предварительно настроенный виджет, у которого в качестве дочернего по умолчанию используется MaterialApp. Вы можете настроить это вручную, но это не обязательно. GetMaterialApp будет создавать маршруты, вводить их, вводить переводы, вводить всё, что вам нужно для навигации. Если вы используете Get только для управления состоянием или зависимостями, нет необходимости использовать GetMaterialApp. GetMaterialApp необходим для навигации, снекбаров, интернационализации, bottomSheets, диалогов и API, связанных с маршрутами и отсутствием контекста. - Примечание²: Этот шаг необходим только в том случае, если вы собираетесь использовать управление маршрутами (`Get.to()`, `Get.back()` и так далее). Если вы не собираетесь его использовать, то шаг 1 выполнять необязательно. - Шаг 2: Создайте свой класс бизнес-логики и поместите в него все переменные, методы и контроллеры. Вы можете сделать любую переменную наблюдаемой, используя простой ".obs". ```dart class Controller extends GetxController{ var count = 0.obs; increment() => count++; } ``` - Шаг 3: Создайте свой View, используйте StatelessWidget и сэкономьте немного оперативной памяти, с Get вам больше не нужно использовать StatefulWidget. ```dart class Home extends StatelessWidget { @override Widget build(context) { // Создайте экземпляр вашего класса с помощью Get.put(), чтобы сделать его доступным для всех "дочерних" маршрутов. final Controller c = Get.put(Controller()); return Scaffold( // Используйте Obx(()=> чтобы обновить Text() как только count изменится. appBar: AppBar(title: Obx(() => Text("Кликов: ${c.count}"))), // Замените 8 строк Navigator.push простым Get.to(). Вам не нужен context! body: Center(child: ElevatedButton( child: Text("Перейти к Other"), onPressed: () => Get.to(Other()))), floatingActionButton: FloatingActionButton(child: Icon(Icons.add), onPressed: c.increment)); } } class Other extends StatelessWidget { // "Попросите" Get найти и предоставить вам ваш Controller, используемый на другой странице. final Controller c = Get.find(); @override Widget build(context){ // Получите доступ к обновленной переменной count return Scaffold(body: Center(child: Text("${c.count}"))); } } ``` Результат: ![](counter-app-gif.gif) Это простой проект, но он уже дает понять, насколько мощным является Get. По мере роста вашего проекта эта разница будет становиться все более значительной. Get был разработан для работы с командами, но он упрощает работу отдельного разработчика. Оптимизируйте ваши сроки, доставляйте всё вовремя без потери производительности. Get не для всех, но, если вы идентифицировали себя с предыщим предложением, Get для вас! # Три столпа ## Управление состоянием В настоящее время для Flutter есть несколько менеджеров состояний. Однако большинство из них связано с использованием ChangeNotifier для обновления виджетов, и это плохой и очень плохой подход к производительности для средних или больших приложений. Вы можете проверить в официальной документации Flutter, что [ChangeNotifier следует использовать с 1 или максимум 2 слушателями](https://api.flutter.dev/flutter/foundation/ChangeNotifier-class.html), что делает его практически непригодным для любого приложения среднего или большого размера. Get не лучше и не хуже, чем любой другой менеджер состояний, но вам следует проанализировать его, а также пункты ниже, чтобы выбрать между использованием Get в чистой форме (Vanilla), либо совместно с другим менеджером состояний. Определенно, Get не враг любого другого менеджера состояний, потому что Get - это микрофреймворк, а не просто менеджер состояний, и его можно использовать отдельно или вместе с ними. Get имеет два разных менеджера состояний: простой менеджер состояний (мы назовем его GetBuilder) и реактивный менеджер состояний (который называется GetX). ### Реактивное управление состоянием Реактивное программирование может оттолкнуть многих людей, потому что считается сложным. GetX превращает реактивное программирование в нечто довольно простое: - Вам не нужно создавать StreamControllers. - Вам не нужно создавать StreamBuilder для каждой переменной. - Вам не нужно создавать класс для каждого состояния. - Вам не нужно создавать геттер начального значения. Реактивное программирование с Get так же просто, как использование setState. Представим, что у вас есть переменная name и вы хотите, чтобы каждый раз, когда вы её изменяете, все виджеты, которые её используют, менялись автоматически. Это ваша переменная: ```dart var name = 'Джонатас Борхес'; ``` Чтобы сделать его наблюдаемым, вам просто нужно добавить в конец ".obs": ```dart var name = 'Джонатас Борхес'.obs; ``` А в пользовательском интерфейсе, если вы хотите отображать это значение и обновлять экран при изменении значений, просто сделайте следующее: ```dart Obx(() => Text("${controller.name}")); ``` Вот и всё. Это _так_ просто. ### Подробнее об управлении состоянием **Более подробное объяснение управления состоянием [здесь](./documentation/ru_RU/state_management.md). Там вы увидите больше примеров, а также разницу между простым менеджером состояния и реактивным менеджером состояния.** Вы получите хорошее представление о мощности GetX. ## Управление маршрутами Если вы собираетесь использовать маршруты / снекбары / диалоги / bottomsheets без контекста, GetX отлично подойдёт вам, просто посмотрите: Добавьте "Get" перед MaterialApp, превратив его в GetMaterialApp. ```dart GetMaterialApp( // Ранее: MaterialApp( home: MyHome(), ) ``` Перейдите на новый экран: ```dart Get.to(NextScreen()); ``` Перейдите на новый экран с именем. Более подробную информацию об именованных маршрутах смотрите [здесь](./documentation/ru_RU/route_management.md#navigation-with-named-routes) ```dart Get.toNamed('/details'); ``` Закрыть снекбар, диалог, bottomsheets, или что-то иное, что вы обычно закрывали с помощью Navigator.pop(context); ```dart Get.back(); ``` Для перехода к следующему экрану без возможности вернуться к предыдущему экрану (для использования в SplashScreens, экранах входа и т. д.) ```dart Get.off(NextScreen()); ``` Для перехода к следующему экрану и отмены всех предыдущих маршрутов (полезно в корзинах для покупок, опросах и тестах) ```dart Get.offAll(NextScreen()); ``` Заметили, что вам не нужно было использовать контекст, чтобы делать что-либо из этого? Это одно из самых больших преимуществ использования Get. Благодаря этому вы можете без проблем выполнять все эти методы из класса контроллера. ### Подробнее об управлении маршрутами **Get работает с именованными маршрутами, а также предлагает более низкий уровень контроля над вашими маршрутами! [Здесь](./documentation/ru_RU/route_management.md) есть подробная документация.** ## Внедрение зависимостей Get имеет простой и мощный менеджер зависимостей, который позволяет вам получить тот же класс, что и ваш BLoC или контроллер, всего одной строкой кода, без Provider context, без InheritedWidget: ```dart Controller controller = Get.put(Controller()); // Вместо Controller controller = Controller(); ``` - Примечание: Если вы используете Get State Manager, обратите больше внимания на API привязок, который упростит подключение вашего представления к контроллеру. Вместо того, чтобы создавать экземпляр вашего класса внутри класса, который вы используете, вы создаете его в экземпляре Get, что сделает его доступным во всем приложении. Таким образом, вы можете использовать свой контроллер (или BLoC) в обычном режиме. **Совет:** Управление зависимостями Get не связано с другими частями пакета, поэтому, если, например, ваше приложение уже использует менеджер состояний (любой, не имеет значения), вам не нужно все это переписывать, вы можете использовать это внедрение зависимостей без проблем. ```dart controller.fetchApi(); ``` Представьте, что вы прошли через множество маршрутов и вам нужны данные, которые остались в вашем контроллере, вам понадобится менеджер состояний в сочетании с Provider или Get_it, верно? Только не с Get. Вам просто нужно попросить Get «найти» ваш контроллер, никаких дополнительных зависимостей вам не потребуется: ```dart Controller controller = Get.find(); // Да, это выглядит как Магия! Get найдет ваш controller и доставит его вам. У вас может быть миллион созданных контроллеров, и Get всегда найдет нужный. ``` И тогда вы сможете восстановить данные вашего контроллера, которые были там получены: ```dart Text(controller.textFromApi); ``` ### Подробнее о внедрении зависимостей **Более подробное объяснение управления зависимостями [здесь](./documentation/ru_RU/dependency_management.md)** # Утилиты ## Интернационализация ### Переводы Переводы хранятся в виде карты пар "ключ-значение". Чтобы добавить собственные переводы, создайте класс и расширьте `Translations`. ```dart import 'package:get/get.dart'; class Messages extends Translations { @override Map> get keys => { 'en_US': { 'hello': 'Hello World', }, 'de_DE': { 'hello': 'Hallo Welt', } }; } ``` #### Использование переводов Просто добавьте `.tr` к указанному ключу, и он будет переведен с использованием текущего значения `Get.locale` и `Get.fallbackLocale`. ```dart Text('title'.tr); ``` ### Локализация Передайте параметры в `GetMaterialApp`, чтобы определить языковой стандарт и переводы. ```dart return GetMaterialApp( translations: Messages(), // ваши переводы locale: Locale('en', 'US'), // перевод будет осуществлен в этой локализации fallbackLocale: Locale('en', 'UK'), // установите резервную локализацию на случай если будет выбрана невалидный локализация. ); ``` #### Изменение локализации Вызовите `Get.updateLocale(locale)`, чтобы обновить локализацию. Затем переводы автоматически используют новый языковой стандарт. ```dart var locale = Locale('en', 'US'); Get.updateLocale(locale); ``` #### Системная локализация Чтобы узнать системную локализацию, вам следует использовать `window.locale`. ```dart import 'dart:ui' as ui; return GetMaterialApp( locale: ui.window.locale, ); ``` ## Изменение темы Пожалуйста, не используйте виджет более высокого уровня, чем `GetMaterialApp`, для его обновления. Это может вызвать повторяющиеся ключи. Многие люди привыкли к старому подходу к созданию виджета «ThemeProvider» только для того, чтобы изменить тему вашего приложения, а это НЕ требуется с GetX ™. Вы можете создать свою собственную тему и просто добавить ее в `Get.changeTheme` без повторяющегося кода: ```dart Get.changeTheme(ThemeData.light()); ``` Если вы хотите создать что-то вроде кнопки, которая изменяет тему, вы можете объединить для этого два API **GetX ™**: - API, который проверяет, используется ли темная тема. - И API смены темы. Вы можете просто поместить это в `onPressed`: ```dart Get.changeTheme(Get.isDarkMode? ThemeData.light(): ThemeData.dark()); ``` Когда `.darkmode` активен, он переключится на _light theme_, и когда _light theme_ станет активной, он изменится на _dark theme_. ## Другие API ```dart // получить текущие аргументы текущего экрана Get.arguments // получить аргументы предыдущего маршрута Get.previousArguments // получить имя предыдущего маршрута Get.previousRoute // получить чистый маршрут, например, чтобы узнать: rawRoute.isFirst() Get.rawRoute // получить доступ к Rounting API из GetObserver Get.routing // проверить, открыт ли снекбар Get.isSnackbarOpen // открыт ли диалог Get.isDialogOpen // открыт ли bottomsheets Get.isBottomSheetOpen // удалить один маршрут Get.removeRoute() // возвращаться назад, пока данный предикат не выполнится Get.until() // идти вперед, удалив предыдущие маршруты, пока данный предикат не выполнится Get.offUntil() // перейти к следующему именованному маршруту, удалив предыдущие маршруты, пока данный предикат не выполнится Get.offNamedUntil() // проверить на какой платформе работает приложение GetPlatform.isAndroid GetPlatform.isIOS GetPlatform.isMacOS GetPlatform.isWindows GetPlatform.isLinux GetPlatform.isFuchsia // проверить тип устройства GetPlatform.isMobile GetPlatform.isDesktop // В вебе все платформы поддерживаются независимо! // Можно узнать, работает ли приложение сейчас в браузере // и на Windows, и на iOS, и на OSX, и на Android и так далее GetPlatform.isWeb // Эквивалент : MediaQuery.of(context).size.height, // но неизменяемый. Get.height Get.width // Текущий контекст навигации Get.context // Получить контекст показанного снекбара/диалога/bottomsheets в любом месте вызова. Get.contextOverlay // Внимание: методы ниже являются расширениями класса BuildContext. // Поскольку доступ к контексту есть в любом месте из вашего UI, // вы можете использовать расширения в любом месте UI кода // Если вам нужна изменяемая высота/ширина (например, настольное или браузерное окно, размер которого можно изменить), вам нужно использовать context context.width context.height // Дает возможность определить половину экрана, треть и так далее. // Полезно для отзывчивых приложений. // param dividedBy (double) optional - default: 1 // param reducedBy (double) optional - default: 0 context.heightTransformer() context.widthTransformer() /// Схоже с MediaQuery.of(context).size context.mediaQuerySize() /// Схоже с MediaQuery.of(context).padding context.mediaQueryPadding() /// Схоже с MediaQuery.of(context).viewPadding context.mediaQueryViewPadding() /// Схоже с MediaQuery.of(context).viewInsets; context.mediaQueryViewInsets() /// Схоже с MediaQuery.of(context).orientation; context.orientation() /// Проверить, в горизонтальном ли режиме устройство context.isLandscape() /// Проверить, в вертикальном ли режиме устройство context.isPortrait() /// Схоже с to MediaQuery.of(context).devicePixelRatio; context.devicePixelRatio() /// Схоже с MediaQuery.of(context).textScaleFactor; context.textScaleFactor() /// Получить shortestSide экрана context.mediaQueryShortestSide() /// Вернет True, если ширина больше 800 context.showNavbar() /// Вернет True, если меньшая сторона меньше 600p context.isPhone() /// Вернет True, если меньшая сторона больше 600p context.isSmallTablet() /// Вернет True, если меньшая сторона больше 720p context.isLargeTablet() /// Вернет True, если текущее устройство — Планшет context.isTablet() /// Возвращает value в зависимости от размера экрана /// Можно устанавливать значения для: /// watch: Если меньшая сторона меньше 300 /// mobile: Если меньшая сторона меньше 600 /// tablet: Если меньшая сторона меньше 1200 /// desktop: Если ширина больше 1200 context.responsiveValue() ``` ### Дополнительные глобальные настройки и ручные настройки GetMaterialApp настраивает все за вас, но если вы хотите настроить Get вручную. ```dart MaterialApp( navigatorKey: Get.key, navigatorObservers: [GetObserver()], ); ``` Вы также сможете использовать собственное промежуточное ПО в `GetObserver`, это ни на что не повлияет. ```dart MaterialApp( navigatorKey: Get.key, navigatorObservers: [ GetObserver(MiddleWare.observer) // Here ], ); ``` Вы можете создать _Глобальные Настройки_ Для `Get`. Просто добавьте `Get.config` в ваш код прежде чем нажимать на любой из маршрутов. Или сделайте это прямо в `GetMaterialApp` ```dart GetMaterialApp( enableLog: true, defaultTransition: Transition.fade, opaqueRoute: Get.isOpaqueRouteDefault, popGesture: Get.isPopGestureEnable, transitionDuration: Get.defaultDurationTransition, defaultGlobalState: Get.defaultGlobalState, ); Get.config( enableLog = true, defaultPopGesture = true, defaultTransition = Transitions.cupertino ) ``` При желании, вы сможете перенаправить все сообщения из `Get`. Если вы хотите использовать свой любимый пакет для логирования и собирать логи там: ```dart GetMaterialApp( enableLog: true, logWriterCallback: localLogWriter, ); void localLogWriter(String text, {bool isError = false}) { // передайте сообщение вашей любимой log-библиотеке // но учитывайте, что даже если enableLog: false, сообщения все равно будут передаваться сюда // узнать значение этого флага можно с помощью GetConfig.isLogEnable } ``` ### Локальные виджеты состояния Эти виджеты позволяют управлять одним значением, сохраняя состояние эфемерным и локальным. У нас есть варианты для Reactive и Simple. Например, вы можете использовать их для переключения obscureText в `TextField`, возможно, для создания кастомного ExpandablePanel или, возможно, для изменения текущего индекса в `BottomNavigationBar` при изменении содержимого body в `Scaffold`. #### ValueBuilder Упрощение `StatefulWidget` который работает с вызовом `.setState` принимающим обновленное значение. ```dart ValueBuilder( initialValue: false, builder: (value, updateFn) => Switch( value: value, onChanged: updateFn, // такая же сигнатура! Вы можете использовать ( newValue ) => updateFn( newValue ) ), // Если нужно вызвать что-то вне метода builder onUpdate: (value) => print("Значение обновлено: $value"), onDispose: () => print("Виджет удален"), ), ``` #### ObxValue Похож на [`ValueBuilder`](#valuebuilder), но это реактивная версия, вы передаёте Rx экземпляр (помните волшебный .obs?) и автоматически обновляетесь... разве это не великолепно? ```dart ObxValue((data) => Switch( value: data.value, onChanged: data, // У Rx есть _callable_ функция! Вы можете использовать (flag) => data.value = flag, ), false.obs, ), ``` ## Полезные советы `.obs`ervables (наблюдатели) (также известные как Rx-типы) имеют широкий спектр внутренних методов и операторов > Очень распространено _мнение_, что свойство с `.obs` **ЯВЛЯЕТСЯ** действительным значением... но не ошибайтесь! > Мы избегаем объявления типа переменной, потому что компилятор Dart достаточно умен, и > код выглядит чище, но: ```dart var message = 'Привет, мир'.obs; print( 'Тип "$message" — ${message.runtimeType}'); ``` Даже если `message` _выводит_ значение String, его тип - **RxString**! Итак, вы не сможете сделать `message.substring( 0, 4 )`. Вы должны получить доступ к реальному `value` внутри _observable_: Самый "используемый способ" это `.value`, но, знаете ли вы, что вы также можете использовать ... ```dart final name = 'GetX'.obs; // "обновляет" поток только если значение отличается от текущего. name.value = 'Хей'; // Все свойства Rx являются "вызываемыми" и возвращают новые значения. // но это не работает с `null`: UI не будет перестроен. name('Привет'); // как геттер, напечатает 'Привет'. name() ; /// числа: final count = 0.obs; // Вы можете использовать все неизменяемые операции с числами! count + 1; // Осторожно! Это можно использовать только если `count` не final, а var count += 1; // Сравнения так же работают: count > 2; /// логические: final flag = false.obs; // переключает значение между true/false flag.toggle(); /// все типы: // обнуляет значение переменной `value`. flag.nil(); // Все toString(), toJson() операции применяются к `value` print( count ); // вызывает `toString()` внутри RxInt final abc = [0,1,2].obs; // Преобразует значение в json массив, выводит RxList // Json поддерживается всеми Rx типами! print('json: ${jsonEncode(abc)}, тип: ${abc.runtimeType}'); // RxMap, RxList и RxSet являются особенными Rx типами: они расширяют нативные типы. // Но вы можете работать со списком как и с обычным списком, хоть и реактивным! abc.add(12); // добавлеет 12 в список, and ОБНОВЛЯЕТ поток. abc[3]; // как списки, возвращает значение с индексом 3. // Сравнение равенства работает с Rx и с его value, но хэш код всегда берется у value final number = 12.obs; print( number == 12 ); // печатает true /// Кастомные Rx Модели: // toJson(), toString() передаются child, так что вы можете перегрузить эти методы в нем, и вызвать print напрямую. class User { String name, last; int age; User({this.name, this.last, this.age}); @override String toString() => '$name $last, возраст: $age'; } final user = User(name: 'Джон', last: 'Доу', age: 33).obs; // `user` – "реактивный", но его свойства – НЕТ! // Так что если мы обновим что-либо внутри user... user.value.name = 'Рой'; // Виджет перестроен не будет! // `Rx` не знает, изменили ли вы что-то у user. // Так что для кастомных классов вам нужно явно "уведомлять" об изменении. user.refresh(); // или мы можем использовать метод `update()`! user.update((value){ value.name='Рой'; }); print( user ); ``` #### GetView Я люблю этот виджет, он такой простой, но такой полезный! Это `const Stateless` виджет, который имеет геттер `controller` для зарегистрированного `Controller`, вот и всё. ```dart class AwesomeController extends GetxController { final String title = 'Моя Удивительная View'; } // ВСЕГДА передавайте `Тип` вашего контроллера! class AwesomeView extends GetView { @override Widget build(BuildContext context) { return Container( padding: EdgeInsets.all(20), child: Text( controller.title ), // просто вызовите `controller.что-то` ); } } ``` #### GetWidget Большинство людей понятия не имеют об этом виджете или путаются при его применении. Вариант его использования редок, но конкретен: он кэширует Controller. Поэтому из-за _cache_, он не может быть `const Stateless`. > Итак, когда вам нужно «кэшировать» контроллер? В случаях использования другой "не распространённой" фичи **GetX**: `Get.create()`. `Get.create(()=>Controller())` будет создавать новый `Controller` каждый раз при вызове `Get.find()`, Это тот самый случай, когда `GetWidget` блистает... поскольку вы можете использовать его, например, для хранения списка элементов Todo. Итак, если виджет будет «перестроен», он сохранит тот же экземпляр контроллера. #### GetxService Этот класс похож на `GetxController`, у него такой же жизненный цикл ( `onInit()`, `onReady()`, `onClose()`). Но внутри нет никакой «логики». Он просто уведомляет систему **GetX** Dependency Injection о том, что этот подкласс **нельзя** удалить из памяти. Так что очень полезно держать ваши «Сервисы» всегда доступными и активными с помощью `Get.find()`. Например: `ApiService`, `StorageService`, `CacheService`. ```dart Future main() async { await initServices(); /// ПОДОЖДИТЕ ИНИЦИАЛИЗАЦИЮ СЕРВИСОВ. runApp(SomeApp()); } /// Умным решением будет проинициализировать сервисы перед вызовом runApp, /// поскольку вы можете контроллировать процесс инициализации /// (может, вам нужно загрузить конфигурацию Темы, ключи API, язык, определенный пользователем... /// Загружайте SettingService прежде чем запускать ApiService. /// Таким образом GetMaterialApp() принимает параметры напрямую, и ему не нужно будет перезагружаться void initServices() async { print('запуск сервисов ...'); /// Здесь вы инициализируете get_storage, hive, shared_pref, /// или что-либо другое асинхронное. await Get.putAsync(() => DbService().init()); await Get.putAsync(SettingsService()).init(); print('Все сервисы запущены...'); } class DbService extends GetxService { Future init() async { print('$runtimeType задержка 2 секунды'); await 2.delay(); print('$runtimeType готов!'); return this; } } class SettingsService extends GetxService { void init() async { print('$runtimeType задержка 1 секунду'); await 1.delay(); print('$runtimeType готов!'); } } ``` Единственный способ удалить `GetxService` - использовать `Get.reset()`, который похож на «горячую перезагрузку» вашего приложения. Так что помните, если вам нужен постоянный экземпляр класса в течение всего жизненного цикла вашего приложения, используйте `GetxService`. # Критические изменения по сравнению с версией 2.0 1- Rx типы: | До | После | | ------- | ---------- | | StringX | `RxString` | | IntX | `RxInt` | | MapX | `RxMap` | | ListX | `RxList` | | NumX | `RxNum` | | DoubleX | `RxDouble` | RxController и GetBuilder теперь объединены, вам больше не нужно запоминать, какой контроллер вы хотите использовать, просто используйте GetxController, он будет работать как для простого управления состоянием, так и для реактивного. 2- NamedRoutes До: ```dart GetMaterialApp( namedRoutes: { '/': GetRoute(page: Home()), } ) ``` Сейчас: ```dart GetMaterialApp( getPages: [ GetPage(name: '/', page: () => Home()), ] ) ``` Для чего это изменение? Часто может потребоваться решить, какая страница будет отображаться с помощью параметра или токена входа, предыдущий подход был негибким, так как он не позволял этого. Вставка страницы в функцию значительно снизила потребление оперативной памяти, поскольку маршруты не будут выделяться в памяти с момента запуска приложения, а также позволил использовать такой подход: ```dart GetStorage box = GetStorage(); GetMaterialApp( getPages: [ GetPage(name: '/', page:(){ return box.hasData('token') ? Home() : Login(); }) ] ) ``` # Почему Getx? 1- Много раз после обновления Flutter многие из ваших пакетов ломались. Иногда случаются ошибки компиляции, часто возникают ошибки, на которые до сих пор нет ответов, и разработчику необходимо знать, откуда возникла ошибка, отслеживать ошибку, только затем попытаться открыть проблему в соответствующем репозитории и увидеть, как проблема решена. Get централизует основные ресурсы для разработки (управление состоянием, зависимостями и маршрутами), позволяя вам добавить один пакет в свой pubspec и начать работу. После обновления Flutter единственное, что вам нужно сделать, это обновить зависимость Get и приступить к работе. Get также решает проблемы совместимости. Как часто бывало, что одна версия пакета несовместима с другой, потому что одна использует зависимость в одной версии, а другая - в другой? Это не проблема при использовании Get, поскольку все находится в одном пакете и полностью совместимо. 2- Flutter - это просто, Flutter - это невероятно, но у Flutter все еще некоторый шаблонный код, который может быть нежелательным для большинства разработчиков, например `Navigator.of(context).push (context, builder [...]`. Get упрощает разработку. Вместо того, чтобы писать 8 строк кода для вызова маршрута, вы можете просто сделать это: `Get.to(Home())` и всё готово, вы перейдёте на следующую страницу. Динамические URL-адреса - это действительно болезненная вещь, которую нужно решать во Flutter в настоящее время, а с GetX это элементарно. Управление состояниями во Flutter и управление зависимостями также вызывает много споров, поскольку в pub есть сотни паттернов. Но нет ничего проще, чем добавить «.obs» в конец вашей переменной и поместить ваш виджет внутри Obx, и всё, все обновления этой переменной будут автоматически обновляться на экране. 3- Лёгкость, не беспокоясь о производительности. Производительность Flutter уже потрясающая, но представьте, что вы используете диспетчер состояний и локатор для распределения классов блоков / хранилищ / контроллеров / и других классов. Вам придётся вручную вызывать исключение этой зависимости, когда она вам не нужна. Но вы когда-нибудь думали о том, чтобы просто использовать свой контроллер, и когда он больше никем не использовался, он просто был бы удален из памяти? Это то, что делает GetX. Благодаря SmartManagement всё, что не используется, удаляется из памяти, и вам не нужно беспокоиться ни о чем, кроме программирования. Вы будете уверены, что потребляете минимум необходимых ресурсов, даже не создав для этого логики. 4- Действительное разделение. Вы могли слышать о концепции разделения представления от бизнес логики. Это не исключительная особенность BLoC, MVC, MVVM и тд, любой стандарт реализует эту концепцию. Однако во Flutter возможно ослабление этой концепции из-за необходимости использования контекста. Если вам нужен контекст для поиска InheritedWidget, он вам нужен в представлении, либо нужно передать контекст как параметр. Мы считаем это решение очень уродливым, и для работы в команде мы всегда будем зависеть от логики представления (View). Getx - необычный подход со стандартным доступом, который хоть и не запрещает использование StatefulWidgets, InitState, и т.д., всегда имеет более чистый аналог. У контроллеров есть жизненные циклы, и когда вам нужно сделать, например, запрос APIREST, вы не зависите ни от чего в представлении. Вы можете использовать onInit для инициирования http-вызова, и когда данные поступят, переменные будут заполнены. Поскольку GetX полностью реактивен (действительно реактивен и работает с потоками), после заполнения элементов все виджеты, использующие эту переменную, будут автоматически обновлены в представлении. Это позволяет людям с опытом работы с пользовательским интерфейсом работать только с виджетами и не отправлять в бизнес-логику ничего, кроме пользовательских событий (например, нажатия кнопки), в то время как люди, работающие с бизнес-логикой, смогут создавать и тестировать бизнес-логику отдельно. Эта библиотека всегда будет обновляться и реализовывать новые функции. Не стесняйтесь предлагать PR. # Сообщества ## Каналы сообщества У GetX очень активное и готовое к взаимовыручке сообщество. Если у вас есть вопросы или вы хотите получить какую-либо помощь относительно использования этого фреймворка, присоединяйтесь к нашим каналам сообщества, на ваш вопрос ответят быстро. Этот репозиторий предназначен исключительно для открытия проблем и запроса ресурсов, но не стесняйтесь быть частью сообщества GetX. | **Slack** | **Discord** | **Telegram** | | :-------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------- | | [![Get on Slack](https://img.shields.io/badge/slack-join-orange.svg)](https://communityinviter.com/apps/getxworkspace/getx) | [![Discord Shield](https://img.shields.io/discord/722900883784073290.svg?logo=discord)](https://discord.com/invite/9Hpt99N) | [![Telegram](https://img.shields.io/badge/chat-on%20Telegram-blue.svg)](https://t.me/joinchat/PhdbJRmsZNpAqSLJL6bH7g) | ## Как внести свой вклад _Хотите внести свой вклад в проект? Вы будем рады отметить вас как одного из наших соавторов. Вот несколько направлений, где вы можете сделать Get (и Flutter) лучше._ - Помощь в переводе readme на другие языки. - Добавление документации в readme (многие функции Get еще не задокументированы). - Напишите статью или сделайте видео, обучающие использованию Get (они будут вставлены в Readme и в будущем в нашу Wiki). - Предложите PRs для кода/тестов. - Новые фичи. Приветствуется любой вклад! ## Статьи и видео - [Dynamic Themes in 3 lines using GetX™](https://medium.com/swlh/flutter-dynamic-themes-in-3-lines-c3b375f292e3) - Tutorial by [Rod Brown](https://github.com/RodBr). - [Complete GetX™ Navigation](https://www.youtube.com/watch?v=RaqPIoJSTtI) - Route management video by Amateur Coder. - [Complete GetX State Management](https://www.youtube.com/watch?v=CNpXbeI_slw) - State management video by Amateur Coder. - [GetX™ Other Features](https://youtu.be/ttQtlX_Q0eU) - Utils, storage, bindings and other features video by Amateur Coder. - [Firestore User with GetX | Todo App](https://www.youtube.com/watch?v=BiV0DcXgk58) - Video by Amateur Coder. - [Firebase Auth with GetX | Todo App](https://www.youtube.com/watch?v=-H-T_BSgfOE) - Video by Amateur Coder. - [The Flutter GetX™ Ecosystem ~ State Management](https://medium.com/flutter-community/the-flutter-getx-ecosystem-state-management-881c7235511d) - State management by [Aachman Garg](https://github.com/imaachman). - [GetX, the all-in-one Flutter package](https://www.youtube.com/watch?v=IYQgtu9TM74) - A brief tutorial covering State Management and Navigation by Thad Carnevalli. - [Build a To-do List App from scratch using Flutter and GetX](https://www.youtube.com/watch?v=EcnqFasHf18) - UI + State Management + Storage video by Thad Carnevalli. - [GetX Flutter Firebase Auth Example](https://medium.com/@jeffmcmorris/getx-flutter-firebase-auth-example-b383c1dd1de2) - Article by Jeff McMorris. ================================================ FILE: README.tr-TR.md ================================================ ![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/get.png) [![pub package](https://img.shields.io/pub/v/get.svg?label=get&color=blue)](https://pub.dev/packages/get) [![popularity](https://badges.bar/get/popularity)](https://pub.dev/packages/sentry/score) [![likes](https://badges.bar/get/likes)](https://pub.dev/packages/get/score) [![pub points](https://badges.bar/get/pub%20points)](https://pub.dev/packages/get/score) ![building](https://github.com/jonataslaw/get/workflows/build/badge.svg) [![style: effective dart](https://img.shields.io/badge/style-effective_dart-40c4ff.svg)](https://pub.dev/packages/effective_dart) [![Discord Shield](https://img.shields.io/discord/722900883784073290.svg?logo=discord)](https://discord.com/invite/9Hpt99N) [![Get on Slack](https://img.shields.io/badge/slack-join-orange.svg)](https://communityinviter.com/apps/getxworkspace/getx) [![Telegram](https://img.shields.io/badge/chat-on%20Telegram-blue.svg)](https://t.me/joinchat/PhdbJRmsZNpAqSLJL6bH7g) Awesome Flutter Buy Me A Coffee ![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/getx.png)
**Languages:** [![English](https://img.shields.io/badge/Language-English-blueviolet?style=for-the-badge)](README.md) [![Vietnamese](https://img.shields.io/badge/Language-Vietnamese-blueviolet?style=for-the-badge)](README-vi.md) [![Indonesian](https://img.shields.io/badge/Language-Indonesian-blueviolet?style=for-the-badge)](README.id-ID.md) [![Urdu](https://img.shields.io/badge/Language-Urdu-blueviolet?style=for-the-badge)](README.ur-PK.md) [![Chinese](https://img.shields.io/badge/Language-Chinese-blueviolet?style=for-the-badge)](README.zh-cn.md) [![Portuguese](https://img.shields.io/badge/Language-Portuguese-blueviolet?style=for-the-badge)](README.pt-br.md) [![Spanish](https://img.shields.io/badge/Language-Spanish-blueviolet?style=for-the-badge)](README-es.md) [![Russian](https://img.shields.io/badge/Language-Russian-blueviolet?style=for-the-badge)](README.ru.md) [![Polish](https://img.shields.io/badge/Language-Polish-blueviolet?style=for-the-badge)](README.pl.md) [![Korean](https://img.shields.io/badge/Language-Korean-blueviolet?style=for-the-badge)](README.ko-kr.md) [![French](https://img.shields.io/badge/Language-French-blueviolet?style=for-the-badge)](README-fr.md) [![Japanese](https://img.shields.io/badge/Language-Japanese-blueviolet?style=for-the-badge)](README.ja-JP.md) [![Turkish](https://img.shields.io/badge/Language-Turkish-blueviolet?style=for-the-badge)](README.tr-TR.md)
- [Get Hakkında](#get-hakkında) - [Kurulum](#kurulum) - [GetX ile Sayaç Uygulaması](#getx-ile-sayaç-uygulaması) - [Üç Temel Kavram](#üç-temel-kavram) - [State Management (Durum Yönetimi)](#state-management-durum-yönetimi) - [Reactive State Manager (Reaktif Durum Yönetimi)](#reactive-state-manager-reaktif-durum-yönetimi) - [State Management Hakkında Daha Fazla Bilgi](#state-management-hakkında-daha-fazla-bilgi) - [Route Management (Rota Yönetimi)](#route-management-rota-yönetimi) - [Route Management Hakkında Daha Fazla Bilgi](#route-management-hakkında-daha-fazla-bilgi) - [Dependency Management (Bağımlılık Yönetimi)](#dependency-management-bağımlılık-yönetimi) - [Dependency Management Hakkında Daha Fazla Bilgi](#dependency-management-hakkında-daha-fazla-bilgi) - [Utils](#utils) - [Internationalization (Uluslararasılaştırma)](#internationalization-uluslararasılaştırma) - [Translations (Çeviriler)](#translations-çeviriler) - [Translations Kullanımı](#translations-kullanımı) - [Locales (Yerel Ayarlar)](#locales-yerel-ayarlar) - [Locale Değiştirme](#locale-değiştirme) - [System locale (Yerel Sistem Ayarları)](#system-locale-yerel-sistem-ayarları) - [Tema Değiştirme](#tema-değiştirme) - [GetConnect](#getconnect) - [Varsayılan Ayarlar](#varsayılan-ayarlar) - [Özel Ayarlarlamalar](#özel-ayarlamalar) - [GetPage Middleware](#getpage-middleware) - [Priority (Öncelik)](#priority-öncelik) - [Redirect (Yönlendirme)](#redirect-yönlendirme) - [onPageCalled](#onpagecalled) - [OnBindingsStart](#onbindingsstart) - [OnPageBuildStart](#onpagebuildstart) - [OnPageBuilt](#onpagebuilt) - [OnPageDispose](#onpagedispose) - [Advanced APIs (Gelişmiş API'ler)](#advanced-apis-gelişmiş-apiler) - [Opsiyonel Genel Ayarlar ve Manuel Ayarlamalar](#opsiyonel-genel-ayarlar-ve-manuel-ayarlamalar) - [Local State Widgets (Yerel Durum Widgetları)](#local-state-widgets-yerel-durum-widgetları) - [ValueBuilder](#valuebuilder) - [ObxValue](#obxvalue) - [Faydalı İpuçları](#faydalı-ipuçları) - [GetView](#getview) - [GetResponsiveView](#getresponsiveview) - [Nasıl Kullanılır?](#nasıl-kullanılır) - [GetWidget](#getwidget) - [GetxService](#getxservice) - [2.0 İle Gelen Büyük Değişiklikler](#20-ile-gelen-büyük-değişiklikler) - [Neden Getx?](#neden-getx) - [Topluluk](#topluluk) - [Topluluk Kanalları](#topluluk-kanalları) - [Nasıl katkıda bulunulur?](#nasıl-katkıda-bulunulur) - [Makaleler ve Videolar](#makaleler-ve-videolar) # Get Hakkında - GetX, Flutter için oldukça basit ve güçlü bir çözüm yoludur. Yüksek performanslı state managment (durum yönetimi), yetenekli dependency injection (bağımlılık enjeksiyonu) ve route management'ı (rota yönetimi) hızlı ve pratik şekilde bir araya getirir. - GetX'in 3 temel ilkesi vardır. Bu ilkeler kütüphanedeki tüm kaynaklar için önemlidir: **ÜRETKENLİK, PERFORMANS VE ORGANİZASYON.** - **PERFORMANS:** GetX, performansa ve kaynakların minimum düzeyde tüketimine odaklanmıştır. GetX, Streams ya da ChangeNotifier kullanmaz. - **ÜRETKENLİK:** GetX kolay ve keyifli bir syntax (yazım kuralları) kullanır. Ne yapmak istersen iste GetX'de her zaman kolay bir çözüm yolu vardır. Saatlerce süren geliştirmeden tasarruf etmeyi ve uygulanın size sağladığı performansı maksimum seviyede kullanmayı mümkün kılar. Normalde geliştirici, controller'ları hafızadan kaldırmakla ilgilenmelidir. GetX ile bunu yapmaya gerek kalmaz çünkü varsayılan olarak kaynaklar kullanılmadığı zaman hafızadan kendiliğinden kaldırılır. Eğer hafızada tutmak istiyorsanız, dependency içinde "permanent: true" olarak tanımlanmanız gerekmektedir. Bu şekilde hem zaman tasarrufu hem de hafızadaki gereksiz dependency'lerin oluşturabileceği riskler azaltmış olur. Dependency yüklemesi varsayılan olarak lazy'dir (tembeldir). - **ORGANİZASYON:** GetX, presentation logic'i (sunum mantığını, business logic'i (iş mantığını), dependency injection'ı, navigasyonu View'dan tamamen ayırmayı sağlar. Route'lar arasında navigasyon için context'e gerek duyulmaz bu sayede widget tree'ye (widget ağacına) bağımlı kalmazsınız. Controllers ya da blocs'daki inheritedWidget'a erişmek için context'e ihtiyaç duyulmaz böylelikle presentation logic ve business logic, view katmanından tamamen ayrılır. Controllers/Models/Blocs sınıflarını widget tree'ye inject (aktarırken) ederken `MultiProvider`'lar kullanılmasına ihtiyaç yoktur. GetX'in kendine ait dependency injection özelliği sayesinde DI'yi de view'dan tamamen ayrır. GetX ile varsayılan olarak temiz kod kullanılarak uygulamadaki her bir özelliğin nerede bulunduğuna ulaşabilirsiniz. Bakım kolaylığının yanı sıra Flutter'da düşünüleyemeyen bir şey olan modülleri paylaşmayı tamamen mümkün kılar. BLoC, Flutter'daki kodları organize etmenin başlangıç noktasıdır. Business logic'i, view'dan ayırır. Bunun gelişmiş hali olarak ortaya çıkan GetX sadece business logic'i ayırmakla kalmayıp aynı zamanda dependency injection'ları, route'ları ve presentation logic'i de view'dan ayırır. Data layer (Veri katmanı) bu sayede bütün katmanların dışında bırakılır. Her şeyin nerde olduğunu bilmek "hello word" oluşturmaktan çok daha kolay bir yoldur. GetX, Flutter SDK'sı ile çok kolay, pratik ve ölçeklenebilir yüksek performanslı uygulamalar yapmanızı sağlar. Birlikte çalışılabilen büyük bir ekosistem içerir. Yeni başlayanlar için oldukça kolay ve uzmanlar için de doğru olandır. Güvenli, stabil, güncel ve Flutter SDK'da varsayılan olarak olmayan büyük kapsamlı APIs kullanabilmeyi sağlar. - GetX şişkin değildir. Çoklu davranış içeren özellikleri kullanarak, herhangi bir endişe olmaksızın programlamaya başlamanızı sağlar. Ancak bu özellikler farklı taraflarda olup sadece kullanıldıktan sonra başlatılır. Sadece state management kullanıyorsanız, sadece bu derlenir. Sadece routes kullanırsanız, state management'dan hiçbir şey derlenmez. - GetX büyük bir ekosistemdir. Geniş bir topluluk, çok sayıda destekçi içerir ve Flutter var olduğu sürece bu korunacaktır. GetX, tek bir kod ile Android, iOS, Web, Mac, Linux, Windows veya kendi server'ınız (sunucunuz) üzerinde çalışmaya elverişlidir. **[Get Server](https://github.com/jonataslaw/get_server) ile frontend üzerinde yaptığınız kodu backend üzerinde tamamen yeniden kullanmanız mümkün**. **Ek olarak, tüm geliştirme süreci hem server'da hem de frontend'de [Get CLI](https://github.com/jonataslaw/get_cli) ile tamamen otomatikleştirilebilir**. **Ayrıca üretkenliğinizi arttırmak için [VSCode eklentileri](https://marketplace.visualstudio.com/items?itemName=get-snippets.get-snippets) ve the [Android Studio/Intellij eklentileri](https://plugins.jetbrains.com/plugin/14975-getx-snippets) vardır.** # Kurulum pubspec.yaml dosyasına Get’i ekleyin: ```yaml dependencies: get: ``` Get’i kullanılacak sayfaya import ediyoruz: ```dart import 'package:get/get.dart'; ``` # GetX ile Sayaç Uygulaması “Sayaç” projesi yeni bir Flutter projesi oluştururken varsayılan olarak gelir. Yorum satırları ile birlikte toplam 100 satır içerir. Get’in gücünü göstermek gerekirse, size bir “sayaç” uygulamasının her bir tıklamada durumunu değiştirip, sayfalar arasında hareket edip ve bunu yaparken state'leri (durumları) aktarıp aynı zamanda organize bir yol izleyerek business logic'i view'dan ayıracağız ve bu YORUM SATIRLARI DAHİL SADECE 26 SATIR İÇERECEK. - 1.Adım: “Get”’i MaterialApp’den önce GetMaterialApp'e dönüştürerek ekliyoruz. ```dart void main() => runApp(GetMaterialApp(home: Home())); ``` - Not: Bu kullanım Flutter’ın sağladığı MaterialApp’i modifiye etmez. Sadece child'ı (çocuğu) MaterialApp olan ve bir öncesinde konfigüre edilmiş bir widget’tır. Kendiniz de manuel olarak konfigüre edebilirsiniz fakat buna gerek yoktur. GetMaterialApp, route’ları oluşturacak ve içine bunları, translation’ları ve route navigation için ihtiyacınız olabilecek olan her şeyi inject edecektir. Eğer Get’i sadece state management ya da dependency management olarak kullanmak isterseniz, GetMaterialApp kullanmanıza gerek yoktur. Bu yapı routes, snackbars, internationalization, bottomSheets, dialogs, ve route'lara bağlı high-level apis kullanımlarında ek olarak da context'in yokluğundaki durumlarda kullanılmalıdır. - Not²: Bu adım sadece route management kullanıldığında gereklidir (`Get.to()`, `Get.back()` ve bunlar gibi). Kullanmayacaksanız 1. adımı yapmanıza gerek yoktur. - 2.Adım: İş mantıklarını içeren sınıfı oluşturup içine tüm değişkenleri metodları ve controller'ları ekleyin.İstediğiniz herhangi bir değişkeni ".obs" ile gözlemlenebilir yapabilirsiniz. ```dart class Controller extends GetxController{ var count = 0.obs; increment() => count++; } ``` - 3.Adım: View'ı oluşturun ve içine StatelessWidget (bu RAM'den tasarruf sağlar) yerleştirin. Get sayesinde StatefulWidget kullanmanıza gerek yoktur. ```dart class Home extends StatelessWidget { @override Widget build(context) { // Sınıfınızın nesnesini getirmek için Get.put() kullanılır. Bu içindeki tüm "child" route'ları görüntülenebilir yapar. final Controller c = Get.put(Controller()); return Scaffold( // Sayaç her değiştiğinde Text() içindeki değerin güncellenmesi için Obx(()=> kullanılır appBar: AppBar(title: Obx(() => Text("Clicks: ${c.count}"))), // Navigator.push içeren 8 satırı basit bir şekilde Get.to() ile değiştirin. Bunda context'e ihtiyacınız kalmamaktadır. body: Center(child: ElevatedButton( child: Text("Go to Other"), onPressed: () => Get.to(Other()))), floatingActionButton: FloatingActionButton(child: Icon(Icons.add), onPressed: c.increment)); } } class Other extends StatelessWidget { // Önceki sayfada zaten kulllandığımız Controller'ı, Get'de "find" diyerek getirmesinini istiyoruz. final Controller c = Get.find(); @override Widget build(context){ // Güncellenen sayaç değişkenine erişiyoruz. return Scaffold(body: Center(child: Text("${c.count}"))); } } ``` Sonuç: ![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/counter-app-gif.gif) Bu basit bir proje ama Get'in ne kadar güçlü olduğunu zaten açıkça ortaya koyuyor. Projeniz büyüdükçe bu gücü daha iyi anlacaksınız. Get, ekiplerle çalışmak üzere tasarlanmıştır. Ayrıca bireysel geliştiricinin de işini oldukça kolaylaştırır. Performanstan ödün vermeden her şeyi zamanında teslim etmenize yardımcı olur. Get tam olarak sana layık! # Üç Temel Kavram ## State management (Durum Yönetimi) Get iki farklı state manager içerir: simple state manager (GetBuilder da denilir) ve reactive state manager (GetX/Obx) ### Reactive State Manager (Reaktif Durum Yönetimi) Reaktif programlama, birçok kişi tarafından karmaşık olduğu söylendiği için kafa karışıklığına yol açabilir. Ancak GetX, reaktif programlamayı oldukça basit bir hale dönüştürür: - StreamController'ları oluşturmanıza ihtiyacınız yoktur. - Her bir değişken için StreamBuilder oluşturmanıza gerekmez. - Her state için sınıf oluşturmanıza gerek yoktur. - Başlangıç değerleri için get metodu oluşturmaya ihtiyaç olmaz. - Code generators kullanmanız gerekmez. Get, reaktif programlamayı setState kullanmak kadar kolay yapmaktadır. Bir isim değişkeniniz olduğunu ve onu her değiştirdiğinizde, onu kullanan tüm widget'ların otomatik olarak güncellendiğini düşünün. Bu sizin sayaç değişkeniniz: ```dart var name = 'Jonatas Borges'; ``` Gözlemlenebilir yapmak için ".obs" ekliyorsunuz ve bu şekli alıyor: ```dart var name = 'Jonatas Borges'.obs; ``` UI'da (kullanıcı arayüzünde) bu değeri göstermek ve değerler değiştiğinde ekranı güncellemek istediğinizde, hemen şunu yapın: ```dart Obx(() => Text("${controller.name}")); ``` Hepsi bu. İşte _bu kadar_ basit. ### State Management Hakkında Daha Fazla Bilgi **State Management'ın daha ayrıntılı bir açıklamasına [buradan](./documentation/en_US/state_management.md) erişebilirsiniz. Orada daha fazla örnek ile simple state manager ve reactive state manager arasındaki farkı görebilirsiniz** GetX'in gücü hakkında daha iyi bir fikir edineceksiniz. ## Route Management (Rota Yönetimi) Eğer routes/snackbars/dialogs/bottomsheets yapılarını context'e ihtiyaç duymadan kullanmak istedğinizde, GetX bunun için biçilmiş kaftan. MaterialApp'i GetMaterialApp ile değiştiriyoruz. ```dart GetMaterialApp( // Önceki hal: MaterialApp( home: MyHome(), ) ``` Yeni ekrana geçmek için: ```dart Get.to(NextScreen()); ``` Name (isim) ile yeni sayfaya geçiş yapılabilir. Named routes (İsimli rotalar) hakkında daha çok bilgiye [buradan](./documentation/tr_TR/route_management.md#navigation-with-named-routes) ulaşabilirsiniz. ```dart Get.toNamed('/details'); ``` Snackbars, dialogs, bottomsheets ve normalde Navigator.pop(context) ile kapatacağınız herhangi bir şeyi kapatmak için; ```dart Get.back(); ``` Önceki ekrana geri dönme seçeneğinin olmadan bir sonraki ekrana gitmek için (örnek olarak SplashScreens, login screens, vs.) ```dart Get.off(NextScreen()); ``` Sonraki ekrana gitmek ve önceki tüm route'ları kapatmak için shopping carts, polls, ve test'lerde kullanılır) ```dart Get.offAll(NextScreen()); ``` Bunlardan herhangi birini yapmak için context kullanmanız gerekmediğini fark ettiniz mi? Get route management'ı kullanmanın en büyük avantajlarından biri de budur. Böylelikle controller sınıfınızda olan tüm metodları endişe olmadan çalıştırabilirsiniz. ### Route Management Hakkında Daha Fazla Bilgi **Get, named routes ile çalışırken route'lar arası geçişleri kolayca kontrol etmenizi sağlar. Daha ayrıntılı doküman için [buraya](./documentation/tr_TR/route_management.md) tıklayabilirsiniz.** ## Dependency Management (Bağımlılık Yönetimi) Get basit ve güçlü bir dependency manager içerir. Sadece tek satır kod ile Bloc ya da Controller'ınızın aynı sınıf nesnesini getirmenizi sağlar. Ayrıca Provider context, ya da inheritedWidget kullanmanıza gerek kalmaz: ```dart Controller controller = Get.put(Controller()); // Controller controller = Controller(); yazmak yerine ``` - Not: Get'in State Manager'ını kullanıyorsanız, API bağlamaya daha çok dikkat edin ki onlar view'ı controller'a kolayca bağlamanızı sağlayacaktır. Kullanacağınız sınıfın içinde başka bir sınıfın nesnesini oluşturmak yerine, Get instance sayesinde uygulamanızın her yerinde aynı sınıf nesnesini kullanabilir hale getirebilirsiniz. Böylelikle controller (ya da Bloc sınıfını) normal bir şekilde kullanabilirsiniz. **İpucu:** Get dependency management, kütüphanenin diğer parçalarından ayrıdır. Örnek olarak eğer uygulamanızda hali hazırda state manager kullanıyorsanız (hangisi olduğu fark etmez), en baştan yazmaya gerek yoktur. Bu dependency injection'ı problem olmadan kullanabilirsiniz. ```dart controller.fetchApi(); ``` Farzedin ki çok fazla sayıda route'larınız ve controller'larınızda erişmeniz gereken data'lar olsun, state manager'ı Provider ya da Get_it gibi kütüphaneler ile bağlamanız gerekmekedir. Get kullanarak bunları bağlamaya ihtiyacınız kalmaz. Get'e sadece "find" diyerek controller'ınızı bulmasını istemeniz yeterlidir. Fazladan dependency'lere ihtiyacınız yoktur: ```dart Controller controller = Get.find(); // Evet, inanılmaz değil mi? Get, controller'ınızı bulup getirecek. Get, milyonlarca controller tanımlanmış da olsa size her zaman doğrusunu getirecektir. ``` Sonrasında üstte alınan controller'daki verilerinizi kullanabileceksiniz: ```dart Text(controller.textFromApi); ``` ### Dependency Management Hakkında Daha Fazla Bilgi **Dependency management'a daha derinden bakmak için [buraya](./documentation/tr_TR/dependency_management.md) tıklayabilirsiniz** # Utils ## Internationalization (Uluslararasılaştırma) ### Translations (Çeviriler) Translations, map halinde basit key-value değerleri tutar. Özel translation'larınızı eklemek için bir sınıf oluşturup `Translations`sınıfını extend edebilirsiniz. ```dart import 'package:get/get.dart'; class Messages extends Translations { @override Map> get keys => { 'en_US': { 'hello': 'Hello World', }, 'de_DE': { 'hello': 'Hallo Welt', } }; } ``` #### Translations kullanımı Önecen belirlenmiş key'e sadece `.tr` eklenince `Get.locale` ve `Get.fallbackLocale` şimdiki değerleri kullanarak otomatik çeviriyi yapacaktır. ```dart Text('title'.tr); ``` #### Tekil ve çoğul çevirisi yapımı ```dart var products = []; Text('singularKey'.trPlural('pluralKey', products.length, Args)); ``` #### Parametreler ile çeviri yapımı ```dart import 'package:get/get.dart'; Map> get keys => { 'en_US': { 'logged_in': 'logged in as @name with email @email', }, 'es_ES': { 'logged_in': 'iniciado sesión como @name con e-mail @email', } }; Text('logged_in'.trParams({ 'name': 'Jhon', 'email': 'jhon@example.com' })); ``` ### Locales (Yerel Ayarlar) Locale ve translation'lar`GetMaterialApp`'in parametreleri içinde atanabilir. ```dart return GetMaterialApp( translations: Messages(), //Çevirileriniz locale: Locale('en', 'US'), //Çeviriler bu locale dilinde gösterilecek fallbackLocale: Locale('en', 'UK'), // Eğer yanlış bir locale olması durumunda gösterilecek fallback locale ); ``` #### Locale değiştirme `Get.updateLocale(locale)` çağrılarak locale güncellenebilir. Çeviriler otomatik olarak yeni locale dilinde olacaktır. ```dart var locale = Locale('en', 'US'); Get.updateLocale(locale); ``` #### System locale (Yerel Sistem Ayarları) Sistem locale'i okumak için `Get.deviceLocale` kullanılır. ```dart return GetMaterialApp( locale: Get.deviceLocale, ); ``` ## Tema Değiştirme Güncellemek için`GetMaterialApp`'in üstüne bir widget koymayın. Bu, kopya key'ler oluşmasını tetikler. Birçok kişi tema değiştirmek için tarih öncesi bir yöntem olan "ThemeProvider" widget'ı oluşturmayı tercih eder. **GetX™** ile buna HİÇ gerek duyulmaz. `Get.changeTheme`ile kendi oluşturduğunuz temanızı hızlı bir şekilde ekleyebilirsiniz: ```dart Get.changeTheme(ThemeData.light()); ``` `onTap`'de Temayı değiştiren bir buton oluşturmak istiyorsanız, bunun için iki **GetX™** API'sini birleştirebilirsiniz: - API, karanlık `Theme`'in kullanıp kullanılmadığını kontrol eder. - `Theme` Change API'yi `onPressed`içine koyabilirsiniz: ```dart Get.changeTheme(Get.isDarkMode? ThemeData.light(): ThemeData.dark()); ``` `.darkmode` aktif hale geldiğinde, tıklandığında _light theme_ 'e döner, _light theme_ aktif olduğunda, _dark theme_'e geçer. ## GetConnect GetConnect backend ile frontend'in http ya da websockets ile kolayca iletişime geçmesini sağlar. ### Varsayılan Ayarlar Sadece GetConnect'den extend ederek gelen GET/POST/PUT/DELETE/SOCKET metodlarını kullanarak Rest API ya da websockets ile kolayca iletişim kurabilirsiniz. ```dart class UserProvider extends GetConnect { // Get request Future getUser(int id) => get('http://youapi/users/$id'); // Post request Future postUser(Map data) => post('http://youapi/users', body: data); // File ile Post request Future> postCases(List image) { final form = FormData({ 'file': MultipartFile(image, filename: 'avatar.png'), 'otherFile': MultipartFile(image, filename: 'cover.png'), }); return post('http://youapi/users/upload', form); } GetSocket userMessages() { return socket('https://yourapi/users/socket'); } } ``` ### Özel Ayarlamalar GetConnect oldukça düzenlenebilir. Kendi base Url'nizi tanımlayabilir, cevapları ve Request'leri düzenleyebilirsiniz. Authenticator oluşturup kendiliğinden authenticate olmasını sağlayabilirsiniz. Size verilen standart decoder ile tüm request'leri ek bir ayar olmadan modellerinize aktarabilirsiniz. ```dart class HomeProvider extends GetConnect { @override void onInit() { // Tüm request'ler jsonEncode'a gider: CasesModel.fromJson() httpClient.defaultDecoder = CasesModel.fromJson; httpClient.baseUrl = 'https://api.covid19api.com'; // baseUrl = 'https://api.covid19api.com'; //baseUrl'yi bu şekilde tanımlar // Http ve websockets kullanılmış ise [httpClient] instance'ına ihtiyaç yoktur. // Gelen tüm request'ler "apikey"'in header özelliğine gier. httpClient.addRequestModifier((request) { request.headers['apikey'] = '12345678'; return request; }); // Eğer sunucu "Brazilya"'dan bir veri gönderse bile // response'dan veriyi kaldırdığın için kullanıcılar göremez. // response önceden getirişmiş olsa bile httpClient.addResponseModifier((request, response) { CasesModel model = response.body; if (model.countries.contains('Brazil')) { model.countries.remove('Brazilll'); } }); httpClient.addAuthenticator((request) async { final response = await get("http://yourapi/token"); final token = response.body['token']; // header veriliyor request.headers['Authorization'] = "$token"; return request; }); // HttpStatus, HttpStatus.unauthorized ise // Autenticator 3 defa çağırılır. httpClient.maxAuthRetries = 3; } } @override Future> getCases(String path) => get(path); } ``` ## GetPage Middleware GetPage'in GetMiddleWare listesi oluşturan ve bunları özel bir sırada çalıştıran bir özelliği vardır. **Not**: GetPage, Middlewares içeriyor ise bu sayfanın tüm çocukları da aynı middlewares'i otomatik olarak içerir. ### Priority (Öncelik) Middlewares'in sıralı çalışması için GetMiddleware'lerin priority'leri (öncelikleri) düzenlenmelidir. ```dart final middlewares = [ GetMiddleware(priority: 2), GetMiddleware(priority: 5), GetMiddleware(priority: 4), GetMiddleware(priority: -8), ]; ``` Middleware'ler bu sırada çalışacaktır: **-8 => 2 => 4 => 5** ### Redirect (Yönlendirme) Çağrılan route'un sayfası arandığında, fonsyon çağırılmış olacaktır. Redirect için RouteSettings kullanılır. Name değeri null verilebilir ve bu olduğu zaman herhangi bir redirect olmayacaktır. ```dart RouteSettings redirect(String route) { final authService = Get.find(); return authService.authed.value ? null : RouteSettings(name: '/login') } ``` ### onPageCalled Bu fonsyon, herhangi bir şey oluşmadan önce sayfa çağırılmak istenildiğinde kullanılır. Sayfada bir şey değiştirmek için ya da yeni bir sayfa vermek için kullanabilirsiniz. ```dart GetPage onPageCalled(GetPage page) { final authService = Get.find(); return page.copyWith(title: 'Welcome ${authService.UserName}'); } ``` ### OnBindingsStart Bu fonsyon Bindings başlatılmadan hemen önce çalışır. Bu sayfa için Bindings'i şu şekilde değiştirebilirsiniz. ```dart List onBindingsStart(List bindings) { final authService = Get.find(); if (authService.isAdmin) { bindings.add(AdminBinding()); } return bindings; } ``` ### OnPageBuildStart Bu fonsyon Bindings başlatıldıktan hemen sonra çalışır. Bindings oluşturulduktan hemen sonra ve widget sayfası oluşturulmadan önce kullanabilirsiniz. ```dart GetPageBuilder onPageBuildStart(GetPageBuilder page) { print('bindings are ready'); return page; } ``` ### OnPageBuilt Bu fonskyon GetPage.page fonskyonu çağrıldıktan hemen sonra çalışır ve fonksyonun sonucunu verir. Sonrasında gösterilecek widget'ı alır. ### OnPageDispose Bu fonsyon sayfadaki ilgili tüm objelerin (Controllers, views, ...) dispose olmasından hemen sonra çalışır. ## Advanced APIs (Gelişmiş API'ler) ```dart // currentScreen'deki arg'ı verir Get.arguments // Önceki route'un name'ini verir. Get.previousRoute // Erişmek için raw route'u verir. Örnek olarak: rawRoute.isFirst() Get.rawRoute // GetObserver'dan Routing API'ye erişim verir. Get.routing // Snackbar açık mı kontrolü yaplır. Get.isSnackbarOpen // Dialog açık mı kontrolü yaplır. Get.isDialogOpen // Bottomsheet açık mı kontrolü yaplır. Get.isBottomSheetOpen // Tek route kaldırılır. Get.removeRoute() // predicate, true döndürene kadar terarlanarak geri gelir. Get.until() // Yeni route a gider ve eski tüm route'ları, predicate, true döndürene kadar kaldırır. Get.offUntil() // named route'a gider ve eski tüm route'ları, predicate, true döndürene kadar kaldırır. Get.offNamedUntil() //Hangi platformda çalıştığı kontrol edilir. GetPlatform.isAndroid GetPlatform.isIOS GetPlatform.isMacOS GetPlatform.isWindows GetPlatform.isLinux GetPlatform.isFuchsia //Cihaz türü kontrol edilir. GetPlatform.isMobile GetPlatform.isDesktop //Web'de tüm platformlar bağımsız olarak desteklenir! //Bir tarayıcının içinde çalışıp çalışmadığınızı anlayabilirsiniz //Windows, iOS, OSX, Android, vs. gibi GetPlatform.isWeb // Aşağıdakine eşittir : MediaQuery.of(context).size.height, // fakat immutable'dır (sabittir). Get.height Get.width // Navigator'e şimdiki context'i verir. Get.context // Önde olan snackbar/dialog/bottomsheet'a nerede olursanız olun context'i verir. Get.contextOverlay // Not: aşağıdaki metodlar context üzerine olan extension'lardır. // UI'ın herhangi bir yerinde context'e erişebilirsiniz ve istediğiniz yerde kullanabilirsiniz. // Eğer değişken bir height/width verileri varsa (örnek olarak Masaüstü ya da tarayıcı gibi ölçeği değişebilen pencereler) context'i kullanmaya ihtiyacınız vardır. context.width context.height // Size ekranın yarısını, üçte birini vb. tanımlamayı sağlar. // Responsive uygulamalar için kullanışlıdır. // param dividedBy (double) optional - default: 1 // param reducedBy (double) optional - default: 0 context.heightTransformer() context.widthTransformer() /// MediaQuery.of(context).size'a benzer context.mediaQuerySize() /// MediaQuery.of(context).padding'e benzer context.mediaQueryPadding() /// MediaQuery.of(context).viewPadding'e benzer context.mediaQueryViewPadding() /// MediaQuery.of(context).viewInsets;'e benzer context.mediaQueryViewInsets() /// MediaQuery.of(context).orientation;'a benzer context.orientation() /// Cihazın yatay modda olup olmadığını kontrol eder. context.isLandscape() /// Cihazın dikey modda olup olmadığını kontrol eder. context.isPortrait() /// MediaQuery.of(context).devicePixelRatio;'ya benzer context.devicePixelRatio() /// MediaQuery.of(context).textScaleFactor;'e benzer context.textScaleFactor() /// Ekranın en kısa kenarını getirir context.mediaQueryShortestSide() /// width 800'den büyük ise true döndürür. context.showNavbar() /// Kısa kenar 600p'den küçük ise true döndürür. context.isPhone() /// Kısa kenar 600p'den büyük ise true döndürür. context.isSmallTablet() /// Kısa kenar 720p'den büyük ise true döndürür. context.isLargeTablet() /// Cihaz tablet ise true döndürür. context.isTablet() /// Ekran boyutuna göre değerini döndürür /// için değer verebilir: /// watch: Kısa kenar 300'den küçük ise /// mobile: Kısa kenar 600'den küçük ise /// tablet: Kısa kenar 1200'den küçük ise /// desktop: width 1200'den büyük ise context.responsiveValue() ``` ### Opsiyonel Genel Ayarlar ve Manuel Ayarlamalar GetMaterialApp çoğu şeyi sizin için otomatik olarak ayarlar, ayrıca kendiniz de isterseniz Get'i manuel olarak ayarlayabilirsiniz. ```dart MaterialApp( navigatorKey: Get.key, navigatorObservers: [GetObserver()], ); ``` `GetObserver` içinde kendi Middleware'ınızı kullanabilirsiniz ve bu hiçbir şeyi etkilemez. ```dart MaterialApp( navigatorKey: Get.key, navigatorObservers: [ GetObserver(MiddleWare.observer) // Burası ], ); ``` `Get` için _Global Settings_ oluşturabilirsiniz. Herhangi bir route'a push yapmadan önce kodunuza `Get.config` eklemeniz yeterli. Ya da doğrudan `GetMaterialApp` içinde tanımlayabilirsiniz. ```dart GetMaterialApp( enableLog: true, defaultTransition: Transition.fade, opaqueRoute: Get.isOpaqueRouteDefault, popGesture: Get.isPopGestureEnable, transitionDuration: Get.defaultDurationTransition, defaultGlobalState: Get.defaultGlobalState, ); Get.config( enableLog = true, defaultPopGesture = true, defaultTransition = Transitions.cupertino ) ``` İsteğe bağlı olarak tüm logging mesajlarını `Get` üzerinden yönlendirebilirsiniz. Kendi istediğiniz logging paketinizi kullanmak ve oradaki log'ları yakalamak istiyorsanız: ```dart GetMaterialApp( enableLog: true, logWriterCallback: localLogWriter, ); void localLogWriter(String text, {bool isError = false}) { // istediğiniz logging paketine mesajı aktarır // eğer, enableLog: false ise log mesajları bu callback içine gönderilir // GetConfig.isLogEnable aracılığıyla isterseniz kontrol edebilirsiniz } ``` ### Local State Widgets (Yerel Durum Widgetları) Bu widget'lar tek bir değeri kontrol etmenize ve durumu geçici ya da yerel olarak tutmanızı sağlar. Reactive ve simple olan yapılar içerir. Örneğin bunları `TextField` içindeki obscureText parametresine bağlayabilirsiniz. İsterseniz kendinizinkini (Expandable Panel vs.) de oluşturabilirsiniz ya da `Scaffold`'daki body'nin içeriği değişirken `BottomNavigationBar` içindeki current index'i değiştirebilirsiniz. #### ValueBuilder Veri güncellemenin bir yolu olan `StatefulWidget`'daki `.setState` yapısının basitleştirilmiş halidir. ```dart ValueBuilder( initialValue: false, builder: (value, updateFn) => Switch( value: value, onChanged: updateFn, // tamamen aynısı! ( newValue ) => updateFn( newValue ) yapısını da kullanabilirsiniz ), // eğer builder metodu dışından bir çağırma işlemi yapılacak ise onUpdate: (value) => print("Value updated: $value"), onDispose: () => print("Widget unmounted"), ), ``` #### ObxValue [`ValueBuilder`](#valuebuilder)'a oldukça benzer olmasının yanında bu Reactive halidir. Rx nesnesine aktarabilir ( .obs yapısını hatıladınız mı?) ve otomatik olarak güncellenmensini sağlayabilirsiniz, resmen muhteşem değil mi ? ```dart ObxValue((data) => Switch( value: data.value, onChanged: data, // Rx, çağrılabilen fonsyon içerir. (flag) => data.value = flag şeklinde kullanılablir. ), false.obs, ), ``` ## Faydalı Ipuçları `.obs` yapıları olan gözlemlenebilirler ( _Rx_ tipleri olarak da bilinirler) oldukça çeşitli internal metodlara ve operatörlere sahiptirler. > `.obs` yapısının gerçek değer olduğunu düşünenler oldukça yaygındır fakat bu yanlış bir düşüncedir. > Değişkenleri, Type declaration yapmaktan kaçınmalıyız. Çünkü Dart'ın derleyicisi zaten bunu anlayacak kadar zekidir. > Kodunuzu daha temiz gösterir fakat: ```dart var message = 'Hello world'.obs; print( 'Message "$message" has Type ${message.runtimeType}'); ``` `message`, gerçek String Type değerini yazdırsa bile aslında bu bir **RxString** Type değeridir! Yani `message.substring( 0, 4 )` şeklinde kullanmazsınız. _observable_ içindeki gerçek `value`'ya (değere) erişmelisiniz. En çok "kullanılan yol" `.value` yapısı ile erişmektir fakat şu şekilde kullanabileceğiniz bir yol daha var... ```dart final name = 'GetX'.obs; // eğer değer şimdikinden farklı ise, sadece stream'i "günceller" name.value = 'Hey'; // Tüm Rx özellikleri "çağrılabilir" ve geriye yeni bir değer döndürür. // Fakat bu yaklaşım `null` değerleri kabul etmez, sonucunda UI rebuild (tekrardan oluşturulmaz) edilmez. name('Hello'); // getter yapmak gibi, 'Hello'yazdırır. name() ; /// numbers: final count = 0.obs; // Tüm non mutable (değişken olmayan) işlemleri num primitives üzerinden yapabilirsiniz. count + 1; // Dikkat edin! Bu sadece `count` değerinin 'final' olmayıp 'var' olduğu değerlerde mümkündür. count += 1; // Ayrıca değerleri kıyaslayabilirsiniz: count > 2; /// booleans: final flag = false.obs; // değer true/false arasında değişir. flag.toggle(); /// tüm tipler için: // "value"'ları null'a çevirir. flag.nil(); // Tüm toString(), toJson() işlemleri `value`'ya aktarılır. print( count ); // RxInt içinden `toString()` çağrılır. final abc = [0,1,2].obs; // Değeri bir json Array (dizi) yapısına çevirir ve RxList şeklinde yazdırır. // Json tüm Rx tipleri tarafından desteklenir. print('json: ${jsonEncode(abc)}, type: ${abc.runtimeType}'); // RxMap, RxList ve RxSet kendi native tiplerinden extend edilen özel Rx tipleridir. // Fakat List ile çalışmak normal listelerle çalışmak gibidir. Reaktiv olmasına rağmen. abc.add(12); // '12' listeye eklenir ve stream GÜNCELLENİR. abc[3]; // List'deki gibi 3. index okunur. // Rx ve value ile çalışmak aynıdır fakat hashCode her zaman value'dan alınır. final number = 12.obs; print( number == 12 ); // çıktı: true /// Özel Rx Modelleri: // toJson(), toString() child'a gönderilir. Böylelikle override'ları onlara implement edebilirsiniz ve doğrudan gözlemlenebiliri print() edebilirsiniz. class User { String name, last; int age; User({this.name, this.last, this.age}); @override String toString() => '$name $last, $age years old'; } final user = User(name: 'John', last: 'Doe', age: 33).obs; // `user`,"reactive" yapıdadır. Fakat içinde özellikleri DEĞİLDİR. // Yani eğer içindeki bazı değerleri değiştirirsek user.value.name = 'Roi'; //widget, rebuild olmayacaktır!, //`Rx`, user içindeki bir şeyi değiştirdiğinizde, bundan haberi olmayacaktır. // Özel sınıflar oluşturmak için, manuel olarak değişiklikleri "notify" etmeliyiz. user.refresh(); // Ya da `update()` metodunu kullanabiliriz user.update((value){ value.name='Roi'; }); print( user ); ``` ## StateMixin `UI` state ile başa çıkmanın başka bir yolu da `StateMixin` kullanmaktır. Bunu implement yapmak için, controller'ınıza `with` kullanarak yanına `StateMixin` eklemekle olur. Bu sizin T modelini kullanmanızı sağlar. ``` dart class Controller extends GetController with StateMixin{} ``` `change()` metodu istediğiniz yerde State'i değiştirmemizi sağlar. Veri(data) ve durum(status) aktarmak için kullanılan yol: ```dart change(data, status: RxStatus.success()); ``` RxStatus şu durumları kullanmanızı sağlar: ``` dart RxStatus.loading(); RxStatus.success(); RxStatus.empty(); RxStatus.error('message'); ``` UI'ın içinde temsil edilmesi için şu şekilde kullanılır: ```dart class OtherClass extends GetView { @override Widget build(BuildContext context) { return Scaffold( body: controller.obx( (state)=>Text(state.name), // Kendi yüklenme göstergenizi (loading indicator) buraya eklebilirsiniz // hali hazırda gelen yapı şudur: Center(child:CircularProgressIndicator()) onLoading: CustomLoadingIndicator(), onEmpty: Text('No data found'), // Keni hata widget'ınızı buraya yazabilirsiniz // hali hazırda gelen yapı şudur: Center(child:Text(error)) onError: (error)=>Text(error), ), ); } ``` #### GetView Bu widget'ı çok seviyorum, oldukça basit ve çok kullanışlı! `const Stateless` Widget, `Controller`'ı tanımlamak için `controller` özelliği içerir. ```dart class AwesomeController extends GetController { final String title = 'My Awesome View'; } // controller'ınızı register etmek için bunu kullandığınız `Type`'a geçirmeyi asla unutmayın. class AwesomeView extends GetView { @override Widget build(BuildContext context) { return Container( padding: EdgeInsets.all(20), child: Text(controller.title), // sadece `controller.something` deyin ); } } ``` #### GetResponsiveView Responsive view oluşturmak için bu widget'tan extend edilir. Bu widget ekran size ve type hakkında bilgiler içeren `screen` özelliğini taşır. ##### Nasıl kullanılır? build yapmanız için 2 yönteminiz vardır. - build yapmak için `builder` metodu ile widget döndürmek - `desktop`, `tablet`,`phone`, `watch` metodları ile bu özel metodlar ekran türü bu metodlarla eşitlendiiği zaman build metodunu tetikler. Mesela ekran [ScreenType.Tablet] ise `tablet` metodu çalışrıtılacaktır. **Not:** Eğer bu metodu kullanırsanız, lütfen `alwaysUseBuilder` özelliğini `false` olarak işaretleyin. `settings` özelliği sayesinde ekranın türüne göre genişlik limiti koyabilirsiniz. ![örnek](https://github.com/SchabanBo/get_page_example/blob/master/docs/Example.gif?raw=true) Bu ekranın kodları [burada](https://github.com/SchabanBo/get_page_example/blob/master/lib/pages/responsive_example/responsive_view.dart) #### GetWidget Çoğu kişinin widget'lar hakkında hiçbir fikri yok ya da kullanırken inanılmaz kafası karışıyor. Bu kullanım oldukça nadir fakat özel: Controller `caches`'leme işlemi _cache_ yüzünden asla bir `const Stateless` oluşamaz. > Peki ne zaman Controller'ı "cache"'yemeye ihtiyacınız olacak? "Çok da yaygın olmayan" bir **GetX** özelliğini kullanıyorsanız: `Get.create()`. `Get.create(()=>Controller())` yapısı siz her `Get.find()` dediğinizde size yeni bir `Controller` oluşturacak. `GetWidget`'ın ışığı burada parlıyor. Örnek olarak bir listede Todo ögelerini tutmak istiyorsanız kullanılır. Eğer widget'ın "rebuilt" olursa, yapı yine aynı controller nesnesini tutmaya devam edecektir. #### GetxService Bu sınıf `GetxController`' bezer. Uygulamanın lifecycle'larını (hayat döngüsü metodlarını) içerir ( `onInit()`, `onReady()`, `onClose()`). Fakat içinde hiçbir mantıksal yapı bulunmaz. Sadece **GetX**'in Dependency Injection sistemini bilgilendirir. Bunun alt sınıfları, memory (bellekten) hiçbir şekilde **kaldırılamaz**. Bu yöntem "Servislerinizi" tutmak için oldukça kullanışlıdır. Servisleriniz bu şekilde her zaman ulaşılabilir ve aktif olur. `Get.find()` metodu buna yeter. Örnek olarak:`ApiService`, `StorageService`, `CacheService`. ```dart Future main() async { await initServices(); /// SERVİSLERİN BAŞLATILMASI BEKLENİR runApp(SomeApp()); } /// Servislerinizi Flutter uygulaması çalışmadan önce başlatılması oldukça mantıklı bir harekettir. /// Uygulama akışını kontrol edebilirsiniz. (belki de birkaç tane Tema düzenlemesi ya da /// apiKey, kulllanıcıdan gelen dil tanımlaması gibi düzenlemeler yapmanız lazımdır...Bu durumna SettingService'i ApiService'den önce çalışması gerekmektedir. /// GetMaterialApp() 'in rebuild yapmasına gerek yoktur. Çünkü değerleri doğrudan alır. void initServices() async { print('starting services ...'); /// Burası sizin get_storage, hive, shared_pref gibi yükelemeleri yaptığınız yer. /// ya da daha fazla özellik bağlamak ve ya async yapıları kullanmak için await Get.putAsync(() => DbService().init()); await Get.putAsync(SettingsService()).init(); print('All services started...'); } class DbService extends GetxService { Future init() async { print('$runtimeType delays 2 sec'); await 2.delay(); print('$runtimeType ready!'); return this; } } class SettingsService extends GetxService { void init() async { print('$runtimeType delays 1 sec'); await 1.delay(); print('$runtimeType ready!'); } } ``` `GetxService`'lerini silmenin tek bir yolu vardır o da `Get.reset()`. Bu yöntem uygulamanıza "Hot Reboot" yapmak gibidir. Eğer uygulamanızın hayat süresi boyunca mutlaka kalıcılığı olmasını istediğiniz bir sınıfın nesnesini oluşturmak istediğiniz zaman `GetxService`'i kullanın. ### Testler Controller'larınızı lifecycle'ları (hayat döngüleri) dahil olmak üzere diğer sınıflar gibi test edebilirsiniz: ```dart class Controller extends GetxController { @override void onInit() { super.onInit(); //Değeri name2 ile değiştirme name.value = 'name2'; } @override void onClose() { name.value = ''; super.onClose(); } final name = 'name1'.obs; void changeName() => name.value = 'name3'; } void main() { test(''' Test the state of the reactive variable "name" across all of its lifecycles''', () { /// Controller'ınızı lifecycle dışında test edebilirsiniz. /// Fakat bunu GetX dependency injection kullanmadığınız taktirde /// kullanmanız önerilmiyor. final controller = Controller(); expect(controller.name.value, 'name1'); /// Eğer kullanıyorsanı istediğiniz her şeyi test edebilirsiniz. /// Her lifecycle sonrası uygulamanızın durumu dahil olmak üzere. Get.put(controller); // onInit çağrıldıktan sonra expect(controller.name.value, 'name2'); /// Bu fonksoynu test edin controller.changeName(); expect(controller.name.value, 'name3'); /// onClose çağrıldıktan sonra Get.delete(); expect(controller.name.value, ''); }); } ``` #### Ipuçları ##### Mockito or mocktail Eğer GetxController/GetxService'inizi mock yapmaya ihtiyacınız varsa GetxController'dan extend etmeniz ve Mock ile mixin'lemelisiniz. ```dart class NotificationServiceMock extends GetxService with Mock implements NotificationService {} ``` ##### Using Get.reset() Eğer widget'ları ya da widget grupllarını test etmek istiyorsanız, testinizin sonunda Get.reset'i kullanın ya da önceki testinizden kalma tüm ayarları sıfırlayın. ##### Get.testMode Eğer controller'larınızın içinden navigation kullanmak istiyorsanız. Main'den önce `Get.testMode = true` şeklinde kullanın. # 2.0 Ile Gelen Büyük Değişiklikler 1- Rx Types : | Önce | Sonra | | ------- | ---------- | | StringX | `RxString` | | IntX | `RxInt` | | MapX | `RxMap` | | ListX | `RxList` | | NumX | `RxNum` | | DoubleX | `RxDouble` | RxController ve GetBuilder şimdi birleştiler. Artık hangi controller'ı kullanmak istediğinizi hatırlamak zorunda değilsiniz. SAdece GetxController diyerek halledin. Bu simple state management ve reactive ile düzgün çalışacaktır. 2- NamedRoutes Önce: ```dart GetMaterialApp( namedRoutes: { '/': GetRoute(page: Home()), } ) ``` Şimdiki: ```dart GetMaterialApp( getPages: [ GetPage(name: '/', page: () => Home()), ] ) ``` Neden bu değişiklik? Genellikle, hangi sayfanın bir parametreden görüntüleneceğine veya bir giriş belirteciden görüntüleneceğine karar vermek gerekebilir, önceki yaklaşım buna izin vermediği için esnek değildi. Sayfayı bir fonksiyona sokmak, RAM tüketimini önemli ölçüde azaltır, çünkü rotalar uygulama başlatılmasından bu yana belleğe tahsis edilmeyecek ve aynı zamanda bu tür bir yaklaşım yapmasına izin verilmeyecek: ```dart GetStorage box = GetStorage(); GetMaterialApp( getPages: [ GetPage(name: '/', page:(){ return box.hasData('token') ? Home() : Login(); }) ] ) ``` # Neden Getx? 1- Birçok kez Flutter güncellendikten sonra, birçok paket çalışmaz hale gelecek. Genelde derleme hataları gerçekleşir. Bu hataların hala cevabı olmayabilir. Geliştiricinin bu hatanın nerden geldiğini bilmesi gereklidir. Sonrasında bu hatayı izleyip bunun hakkında repository'de issue açması ve sorunun çözülmesini beklemelidir. Get, geliştirme için gereken ana kaynaklarını (State, dependency ve route management) merkezde toplar, pubspec'e tek bir paket eklemeye izin verir ve çalışmaya başlar. Flutter güncellendikten sonra tek yapmanız gereken Get dependency'yi güncellemek ve çalışmaya başlamaktır. Get uyumluluk problemlerini de çözer. Paketler arasında genelde güncellemeler sonrası uyumsuzluklar olabilir. Get'in kendi içinde her şey birbiri ile uyumlu olduğundan bunun için endişelenmenize gerek yoktur. 2- Flutter oldukça kolay kullanımı olan inanılmaz olmasının yanı sıra birçok geliştirici tarafından istenmeyen `Navigator.of(context).push (context, builder [...]` gibi ezber yapılar içerir. Get geliştirmeyi basitleştirir. Route çağırmak için 8 kod yazmak yerine sadece `Get.to(Home())` diyerek bir sonraki sayfaya geçebilirsin. Dynamic web urls ile çalışmak mevcut Flutter ile çalışırken zorlayıcı olabilir. Get ile durum tam tersi hal alır ve işleri çok kolay bir hale getirir. Flutter'da State'leri yönetmek için dependency'leri pub içerindeki yüzlerce kütüphane arasından seçmek birçok tartışmayı da beraberinde getiren bir konudur. Get sayesinde ekranda değişkeni otomatik olarak güncellemesini sağlamak için değişkeninizin sonuna ".obs" eklemek ve widget'ınızı Obx ile sarmalamak yeterlidir. 3- Performansı kafaya takmayın. Flutter'ın performansı zaten çok iyi. Bir de state manager kullanırken ve blocs/stores/controllers gibi sınıflarınızı locator ile yönetirken de aynı performansı aldığınızı düşünün. İhtiyacınız olmadığında dependency'lerinizi dışarıdan çağırmak zorunda kalacaksınız. Hiç düşündünüz mü, basitçe kullandığınız controller'ınızın artık kimse tarafından kullanılmadığında kolayca bellekten silindiğini? İşte GetX bunu yapar. SmartManagement sayesinde kullanılmayan her şey endişelenmenize gerek kalmadan otomatik olarak hafızadan silinir. Bunun için bir logic yaratmaya gerek bile kalmadan, kaynakları minimum ölçüde tükettiğinize emin olabilirsiniz. 4- Gerçek ayrıştırma: "View ile the business logic (iş mantığını) birbirlerinden ayırmak" kavramını duymuş olabilirsiniz. Bu sadece BLoC, MVC, MVVM'ye özgü bir özellik değildir, piyasadaki diğer standart tasarım desenlerinde de mevcuttur. Ancak bu kavram Flutter'da context kullanımına bağlı olarak kolaylaştırılabilir. Bir InheritedWidget bulabilmek için context'e ihtiyaç duyduğunuzda, bunu view'da yapmalı ya da parametre ile aktarmalıyız. Ben bu yöntemi oldukça çirkin buluyorum. Ayrıca bir ekip ile çalışırken View'daki iş mantığına hep bağımlı olacağız. GetX standart yaklaşımı benimsemez ve StatefulWidgets, InitState, vb. yapılarını tamamen ortadan kaldırmaz. Daha temiz bir yaklaşım sunar. Controller'ların yaşam döngüleri vardır. Mesela APIREST talebi yaptığında view'a bağlı olmak zorunda değilsin. Http çağrısı başlatmak için "onInit" kullanabilirsiniz. Veriler geldiğinde yerleştirilecektir. GEtX tamamen reaktif (cidden,stream'lerin altında çalışır) olduğu için tüm item'lar doldurulduğunda o değişkeni kullanan tüm widget'lar view'da otomatik olarak güncellenecektir. Bu UI uzmanlığına sahip kişilerin sadece widget'larla çalışmasını sağlar ve kullanıcı etkinlikleri dışında iş mantığına hiçbir şey göndermek zorunda değildir (bir düğmeye tıklamak gibi). İş mantığı ile çalışan insanlar, bunu ayrı olarak oluşturmak ve test etmek konusunda serbest olacaktır. Bu kütüphane her zaman güncellenebilir ve yeni özellikler eklenebilir olacaktır. PR ve contribute yapmakta tamamen özgürsünüz. # Topluluk ## Topluluk Kanalları GetX oldukça aktif ve yardımsever bir topluluğa sahiptir. Bu framework kullanımıyla ilgili sorularınız varsa veya herhangi bir yardım istiyorsanız, lütfen topluluk kanallarımıza katılın, sorunuz daha hızlı yanıtlanacak ki bunun için en uygun yer burasıdır. Repository'de issues açabilir ve kaynak talep edebilirsiniz. GetX topluluğunun bir parçası olmaktan çekinmeyin. | **Slack** | **Discord** | **Telegram** | | :-------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------- | | [![Get on Slack](https://img.shields.io/badge/slack-join-orange.svg)](https://communityinviter.com/apps/getxworkspace/getx) | [![Discord Shield](https://img.shields.io/discord/722900883784073290.svg?logo=discord)](https://discord.com/invite/9Hpt99N) | [![Telegram](https://img.shields.io/badge/chat-on%20Telegram-blue.svg)](https://t.me/joinchat/PhdbJRmsZNpAqSLJL6bH7g) | ## Nasıl katkıda bulunulur? _Projeye katkıda bulunmak ister misiniz? Sizi destekçilerimizden biri olarak öne çıkarmaktan gurur duyacağız. İşte katkıda bulunabileceğiniz ve Get'i (ve Flutter'ı) daha da iyi hale getirebileceğiniz bazı noktalar._ - Readme dosyasının diğer dillere çevrilmesine yardımcı olmak. - Readme'ye dokümanlar eklemek (birçok Get fonsyonu henüz belgelenmedi). - Get'in kullanımını öğretmek için makaleler yazabilir ya da videolar çekebilirsiniz (Bunlar Readme ve gelecekte Wiki'ye eklenecek). - Kod ve test PR'ları önermek. - Yeni fonksiyonlar eklemek. Her türlü yardım için teşekkürler. ## Makaleler ve videolar - [Flutter Getx EcoSystem package for arabic people](https://www.youtube.com/playlist?list=PLV1fXIAyjeuZ6M8m56zajMUwu4uE3-SL0) - Tutorial by [Pesa Coder](https://github.com/UsamaElgendy). - [Dynamic Themes in 3 lines using GetX™](https://medium.com/swlh/flutter-dynamic-themes-in-3-lines-c3b375f292e3) - Tutorial by [Rod Brown](https://github.com/RodBr). - [Complete GetX™ Navigation](https://www.youtube.com/watch?v=RaqPIoJSTtI) - Route management video by Amateur Coder. - [Complete GetX State Management](https://www.youtube.com/watch?v=CNpXbeI_slw) - State management video by Amateur Coder. - [GetX™ Other Features](https://youtu.be/ttQtlX_Q0eU) - Utils, storage, bindings and other features video by Amateur Coder. - [Firestore User with GetX | Todo App](https://www.youtube.com/watch?v=BiV0DcXgk58) - Video by Amateur Coder. - [Firebase Auth with GetX | Todo App](https://www.youtube.com/watch?v=-H-T_BSgfOE) - Video by Amateur Coder. - [The Flutter GetX™ Ecosystem ~ State Management](https://medium.com/flutter-community/the-flutter-getx-ecosystem-state-management-881c7235511d) - State management by [Aachman Garg](https://github.com/imaachman). - [The Flutter GetX™ Ecosystem ~ Dependency Injection](https://medium.com/flutter-community/the-flutter-getx-ecosystem-dependency-injection-8e763d0ec6b9) - Dependency Injection by [Aachman Garg](https://github.com/imaachman). - [GetX, the all-in-one Flutter package](https://www.youtube.com/watch?v=IYQgtu9TM74) - A brief tutorial covering State Management and Navigation by Thad Carnevalli. - [Build a To-do List App from scratch using Flutter and GetX](https://www.youtube.com/watch?v=EcnqFasHf18) - UI + State Management + Storage video by Thad Carnevalli. - [GetX Flutter Firebase Auth Example](https://medium.com/@jeffmcmorris/getx-flutter-firebase-auth-example-b383c1dd1de2) - Article by Jeff McMorris. - [Flutter State Management with GetX – Complete App](https://www.appwithflutter.com/flutter-state-management-with-getx/) - by App With Flutter. - [Flutter Routing with Animation using Get Package](https://www.appwithflutter.com/flutter-routing-using-get-package/) - by App With Flutter. - [A minimal example on dartpad](https://dartpad.dev/2b3d0d6f9d4e312c5fdbefc414c1727e?) - by [Roi Peker](https://github.com/roipeker) ================================================ FILE: README.ur-PK.md ================================================ ![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/get.png) **🌎 اردو ( Selected ✔) [| انگریزی |](README.md) [| ویتنامی |](README-vi.md) [| انڈونیشی |](README.id-ID.md) [چینی |](README.zh-cn.md) [برازیلی پرتگالی |](README.pt-br.md) [ہسپانوی |](README-es.md) [روسی |](README.ru.md) [پولش |](README.pl.md) [کورین |](README.ko-kr.md), [French](README-fr.md)** [![pub package](https://img.shields.io/pub/v/get.svg?label=get&color=blue)](https://pub.dev/packages/get) [![popularity](https://badges.bar/get/popularity)](https://pub.dev/packages/sentry/score) [![likes](https://badges.bar/get/likes)](https://pub.dev/packages/get/score) [![pub points](https://badges.bar/get/pub%20points)](https://pub.dev/packages/get/score) ![building](https://github.com/jonataslaw/get/workflows/build/badge.svg) [![style: effective dart](https://img.shields.io/badge/style-effective_dart-40c4ff.svg)](https://pub.dev/packages/effective_dart) [![Discord Shield](https://img.shields.io/discord/722900883784073290.svg?logo=discord)](https://discord.com/invite/9Hpt99N) [![Get on Slack](https://img.shields.io/badge/slack-join-orange.svg)](https://communityinviter.com/apps/getxworkspace/getx) [![Telegram](https://img.shields.io/badge/chat-on%20Telegram-blue.svg)](https://t.me/joinchat/PhdbJRmsZNpAqSLJL6bH7g) Awesome Flutter Buy Me A Coffee ![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/getx.png) - [تعارف](#تعارف) - [انسٹال](#انسٹال) - [کاؤنٹرایپ](#کاؤنٹرایپ) - [تین ستون](#تین-ستون) - [اسٹیٹ مینجمنٹ](#اسٹیٹ-مینجمنٹ) - [ری ایکٹو اسٹیٹ منیجر](#ری-ایکٹو-اسٹیٹ-منیجر) - [اسٹیٹ مینجمنٹ کے بارے میں مزید تفصیلات](#اسٹیٹ-مینجمنٹ-کے-بارے-میں-مزید-تفصیلات) - [روٹ مینجمنٹ](#روٹ-مینجمنٹ) - [روٹ مینجمنٹ کے بارے میں مزید تفصیلات](#روٹ-مینجمنٹ-کے-بارے-میں-مزید-تفصیلات) - [انحصار کا انتظام](#انحصار-کا-انتظام) - [انحصار کے انتظام کے بارے میں مزید تفصیلات](#انحصار-کے-انتظام-کے-بارے-میں-مزید-تفصیلات) - [استعمال](#استعمال) - [عالمگیریت](#عالمگیریت) - [ترجمہ](#ترجمہ) - [ترجمہ کا استعمال](#ترجمہ-کا-استعمال) - [مقامی](#مقامی) - [مقام کی تبدیلی](#مقام-کی-تبدیلی) - [سسٹم لوکیشن](#سسٹم-لوکیشن) - [تھیم کی تبدیلی](#تھیم-کی-تبدیلی) - [رابطے کا قیام](#رابطے-کا-قیام) - [ڈیفالٹ کنکشن کا قیام](#ڈیفالٹ-کنکشن-کا-قیام) - [خود سے رابطے کا قیام](#خود-سے-رابطے-کا-قیام) - [گیٹ پیج مڈل ویئر](#گیٹ-پیج-مڈل-ویئر) - [ترجیح](#ترجیح) - [ری ڈائریکٹ](#ری-ڈائریکٹ) - [جب پیج کی درخواست کی جائے](#جب-پیج-کی-درخواست-کی-جائے) - [آنبائنڈنگ اسٹارٹ](#آنبائنڈنگ-اسٹارٹ) - [آنپیج بلڈ اسٹارٹ](#آنپیج-بلڈ-اسٹارٹ) - [جب پیج لوڈ ہو](#جب-پیج-لوڈ-ہو) - [جب صفحہ تصرف ہوجائے](#جب-صفحہ-تصرف-ہوجائے) - [دوسرے اعلی درجے کی APIs](#دوسرے-اعلی-درجے-کی-apis) - [اختیاری عالمی ترتیبات اور دستی تشکیلات](#اختیاری-عالمی-ترتیبات-اور-دستی-تشکیلات) - [مقامی اسٹیٹ ویجٹ](#مقامی-اسٹیٹ-ویجٹ) - [ویلیو بلڈر](#ویلیو-بلڈر) - [اوبکس ویلیو](#اوبکس-ویلیو) - [کارآمد نکات](#کارآمد-نکات) - [گیٹ ویو](#گیٹ-ویو) - [گیٹ ویجٹ](#گیٹ-ویجٹ) - [گیٹکس سروس](#گیٹکس-سروس) - [پچھلے ورژن سے اہم تبدیلیاں](#پچھلے-ورژن-سے-اہم-تبدیلیاں) - [گیٹکس کیوں؟](#گیٹکس-کیوں) - [سماجی خدمات](#سماجی-خدمات) - [کمیونٹی چینلز](#کمیونٹی-چینلز) - [کس طرح شراکت کریں](#کس-طرح-شراکت-کریں) - [مضامین اور ویڈیوز](#مضامین-اور-ویڈیوز) # تعارف گیٹ ایکس اسٹیٹ مینجمنٹ کے لئے ایک ہلکا پھلکا اور طاقتور حل ہے۔ یہ تیز اور عملی انداز میں اعلی کارکردگی والی اسٹسٹ مینجمنٹ ، ذہین انحصار انجکشن ، اور روٹ مینجمنٹ کو یکجا کرتا ہے۔ گیٹ ایکس کے 3 بنیادی اصول ہیں ، اس کا مطلب یہ ہے کہ لائبریری میں موجود تمام وسائل کی ترجیح یہی ہے: **پروڈکٹیوٹی, کارکردگی اور تنظیم** **پروڈکٹیوٹی :** گیٹ ایکس کارکردگی اور وسائل کی کم سے کم کھپت پر مرکوز ہے۔ گیٹ ایکس اسٹریمز یا چینج نوٹیفائر استعمال نہیں کرتا ہے۔ **کارکردگی :** گیٹ ایکس ایک آسان اور خوشگوار ترکیب استعمال کرتا ہے۔ اس سے کوئی فرق نہیں پڑتا ہے کہ آپ کیا کرنا چاہتے ہیں ، گیٹ-ایکس کے ساتھ ہمیشہ ایک آسان راستہ رہتا ہے۔ اس سے کوڈنگ کے کئی گھنٹوں کی بچت ہوگی اور یہ آپ کی ایپلیکیشن فراہم کرنے والی زیادہ سے زیادہ کارکردگی کو نکال دے گی۔ عام طور پر ، ڈویلپر میموری سے کنٹرولرز کو ہٹانے سے متعلق رہنا چاہئے۔ گیٹ-ایکس کے ساتھ یہ ضروری نہیں ہے ، کیونکہ وسائل میموری سے حذف ہوجاتے ہیں جب وہ بطور ڈیفالٹ استعمال نہیں ہوتے ہیں۔ اگر آپ اسے یاد میں رکھنا چاہتے ہیں تو ، آپ کو اپنی انحصار میں واضح طور پر "مستقل: سچ" کا اعلان کرنا ہوگا۔ اس طرح ، وقت کی بچت کے علاوہ ، آپ کو میموری پر غیر ضروری انحصار کرنے کا خطرہ بھی کم ہوتا ہے۔ انحصار لوڈنگ ڈیفالٹ کے لحاظ سے بھی سست ہے۔ **تنظیم :** گیٹ-ایکس کی مدد سے منظر ، نمائش کی منطق ، کاروباری منطق ، انحصار انجیکشن ، اور نیویگیشن کی مجموعی ڈوپولنگ کی اجازت دیتا ہے۔ راستوں کے مابین تشریف لے جانے کے لئے سیاق و سباق کی ضرورت نہیں ہے ، لہذا ، آپ اس کے لئے ویجیٹ ٹری (ویژنائزیشن) پر منحصر نہیں ہیں۔ وراثت میں ملنے والے ویجیٹ کے ذریعے اپنے کنٹرولرز / بلاکس تک رسائی حاصل کرنے کے لئے سیاق و سباق کی ضرورت نہیں ہے ، لہذا آپ اپنی پریزنٹیشن منطق اور کاروباری منطق کو اپنی نظریاتی پرت سے مکمل طور پر ڈوپل کرتے ہیں۔ آپ کو متعدد فراہم کنندگان کے ذریعہ اپنے ویجیٹ ٹری میں اپنے کنٹرولرز / ماڈلز / بلاکس کی کلاسیں انجیکشن کرنے کی ضرورت نہیں ہے ، کیونکہ یہ گیٹ ایکس اپنی انحصار انجیکشن کی خصوصیت استعمال کرتا ہے ، جس سے ڈی آئی کو اس کے نظارے کو مکمل طور پر خارج کردیتی ہے۔ گیٹ-ایکس کے ذریعے آپ جانتے ہو کہ اپنی درخواست کی ہر خصوصیت کو کہاں تلاش کرنا ہے ، بطور ڈیفالٹ صاف ستھرا۔ بحالی کی سہولت فراہم کرنے کے علاوہ ، یہ ماڈیولوں کی شیئرنگ کو بھی یقینی بناتا ہے ، ایسی کوئی چیز جو اس وقت تک پھڑپھڑ پھینک کر ناقابل فہم تھی ، اور کچھ مکمل طور پر ممکن تھا۔ بی ایل او سی پھڑپھڑا میں کوڈ کو منظم کرنے کا نقطہ آغاز تھا ، یہ کاروباری منطق کو تصور سے الگ کرتا ہے۔ گیٹ ایکس اس کا فطری ارتقا ہے ، جس سے نہ صرف کاروباری منطق کو الگ کیا جائے بلکہ پیش کش کی منطق بھی۔ انحصار اور راستوں کا بونس انجیکشن بھی ڈوپل ہوچکا ہے ، اور ڈیٹا لیئر ان سب سے باہر ہے۔ آپ جانتے ہیں کہ سب کچھ کہاں ہے ، اور یہ سب کچھ ہیلو دنیا کی تعمیر سے زیادہ آسان طریقے سے ہے۔ گیٹ-ایکس ، فلٹر ایس ڈی کے کے ساتھ اعلی کارکردگی کی ایپلی کیشنز کی تعمیر کا آسان ترین ، عملی اور اسکیل ایبل طریقہ ہے ، جس کے ارد گرد ایک بہت بڑا ماحولیاتی نظام ہے جو کامل کے ساتھ مل کر کام کرتا ہے ، ابتدائی افراد کے لئے آسان اور ماہرین کے لئے درست ہے۔ یہ محفوظ ، مستحکم ، تازہ ترین ہے ، اور APIs کی ایک بہت بڑی رینج پیش کرتا ہے جو پہلے سے طے شدہ فلوٹر SDK پر موجود نہیں ہے۔ گیٹ ایکس پھولا ہوا نہیں ہے۔ اس میں بہت ساری خصوصیات ہیں جو آپ کو کسی بھی چیز کی فکر کیے بغیر پروگرامنگ شروع کرنے کی اجازت دیتی ہیں ، لیکن ان خصوصیات میں سے ہر ایک الگ الگ کنٹینر میں ہے ، اور صرف استعمال کے بعد شروع کی گئی ہے۔ اگر آپ صرف اسٹیٹ مینجمنٹ کا استعمال کرتے ہیں تو صرف اسٹیٹ مینجمنٹ مرتب کی جائے گی۔ اگر آپ صرف راستے ہی استعمال کرتے ہیں تو ، اسٹیٹ مینجمنٹ کی طرف سے کوئی بھی چیز مرتب نہیں کی جائے گی۔ گیٹ ایکس میں ایک بہت بڑا ماحولیاتی نظام ، ایک بڑی برادری ، بڑی تعداد میں تعاون کار موجود ہے ، اور جب تک پھڑپھڑ موجود ہے اس کو برقرار رکھا جائے گا۔ گیٹ ایکس بھی اسی کوڈ کے ساتھ اینڈروئیڈ ، آئی او ایس ، ویب ، میک ، لینکس ، ونڈوز اور اپنے سرور پر چلنے کے قابل ہے۔ **یہ ممکن ہے کہ اپنے پس منظر میں فرنٹ اینڈ پر تیار کردہ اپنے کوڈ کو پوری طرح سے استعمال کریں [گیٹ ایکس سرور](https://github.com/jonataslaw/get_server)**. **اس کے علاوہ ، سرور پر اور سامنے والے اختتام پر ، پوری ترقی کا عمل مکمل طور پر خودکار ہوسکتا ہے [CLI حاصل کریں](https://github.com/jonataslaw/get_cli)**. **اس کے علاوہ ، آپ کی پیداوری کو مزید بڑھانے کے لئے ، ہمارے پاس ہے [VSCode](https://marketplace.visualstudio.com/items?itemName=get-snippets.get-snippets) اور [Android Studio/Intellij](https://plugins.jetbrains.com/plugin/14975-getx-snippets)** # انسٹال کوڈ میں گیٹ ایکس کی تنصیب ```yaml # pubspec.yaml dependencies: get: ``` ان فائلوں میں امپورٹ کریں جو استعمال ہوں گی ```dart import 'package:get/get.dart'; ``` # کاؤنٹرایپ گیٹ-ایکس کی طاقت کو ظاہر کرنے کے ل I ، میں یہ ظاہر کروں گا کہ کس طرح ہر کلک کے ساتھ "کاؤنٹر" بنانا ہے ، کس طرح صفحات کے مابین تبادلہ کرنا اور اسکرینوں کے درمیان اسٹسٹ کو مشترکہ انداز میں بانٹنا ، کاروباری منطق کو صرف نظر سے الگ کرنا ، 26 لائنز کوڈ شامل تبصرے۔ - پہلا قدم : اپنے میٹریل ایپ سے پہلے "گیٹ" شامل کریں ، اسے گیٹ میٹریئل ایپ میں تبدیل کریں ```dart void main() => runApp(GetMaterialApp(home: Home())); ``` نوٹ: اس سے مٹیریل ایپ میں ترمیم نہیں ہوتی ، گیٹ میٹیرال ایپ کوئی ترمیم شدہ میٹریل ایپ نہیں ہے ، یہ ایک کنفیگریڈ ویجیٹ ہے ، جس میں بطور سی سی فلڈ میٹریل ایپ ہے۔ آپ اسے دستی طور پر تشکیل دے سکتے ہیں ، لیکن یہ یقینی طور پر ضروری نہیں ہے۔ گیٹ میٹریئل ایپ راستوں کو تخلیق کرے گی ، انہیں انجیکشن دے گی ، ترجمہ انجیکشن کرے گی ، روٹ نیویگیشن کے لئے آپ کی ضرورت کی ہر چیز کو انجیکشن دے گی۔ اگر آپ صرف ریاستی انتظام یا انحصار کے انتظام کے لئے گیٹیکس کا استعمال کرتے ہیں تو ، گیٹ میٹریئل ایپ کو استعمال کرنے کی ضرورت نہیں ہے۔ گیٹ میٹیرال ایپ راستوں ، سنیکبارز ، عالمگیریت ، نچلی شیٹس ، مکالموں ، اور روٹس سے متعلق اعلی سطحی اپس اور سیاق و سباق کی عدم موجودگی کے لئے ضروری ہے۔ یہ اقدام صرف اس صورت میں ضروری ہے اگر آپ روٹ مینجمنٹ کا استعمال کریں گے (`Get.to()`, `Get.back()`). اگر آپ اسے استعمال نہیں کریں گے تو پھر ضروری نہیں ہے کہ قدم 1 کریں - دوسرا مرحلہ : اپنی کاروباری منطق کلاس بنائیں اور اس کے اندر تمام متغیرات ، طریقے اور کنٹرولر رکھیں۔ کا استعمال کرتے ہوئے کسی بھی متغیر کو قابل مشاہدہ کرسکتے ہیں ".obs". ```dart class Controller extends GetxController{ var count = 0.obs; increment() => count++; } ``` - تیسرا قدم : اپنا نظارہ بنائیں ، اسٹیٹ لیس ویجیٹ استعمال کریں اور کچھ رام کو بچائیں ، گیٹ-ایکس کی مدد سے آپ کو اب اسٹیٹ فل ویجٹ استعمال کرنے کی ضرورت نہیں ہوگی۔ ```dart class Home extends StatelessWidget { @override Widget build(context) { // آپ کی کلاس کا آغاز // Instantiate your class using Get.put() to make it available for all "child" routes there. final Controller c = Get.put(Controller()); return Scaffold( // Use Obx(()=> to update Text() whenever count is changed. appBar: AppBar(title: Obx(() => Text("Clicks: ${c.count}"))), // Replace the 8 lines Navigator.push by a simple Get.to(). You don't need context body: Center(child: ElevatedButton( child: Text("Go to Other"), onPressed: () => Get.to(Other()))), floatingActionButton: FloatingActionButton(child: Icon(Icons.add), onPressed: c.increment)); } } class Other extends StatelessWidget { // You can ask Get to find a Controller that is being used by another page and redirect you to it. final Controller c = Get.find(); @override Widget build(context){ // Access the updated count variable return Scaffold(body: Center(child: Text("${c.count}"))); } } ``` نتیجہ ![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/counter-app-gif.gif) یہ ایک سادہ پروجیکٹ ہے لیکن اس سے پہلے ہی یہ واضح ہوجاتا ہے کہ گیٹ کتنا طاقتور ہے۔ جیسے جیسے آپ کا پروجیکٹ بڑھتا جائے گا ، یہ فرق مزید نمایاں ہوتا جائے گا۔ گیٹ کو ٹیموں کے ساتھ کام کرنے کے لئے ڈیزائن کیا گیا تھا ، لیکن اس سے ایک فرد ڈویلپر کا کام آسان ہوجاتا ہے۔ اپنی آخری تاریخ کو بہتر بنائیں ، کارکردگی کو کھونے کے بغیر وقت پر سب کچھ فراہم کریں۔ اگر آپ نے اس جملے کی نشاندہی کی ہے تو ، گیٹ-ایکس آپ کے لئے ہے! # تین ستون ## اسٹیٹ مینجمنٹ گیٹ کے دو مختلف مینیجر ہوتے ہیں: سادہ ریاستی مینیجر (ہم اسے گیٹ بلڈر کہیں گے) اور رد عمل کا مظاہرہ کرنے والے مینیجر (گیٹ-ایکس / اوب-ایکس) ### ری ایکٹو اسٹیٹ منیجر ری ایکٹیو پروگرامنگ بہت سے لوگوں کو اجنبی بنا سکتا ہے کیونکہ ایسا کہا جاتا ہے کہ یہ پیچیدہ ہے۔ گیٹ ایکس نے رد عمل مندانہ پروگرامنگ کو کسی آسان چیز میں تبدیل کردیا: - آپ کو اسٹریمکنٹرولر بنانے کی ضرورت نہیں ہوگی - آپ کو ہر متغیر کے لئے ایک اسٹریم بلڈر بنانے کی ضرورت نہیں ہوگی - آپ کو ہر ریاست کے لئے کلاس بنانے کی ضرورت نہیں ہوگی - آپ کو ابتدائی قدر کے لئے گیٹ ایکس بنانے کی ضرورت نہیں ہوگی - آپ کو کوڈ جنریٹر استعمال کرنے کی ضرورت نہیں ہوگی گیٹ-ایکس کے ساتھ قابل عمل پروگرامنگ اتنا ہی آسان ہے جتنا سیٹ اسٹیٹ کے استعمال سے۔ آئیے تصور کریں کہ آپ کے پاس متغیر ہے اور چاہتے ہیں کہ جب بھی آپ اسے تبدیل کریں ، اس کا استعمال کرنے والے تمام وجیٹس خود بخود تبدیل ہوجائیں۔ یہ آپ کی گنتی متغیر ہے: ```dart var name = 'Jonatas Borges'; ``` ".obs" اسے مشاہدہ کرنے کے لئے؛ آپ کو اس کے آخر میں شامل کرنے کی ضرورت ہے ```dart var name = 'Jonatas Borges'.obs; ``` اور صارف کے انٹرفیس میں ، جب آپ اس نمبر کو دکھانا چاہتے ہیں اور جب بھی اس کی اہمیت بدل جاتی ہے تو اسکرین کو اپ ڈیٹ کرنا چاہتے ہیں ، صرف یہ کریں: ```dart Obx(() => Text("${controller.name}")); ``` بس۔ یہ آسان ہے. ### اسٹیٹ مینجمنٹ کے بارے میں مزید تفصیلات **اسٹیٹ مینجمنٹ کی مزید گہرائی سے وضاحت ملاحظہ کریں [یہاں](./documentation/en_US/state_management.md). وہاں آپ مزید مثالیں دیکھیں گے اور آسان ریاستی مینیجر اور رد عمل ریاست کے مینیجر کے مابین بھی فرق** آپ کو گیٹ ایکس پاور کا ایک اچھا خیال ملے گا۔ ## روٹ مینجمنٹ اگر آپ سیاق و سباق کے بغیر راستے / سنیکبارز / مکالمے / بوتل شیٹ استعمال کرنے جارہے ہیں تو گیٹ ایکس آپ کے لئے بھی بہترین ہے ، بس اسے دیکھیں: اپنے میٹریل ایپ سے پہلے "گیٹ" شامل کریں ، اسے گیٹ میٹریئل ایپ میں تبدیل کریں ```dart GetMaterialApp( // Before: MaterialApp( home: MyHome(), ) ``` نئی اسکرین پر جائیں: ```dart Get.to(NextScreen()); ``` نام کے ساتھ نئی اسکرین پر جائیں۔ نامزد راستوں کے بارے میں مزید تفصیلات دیکھیں [یہاں](./documentation/en_US/route_management.md#navigation-with-named-routes) ```dart Get.toNamed('/details'); ``` سنیک بار ، ڈائیلاگ ، نیچے شیٹ کو بند کریں Navigator.pop(context); ```dart Get.back(); ``` اگلی اسکرین پر جانے کے لئے اور پچھلی اسکرین پر واپس جانے کا کوئی آپشن نہیں (اسپلش اسکرین ، لاگ ان اسکرینوں وغیرہ میں استعمال کیلئے) ```dart Get.off(NextScreen()); ``` اگلی سکرین پر جانے اور پچھلے سبھی راستوں کو منسوخ کرنے کے لئے (شاپنگ کارٹس ، پولز اور ٹیسٹوں میں کارآمد) ```dart Get.offAll(NextScreen()); ``` غور کیا کہ آپ کو ان میں سے کوئی بھی کام کرنے کے لئے سیاق و سباق کا استعمال نہیں کرنا پڑا؟ گیٹ روٹ مینجمنٹ کو استعمال کرنے کا سب سے بڑا فائدہ یہ ہے۔ اس کی مدد سے ، آپ اپنے کنٹرولر کلاس کے اندر ، تشویش کے بغیر ، ان تمام طریقوں کو انجام دے سکتے ہیں۔ ### روٹ مینجمنٹ کے بارے میں مزید تفصیلات **گیٹ ایکس نامی روٹ کے ساتھ کام کرتا ہے اور اپنے راستوں پر نچلی سطح کا کنٹرول بھی پیش کرتا ہے! ایک گہرائی میں دستاویزات موجود ہیں [یہاں](./documentation/en_US/route_management.md)** ## انحصار کا انتظام گیٹ ایکس کے پاس ایک سادہ اور طاقتور انحصار منیجر ہے جو آپ کو اپنے بلاک یا کنٹرولر کی طرح ایک ہی کلاس کو دوبارہ حاصل کرنے کی سہولت دیتا ہے جس میں کوڈ کی صرف 1 لائنز ، کوئی فراہم کنندہ سیاق و سباق ، کوئی وراثت والا ویجٹ نہیں ہے۔ ```dart Controller controller = Get.put(Controller()); // Rather Controller controller = Controller(); ``` نوٹ: اگر آپ گیٹ اسٹیٹ منیجر استعمال کررہے ہیں تو ، اے پی آئی کی پابندیوں پر زیادہ توجہ دیں ، جس سے آپ کے قول کو اپنے کنٹرولر سے مربوط کرنے میں آسانی ہوگی۔ آپ جس کلاس کو استعمال کررہے ہیں اس میں اپنی کلاس کو تیز کرنے کے بجائے ، آپ اسے حاصل کریں مثال کے طور پر اندر داخل کررہے ہیں ، جس سے یہ آپ کے ایپ میں دستیاب ہوگا۔ لہذا آپ اپنے کنٹرولر (یا کلاس بلاک) کو عام طور پر استعمال کرسکتے ہیں **اشارہ:**گیٹ ایکس انحصار کا انتظام پیکیج کے دوسرے حصوں سے گر گیا ہے ، لہذا اگر مثال کے طور پر آپ کی ایپ پہلے ہی اسٹیٹ مینیجر کو استعمال کررہی ہے (کوئی بھی ، اس سے کوئی فرق نہیں پڑتا ہے) ، آپ کو یہ سب کچھ دوبارہ لکھنے کی ضرورت نہیں ہے ، آپ اس انحصار کو استعمال کرسکتے ہیں۔ ```dart controller.fetchApi(); ``` ذرا تصور کریں کہ آپ نے متعدد راستوں سے گھوما ہوا ہے ، اور آپ کو ایک ایسے ڈیٹا کی ضرورت ہے جو آپ کے کنٹرولر میں پیچھے رہ گیا ہو ، آپ کو فراہم کنندہ یا گیٹ_یٹ کے ساتھ مل کر ایک ریاستی مینیجر کی ضرورت ہوگی ، صحیح؟ گیٹ ایکس کے ساتھ نہیں۔ آپ کو اپنے کنٹرولر کے ل "" ڈھونڈنے "کے لئے گیٹ ایکس سے پوچھنے کی ضرورت ہے ، آپ کو کسی بھی اضافی انحصار کی ضرورت نہیں ہے۔ ```dart Controller controller = Get.find(); ``` اور پھر آپ اپنا کنٹرولر ڈیٹا دوبارہ حاصل کرنے میں کامیاب ہوجائیں گے جو وہاں واپس حاصل کیا گیا تھا ```dart Text(controller.textFromApi); ``` ### انحصار کے انتظام کے بارے میں مزید تفصیلات **انحصار کے انتظام کی مزید گہرائی سے وضاحت ملاحظہ کریں [یہاں](./documentation/en_US/dependency_management.md)** # استعمال ## عالمگیریت ### ترجمہ ترجمہ کو ایک آسان کلیدی قدر والے لغت کے نقشے کے طور پر رکھا جاتا ہے۔ حسب ضرورت ترجمہ شامل کرنے کے لئے ، ایک کلاس تشکیل دیں اور توسیع کریں `Translations` ```dart import 'package:get/get.dart'; class Messages extends Translations { @override Map> get keys => { 'en_US': { 'hello': 'Hello World', }, 'de_DE': { 'hello': 'Hallo Welt', } }; } ``` #### ترجمہ کا استعمال بس ضمیمہ کریں `.tr` مخصوص کی میں اور اس کی موجودہ قیمت کا استعمال کرتے ہوئے ترجمہ کیا جائے گا`Get.locale` اور `Get.fallbackLocale`. ```dart Text('title'.tr); ``` ### مقامی مقام اور ترجمے کی وضاحت کے لئے پیرامیٹرز کو `گیٹ میٹیرال ایپ` پاس کریں۔ ```dart return GetMaterialApp( translations: Messages(), // your translations locale: Locale('en', 'US'), // translations will be displayed in that locale fallbackLocale: Locale('en', 'UK'), // specify the fallback locale in case an invalid locale is selected. ); ``` #### مقام کی تبدیلی لوکل کو اپ ڈیٹ کرنے کے لئے کال کریں گیٹ۔ اپ ڈیٹ لوکل (لوکل)۔ پھر ترجمے خود بخود نیا مقام استعمال کرتے ہیں۔ ```dart var locale = Locale('en', 'US'); Get.updateLocale(locale); ``` #### سسٹم لوکیشن ڈیوائس لوکل حاصل کرنے کے لئے اس لائن کو استعمال کریں ```dart return GetMaterialApp( locale: Get.deviceLocale, ); ``` ## تھیم کی تبدیلی برائے کرم `گیٹ میٹرال ایپ` سے زیادہ کسی بھی اعلی سطح کے ویجیٹ کو اپ ڈیٹ کرنے کیلئے استعمال نہ کریں۔ اس سے ڈپلیکیٹ کیز کو متحرک کیا جاسکتا ہے۔ بہت سارے لوگ صرف اپنی ایپ کے تھیم کو تبدیل کرنے کے لئے "تھیم پیڈائزر" ویجیٹ بنانے کے پراگیتہاسک نقطہ نظر کے عادی ہیں ، اور یہ ** گیٹ ایکس ™ ** کے ساتھ یقینی طور پر ضروری نہیں ہے۔ آپ اپنا کسٹم تھیم تشکیل دے سکتے ہیں اور اس کے لئے کسی بھی بوائلر پلیٹ کے بغیر اسے `گیٹ.چینج تھیم` میں شامل کرسکتے ہیں: ```dart Get.changeTheme(ThemeData.light()); ``` اگر آپ بٹن کی طرح کوئی چیز بنانا چاہتے ہیں جو تھیم کو `آن ٹیپ میں تبدیل کردے ، تو آپ اس کے لئے دو ** گیٹ ایکس ™ ** اے پی پی کو جوڑ سکتے ہیں: - اے پی آئی جو چیک کرتا ہے کہ آیا گہرا `تھیم` استعمال کیا جارہا ہے۔ - اور `تھیم` کی تبدیلی ، آپ اسے صرف `آن پیسڈ` میں ڈال سکتے ہیں۔ ```dart Get.changeTheme(Get.isDarkMode? ThemeData.light(): ThemeData.dark()); ``` جب ڈارک موڈ چالو ہوجاتا ہے ، تو وہ _ لائٹ تھیم_ میں تبدیل ہوجائے گا ، اور جب _ لائٹ تھیم_ فعال ہوجائے گا ، تو یہ _ ڈارک تھیم_ میں بدل جائے گا۔ ## رابطے کا قیام گیٹ کنیکٹ آپ کی پیٹھ سے اپنے سامنے تک HTTP یا ویب ساکٹس کے ذریعہ مواصلت کرنے کا ایک آسان طریقہ ہے ### ڈیفالٹ کنکشن کا قیام آپ آرام سے گیٹ کنیکٹ کو بڑھا سکتے ہیں اور GET / POST / PUT / DELETE / SOCKET طریقوں کو اپنے ریسٹ API یا ویب ساکٹس کے ساتھ بات چیت کرسکتے ہیں۔ ```dart class UserProvider extends GetConnect { // Get request Future getUser(int id) => get('http://youapi/users/$id'); // Post request Future postUser(Map data) => post('http://youapi/users', body: data); // Post request with File Future> postCases(List image) { final form = FormData({ 'file': MultipartFile(image, filename: 'avatar.png'), 'otherFile': MultipartFile(image, filename: 'cover.png'), }); return post('http://youapi/users/upload', form); } GetSocket userMessages() { return socket('https://yourapi/users/socket'); } } ``` ### خود سے رابطے کا قیام گیٹ کنیکٹ انتہائی حسب ضرورت ہے آپ درخواست کو تبدیل کرنے والے ، جواب دہندگان کے بطور ، جواب دہندگان کی حیثیت سے ، ایک مستند کی وضاحت ، اور حتی کہ کوششوں کی تعداد بھی کرسکتے ہیں جس میں وہ خود کو مستند کرنے کی کوشش کرے گی ، اس کے علاوہ یہ ایک معیاری ڈیکوڈر کی وضاحت کے امکان کو بھی فراہم کرے گی جو تبدیل ہوجائے گی۔ آپ کی ساری درخواستیں آپ میں اضافی تشکیل کے بغیر ماڈل کرتی ہیں۔ ```dart class HomeProvider extends GetConnect { @override void onInit() { // All request will pass to jsonEncode so CasesModel.fromJson() httpClient.defaultDecoder = CasesModel.fromJson; httpClient.baseUrl = 'https://api.covid19api.com'; // baseUrl = 'https://api.covid19api.com'; // It define baseUrl to // Http and websockets if used with no [httpClient] instance // It's will attach 'apikey' property on header from all requests httpClient.addRequestModifier((request) { request.headers['apikey'] = '12345678'; return request; }); // Even if the server sends data from the country "Brazil", // it will never be displayed to users, because you remove // that data from the response, even before the response is delivered httpClient.addResponseModifier((request, response) { CasesModel model = response.body; if (model.countries.contains('Brazil')) { model.countries.remove('Brazilll'); } }); httpClient.addAuthenticator((request) async { final response = await get("http://yourapi/token"); final token = response.body['token']; // Set the header request.headers['Authorization'] = "$token"; return request; }); //Autenticator will be called 3 times if HttpStatus is //HttpStatus.unauthorized httpClient.maxAuthRetries = 3; } @override Future> getCases(String path) => get(path); } ``` ## گیٹ پیج مڈل ویئر گیٹ پیج کے پاس اب نئی پراپرٹی ہے جو گیٹ میڈل ویئر کی فہرست لیتی ہے اور انہیں مخصوص ترتیب میں چلاتی ہے۔ نوٹ: جب گیٹ پیج کے مڈل ویئرز ہوں گے تو ، اس صفحے کے سبھی بچوں میں ایک جیسے مڈل ویئرز خودبخود ہوں گے۔ ### ترجیح مڈل ویئر کو چلانے کا آرڈر گیٹ میڈل ویئر میں ترجیحی طور پر ترتیب دیا جاسکتا ہے۔ ```dart final middlewares = [ GetMiddleware(priority: 2), GetMiddleware(priority: 5), GetMiddleware(priority: 4), GetMiddleware(priority: -8), ]; ``` وہ مڈل ویئر اسی ترتیب سے چلائے جائیں گے **-8 => 2 => 4 => 5** ### ری ڈائریکٹ اس فنکشن کو اس وقت کہا جائے گا جب کال والے راستے کے صفحے کی تلاش کی جا رہی ہو۔ اس کو ری ڈائریکٹ کرنے کے نتیجے میں روٹ سیٹنگز لیتے ہیں۔ یا اسے کالعدم کردیں اور کوئی ردوبدل نہیں ہوگا۔ ```dart RouteSettings redirect(String route) { final authService = Get.find(); return authService.authed.value ? null : RouteSettings(name: '/login') } ``` ### جب پیج کی درخواست کی جائے جب اس صفحے کو کچھ بھی تخلیق کرنے سے پہلے بلایا جائے گا تو اس فنکشن کو کہا جائے گا آپ اسے صفحہ کے بارے میں کچھ تبدیل کرنے یا نیا صفحہ دینے کیلئے استعمال کرسکتے ہیں ```dart GetPage onPageCalled(GetPage page) { final authService = Get.find(); return page.copyWith(title: 'Welcome ${authService.UserName}'); } ``` ### آنبائنڈنگ اسٹارٹ اس فنکشن کو بائنڈنگ شروع کرنے سے پہلے ہی کہا جائے گا۔ یہاں آپ اس صفحے کے لئے پابندیوں کو تبدیل کرسکتے ہیں۔ ```dart List onBindingsStart(List bindings) { final authService = Get.find(); if (authService.isAdmin) { bindings.add(AdminBinding()); } return bindings; } ``` ### آنپیج بلڈ اسٹارٹ اس فنکشن کو بائنڈنگ شروع کرنے کے بعد ہی کہا جائے گا۔ یہاں آپ اس کے بعد اور پیج ویجیٹ بنانے سے پہلے پابندیوں کو تخلیق کرنے کے بعد کچھ کرسکتے ہیں۔ ```dart GetPageBuilder onPageBuildStart(GetPageBuilder page) { print('bindings are ready'); return page; } ``` ### جب پیج لوڈ ہو اس فنکشن کو گیٹ پیج ڈاٹ پیج فنکشن کے بلانے کے ٹھیک ہی بعد میں کہا جائے گا اور آپ کو اس فنکشن کا نتیجہ پیش کرے گا۔ اور دکھایا جائے گا کہ ویجیٹ لے لو. ### جب صفحہ تصرف ہوجائے اس فنکشن کو صفحے کے تمام متعلقہ اشیاء (کنٹرولرز ، آراء ، ...) کو ضائع کرنے کے بعد ہی کہا جائے گا۔ ## دوسرے اعلی درجے کی APIs ```dart // give the current args from currentScreen Get.arguments // give name of previous route Get.previousRoute // give the raw route to access for example, rawRoute.isFirst() Get.rawRoute // give access to Routing API from GetObserver Get.routing // check if snackbar is open Get.isSnackbarOpen // check if dialog is open Get.isDialogOpen // check if bottomsheet is open Get.isBottomSheetOpen // remove one route. Get.removeRoute() // back repeatedly until the predicate returns true. Get.until() // go to next route and remove all the previous routes until the predicate returns true. Get.offUntil() // go to next named route and remove all the previous routes until the predicate returns true. Get.offNamedUntil() //Check in what platform the app is running GetPlatform.isAndroid GetPlatform.isIOS GetPlatform.isMacOS GetPlatform.isWindows GetPlatform.isLinux GetPlatform.isFuchsia //Check the device type GetPlatform.isMobile GetPlatform.isDesktop //All platforms are supported independently in web! //You can tell if you are running inside a browser //on Windows, iOS, OSX, Android, etc. GetPlatform.isWeb // Equivalent to : MediaQuery.of(context).size.height, // but immutable. Get.height Get.width // Gives the current context of the Navigator. Get.context // Gives the context of the snackbar/dialog/bottomsheet in the foreground, anywhere in your code. Get.contextOverlay // Note: the following methods are extensions on context. Since you // have access to context in any place of your UI, you can use it anywhere in the UI code // If you need a changeable height/width (like Desktop or browser windows that can be scaled) you will need to use context. context.width context.height // Gives you the power to define half the screen, a third of it and so on. // Useful for responsive applications. // param dividedBy (double) optional - default: 1 // param reducedBy (double) optional - default: 0 context.heightTransformer() context.widthTransformer() /// Similar to MediaQuery.of(context).size context.mediaQuerySize() /// Similar to MediaQuery.of(context).padding context.mediaQueryPadding() /// Similar to MediaQuery.of(context).viewPadding context.mediaQueryViewPadding() /// Similar to MediaQuery.of(context).viewInsets; context.mediaQueryViewInsets() /// Similar to MediaQuery.of(context).orientation; context.orientation() /// Check if device is on landscape mode context.isLandscape() /// Check if device is on portrait mode context.isPortrait() /// Similar to MediaQuery.of(context).devicePixelRatio; context.devicePixelRatio() /// Similar to MediaQuery.of(context).textScaleFactor; context.textScaleFactor() /// Get the shortestSide from screen context.mediaQueryShortestSide() /// True if width be larger than 800 context.showNavbar() /// True if the shortestSide is smaller than 600p context.isPhone() /// True if the shortestSide is largest than 600p context.isSmallTablet() /// True if the shortestSide is largest than 720p context.isLargeTablet() /// True if the current device is Tablet context.isTablet() /// اسکرین کے سائز کے مطابق ایک قیمت لوٹاتا ہے /// اس کی قیمت دے سکتے ہیں: /// واچ: اگر مختصر ترین جگہ 300 سے چھوٹی ہے /// موبائل: اگر مختصر ترین سائٹ 600 سے چھوٹی ہے /// ٹیبلٹ: اگر مختصر ترین سائٹ 1200 سے چھوٹی ہے /// ڈیسک ٹاپ: اگر چوڑائی 1200 سے زیادہ ہے context.responsiveValue() ``` ### اختیاری عالمی ترتیبات اور دستی تشکیلات گیٹ میٹریئل ایپ آپ کے لئے ہر چیز کو کنفیگر کرتی ہے ، لیکن اگر آپ تشکیل کرنا چاہتے ہیں تو دستی طور پر حاصل کریں۔ ```dart MaterialApp( navigatorKey: Get.key, navigatorObservers: [GetObserver()], ); ``` آپ `گیٹ اوزرور` کے اندر اپنا مڈل ویئر بھی استعمال کرسکیں گے ، اس سے کسی بھی چیز پر اثر نہیں پڑے گا۔ ```dart MaterialApp( navigatorKey: Get.key, navigatorObservers: [ GetObserver(MiddleWare.observer) // Here ], ); ``` آپ `گیٹ` کیلئے _ عالمی ترتیبات_ تشکیل دے سکتے ہیں۔ کسی بھی راستے کو آگے بڑھانے سے پہلے صرف اپنے کوڈ میں `گیٹ کنفیگ` شامل کریں۔ یا اسے اپنے `گیٹ میٹیرال ایپ` میں براہ راست کریں ```dart GetMaterialApp( enableLog: true, defaultTransition: Transition.fade, opaqueRoute: Get.isOpaqueRouteDefault, popGesture: Get.isPopGestureEnable, transitionDuration: Get.defaultDurationTransition, defaultGlobalState: Get.defaultGlobalState, ); Get.config( enableLog = true, defaultPopGesture = true, defaultTransition = Transitions.cupertino ) ``` آپ لاگ ان پیغامات کو اختیاری طور پر `گیٹ` سے دوبارہ بھیج سکتے ہیں۔ اگر آپ خود اپنا ، پسندیدہ لاگنگ پیکیج استعمال کرنا چاہتے ہیں تو ، اور وہاں موجود نوشتہ جات پر قبضہ کرنا چاہتے ہیں: ```dart GetMaterialApp( enableLog: true, logWriterCallback: localLogWriter, ); void localLogWriter(String text, {bool isError = false}) { // pass the message to your favourite logging package here // please note that even if enableLog: false log messages will be pushed in this callback // you get check the flag if you want through GetConfig.isLogEnable } ``` ### مقامی اسٹیٹ ویجٹ یہ وجیٹس آپ کو ایک ہی قیمت کا انتظام کرنے ، اور مقامی طور پر ریاست کو دائمی اور مقامی رکھنے کی اجازت دیتے ہیں۔ ہمارے پاس ری ایکٹیو اور سادہ ذائقے ہیں۔ مثال کے طور پر ، آپ ان کو ٹیکسٹ فیلڈ میں چھپے ہوئے متن کو ٹوگل کرنے کے لئے استعمال کرسکتے ہیں ، شاید کوئی رواج بنائیں توسیع پذیر پینل ، یا ہوسکتا ہے کہ موجودہ فہرست میں ترمیم کرکے نیچے کی نیویگیشن بار میں مواد کو تبدیل کرتے ہوئے `Scaffold` میں جسم کا #### ویلیو بلڈر `StatefulWidget` کی ایک سادگی جو` .setState` کال بیک کے ساتھ کام کرتی ہے جو تازہ ترین قیمت لیتی ہے۔ ```dart ValueBuilder( initialValue: false, builder: (value, updateFn) => Switch( value: value, onChanged: updateFn, // same signature! you could use ( newValue ) => updateFn( newValue ) ), // if you need to call something outside the builder method. onUpdate: (value) => print("Value updated: $value"), onDispose: () => print("Widget unmounted"), ), ``` #### اوبکس ویلیو اس طرح آپ کو قیمت ملتی ہے ```dart ObxValue((data) => Switch( value: data.value, onChanged: data, // Rx has a _callable_ function! You could use (flag) => data.value = flag, ), false.obs, ), ``` ## کارآمد نکات `.obs`ervables ( _Rx_ Types) ```dart var message = 'Hello world'.obs; print( 'Message "$message" has Type ${message.runtimeType}'); ``` `message` --> **RxString** [x] `message.substring( 0, 4 )`. [o] `.value` ```dart final name = 'GetX'.obs; // only "updates" the stream, if the value is different from the current one. name.value = 'Hey'; // All Rx properties are "callable" and returns the new value. // but this approach does not accepts `null`, the UI will not rebuild. name('Hello'); // is like a getter, prints 'Hello'. name() ; /// numbers: final count = 0.obs; // You can use all non mutable operations from num primitives! count + 1; // Watch out! this is only valid if `count` is not final, but var count += 1; // You can also compare against values: count > 2; /// booleans: final flag = false.obs; // switches the value between true/false flag.toggle(); /// all types: // Sets the `value` to null. flag.nil(); // All toString(), toJson() operations are passed down to the `value` print( count ); // calls `toString()` inside for RxInt final abc = [0,1,2].obs; // Converts the value to a json Array, prints RxList // Json is supported by all Rx types! print('json: ${jsonEncode(abc)}, type: ${abc.runtimeType}'); // RxMap, RxList and RxSet are special Rx types, that extends their native types. // but you can work with a List as a regular list, although is reactive! abc.add(12); // pushes 12 to the list, and UPDATES the stream. abc[3]; // like Lists, reads the index 3. // equality works with the Rx and the value, but hashCode is always taken from the value final number = 12.obs; print( number == 12 ); // prints > true /// Custom Rx Models: // toJson(), toString() are deferred to the child, so you can implement override on them, and print() the observable directly. class User { String name, last; int age; User({this.name, this.last, this.age}); @override String toString() => '$name $last, $age years old'; } final user = User(name: 'John', last: 'Doe', age: 33).obs; // `user` is "reactive", but the properties inside ARE NOT! // So, if we change some variable inside of it... user.value.name = 'Roi'; // The widget will not rebuild!, // `Rx` don't have any clue when you change something inside user. // So, for custom classes, we need to manually "notify" the change. user.refresh(); // or we can use the `update()` method! user.update((value){ value.name='Roi'; }); print( user ); ``` #### گیٹ ویو مجھے یہ ویجیٹ پسند ہے ، بہت آسان ، پھر بھی ، اتنا مفید ہے! ایک کانسٹیٹ اسٹیٹ لیس ویجیٹ ہے جس میں رجسٹرڈ `کنٹرولر` کے لئے حاصل کرنے والا `کنٹرولر` ہے ، بس۔ ```dart class AwesomeController extends GetxController { final String title = 'My Awesome View'; } // ALWAYS remember to pass the `Type` you used to register your controller! class AwesomeView extends GetView { @override Widget build(BuildContext context) { return Container( padding: EdgeInsets.all(20), child: Text(controller.title), // just call `controller.something` ); } } ``` #### گیٹ ویجٹ زیادہ تر لوگوں کو اس ویجیٹ کے بارے میں کوئی اندازہ نہیں ہے ، یا اس کے استعمال کو پوری طرح سے الجھن میں ہے استعمال کا معاملہ بہت کم ہے ، لیکن بہت ہی خاص ہے: یہ ایک کنٹرولر کی مدد کرتا ہے کیچ_کی وجہ سے ، `مجاز اسٹیٹ لیس نہیں ہوسکتا ہے > تو ، جب آپ کو ایک کنٹرولر "کیش" کرنے کی ضرورت ہے؟ اگر آپ استعمال کرتے ہیں تو ، ** گیٹ ایکس ** کی ایک اور "اتنی عام نہیں" خصوصیت: `گیٹ.کریٹ`۔ `Get.create(()=>Controller())` ایک نیا پیدا کرے گا `Controller` ہر بار جب آپ کال کریں گے `Get.find()`, اسی جگہ پر `گیٹ ویجٹ` چمکتا ہے ... جیسے کہ آپ اسے استعمال کرسکتے ہیں ، مثال کے طور پر ، ٹوڈو اشیاء کی ایک فہرست رکھنے کے ل. لہذا ، اگر آپکے پاس وجٹس کو "دوبارہ تعمیر" ہو جاتا ہے تو ، یہ وہی کنٹرولر مثال برقرار رکھے گا۔ #### گیٹکس سروس This class is like a `GetxController`, it shares the same lifecycle ( `onInit()`, `onReady()`, `onClose()`). But has no "logic" inside of it. It just notifies **GetX** Dependency Injection system, that this subclass **can not** be removed from memory. So is super useful to keep your "Services" always reachable and active with `Get.find()`. Like: `ApiService`, `StorageService`, `CacheService`. ```dart Future main() async { await initServices(); /// AWAIT SERVICES INITIALIZATION. runApp(SomeApp()); } /// Is a smart move to make your Services intiialize before you run the Flutter app. /// as you can control the execution flow (maybe you need to load some Theme configuration, /// apiKey, language defined by the User... so load SettingService before running ApiService. /// so GetMaterialApp() doesnt have to rebuild, and takes the values directly. void initServices() async { print('starting services ...'); /// Here is where you put get_storage, hive, shared_pref initialization. /// or moor connection, or whatever that's async. await Get.putAsync(() => DbService().init()); await Get.putAsync(SettingsService()).init(); print('All services started...'); } class DbService extends GetxService { Future init() async { print('$runtimeType delays 2 sec'); await 2.delay(); print('$runtimeType ready!'); return this; } } class SettingsService extends GetxService { void init() async { print('$runtimeType delays 1 sec'); await 1.delay(); print('$runtimeType ready!'); } } ``` `گیٹکسسروس` کو اصل میں حذف کرنے کا واحد راستہ ،`گیٹ.ریسیٹ` ہے جو ایک جیسے ہے آپ کی ایپ کا "گرم ریبوٹ"۔ لہذا ، یاد رکھیں ، اگر آپ کو دوران کلاس مثال کے طور پر مطلق استقامت کی ضرورت ہو اپنی ایپ کی زندگی بھر ، `گیٹکسسروس` استعمال کریں۔ # پچھلے ورژن سے اہم تبدیلیاں 1. آر ایکس اقسام: | Before | After | | ------- | ---------- | | StringX | `RxString` | | IntX | `RxInt` | | MapX | `RxMap` | | ListX | `RxList` | | NumX | `RxNum` | | DoubleX | `RxDouble` | آر ایکس کنٹرولر اور گیٹ بلڈر اب آپس میں مل گئے ہیں ، اب آپ کو یہ حفظ کرنے کی ضرورت نہیں ہے کہ آپ کون سے کنٹرولر استعمال کرنا چاہتے ہیں ، صرف گیٹکسکنٹرولر کا استعمال کریں ، یہ سادہ سسٹم مینجمنٹ اور رد عمل کے بھی کام کرے گا۔ 2. نامزد روٹس پہلے: ```dart GetMaterialApp( namedRoutes: { '/': GetRoute(page: Home()), } ) ``` اب: ```dart GetMaterialApp( getPages: [ GetPage(name: '/', page: () => Home()), ] ) ``` یہ تبدیلی کیوں؟ اکثر ، یہ فیصلہ کرنا ضروری ہوسکتا ہے کہ پیرامیٹر ، یا لاگ ان ٹوکن سے کون سا صفحہ ڈسپلے ہوگا ، پچھلا نقطہ نظر پیچیدہ تھا ، کیونکہ اس نے اس کی اجازت نہیں دی۔ صفحہ کو کسی فنکشن میں داخل کرنے سے رام کی کھپت میں نمایاں کمی واقع ہوئی ہے ، کیونکہ ایپ شروع ہونے کے بعد سے روٹوں کو میموری میں مختص نہیں کیا جائے گا ، اور اس طرح اس طرح کے نقطہ نظر کو کرنے کی بھی اجازت دی گئی ہے۔ ```dart GetStorage box = GetStorage(); GetMaterialApp( getPages: [ GetPage(name: '/', page:(){ return box.hasData('token') ? Home() : Login(); }) ] ) ``` # گیٹکس کیوں؟ 1. فلٹر کی تازہ کاری کے بعد ، آپ کے بہت سے پیکیجز ٹوٹ جائیں گے۔ بعض اوقات تالیف کی غلطیاں ہوتی ہیں ، غلطیاں اکثر ظاہر ہوتی ہیں کہ اب بھی اس کے بارے میں کوئی جواب نہیں ملتا ہے ، اور ڈویلپر کو یہ جاننے کی ضرورت ہوتی ہے کہ غلطی کہاں سے ہوئی ہے ، غلطی کو ٹریک کریں ، تب ہی متعلقہ ذخیرہ میں کوئی مسئلہ کھولنے کی کوشش کریں ، اور دیکھیں کہ اس کا مسئلہ حل ہوتا ہے۔ ترقی کے مرکزی وسائل کو مرکز بنائیں (ریاست ، انحصار اور روٹ مینجمنٹ) ، آپ کو اپنے پبسپیک میں ایک پیکیج شامل کرنے اور کام شروع کرنے کی اجازت دے۔ پھڑپھڑانے کی تازہ کاری کے بعد ، آپ کو صرف انحصار کرنے کی ضرورت ہے گیٹ انحصار کو اپ ڈیٹ کریں ، اور کام کریں۔ مطابقت کے مسائل کو بھی حل کریں حاصل کریں۔ ایک پیکج کا ورژن کتنی بار دوسرے کے ورژن کے ساتھ مطابقت نہیں رکھتا ہے ، کیونکہ ایک ورژن میں انحصار استعمال کرتا ہے ، اور دوسرا دوسرے ورژن میں۔ گیٹ کو استعمال کرنے میں بھی یہ کوئی تشویش نہیں ہے ، کیونکہ سب کچھ ایک ہی پیکج میں ہے اور مکمل طور پر ہم آہنگ ہے۔ 2. فلٹر آسان ہے .فلٹر ناقابل یقین ہے ، لیکن .فلٹر کے پاس اب بھی کچھ بوائلرپلیٹ موجود ہے جو زیادہ تر ڈویلپرز کے لئے ناپسندیدہ ہوسکتا ہے ، جیسے `Navigator.of(context).push (context, builder [...]`. پروگرامنگ کو آسان بنائیں۔ صرف راستے پر کال کرنے کے لئے 8 لائنوں کے کوڈ لکھنے کے بجائے ، آپ صرف یہ کرسکتے ہیں: `Get.to(Home())` اور آپ کر چکے ہیں ، آپ اگلے صفحے پر جائیں گے۔ متحرک ویب یو آر ایل ایک بہت تکلیف دہ چیز ہے جس کے ساتھ کرنا ہے ۔فلٹر فی الحال ، اور یہ کہ گیٹیکس کے ساتھ احمقانہ حد تک آسان ہے۔ .. فلٹر میں ریاستوں کا انتظام کرنا ، اور انحصار کا انتظام کرنا بھی ایک ایسی چیز ہے جو بہت ساری بحثیں پیدا کرتی ہے ، کیوں کہ پب میں سیکڑوں نمونوں کی موجودگی موجود ہے۔ لیکن آپ کے متغیر کے اختتام پر `.obs` شامل کرنے جتنا آسان کوئی چیز نہیں ہے ، اور اپنے ویجیٹ کو کسی اوکس کے اندر رکھ دیں ، اور بات یہ ہے کہ اس متغیر کی تمام تر تازہ کاری خود بخود اسکرین پر اپ ڈیٹ ہوجائے گی۔ 3. کارکردگی کی فکر کئے بغیر آسانی۔ .فلٹر کی کارکردگی پہلے ہی حیرت انگیز ہے ، لیکن تصور کریں کہ آپ اپنے بلاکس / اسٹورز / کنٹرولرز / وغیرہ کلاسوں کو تقسیم کرنے کے لئے اسٹیٹ مینیجر اور لوکیٹر کا استعمال کرتے ہیں۔ جب آپ کو ضرورت نہ ہو تو آپ کو دستی طور پر اس انحصار کے اخراج کو کال کرنا پڑے گا۔ لیکن کیا آپ نے کبھی اپنے کنٹرولر کو محض استعمال کرنے کے بارے میں سوچا ہے ، اور جب اب یہ کسی کے ذریعہ استعمال نہیں ہو رہا تھا تو ، اسے آسانی سے میموری سے حذف کردیا جائے گا؟ گیٹ ایکس یہی کرتا ہے۔ اسمارٹ مینجمنٹ کے ساتھ ، ہر وہ چیز جو استعمال نہیں ہورہی ہے اسے میموری سے حذف کردیا جاتا ہے ، اور آپ کو پروگرامنگ کے علاوہ کسی بھی چیز کی فکر کرنے کی ضرورت نہیں ہے۔ آپ کو یقین دلایا جائے گا کہ آپ کم از کم ضروری وسائل بروئے کار لا رہے ہیں ، حتی کہ اس کے لئے بھی کوئی منطق پیدا نہیں کیا۔ 4. اصل ڈیکوپلنگ۔ آپ نے یہ نظریہ "کاروبار کی منطق سے نظریہ کو الگ کریں" سنا ہوگا۔ یہ ریاستی انتظام کے دیگر حلوں کی کوئی خاصیت نہیں ہے اور مارکیٹ میں کسی دوسرے معیار کا یہ تصور ہے۔ تاہم ، سیاق و سباق کے استعمال کی وجہ سے پھڑپھڑ میں اکثر اس تصور کو کم کیا جاسکتا ہے۔ اگر آپ کو وراثت والے ویجیٹ کو تلاش کرنے کے لئے سیاق و سباق کی ضرورت ہوتی ہے تو ، آپ کو اس کی نظر میں ضرورت ہوگی ، یا پیرامیٹر کے ذریعہ سیاق و سباق کو منتقل کریں۔ مجھے خاص طور پر یہ حل بہت ہی بدصورت معلوم ہوتا ہے ، اور ٹیموں میں کام کرنے کے لئے ہمیں ہمیشہ ویو کی کاروباری منطق پر انحصار کرنا پڑے گا۔ گیٹکس معیاری نقطہ نظر کے ساتھ غیر روایتی ہے ، اور اگرچہ اس میں اسٹیٹ فل وِیجٹس ، انیسٹیٹ وغیرہ کے استعمال پر مکمل پابندی نہیں ہے تو ، اس کا ہمیشہ ایسا ہی نقطہ نظر ہوتا ہے جو صاف ستھرا ہوسکتا ہے۔ کنٹرولرز کے پاس زندگی کا دور رہتا ہے ، اور جب آپ کو مثال کے طور پر درخواست دینے کی ضرورت ہوتی ہے تو ، آپ کو نظر میں کسی چیز پر انحصار نہیں کرنا ہوتا ہے۔ آپ ایچ ٹی ٹی پی کال شروع کرنے کے لئے اونٹ کا استعمال کرسکتے ہیں ، اور جب ڈیٹا آجائے گا تو متغیرات آباد ہوجائیں گے۔ چونکہ گیٹ ایکس مکمل طور پر رد عمل مند ہے (واقعتا، ، اور نہروں کے تحت کام کرتا ہے) ، ایک بار جب سامان بھر جاتا ہے تو ، اس متغیر کو استعمال کرنے والے تمام ویجٹ خود بخود منظر میں اپ ڈیٹ ہوجائیں گے۔ اس سے UI کی مہارت رکھنے والے افراد کو صرف وگیٹس کے ساتھ کام کرنے کا موقع ملتا ہے ، اور صارف کے واقعات (جیسے بٹن پر کلک کرنے کے علاوہ) کے علاوہ کاروباری منطق پر کچھ بھی نہیں بھیجنا پڑتا ہے ، جبکہ کاروباری منطق کے ساتھ کام کرنے والے افراد الگ الگ کاروبار کی منطق کی تخلیق اور جانچ کر سکتے ہیں۔ اس لائبریری کو ہمیشہ اپ ڈیٹ کیا جائے گا اور نئی خصوصیات کو نافذ کیا جائے گا۔ بلا جھجک پی آر پیش کریں اور ان میں اپنا حصہ ڈالیں۔ # سماجی خدمات ## کمیونٹی چینلز گیٹ ایکس میں انتہائی فعال اور مددگار کمیونٹی ہے۔ اگر آپ کے ذہن میں سوالات ہیں ، یا اس فریم ورک کے استعمال کے سلسلے میں کوئی مدد چاہتے ہیں تو ، براہ کرم ہمارے کمیونٹی چینلز میں شامل ہوں ، آپ کے سوال کا زیادہ جلد جواب دیا جائے گا ، اور یہ سب سے موزوں جگہ ہوگی۔ یہ ذخیر. مسائل کو کھولنے ، اور وسائل کی درخواست کرنے کے لئے خصوصی ہے ، لیکن گیٹ ایکس کمیونٹی کا حصہ بننے میں آزاد محسوس کرتے ہیں۔ | **Slack** | **Discord** | **Telegram** | | :-------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------- | | [![Get on Slack](https://img.shields.io/badge/slack-join-orange.svg)](https://communityinviter.com/apps/getxworkspace/getx) | [![Discord Shield](https://img.shields.io/discord/722900883784073290.svg?logo=discord)](https://discord.com/invite/9Hpt99N) | [![Telegram](https://img.shields.io/badge/chat-on%20Telegram-blue.svg)](https://t.me/joinchat/PhdbJRmsZNpAqSLJL6bH7g) | ## کس طرح شراکت کریں منصوبے میں شراکت کرنا چاہتے ہیں؟ ہمیں اپنے ایک ساتھی کی حیثیت سے آپ کو اجاگر کرنے پر فخر ہوگا۔ یہاں کچھ نکات ہیں جہاں آپ اپنا حصہ ڈال سکتے ہیں اور گیٹ (اور پھڑپھڑنا) کو اور بہتر بنا سکتے ہیں۔ - ریڈمی کو دوسری زبانوں میں ترجمہ کرنے میں مدد کرنا۔ - دستاویزات کو ریڈ می میں شامل کرنا (گیٹ کے بہت سارے کام ابھی دستاویزی نہیں ہوئے ہیں)۔ - مضامین لکھیں یا ویڈیوز بنائیں جس کی تعلیم دیتے ہیں کہ گیٹ (ان کو ریڈیم میں اور مستقبل میں ہمارے ویکی میں داخل کیا جائے گا) کو کس طرح استعمال کیا جائے۔ - کوڈ / ٹیسٹ کے لئے پی آر پیش کرنا۔ - نئے افعال سمیت. کسی بھی شراکت کا خیرمقدم ہے! ## مضامین اور ویڈیوز - [Dynamic Themes in 3 lines using GetX™](https://medium.com/swlh/flutter-dynamic-themes-in-3-lines-c3b375f292e3) - Tutorial by [Rod Brown](https://github.com/RodBr). - [Complete GetX™ Navigation](https://www.youtube.com/watch?v=RaqPIoJSTtI) - Route management video by Amateur Coder. - [Complete GetX State Management](https://www.youtube.com/watch?v=CNpXbeI_slw) - State management video by Amateur Coder. - [GetX™ Other Features](https://youtu.be/ttQtlX_Q0eU) - Utils, storage, bindings and other features video by Amateur Coder. - [Firestore User with GetX | Todo App](https://www.youtube.com/watch?v=BiV0DcXgk58) - Video by Amateur Coder. - [Firebase Auth with GetX | Todo App](https://www.youtube.com/watch?v=-H-T_BSgfOE) - Video by Amateur Coder. - [The Flutter GetX™ Ecosystem ~ State Management](https://medium.com/flutter-community/the-flutter-getx-ecosystem-state-management-881c7235511d) - State management by [Aachman Garg](https://github.com/imaachman). - [The Flutter GetX™ Ecosystem ~ Dependency Injection](https://medium.com/flutter-community/the-flutter-getx-ecosystem-dependency-injection-8e763d0ec6b9) - Dependency Injection by [Aachman Garg](https://github.com/imaachman). - [GetX, the all-in-one Flutter package](https://www.youtube.com/watch?v=IYQgtu9TM74) - A brief tutorial covering State Management and Navigation by Thad Carnevalli. - [Build a To-do List App from scratch using Flutter and GetX](https://www.youtube.com/watch?v=EcnqFasHf18) - UI + State Management + Storage video by Thad Carnevalli. - [GetX Flutter Firebase Auth Example](https://medium.com/@jeffmcmorris/getx-flutter-firebase-auth-example-b383c1dd1de2) - Article by Jeff McMorris. - [Flutter State Management with GetX – Complete App](https://www.appwithflutter.com/flutter-state-management-with-getx/) - by App With Flutter. - [Flutter Routing with Animation using Get Package](https://www.appwithflutter.com/flutter-routing-using-get-package/) - by App With Flutter. ================================================ FILE: README.zh-cn.md ================================================ ![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/get.png) _语言: 中文, [英文](README.md), [越南文](README-vi.md), [印度尼西亚](README.id-ID.md), [乌尔都语](README.ur-PK.md), [巴西葡萄牙语](README.pt-br.md), [俄语](README.ru.md), [西班牙语](README-es.md), [波兰语](README.pl.md), [韩国语](README.ko-kr.md), [法语](README-fr.md), [French](README-fr.md)._ [![pub package](https://img.shields.io/pub/v/get.svg?label=get&color=blue)](https://pub.dev/packages/get) [![popularity](https://badges.bar/get/popularity)](https://pub.dev/packages/sentry/score) [![likes](https://badges.bar/get/likes)](https://pub.dev/packages/get/score) [![pub points](https://badges.bar/get/pub%20points)](https://pub.dev/packages/get/score) ![building](https://github.com/jonataslaw/get/workflows/build/badge.svg) [![style: effective dart](https://img.shields.io/badge/style-effective_dart-40c4ff.svg)](https://pub.dev/packages/effective_dart) [![Discord Shield](https://img.shields.io/discord/722900883784073290.svg?logo=discord)](https://discord.com/invite/9Hpt99N) [![Get on Slack](https://img.shields.io/badge/slack-join-orange.svg)](https://communityinviter.com/apps/getxworkspace/getx) [![Telegram](https://img.shields.io/badge/chat-on%20Telegram-blue.svg)](https://t.me/joinchat/PhdbJRmsZNpAqSLJL6bH7g) Awesome Flutter Buy Me A Coffee ![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/getx.png) - [关于Get](#关于get) - [安装](#安装) - [GetX的计数器示例](#getx的计数器示例) - [三大功能](#三大功能) - [状态管理](#状态管理) - [响应式状态管理器](#响应式状态管理器) - [关于状态管理的更多细节](#关于状态管理的更多细节) - [路由管理](#路由管理) - [关于路由管理的更多细节](#关于路由管理的更多细节) - [依赖管理](#依赖管理) - [关于依赖管理的更多细节](#关于依赖管理的更多细节) - [实用工具](#实用工具) - [国际化](#国际化) - [翻译](#翻译) - [使用翻译](#使用翻译) - [语言](#语言) - [改变语言](#改变语言) - [系统语言](#系统语言) - [改变主题](#改变主题) - [GetConnect](#getconnect) - [默认配置](#默认配置) - [自定义配置](#自定义配置) - [GetPage 中间件](#getpage-中间件) - [优先级](#优先级) - [Redirect](#redirect) - [onPageCalled](#onpagecalled) - [OnBindingsStart](#onbindingsstart) - [OnPageBuildStart](#onpagebuildstart) - [OnPageBuilt](#onpagebuilt) - [OnPageDispose](#onpagedispose) - [其他高级API](#其他高级api) - [可选的全局设置和手动配置](#可选的全局设置和手动配置) - [局部状态组件](#局部状态组件) - [ValueBuilder](#valuebuilder) - [ObxValue](#obxvalue) - [有用的提示](#有用的提示) - [GetView](#getview) - [GetWidget](#getwidget) - [GetxService](#getxservice) - [从2.0开始的兼容性变化](#从20开始的兼容性变化) - [为什么选择Getx?](#为什么选择getx) - [社区](#社区) - [社区渠道](#社区渠道) - [如何做贡献](#如何做贡献) - [文章和视频](#文章和视频) # 关于Get - GetX 是 Flutter 上的一个轻量且强大的解决方案:高性能的状态管理、智能的依赖注入和便捷的路由管理。 - GetX 有3个基本原则: - **性能:** GetX 专注于性能和最小资源消耗。GetX 打包后的apk占用大小和运行时的内存占用与其他状态管理插件不相上下。如果你感兴趣,这里有一个[性能测试](https://github.com/jonataslaw/benchmarks)。 - **效率:** GetX 的语法非常简捷,并保持了极高的性能,能极大缩短你的开发时长。 - **结构:** GetX 可以将界面、逻辑、依赖和路由完全解耦,用起来更清爽,逻辑更清晰,代码更容易维护。 - GetX 并不臃肿,却很轻量。如果你只使用状态管理,只有状态管理模块会被编译,其他没用到的东西都不会被编译到你的代码中。它拥有众多的功能,但这些功能都在独立的容器中,只有在使用后才会启动。 - Getx有一个庞大的生态系统,能够在Android、iOS、Web、Mac、Linux、Windows和你的服务器上用同样的代码运行。 **通过[Get Server](https://github.com/jonataslaw/get_server)** 可以在你的后端完全重用你在前端写的代码。 **此外,通过[Get CLI](https://github.com/jonataslaw/get_cli)**,无论是在服务器上还是在前端,整个开发过程都可以完全自动化。 **此外,为了进一步提高您的生产效率,我们还为您准备了一些插件** - **getx_template**:一键生成每个页面必需的文件夹、文件、模板代码等等 - [Android Studio/Intellij插件](https://plugins.jetbrains.com/plugin/15919-getx) - **GetX Snippets**:输入少量字母,自动提示选择后,可生成常用的模板代码 - [Android Studio/Intellij扩展](https://plugins.jetbrains.com/plugin/14975-getx-snippets) - [VSCode扩展](https://marketplace.visualstudio.com/items?itemName=get-snippets.get-snippets) # 安装 将 Get 添加到你的 pubspec.yaml 文件中。 ```yaml dependencies: get: ``` 在需要用到的文件中导入,它将被使用。 ```dart import 'package:get/get.dart'; ``` # GetX的计数器示例 Flutter默认创建的 "计数器 "项目有100多行(含注释),为了展示Get的强大功能,我将使用 GetX 重写一个"计数器 Plus版",实现: - 每次点击都能改变状态 - 在不同页面之间切换 - 在不同页面之间共享状态 - 将业务逻辑与界面分离 而完成这一切只需 **26 行代码(含注释)** - 步骤1. 在你的MaterialApp前添加 "Get",将其变成GetMaterialApp。 ```dart void main() => runApp(GetMaterialApp(home: Home())); ``` - 注意:这并不能修改Flutter的MaterialApp,GetMaterialApp并不是修改后的MaterialApp,它只是一个预先配置的Widget,它的子组件是默认的MaterialApp。你可以手动配置,但绝对没有必要。GetMaterialApp会创建路由,注入它们,注入翻译,注入你需要的一切路由导航。如果你只用Get来进行状态管理或依赖管理,就没有必要使用GetMaterialApp。GetMaterialApp对于路由、snackbar、国际化、bottomSheet、对话框以及与路由相关的高级apis和没有上下文(context)的情况下是必要的。 - 注2: 只有当你要使用路由管理(`Get.to()`, `Get.back()`等)时才需要这一步。如果你不打算使用它,那么就不需要做第1步。 - 第二步: 创建你的业务逻辑类,并将所有的变量,方法和控制器放在里面。 你可以使用一个简单的".obs "使任何变量成为可观察的。 ```dart class Controller extends GetxController{ var count = 0.obs; increment() => count++; } ``` - 第三步: 创建你的界面,使用StatelessWidget节省一些内存,使用Get你可能不再需要使用StatefulWidget。 ```dart class Home extends StatelessWidget { @override Widget build(context) { // 使用Get.put()实例化你的类,使其对当下的所有子路由可用。 final Controller c = Get.put(Controller()); return Scaffold( // 使用Obx(()=>每当改变计数时,就更新Text()。 appBar: AppBar(title: Obx(() => Text("Clicks: ${c.count}"))), // 用一个简单的Get.to()即可代替Navigator.push那8行,无需上下文! body: Center(child: ElevatedButton( child: Text("Go to Other"), onPressed: () => Get.to(Other()))), floatingActionButton: FloatingActionButton(child: Icon(Icons.add), onPressed: c.increment)); } } class Other extends StatelessWidget { // 你可以让Get找到一个正在被其他页面使用的Controller,并将它返回给你。 final Controller c = Get.find(); @override Widget build(context){ // 访问更新后的计数变量 return Scaffold(body: Center(child: Text("${c.count}"))); } } ``` 结果: ![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/counter-app-gif.gif) 这是一个简单的项目,但它已经让人明白Get的强大。随着项目的发展,这种差异将变得更加显著。 Get的设计是为了与团队合作,但它也可以让个人开发者的工作变得更简单。 加快开发速率,在不损失性能的情况下按时交付一切。Get并不适合每一个人,但如果你认同这句话,Get就是为你准备的! # 三大功能 ## 状态管理 目前,Flutter有几种状态管理器。但是,它们中的大多数都涉及到使用ChangeNotifier来更新widget,这对于中大型应用的性能来说是一个很糟糕的方法。你可以在Flutter的官方文档中查看到,[ChangeNotifier应该使用1个或最多2个监听器](https://api.flutter.dev/flutter/foundation/ChangeNotifier-class.html),这使得它们实际上无法用于任何中等或大型应用。 Get 并不是比任何其他状态管理器更好或更差,而是说你应该分析这些要点以及下面的要点来选择只用Get,还是与其他状态管理器结合使用。 Get不是其他状态管理器的敌人,因为Get是一个微框架,而不仅仅是一个状态管理器,既可以单独使用,也可以与其他状态管理器结合使用。 Get有两个不同的状态管理器:简单的状态管理器(GetBuilder)和响应式状态管理器(GetX)。 ### 响应式状态管理器 响应式编程可能会让很多人感到陌生,因为觉得它很复杂,但是GetX将响应式编程变得非常简单。 - 你不需要创建StreamControllers. - 你不需要为每个变量创建一个StreamBuilder。 - 你不需要为每个状态创建一个类。 - 你不需要为一个初始值创建一个get。 使用 Get 的响应式编程就像使用 setState 一样简单。 让我们想象一下,你有一个名称变量,并且希望每次你改变它时,所有使用它的小组件都会自动刷新。 这就是你的计数变量。 ```dart var name = 'Jonatas Borges'; ``` 要想让它变得可观察,你只需要在它的末尾加上".obs"。 ```dart var name = 'Jonatas Borges'.obs; ``` 而在UI中,当你想显示该值并在值变化时更新页面,只需这样做。 ```dart Obx(() => Text("${controller.name}")); ``` 这就是全部,就这么简单。 ### 关于状态管理的更多细节 **关于状态管理更深入的解释请查看[这里](./documentation/zh_CN/state_management.md)。在那里你将看到更多的例子,以及简单的状态管理器和响应式状态管理器之间的区别**。 你会对GetX的能力有一个很好的了解。 ## 路由管理 如果你想免上下文(context)使用路由/snackbars/dialogs/bottomsheets,GetX对你来说也是极好的,来吧展示: 在你的MaterialApp前加上 "Get",把它变成GetMaterialApp。 ```dart GetMaterialApp( // Before: MaterialApp( home: MyHome(), ) ``` 导航到新页面 ```dart Get.to(NextScreen()); ``` 用别名导航到新页面。查看更多关于命名路由的详细信息[这里](./documentation/zh_CN/route_management.md#navigation-with-named-routes) ```dart Get.toNamed('/details'); ``` 要关闭snackbars, dialogs, bottomsheets或任何你通常会用Navigator.pop(context)关闭的东西。 ```dart Get.back(); ``` 进入下一个页面,但没有返回上一个页面的选项(用于闪屏页,登录页面等)。 ```dart Get.off(NextScreen()); ``` 进入下一个页面并取消之前的所有路由(在购物车、投票和测试中很有用)。 ```dart Get.offAll(NextScreen()); ``` 注意到你不需要使用context来做这些事情吗?这就是使用Get路由管理的最大优势之一。有了它,你可以在你的控制器类中执行所有这些方法,而不用担心context在哪里。 ### 关于路由管理的更多细节 **关于别名路由,和对路由的低级控制,请看[这里](./documentation/zh_CN/route_management.md)**。 ## 依赖管理 Get有一个简单而强大的依赖管理器,它允许你只用1行代码就能检索到与你的Bloc或Controller相同的类,无需Provider context,无需inheritedWidget。 ```dart Controller controller = Get.put(Controller()); // 而不是 Controller controller = Controller(); ``` - 注意:如果你使用的是Get的状态管理器,请多注意绑定api,这将使你的界面更容易连接到你的控制器。 你是在Get实例中实例化它,而不是在你使用的类中实例化你的类,这将使它在整个App中可用。 所以你可以正常使用你的控制器(或类Bloc)。 **提示:** Get依赖管理与包的其他部分是解耦的,所以如果你的应用已经使用了一个状态管理器(任何一个,都没关系),你不需要全部重写,你可以使用这个依赖注入。 ```dart controller.fetchApi(); ``` 想象一下,你已经浏览了无数条路由,现在你需要拿到一个被遗留在控制器中的数据,那你需要一个状态管理器与Provider或Get_it一起使用来拿到它,对吗?用Get则不然,Get会自动为你的控制器找到你想要的数据,而你甚至不需要任何额外的依赖关系。 ```dart Controller controller = Get.find(); //是的,它看起来像魔术,Get会找到你的控制器,并将其提供给你。你可以实例化100万个控制器,Get总会给你正确的控制器。 ``` 然后你就可以恢复你在后面获得的控制器数据。 ```dart Text(controller.textFromApi); ``` ### 关于依赖管理的更多细节 **关于依赖管理的更深入解释请看[此处](./documentation/zh_CN/dependency_management.md)**。 # 实用工具 ## 国际化 ### 翻译 翻译被保存为一个简单的键值字典映射。 要添加自定义翻译,请创建一个类并扩展`翻译`。 ```dart import 'package:get/get.dart'; class Messages extends Translations { @override Map> get keys => { 'zh_CN': { 'hello': '你好 世界', }, 'de_DE': { 'hello': 'Hallo Welt', } }; } ``` #### 使用翻译 只要将`.tr`追加到指定的键上,就会使用`Get.locale`和`Get.fallbackLocale`的当前值进行翻译。 ```dart Text('title'.tr); ``` ### 语言 传递参数给`GetMaterialApp`来定义语言和翻译。 ```dart return GetMaterialApp( translations: Messages(), // 你的翻译 locale: Locale('zh', 'CN'), // 将会按照此处指定的语言翻译 fallbackLocale: Locale('en', 'US'), // 添加一个回调语言选项,以备上面指定的语言翻译不存在 ); ``` #### 改变语言 调用`Get.updateLocale(locale)`来更新语言环境。然后翻译会自动使用新的locale。 ```dart var locale = Locale('en', 'US'); Get.updateLocale(locale); ``` #### 系统语言 要读取系统语言,可以使用`window.locale`。 ```dart import 'dart:ui' as ui; return GetMaterialApp( locale: ui.window.locale, ); ``` ## 改变主题 请不要使用比`GetMaterialApp`更高级别的widget来更新主题,这可能会造成键重复。很多人习惯于创建一个 "ThemeProvider "的widget来改变应用主题,这在**GetX™**中是绝对没有必要的。 你可以创建你的自定义主题,并简单地将其添加到`Get.changeTheme`中,而无需任何模板。 ```dart Get.changeTheme(ThemeData.light()); ``` 如果你想在 "onTap "中创建类似于改变主题的按钮,你可以结合两个**GetX™** API来实现。 - 检查是否使用了深色的 "Theme "的API,以及 "Theme "更改API。 - 而`Theme` Change API,你可以把下面的代码放在`onPressed`里。 ```dart Get.changeTheme(Get.isDarkMode? ThemeData.light(): ThemeData.dark()); ``` 当`.darkmode`被激活时,它将切换到light主题,当light主题被激活时,它将切换到dark主题。 ## GetConnect GetConnect可以便捷的通过http或websockets进行前后台通信。 ### 默认配置 你能轻松的通过extend GetConnect就能使用GET/POST/PUT/DELETE/SOCKET方法与你的Rest API或websockets通信。 ```dart class UserProvider extends GetConnect { // Get request Future getUser(int id) => get('http://youapi/users/$id'); // Post request Future postUser(Map data) => post('http://youapi/users', body: data); // Post request with File Future> postCases(List image) { final form = FormData({ 'file': MultipartFile(image, filename: 'avatar.png'), 'otherFile': MultipartFile(image, filename: 'cover.png'), }); return post('http://youapi/users/upload', form); } GetSocket userMessages() { return socket('https://yourapi/users/socket'); } } ``` ### 自定义配置 GetConnect具有多种自定义配置。你可以配置base Url,配置响应,配置请求,添加权限验证,甚至是尝试认证的次数,除此之外,还可以定义一个标准的解码器,该解码器将把您的所有请求转换为您的模型,而不需要任何额外的配置。 ```dart class HomeProvider extends GetConnect { @override void onInit() { // All request will pass to jsonEncode so CasesModel.fromJson() httpClient.defaultDecoder = CasesModel.fromJson; httpClient.baseUrl = 'https://api.covid19api.com'; // baseUrl = 'https://api.covid19api.com'; // It define baseUrl to // Http and websockets if used with no [httpClient] instance // It's will attach 'apikey' property on header from all requests httpClient.addRequestModifier((request) { request.headers['apikey'] = '12345678'; return request; }); // Even if the server sends data from the country "Brazil", // it will never be displayed to users, because you remove // that data from the response, even before the response is delivered httpClient.addResponseModifier((request, response) { CasesModel model = response.body; if (model.countries.contains('Brazil')) { model.countries.remove('Brazilll'); } }); httpClient.addAuthenticator((request) async { final response = await get("http://yourapi/token"); final token = response.body['token']; // Set the header request.headers['Authorization'] = "$token"; return request; }); //Autenticator will be called 3 times if HttpStatus is //HttpStatus.unauthorized httpClient.maxAuthRetries = 3; } @override Future> getCases(String path) => get(path); } ``` ## GetPage 中间件 GetPage现在有个新的参数可以把列表中的Get中间件按指定顺序执行。 **注意**: 当GetPage有中间件时,所有的子page会自动有相同的中间件。 ### 优先级 设置中间件的优先级定义Get中间件的执行顺序。 ```dart final middlewares = [ GetMiddleware(priority: 2), GetMiddleware(priority: 5), GetMiddleware(priority: 4), GetMiddleware(priority: -8), ]; ``` 这些中间件会按这个顺序执行 **-8 => 2 => 4 => 5** ### Redirect 当被调用路由的页面被搜索时,这个函数将被调用。它将RouteSettings作为重定向的结果。或者给它null,就没有重定向了。 ```dart RouteSettings redirect(String route) { final authService = Get.find(); return authService.authed.value ? null : RouteSettings(name: '/login') } ``` ### onPageCalled 在调用页面时,创建任何东西之前,这个函数会先被调用。 您可以使用它来更改页面的某些内容或给它一个新页面。 ```dart GetPage onPageCalled(GetPage page) { final authService = Get.find(); return page.copyWith(title: 'Welcome ${authService.UserName}'); } ``` ### OnBindingsStart 这个函数将在绑定初始化之前被调用。 在这里,您可以更改此页面的绑定。 ```dart List onBindingsStart(List bindings) { final authService = Get.find(); if (authService.isAdmin) { bindings.add(AdminBinding()); } return bindings; } ``` ### OnPageBuildStart 这个函数将在绑定初始化之后被调用。 在这里,您可以在创建绑定之后和创建页面widget之前执行一些操作。 ```dart GetPageBuilder onPageBuildStart(GetPageBuilder page) { print('bindings are ready'); return page; } ``` ### OnPageBuilt 这个函数将在GetPage.page调用后被调用,并给出函数的结果,并获取将要显示的widget。 ### OnPageDispose 这个函数将在处理完页面的所有相关对象(Controllers, views, ...)之后被调用。 ## 其他高级API ```dart // 给出当前页面的args。 Get.arguments //给出以前的路由名称 Get.previousRoute // 给出要访问的原始路由,例如,rawRoute.isFirst() Get.rawRoute // 允许从GetObserver访问Rounting API。 Get.routing // 检查 snackbar 是否打开 Get.isSnackbarOpen // 检查 dialog 是否打开 Get.isDialogOpen // 检查 bottomsheet 是否打开 Get.isBottomSheetOpen // 删除一个路由。 Get.removeRoute() //反复返回,直到表达式返回真。 Get.until() // 转到下一条路由,并删除所有之前的路由,直到表达式返回true。 Get.offUntil() // 转到下一个命名的路由,并删除所有之前的路由,直到表达式返回true。 Get.offNamedUntil() //检查应用程序在哪个平台上运行。 GetPlatform.isAndroid GetPlatform.isIOS GetPlatform.isMacOS GetPlatform.isWindows GetPlatform.isLinux GetPlatform.isFuchsia //检查设备类型 GetPlatform.isMobile GetPlatform.isDesktop //所有平台都是独立支持web的! //你可以知道你是否在浏览器内运行。 //在Windows、iOS、OSX、Android等系统上。 GetPlatform.isWeb // 相当于.MediaQuery.of(context).size.height, //但不可改变。 Get.height Get.width // 提供当前上下文。 Get.context // 在你的代码中的任何地方,在前台提供 snackbar/dialog/bottomsheet 的上下文。 Get.contextOverlay // 注意:以下方法是对上下文的扩展。 // 因为在你的UI的任何地方都可以访问上下文,你可以在UI代码的任何地方使用它。 // 如果你需要一个可改变的高度/宽度(如桌面或浏览器窗口可以缩放),你将需要使用上下文。 context.width context.height // 让您可以定义一半的页面、三分之一的页面等。 // 对响应式应用很有用。 // 参数: dividedBy (double) 可选 - 默认值:1 // 参数: reducedBy (double) 可选 - 默认值:0。 context.heightTransformer() context.widthTransformer() /// 类似于 MediaQuery.of(context).size。 context.mediaQuerySize() /// 类似于 MediaQuery.of(context).padding。 context.mediaQueryPadding() /// 类似于 MediaQuery.of(context).viewPadding。 context.mediaQueryViewPadding() /// 类似于 MediaQuery.of(context).viewInsets。 context.mediaQueryViewInsets() /// 类似于 MediaQuery.of(context).orientation; context.orientation() ///检查设备是否处于横向模式 context.isLandscape() ///检查设备是否处于纵向模式。 context.isPortrait() ///类似于MediaQuery.of(context).devicePixelRatio。 context.devicePixelRatio() ///类似于MediaQuery.of(context).textScaleFactor。 context.textScaleFactor() ///查询设备最短边。 context.mediaQueryShortestSide() ///如果宽度大于800,则为真。 context.showNavbar() ///如果最短边小于600p,则为真。 context.isPhone() ///如果最短边大于600p,则为真。 context.isSmallTablet() ///如果最短边大于720p,则为真。 context.isLargeTablet() ///如果当前设备是平板电脑,则为真 context.isTablet() ///根据页面大小返回一个值。 ///可以给值为: ///watch:如果最短边小于300 ///mobile:如果最短边小于600 ///tablet:如果最短边(shortestSide)小于1200 ///desktop:如果宽度大于1200 context.responsiveValue() ``` ### 可选的全局设置和手动配置 GetMaterialApp为你配置了一切,但如果你想手动配置Get。 ```dart MaterialApp( navigatorKey: Get.key, navigatorObservers: [GetObserver()], ); ``` 你也可以在`GetObserver`中使用自己的中间件,这不会影响任何事情。 ```dart MaterialApp( navigatorKey: Get.key, navigatorObservers: [ GetObserver(MiddleWare.observer) // Here ], ); ``` 你可以为 "Get "创建_全局设置。只需在推送任何路由之前将`Get.config`添加到你的代码中。 或者直接在你的`GetMaterialApp`中做。 ```dart GetMaterialApp( enableLog: true, defaultTransition: Transition.fade, opaqueRoute: Get.isOpaqueRouteDefault, popGesture: Get.isPopGestureEnable, transitionDuration: Get.defaultDurationTransition, defaultGlobalState: Get.defaultGlobalState, ); Get.config( enableLog = true, defaultPopGesture = true, defaultTransition = Transitions.cupertino ) ``` 你可以选择重定向所有来自`Get`的日志信息。 如果你想使用你自己喜欢的日志包,并想查看那里的日志。 ```dart GetMaterialApp( enableLog: true, logWriterCallback: localLogWriter, ); void localLogWriter(String text, {bool isError = false}) { // 在这里把信息传递给你最喜欢的日志包。 // 请注意,即使enableLog: false,日志信息也会在这个回调中被推送。 // 如果你想的话,可以通过GetConfig.isLogEnable来检查这个标志。 } ``` ### 局部状态组件 这些Widgets允许您管理一个单一的值,并保持状态的短暂性和本地性。 我们有Reactive和Simple两种风格。 例如,你可以用它们来切换`TextField`中的obscureText,也许可以创建一个自定义的可扩展面板(Expandable Panel),或者在"Scaffold "的主体中改变内容的同时修改`BottomNavigationBar`中的当前索引。 #### ValueBuilder `StatefulWidget`的简化,它与`.setState`回调一起工作,并接受更新的值。 ```dart ValueBuilder( initialValue: false, builder: (value, updateFn) => Switch( value: value, onChanged: updateFn, // 你可以用( newValue )=> updateFn( newValue )。 ), // 如果你需要调用 builder 方法之外的东西。 onUpdate: (value) => print("Value updated: $value"), onDispose: () => print("Widget unmounted"), ), ``` #### ObxValue 类似于[`ValueBuilder`](#valuebuilder),但这是Reactive版本,你需要传递一个Rx实例(还记得神奇的.obs吗?自动更新......是不是很厉害?) ```dart ObxValue((data) => Switch( value: data.value, onChanged: data, // Rx 有一个 _callable_函数! 你可以使用 (flag) => data.value = flag, ), false.obs, ), ``` ## 有用的提示 `.obs`ervables (也称为_Rx_ Types)有各种各样的内部方法和操作符。 > `.obs`的属性**是**实际值,不要搞错了! > 我们避免了变量的类型声明,因为Dart的编译器足够聪明,而且代码 > 看起来更干净,但: ```dart var message = 'Hello world'.obs; print( 'Message "$message" has Type ${message.runtimeType}'); ``` 即使`message` _prints_实际的字符串值,类型也是**RxString**! 所以,你不能做`message.substring( 0, 4 )`。 你必须在_observable_里面访问真正的`value`。 最常用的方法是".value", 但是你也可以用... ```dart final name = 'GetX'.obs; //只有在值与当前值不同的情况下,才会 "更新 "流。 name.value = 'Hey'; // 所有Rx属性都是 "可调用 "的,并返回新的值。 //但这种方法不接受 "null",UI将不会重建。 name('Hello'); // 就像一个getter,打印'Hello'。 name() ; ///数字。 final count = 0.obs; // 您可以使用num基元的所有不可变操作! count + 1; // 注意!只有当 "count "不是final时,这才有效,除了var count += 1; // 你也可以与数值进行比较。 count > 2; /// booleans: final flag = false.obs; // 在真/假之间切换数值 flag.toggle(); /// 所有类型。 // 将 "value "设为空。 flag.nil(); // 所有的toString()、toJson()操作都会向下传递到`value`。 print( count ); // 在内部调用 "toString() "来GetRxInt final abc = [0,1,2].obs; // 将值转换为json数组,打印RxList。 // 所有Rx类型都支持Json! print('json: ${jsonEncode(abc)}, type: ${abc.runtimeType}'); // RxMap, RxList 和 RxSet 是特殊的 Rx 类型,扩展了它们的原生类型。 // 但你可以像使用普通列表一样使用列表,尽管它是响应式的。 abc.add(12); // 将12添加到列表中,并更新流。 abc[3]; // 和Lists一样,读取索引3。 // Rx和值是平等的,但hashCode总是从值中提取。 final number = 12.obs; print( number == 12 ); // prints > true ///自定义Rx模型。 // toJson(), toString()都是递延给子代的,所以你可以在它们上实现覆盖,并直接打印()可观察的内容。 class User { String name, last; int age; User({this.name, this.last, this.age}); @override String toString() => '$name $last, $age years old'; } final user = User(name: 'John', last: 'Doe', age: 33).obs; // `user`是 "响应式 "的,但里面的属性却不是! // 所以,如果我们改变其中的一些变量: user.value.name = 'Roi'; // 小部件不会重建! // 对于自定义类,我们需要手动 "通知 "改变。 user.refresh(); // 或者我们可以使用`update()`方法! user.update((value){ value.name='Roi'; }); print( user ); ``` #### GetView 我很喜欢这个Widget,很简单,很有用。 它是一个对已注册的`Controller`有一个名为`controller`的getter的`const Stateless`的Widget,仅此而已。 ```dart class AwesomeController extends GetxController { final String title = 'My Awesome View'; } // 一定要记住传递你用来注册控制器的`Type`! class AwesomeView extends GetView { @override Widget build(BuildContext context) { return Container( padding: EdgeInsets.all(20), child: Text( controller.title ), // 只需调用 "controller.something"。 ); } } ``` #### GetWidget 大多数人都不知道这个Widget,或者完全搞不清它的用法。 这个用例非常少见且特殊:它 "缓存 "了一个Controller,由于_cache_,不能成为一个 "const Stateless"(因为_cache_,所以不能成为一个`const Stateless`)。 > 那么,什么时候你需要 "缓存 "一个Controller? 如果你使用了**GetX**的另一个 "不常见 "的特性 `Get.create()` `Get.create(()=>Controller())` 会在每次调用时生成一个新的`Controller` `Get.find()` 你可以用它来保存Todo项目的列表,如果小组件被 "重建",它将保持相同的控制器实例。 #### GetxService 这个类就像一个 "GetxController",它共享相同的生命周期("onInit()"、"onReady()"、"onClose()")。 但里面没有 "逻辑"。它只是通知**GetX**的依赖注入系统,这个子类**不能**从内存中删除。 所以这对保持你的 "服务 "总是可以被`Get.find()`获取到并保持运行是超级有用的。比如 `ApiService`,`StorageService`,`CacheService`。 ```dart Future main() async { await initServices(); /// 等待服务初始化. runApp(SomeApp()); } /// 在你运行Flutter应用之前,让你的服务初始化是一个明智之举。 ////因为你可以控制执行流程(也许你需要加载一些主题配置,apiKey,由用户自定义的语言等,所以在运行ApiService之前加载SettingService。 ///所以GetMaterialApp()不需要重建,可以直接取值。 void initServices() async { print('starting services ...'); ///这里是你放get_storage、hive、shared_pref初始化的地方。 ///或者moor连接,或者其他什么异步的东西。 await Get.putAsync(() => DbService().init()); await Get.putAsync(SettingsService()).init(); print('All services started...'); } class DbService extends GetxService { Future init() async { print('$runtimeType delays 2 sec'); await 2.delay(); print('$runtimeType ready!'); return this; } } class SettingsService extends GetxService { void init() async { print('$runtimeType delays 1 sec'); await 1.delay(); print('$runtimeType ready!'); } } ``` 实际删除一个`GetxService`的唯一方法是使用`Get.reset()`,它就像"热重启 "你的应用程序。 所以如果你需要在你的应用程序的生命周期内对一个类实例进行绝对的持久化,请使用`GetxService`。 # 从2.0开始的兼容性变化 1- Rx类型。 | Before | After | | ------- | ---------- | | StringX | `RxString` | | IntX | `RxInt` | | MapX | `RxMap` | | ListX | `RxList` | | NumX | `RxNum` | | DoubleX | `RxDouble` | 现在RxController和GetBuilder已经合并了,你不再需要记住你要用哪个控制器,只要用GetxController就可以了,它可以用于简单的状态管理,也可以用于响应式。 2- 别名路由 之前: ```dart GetMaterialApp( namedRoutes: { '/': GetRoute(page: Home()), } ) ``` 现在: ```dart GetMaterialApp( getPages: [ GetPage(name: '/', page: () => Home()), ] ) ``` 为什么要做这样的改变? 通常情况下,可能需要通过一个参数,或者一个登录令牌来决定显示哪个页面。 将页面插入到一个函数中,大大降低了RAM的消耗,因为自从应用程序启动后,路由将不会在内存中分配。 ```dart GetStorage box = GetStorage(); GetMaterialApp( getPages: [ GetPage(name: '/', page:(){ return box.hasData('token') ? Home() : Login(); }) ] ) ``` # 为什么选择Getx? 1- Flutter更新后,很多时候,你的很多包都会坏掉。有时会发生编译错误,经常出现的错误,至今仍没有答案,开发者需要知道错误的来源,跟踪错误,才会尝试在相应的仓库中开一个问题,并看到其问题的解决。Get集中了开发的主要资源(状态、依赖和路由管理),让你可以在pubspec中添加一个包,然后开始工作。Flutter更新后,你唯一需要做的就是更新Get依赖,然后开始工作。Get还可以解决兼容性问题。有多少次,一个包的版本与另一个包的版本不兼容,因为一个包在一个版本中使用了依赖,而另一个包在另一个版本中使用了依赖?使用Get也不用担心这个问题,因为所有的东西都在同一个包里,是完全兼容的。 2- Flutter很简单,Flutter很不可思议,但是Flutter仍然有一些代码,对于大多数开发者来说可能是不需要的,比如`Navigator.of(context).push (context, builder [...]`,你写了8行代码仅仅只为了调用一个路由。而使用Get只需`Get.to(Home())`就完成了,你将进入下一个页面。动态网页URL是目前Flutter中非常痛苦的一件事,而用GetX则非常简单。在Flutter中管理状态,管理依赖关系也产生了很多讨论,因为pub中的模式有上百种。但是没有什么比在你的变量末尾加一个".obs "更简单的了,把你的widget放在一个Obx里面,就这样,所有对这个变量的更新都会在页面上自动更新。 3-轻松,不用担心性能。Flutter的性能已经很惊人了,但是想象一下,你使用一个状态管理器,和一个定位器来分布你的blocs/stores/controllers/等等类。当你不需要那个依赖的时候,你必须手动调用排除它。但是,你有没有想过简单地使用你的控制器,当它不再被任何人使用时,它会简单地从内存中删除?这就是GetX所做的。有了SmartManagement,所有不被使用的东西都会从内存中删除,除了编程,您不应该担心任何事情。GetX将保证您消耗的是最低限度的必要资源,甚至没有为此创建一个逻辑。 4-实际解耦。你可能听说过 "将界面与业务逻辑分离 "的概念。这并不是BLoC、MVC、MVVM的特例,市面上的其他标准都有这个概念。但是,由于使用了上下文(context),这个概念在Flutter中往往可以得到缓解。 如果你需要上下文来寻找InheritedWidget,你需要在界面中找到它,或者通过参数传递上下文。我特别觉得这种解决方案非常丑陋,要在团队中工作,我们总会对View的业务逻辑产生依赖。Getx与标准的做法不一样,虽然它并没有完全禁止使用StatefulWidgets、InitState等,但它总有类似的方法,可以更干净。控制器是有生命周期的,例如当你需要进行APIREST请求时,你不依赖于界面中的任何东西。你可以使用onInit来启动http调用,当数据到达时,变量将被填充。由于GetX是完全响应式的(真的,在流下工作),一旦项目被填充,所有使用该变量的widgets将在界面中自动更新。这使得具有UI专业知识的人只需要处理widget,除了用户事件(比如点击按钮)之外,不需要向业务逻辑发送任何东西,而处理业务逻辑的人将可以自由地单独创建和测试业务逻辑。 这个库会一直更新和实现新的功能。欢迎提供PR,并为其做出贡献。 # 社区 ## 社区渠道 GetX拥有一个非常活跃且乐于助人的社区。如果你有问题,或者想得到关于这个框架使用的任何帮助,请加入我们的社区频道。这个资源库是提问、申请资源的专用库,欢迎随时加入GetX社区。 | **Slack** | **Discord** | **Telegram** | | :-------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------- | | [![Get on Slack](https://img.shields.io/badge/slack-join-orange.svg)](https://communityinviter.com/apps/getxworkspace/getx) | [![Discord Shield](https://img.shields.io/discord/722900883784073290.svg?logo=discord)](https://discord.com/invite/9Hpt99N) | [![Telegram](https://img.shields.io/badge/chat-on%20Telegram-blue.svg)](https://t.me/joinchat/PhdbJRmsZNpAqSLJL6bH7g) | ## 如何做贡献 _想为项目做贡献吗?我们将自豪地强调你是我们的合作者之一。以下是您可以做出贡献并使Get(和Flutter)变得更好的几点。 - 帮助将readme翻译成其他语言。 - 为readme添加文档(Get的很多功能还没有被记录下来)。 - 撰写文章或制作视频,教大家如何使用Get(它们将被记录到readme和未来的Wiki中)。 - 提供代码/测试的PR。 - 包括新功能。 欢迎任何贡献 ## 文章和视频 - [Dynamic Themes in 3 lines using GetX™](https://medium.com/swlh/flutter-dynamic-themes-in-3-lines-c3b375f292e3) - Tutorial by [Rod Brown](https://github.com/RodBr). - [Complete GetX™ Navigation](https://www.youtube.com/watch?v=RaqPIoJSTtI) - Route management video by Amateur Coder. - [Complete GetX State Management](https://www.youtube.com/watch?v=CNpXbeI_slw) - State management video by Amateur Coder. - [GetX™ Other Features](https://youtu.be/ttQtlX_Q0eU) - utils, storage, bindings and other features video by Amateur Coder. - [Firestore User with GetX | Todo App](https://www.youtube.com/watch?v=BiV0DcXgk58) - Video by Amateur Coder. - [Firebase Auth with GetX | Todo App](https://www.youtube.com/watch?v=-H-T_BSgfOE) - Video by Amateur Coder. - [The Flutter GetX™ Ecosystem ~ State Management](https://medium.com/flutter-community/the-flutter-getx-ecosystem-state-management-881c7235511d) - State management by [Aachman Garg](https://github.com/imaachman). - [GetX, the all-in-one Flutter package](https://www.youtube.com/watch?v=IYQgtu9TM74) - A brief tutorial covering State Management and Navigation by Thad Carnevalli. - [Build a To-do List App from scratch using Flutter and GetX](https://www.youtube.com/watch?v=EcnqFasHf18) - UI + State Management + Storage video by Thad Carnevalli. - [GetX Flutter Firebase Auth Example](https://medium.com/@jeffmcmorris/getx-flutter-firebase-auth-example-b383c1dd1de2) - Article by Jeff McMorris. - [Flutter State Management with GetX – Complete App](https://www.appwithflutter.com/flutter-state-management-with-getx/) - by App With Flutter. - [Flutter Routing with Animation using Get Package](https://www.appwithflutter.com/flutter-routing-using-get-package/) - by App With Flutter. - [Flutter GetX use --- simple charm!](https://github.com/CNAD666/getx_template/blob/main/docs/Use%20of%20Flutter%20GetX---simple%20charm!.md) - CNAD666 - [Flutter GetX使用---简洁的魅力!](https://juejin.cn/post/6924104248275763208) ================================================ FILE: _config.yml ================================================ theme: jekyll-theme-cayman ================================================ FILE: analysis_options.yaml ================================================ # Include option is buggy: include: package:flutter_lints/flutter.yaml analyzer: errors: unintended_html_in_doc_comment: ignore # In case the include issue gets fixed, lines below INCLUDE_FIX # can be removed ================================================ FILE: documentation/ar_EG/dependency_management.md ================================================ # Dependency Management - [Dependency Management](#dependency-management) - [Instancing methods](#instancing-methods) - [Get.put()](#getput) - [Get.lazyPut](#getlazyput) - [Get.putAsync](#getputasync) - [Get.create](#getcreate) - [Using instantiated methods/classes](#using-instantiated-methodsclasses) - [Specifying an alternate instance](#specifying-an-alternate-instance) - [Differences between methods](#differences-between-methods) - [Bindings](#bindings) - [Bindings class](#bindings-class) - [BindingsBuilder](#bindingsbuilder) - [SmartManagement](#smartmanagement) - [How to change](#how-to-change) - [SmartManagement.full](#smartmanagementfull) - [SmartManagement.onlyBuilders](#smartmanagementonlybuilders) - [SmartManagement.keepFactory](#smartmanagementkeepfactory) - [How bindings work under the hood](#how-bindings-work-under-the-hood) - [Notes](#notes) Get has a simple and powerful dependency manager that allows you to retrieve the same class as your Bloc or Controller with just 1 lines of code, no Provider context, no inheritedWidget: ```dart Controller controller = Get.put(Controller()); // Rather Controller controller = Controller(); ``` Instead of instantiating your class within the class you are using, you are instantiating it within the Get instance, which will make it available throughout your App. So you can use your controller (or Bloc class) normally - Note: If you are using Get's State Manager, pay more attention to the [Bindings](#bindings) api, which will make easier to connect your view to your controller. - Note²: Get dependency management is decloupled from other parts of the package, so if for example your app is already using a state manager (any one, it doesn't matter), you don't need to change that, you can use this dependency injection manager with no problems at all ## Instancing methods The methods and it's configurable parameters are: ### Get.put() The most common way of inserting a dependency. Good for the controllers of your views for example. ```dart Get.put(SomeClass()); Get.put(LoginController(), permanent: true); Get.put(ListItemController, tag: "some unique string"); ``` This is all options you can set when using put: ```dart Get.put( // mandatory: the class that you want to get to save, like a controller or anything // note: "S" means that it can be a class of any type S dependency // optional: this is for when you want multiple classess that are of the same type // since you normally get a class by using Get.find(), // you need to use tag to tell which instance you need // must be unique string String tag, // optional: by default, get will dispose instances after they are not used anymore (example, // the controller of a view that is closed), but you might need that the instance // to be kept there throughout the entire app, like an instance of sharedPreferences or something // so you use this // defaults to false bool permanent = false, // optional: allows you after using an abstract class in a test, replace it with another one and follow the test. // defaults to false bool overrideAbstract = false, // optional: allows you to create the dependency using function instead of the dependency itself. // this one is not commonly used InstanceBuilderCallback builder, ) ``` ### Get.lazyPut It is possible to lazyLoad a dependency so that it will be instantiated only when is used. Very useful for computational expensive classes or if you want to instantiate several classes in just one place (like in a Bindings class) and you know you will not gonna use that class at that time. ```dart /// ApiMock will only be called when someone uses Get.find for the first time Get.lazyPut(() => ApiMock()); Get.lazyPut( () { // ... some logic if needed return FirebaseAuth(); }, tag: Math.random().toString(), fenix: true ) Get.lazyPut( () => Controller() ) ``` This is all options you can set when using lazyPut: ```dart Get.lazyPut( // mandatory: a method that will be executed when your class is called for the first time InstanceBuilderCallback builder, // optional: same as Get.put(), it is used for when you want multiple different instance of a same class // must be unique String tag, // optional: It is similar to "permanent", the difference is that the instance is discarded when // is not being used, but when it's use is needed again, Get will recreate the instance // just the same as "SmartManagement.keepFactory" in the bindings api // defaults to false bool fenix = false ) ``` ### Get.putAsync If you want to register an asynchronous instance, you can use `Get.putAsync`: ```dart Get.putAsync(() async { final prefs = await SharedPreferences.getInstance(); await prefs.setInt('counter', 12345); return prefs; }); Get.putAsync( () async => await YourAsyncClass() ) ``` This is all options you can set when using putAsync: ```dart Get.putAsync( // mandatory: an async method that will be executed to instantiate your class AsyncInstanceBuilderCallback builder, // optional: same as Get.put(), it is used for when you want multiple different instance of a same class // must be unique String tag, // optional: same as in Get.put(), used when you need to maintain that instance alive in the entire app // defaults to false bool permanent = false ) ``` ### Get.create This one is tricky. A detailed explanation of what this is and the differences between the other one can be found on [Differences between methods:](#differences-between-methods) section ```dart Get.Create(() => SomeClass()); Get.Create(() => LoginController()); ``` This is all options you can set when using create: ```dart Get.create( // required: a function that returns a class that will be "fabricated" every // time `Get.find()` is called // Example: Get.create(() => YourClass()) FcBuilderFunc builder, // optional: just like Get.put(), but it is used when you need multiple instances // of a of a same class // Useful in case you have a list that each item need it's own controller // needs to be a unique string. Just change from tag to name String name, // optional: just like int`Get.put()`, it is for when you need to keep the // instance alive thoughout the entire app. The difference is in Get.create // permanent is true by default bool permanent = true ``` ## Using instantiated methods/classes Imagine that you have navigated through numerous routes, and you need a data that was left behind in your controller, you would need a state manager combined with the Provider or Get_it, correct? Not with Get. You just need to ask Get to "find" for your controller, you don't need any additional dependencies: ```dart final controller = Get.find(); // OR Controller controller = Get.find(); // Yes, it looks like Magic, Get will find your controller, and will deliver it to you. // You can have 1 million controllers instantiated, Get will always give you the right controller. ``` And then you will be able to recover your controller data that was obtained back there: ```dart Text(controller.textFromApi); ``` Since the returned value is a normal class, you can do anything you want: ```dart int count = Get.find().getInt('counter'); print(count); // out: 12345 ``` To remove an instance of Get: ```dart Get.delete(); //usually you don't need to do this because GetX already delete unused controllers ``` ## Specifying an alternate instance A currently inserted instance can be replaced with a similar or extended class instance by using the `replace` or `lazyReplace` method. This can then be retrieved by using the original class. ```dart abstract class BaseClass {} class ParentClass extends BaseClass {} class ChildClass extends ParentClass { bool isChild = true; } Get.put(ParentClass()); Get.replace(ChildClass()); final instance = Get.find(); print(instance is ChildClass); //true class OtherClass extends BaseClass {} Get.lazyReplace(() => OtherClass()); final instance = Get.find(); print(instance is ChildClass); // false print(instance is OtherClass); //true ``` ## Differences between methods First, let's of the `fenix` of Get.lazyPut and the `permanent` of the other methods. The fundamental difference between `permanent` and `fenix` is how you want to store your instances. Reinforcing: by default, GetX deletes instances when they are not in use. It means that: If screen 1 has controller 1 and screen 2 has controller 2 and you remove the first route from stack, (like if you use `Get.off()` or `Get.offNamed()`) the controller 1 lost its use so it will be erased. But if you want to opt for using `permanent:true`, then the controller will not be lost in this transition - which is very useful for services that you want to keep alive throughout the entire application. `fenix` in the other hand is for services that you don't worry in losing between screen changes, but when you need that service, you expect that it is alive. So basically, it will dispose the unused controller/service/class, but when you need it, it will "recreate from the ashes" a new instance. Proceeding with the differences between methods: - Get.put and Get.putAsync follows the same creation order, with the difference that the second uses an asynchronous method: those two methods creates and initializes the instance. That one is inserted directly in the memory, using the internal method `insert` with the parameters `permanent: false` and `isSingleton: true` (this isSingleton parameter only purpose is to tell if it is to use the dependency on `dependency` or if it is to use the dependency on `FcBuilderFunc`). After that, `Get.find()` is called that immediately initialize the instances that are on memory. - Get.create: As the name implies, it will "create" your dependency! Similar to `Get.put()`, it also calls the internal method `insert` to instancing. But `permanent` became true and `isSingleton` became false (since we are "creating" our dependency, there is no way for it to be a singleton instace, that's why is false). And because it has `permanent: true`, we have by default the benefit of not losing it between screens! Also, `Get.find()` is not called immediately, it wait to be used in the screen to be called. It is created this way to make use of the parameter `permanent`, since then, worth noticing, `Get.create()` was made with the goal of create not shared instances, but don't get disposed, like for example a button in a listView, that you want a unique instance for that list - because of that, Get.create must be used together with GetWidget. - Get.lazyPut: As the name implies, it is a lazy proccess. The instance is create, but it is not called to be used immediately, it remains waiting to be called. Contrary to the other methods, `insert` is not called here. Instead, the instance is inserted in another part of the memory, a part responsible to tell if the instance can be recreated or not, let's call it "factory". If we want to create something to be used later, it will not be mix with things been used right now. And here is where `fenix` magic enters: if you opt to leaving `fenix: false`, and your `smartManagement` are not `keepFactory`, then when using `Get.find` the instance will change the place in the memory from the "factory" to common instance memory area. Right after that, by default it is removed from the "factory". Now, if you opt for `fenix: true`, the instance continues to exist in this dedicated part, even going to the common area, to be called again in the future. ## Bindings One of the great differentials of this package, perhaps, is the possibility of full integration of the routes, state manager and dependency manager. When a route is removed from the Stack, all controllers, variables, and instances of objects related to it are removed from memory. If you are using streams or timers, they will be closed automatically, and you don't have to worry about any of that. In version 2.10 Get completely implemented the Bindings API. Now you no longer need to use the init method. You don't even have to type your controllers if you don't want to. You can start your controllers and services in the appropriate place for that. The Binding class is a class that will decouple dependency injection, while "binding" routes to the state manager and dependency manager. This allows Get to know which screen is being displayed when a particular controller is used and to know where and how to dispose of it. In addition, the Binding class will allow you to have SmartManager configuration control. You can configure the dependencies to be arranged when removing a route from the stack, or when the widget that used it is laid out, or neither. You will have intelligent dependency management working for you, but even so, you can configure it as you wish. ### Bindings class - Create a class and implements Binding ```dart class HomeBinding implements Bindings {} ``` Your IDE will automatically ask you to override the "dependencies" method, and you just need to click on the lamp, override the method, and insert all the classes you are going to use on that route: ```dart class HomeBinding implements Bindings { @override void dependencies() { Get.lazyPut(() => HomeController()); Get.put(()=> Api()); } } class DetailsBinding implements Bindings { @override void dependencies() { Get.lazyPut(() => DetailsController()); } } ``` Now you just need to inform your route, that you will use that binding to make the connection between route manager, dependencies and states. - Using named routes: ```dart getPages: [ GetPage( name: '/', page: () => HomeView(), binding: HomeBinding(), ), GetPage( name: '/details', page: () => DetailsView(), binding: DetailsBinding(), ), ]; ``` - Using normal routes: ```dart Get.to(Home(), binding: HomeBinding()); Get.to(DetailsView(), binding: DetailsBinding()) ``` There, you don't have to worry about memory management of your application anymore, Get will do it for you. The Binding class is called when a route is called, you can create an "initialBinding in your GetMaterialApp to insert all the dependencies that will be created. ```dart GetMaterialApp( initialBinding: SampleBind(), home: Home(), ); ``` ### BindingsBuilder The default way of creating a binding is by creating a class that implements Bindings. But alternatively, you can use `BindingsBuilder` callback so that you can simply use a function to instantiate whatever you desire. Example: ```dart getPages: [ GetPage( name: '/', page: () => HomeView(), binding: BindingsBuilder(() { Get.lazyPut(() => ControllerX()); Get.put(()=> Api()); }), ), GetPage( name: '/details', page: () => DetailsView(), binding: BindingsBuilder(() { Get.lazyPut(() => DetailsController()); }), ), ]; ``` That way you can avoid to create one Binding class for each route making this even simpler. Both ways of doing work perfectly fine and we want you to use what most suit your tastes. ### SmartManagement GetX by default disposes unused controllers from memory, even if a failure occurs and a widget that uses it is not properly disposed. This is what is called the `full` mode of dependency management. But if you want to change the way GetX controls the disposal of classes, you have `SmartManagement` class that you can set different behaviors. #### How to change If you want to change this config (which you usually don't need) this is the way: ```dart void main () { runApp( GetMaterialApp( smartManagement: SmartManagement.onlyBuilders //here home: Home(), ) ) } ``` #### SmartManagement.full It is the default one. Dispose classes that are not being used and were not set to be permanent. In the majority of the cases you will want to keep this config untouched. If you new to GetX then don't change this. #### SmartManagement.onlyBuilders With this option, only controllers started in `init:` or loaded into a Binding with `Get.lazyPut()` will be disposed. If you use `Get.put()` or `Get.putAsync()` or any other approach, SmartManagement will not have permissions to exclude this dependency. With the default behavior, even widgets instantiated with "Get.put" will be removed, unlike SmartManagement.onlyBuilders. #### SmartManagement.keepFactory Just like SmartManagement.full, it will remove it's dependencies when it's not being used anymore. However, it will keep their factory, which means it will recreate the dependency if you need that instance again. ### How bindings work under the hood Bindings creates transitory factories, which are created the moment you click to go to another screen, and will be destroyed as soon as the screen-changing animation happens. This happens so fast that the analyzer will not even be able to register it. When you navigate to this screen again, a new temporary factory will be called, so this is preferable to using SmartManagement.keepFactory, but if you don't want to create Bindings, or want to keep all your dependencies on the same Binding, it will certainly help you. Factories take up little memory, they don't hold instances, but a function with the "shape" of that class you want. This has a very low cost in memory, but since the purpose of this lib is to get the maximum performance possible using the minimum resources, Get removes even the factories by default. Use whichever is most convenient for you. ## Notes - DO NOT USE SmartManagement.keepFactory if you are using multiple Bindings. It was designed to be used without Bindings, or with a single Binding linked in the GetMaterialApp's initialBinding. - Using Bindings is completely optional, if you want you can use `Get.put()` and `Get.find()` on classes that use a given controller without any problem. However, if you work with Services or any other abstraction, I recommend using Bindings for a better organization. ================================================ FILE: documentation/ar_EG/route_management.md ================================================ - [Route Management](#route-management) - [How to use](#how-to-use) - [Navigation without named routes](#navigation-without-named-routes) - [Navigation with named routes](#navigation-with-named-routes) - [Send data to named Routes](#send-data-to-named-routes) - [Dynamic urls links](#dynamic-urls-links) - [Middleware](#middleware) - [Navigation without context](#navigation-without-context) - [SnackBars](#snackbars) - [Dialogs](#dialogs) - [BottomSheets](#bottomsheets) - [Nested Navigation](#nested-navigation) # Route Management This is the complete explanation of all there is to Getx when the matter is route management. ## How to use Add this to your pubspec.yaml file: ```yaml dependencies: get: ``` If you are going to use routes/snackbars/dialogs/bottomsheets without context, or use the high-level Get APIs, you need to simply add "Get" before your MaterialApp, turning it into GetMaterialApp and enjoy! ```dart GetMaterialApp( // Before: MaterialApp( home: MyHome(), ) ``` ## Navigation without named routes To navigate to a new screen: ```dart Get.to(NextScreen()); ``` To close snackbars, dialogs, bottomsheets, or anything you would normally close with Navigator.pop(context); ```dart Get.back(); ``` To go to the next screen and no option to go back to the previous screen (for use in SplashScreens, login screens and etc.) ```dart Get.off(NextScreen()); ``` To go to the next screen and cancel all previous routes (useful in shopping carts, polls, and tests) ```dart Get.offAll(NextScreen()); ``` To navigate to the next route, and receive or update data as soon as you return from it: ```dart var data = await Get.to(Payment()); ``` on other screen, send a data for previous route: ```dart Get.back(result: 'success'); ``` And use it: ex: ```dart if(data == 'success') madeAnything(); ``` Don't you want to learn our syntax? Just change the Navigator (uppercase) to navigator (lowercase), and you will have all the functions of the standard navigation, without having to use context Example: ```dart // Default Flutter navigator Navigator.of(context).push( context, MaterialPageRoute( builder: (BuildContext context) { return HomePage(); }, ), ); // Get using Flutter syntax without needing context navigator.push( MaterialPageRoute( builder: (_) { return HomePage(); }, ), ); // Get syntax (It is much better, but you have the right to disagree) Get.to(HomePage()); ``` ## Navigation with named routes - If you prefer to navigate by namedRoutes, Get also supports this. To navigate to nextScreen ```dart Get.toNamed("/NextScreen"); ``` To navigate and remove the previous screen from the tree. ```dart Get.offNamed("/NextScreen"); ``` To navigate and remove all previous screens from the tree. ```dart Get.offAllNamed("/NextScreen"); ``` To define routes, use GetMaterialApp: ```dart void main() { runApp( GetMaterialApp( initialRoute: '/', getPages: [ GetPage(name: '/', page: () => MyHomePage()), GetPage(name: '/second', page: () => Second()), GetPage( name: '/third', page: () => Third(), transition: Transition.zoom ), ], ) ); } ``` To handle navigation to non-defined routes (404 error), you can define an unknownRoute page in GetMaterialApp. ```dart void main() { runApp( GetMaterialApp( unknownRoute: GetPage(name: '/notfound', page: () => UnknownRoutePage()), initialRoute: '/', getPages: [ GetPage(name: '/', page: () => MyHomePage()), GetPage(name: '/second', page: () => Second()), ], ) ); } ``` ### Send data to named Routes Just send what you want for arguments. Get accepts anything here, whether it is a String, a Map, a List, or even a class instance. ```dart Get.toNamed("/NextScreen", arguments: 'Get is the best'); ``` on your class or controller: ```dart print(Get.arguments); //print out: Get is the best ``` ### Dynamic urls links Get offer advanced dynamic urls just like on the Web. Web developers have probably already wanted this feature on Flutter, and most likely have seen a package promise this feature and deliver a totally different syntax than a URL would have on web, but Get also solves that. ```dart Get.offAllNamed("/NextScreen?device=phone&id=354&name=Enzo"); ``` on your controller/bloc/stateful/stateless class: ```dart print(Get.parameters['id']); // out: 354 print(Get.parameters['name']); // out: Enzo ``` You can also receive NamedParameters with Get easily: ```dart void main() { runApp( GetMaterialApp( initialRoute: '/', getPages: [ GetPage( name: '/', page: () => MyHomePage(), ), GetPage( name: '/profile/', page: () => MyProfile(), ), //You can define a different page for routes with arguments, and another without arguments, but for that you must use the slash '/' on the route that will not receive arguments as above. GetPage( name: '/profile/:user', page: () => UserProfile(), ), GetPage( name: '/third', page: () => Third(), transition: Transition.cupertino ), ], ) ); } ``` Send data on route name ```dart Get.toNamed("/profile/34954"); ``` On second screen take the data by parameter ```dart print(Get.parameters['user']); // out: 34954 ``` or send multiple parameters like this ```dart Get.toNamed("/profile/34954?flag=true&country=italy"); ``` or ```dart var parameters = {"flag": "true","country": "italy",}; Get.toNamed("/profile/34954", parameters: parameters); ``` On second screen take the data by parameters as usually ```dart print(Get.parameters['user']); print(Get.parameters['flag']); print(Get.parameters['country']); // out: 34954 true italy ``` And now, all you need to do is use Get.toNamed() to navigate your named routes, without any context (you can call your routes directly from your BLoC or Controller class), and when your app is compiled to the web, your routes will appear in the url <3 ### Middleware If you want to listen Get events to trigger actions, you can to use routingCallback to it ```dart GetMaterialApp( routingCallback: (routing) { if(routing.current == '/second'){ openAds(); } } ) ``` If you are not using GetMaterialApp, you can use the manual API to attach Middleware observer. ```dart void main() { runApp( MaterialApp( onGenerateRoute: Router.generateRoute, initialRoute: "/", navigatorKey: Get.key, navigatorObservers: [ GetObserver(MiddleWare.observer), // HERE !!! ], ), ); } ``` Create a MiddleWare class ```dart class MiddleWare { static observer(Routing routing) { /// You can listen in addition to the routes, the snackbars, dialogs and bottomsheets on each screen. ///If you need to enter any of these 3 events directly here, ///you must specify that the event is != Than you are trying to do. if (routing.current == '/second' && !routing.isSnackbar) { Get.snackbar("Hi", "You are on second route"); } else if (routing.current =='/third'){ print('last route called'); } } } ``` Now, use Get on your code: ```dart class First extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( leading: IconButton( icon: Icon(Icons.add), onPressed: () { Get.snackbar("hi", "i am a modern snackbar"); }, ), title: Text('First Route'), ), body: Center( child: ElevatedButton( child: Text('Open route'), onPressed: () { Get.toNamed("/second"); }, ), ), ); } } class Second extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( leading: IconButton( icon: Icon(Icons.add), onPressed: () { Get.snackbar("hi", "i am a modern snackbar"); }, ), title: Text('second Route'), ), body: Center( child: ElevatedButton( child: Text('Open route'), onPressed: () { Get.toNamed("/third"); }, ), ), ); } } class Third extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Third Route"), ), body: Center( child: ElevatedButton( onPressed: () { Get.back(); }, child: Text('Go back!'), ), ), ); } } ``` ## Navigation without context ### SnackBars To have a simple SnackBar with Flutter, you must get the context of Scaffold, or you must use a GlobalKey attached to your Scaffold ```dart final snackBar = SnackBar( content: Text('Hi!'), action: SnackBarAction( label: 'I am a old and ugly snackbar :(', onPressed: (){} ), ); // Find the Scaffold in the widget tree and use // it to show a SnackBar. Scaffold.of(context).showSnackBar(snackBar); ``` With Get: ```dart Get.snackbar('Hi', 'i am a modern snackbar'); ``` With Get, all you have to do is call your Get.snackbar from anywhere in your code or customize it however you want! ```dart Get.snackbar( "Hey i'm a Get SnackBar!", // title "It's unbelievable! I'm using SnackBar without context, without boilerplate, without Scaffold, it is something truly amazing!", // message icon: Icon(Icons.alarm), shouldIconPulse: true, onTap:(){}, barBlur: 20, isDismissible: true, duration: Duration(seconds: 3), ); ////////// ALL FEATURES ////////// // Color colorText, // Duration duration, // SnackPosition snackPosition, // Widget titleText, // Widget messageText, // bool instantInit, // Widget icon, // bool shouldIconPulse, // double maxWidth, // EdgeInsets margin, // EdgeInsets padding, // double borderRadius, // Color borderColor, // double borderWidth, // Color backgroundColor, // Color leftBarIndicatorColor, // List boxShadows, // Gradient backgroundGradient, // TextButton mainButton, // OnTap onTap, // bool isDismissible, // bool showProgressIndicator, // AnimationController progressIndicatorController, // Color progressIndicatorBackgroundColor, // Animation progressIndicatorValueColor, // SnackStyle snackStyle, // Curve forwardAnimationCurve, // Curve reverseAnimationCurve, // Duration animationDuration, // double barBlur, // double overlayBlur, // Color overlayColor, // Form userInputForm /////////////////////////////////// ``` If you prefer the traditional snackbar, or want to customize it from scratch, including adding just one line (Get.snackbar makes use of a mandatory title and message), you can use `Get.rawSnackbar();` which provides the RAW API on which Get.snackbar was built. ### Dialogs To open dialog: ```dart Get.dialog(YourDialogWidget()); ``` To open default dialog: ```dart Get.defaultDialog( onConfirm: () => print("Ok"), middleText: "Dialog made in 3 lines of code" ); ``` You can also use Get.generalDialog instead of showGeneralDialog. For all other Flutter dialog widgets, including cupertinos, you can use Get.overlayContext instead of context, and open it anywhere in your code. For widgets that don't use Overlay, you can use Get.context. These two contexts will work in 99% of cases to replace the context of your UI, except for cases where inheritedWidget is used without a navigation context. ### BottomSheets Get.bottomSheet is like showModalBottomSheet, but don't need of context. ```dart Get.bottomSheet( Container( child: Wrap( children: [ ListTile( leading: Icon(Icons.music_note), title: Text('Music'), onTap: () {} ), ListTile( leading: Icon(Icons.videocam), title: Text('Video'), onTap: () {}, ), ], ), ) ); ``` ## Nested Navigation Get made Flutter's nested navigation even easier. You don't need the context, and you will find your navigation stack by Id. - NOTE: Creating parallel navigation stacks can be dangerous. The ideal is not to use NestedNavigators, or to use sparingly. If your project requires it, go ahead, but keep in mind that keeping multiple navigation stacks in memory may not be a good idea for RAM consumption. See how simple it is: ```dart Navigator( key: Get.nestedKey(1), // create a key by index initialRoute: '/', onGenerateRoute: (settings) { if (settings.name == '/') { return GetPageRoute( page: () => Scaffold( appBar: AppBar( title: Text("Main"), ), body: Center( child: TextButton( color: Colors.blue, onPressed: () { Get.toNamed('/second', id:1); // navigate by your nested route by index }, child: Text("Go to second"), ), ), ), ); } else if (settings.name == '/second') { return GetPageRoute( page: () => Center( child: Scaffold( appBar: AppBar( title: Text("Main"), ), body: Center( child: Text("second") ), ), ), ); } } ), ``` ================================================ FILE: documentation/ar_EG/state_management.md ================================================ * [State Management](#state-management) + [Reactive State Manager](#reactive-state-manager) - [Advantages](#advantages) - [Maximum performance:](#maximum-performance) - [Declaring a reactive variable](#declaring-a-reactive-variable) - [Having a reactive state, is easy.](#having-a-reactive-state-is-easy) - [Using the values in the view](#using-the-values-in-the-view) - [Conditions to rebuild](#conditions-to-rebuild) - [Where .obs can be used](#where-obs-can-be-used) - [Note about Lists](#note-about-lists) - [Why i have to use .value](#why-i-have-to-use-value) - [Obx()](#obx) - [Workers](#workers) + [Simple State Manager](#simple-state-manager) - [Advantages](#advantages-1) - [Usage](#usage) - [How it handles controllers](#how-it-handles-controllers) - [You won't need StatefulWidgets anymore](#you-wont-need-statefulwidgets-anymore) - [Why it exists](#why-it-exists) - [Other ways of using it](#other-ways-of-using-it) - [Unique IDs](#unique-ids) + [Mixing the two state managers](#mixing-the-two-state-managers) + [GetBuilder vs GetX vs Obx vs MixinBuilder](#getbuilder-vs-getx-vs-obx-vs-mixinbuilder) # State Management GetX does not use Streams or ChangeNotifier like other state managers. Why? In addition to building applications for android, iOS, web, linux, macos and linux, with GetX you can build server applications with the same syntax as Flutter/GetX. In order to improve response time and reduce RAM consumption, we created GetValue and GetStream, which are low latency solutions that deliver a lot of performance, at a low operating cost. We use this base to build all of our resources, including state management. * _Complexity_: Some state managers are complex and have a lot of boilerplate. With GetX you don't have to define a class for each event, the code is highly clean and clear, and you do a lot more by writing less. Many people have given up on Flutter because of this topic, and they now finally have a stupidly simple solution for managing states. * _No code generators_: You spend half your development time writing your application logic. Some state managers rely on code generators to have minimally readable code. Changing a variable and having to run build_runner can be unproductive, and often the waiting time after a flutter clean will be long, and you will have to drink a lot of coffee. With GetX everything is reactive, and nothing depends on code generators, increasing your productivity in all aspects of your development. * _It does not depend on context_: You probably already needed to send the context of your view to a controller, making the View's coupling with your business logic high. You have probably had to use a dependency for a place that has no context, and had to pass the context through various classes and functions. This just doesn't exist with GetX. You have access to your controllers from within your controllers without any context. You don't need to send the context by parameter for literally nothing. * _Granular control_: most state managers are based on ChangeNotifier. ChangeNotifier will notify all widgets that depend on it when notifyListeners is called. If you have 40 widgets on one screen, which have a variable of your ChangeNotifier class, when you update one, all of them will be rebuilt. With GetX, even nested widgets are respected. If you have Obx watching your ListView, and another watching a checkbox inside the ListView, when changing the CheckBox value, only it will be updated, when changing the List value, only the ListView will be updated. * _It only reconstructs if its variable REALLY changes_: GetX has flow control, that means if you display a Text with 'Paola', if you change the observable variable to 'Paola' again, the widget will not be reconstructed. That's because GetX knows that 'Paola' is already being displayed in Text, and will not do unnecessary reconstructions. Most (if not all) current state managers will rebuild on the screen. ## Reactive State Manager Reactive programming can alienate many people because it is said to be complicated. GetX turns reactive programming into something quite simple: * You won't need to create StreamControllers. * You won't need to create a StreamBuilder for each variable * You will not need to create a class for each state. * You will not need to create a get for an initial value. Reactive programming with Get is as easy as using setState. Let's imagine that you have a name variable and want that every time you change it, all widgets that use it are automatically changed. This is your count variable: ``` dart var name = 'Jonatas Borges'; ``` To make it observable, you just need to add ".obs" to the end of it: ``` dart var name = 'Jonatas Borges'.obs; ``` That's all. It's *that* simple. From now on, we might refer to this reactive-".obs"(ervables) variables as _Rx_. What did we do under the hood? We created a `Stream` of `String` s, assigned the initial value `"Jonatas Borges"` , we notified all widgets that use `"Jonatas Borges"` that they now "belong" to this variable, and when the _Rx_ value changes, they will have to change as well. This is the **magic of GetX**, thanks to Dart's capabilities. But, as we know, a `Widget` can only be changed if it is inside a function, because static classes do not have the power to "auto-change". You will need to create a `StreamBuilder` , subscribe to this variable to listen for changes, and create a "cascade" of nested `StreamBuilder` if you want to change several variables in the same scope, right? No, you don't need a `StreamBuilder` , but you are right about static classes. Well, in the view, we usually have a lot of boilerplate when we want to change a specific Widget, that's the Flutter way. With **GetX** you can also forget about this boilerplate code. `StreamBuilder( … )` ? `initialValue: …` ? `builder: …` ? Nope, you just need to place this variable inside an `Obx()` Widget. ``` dart Obx (() => Text (controller.name)); ``` _What do you need to memorize?_ Only `Obx(() =>` . You are just passing that Widget through an arrow-function into an `Obx()` (the "Observer" of the _Rx_). `Obx` is pretty smart, and will only change if the value of `controller.name` changes. If `name` is `"John"` , and you change it to `"John"` ( `name.value = "John"` ), as it's the same `value` as before, nothing will change on the screen, and `Obx` , to save resources, will simply ignore the new value and not rebuild the Widget. **Isn't that amazing?** > So, what if I have 5 _Rx_ (observable) variables within an `Obx` ? It will just update when **any** of them changes. > And if I have 30 variables in a class, when I update one, will it update **all** the variables that are in that class? Nope, just the **specific Widget** that uses that _Rx_ variable. So, **GetX** only updates the screen, when the _Rx_ variable changes it's value. ``` final isOpen = false.obs; // NOTHING will happen... same value. void onButtonTap() => isOpen.value=false; ``` ### Advantages **GetX()** helps you when you need **granular** control over what's being updated. If you do not need `unique IDs` , because all your variables will be modified when you perform an action, then use `GetBuilder` , because it's a Simple State Updater (in blocks, like `setState()` ), made in just a few lines of code. It was made simple, to have the least CPU impact, and just to fulfill a single purpose (a _State_ rebuild) and spend the minimum resources possible. If you need a **powerful** State Manager, you can't go wrong with **GetX**. It doesn't work with variables, but __flows__, everything in it are `Streams` under the hood. You can use _rxDart_ in conjunction with it, because everything are `Streams`, you can listen to the `event` of each "_Rx_ variable", because everything in it are `Streams`. It is literally a _BLoC_ approach, easier than _MobX_, and without code generators or decorations. You can turn **anything** into an _"Observable"_ with just a `.obs` . ### Maximum performance: In addition to having a smart algorithm for minimal rebuilds, **GetX** uses comparators to make sure the State has changed. If you experience any errors in your app, and send a duplicate change of State, **GetX** will ensure it will not crash. With **GetX** the State only changes if the `value` change. That's the main difference between **GetX**, and using _ `computed` from MobX_. When joining two __observables__, and one changes; the listener of that _observable_ will change as well. With **GetX**, if you join two variables, `GetX()` (similar to `Observer()` ) will only rebuild if it implies a real change of State. ### Declaring a reactive variable You have 3 ways to turn a variable into an "observable". 1 - The first is using **`Rx{Type}`**. ``` dart // initial value is recommended, but not mandatory final name = RxString(''); final isLogged = RxBool(false); final count = RxInt(0); final balance = RxDouble(0.0); final items = RxList([]); final myMap = RxMap({}); ``` 2 - The second is to use **`Rx`** and use Darts Generics, `Rx` ``` dart final name = Rx(''); final isLogged = Rx(false); final count = Rx(0); final balance = Rx(0.0); final number = Rx(0); final items = Rx>([]); final myMap = Rx>({}); // Custom classes - it can be any class, literally final user = Rx(); ``` 3 - The third, more practical, easier and preferred approach, just add **`.obs`** as a property of your `value` : ``` dart final name = ''.obs; final isLogged = false.obs; final count = 0.obs; final balance = 0.0.obs; final number = 0.obs; final items = [].obs; final myMap = {}.obs; // Custom classes - it can be any class, literally final user = User().obs; ``` ##### Having a reactive state, is easy. As we know, _Dart_ is now heading towards _null safety_. To be prepared, from now on, you should always start your _Rx_ variables with an **initial value**. > Transforming a variable into an _observable_ + _initial value_ with **GetX** is the simplest, and most practical approach. You will literally add a " `.obs` " to the end of your variable, and **that’s it**, you’ve made it observable, and its `.value` , well, will be the _initial value_). ### Using the values in the view ``` dart // controller file final count1 = 0.obs; final count2 = 0.obs; int get sum => count1.value + count2.value; ``` ``` dart // view file GetX( builder: (controller) { print("count 1 rebuild"); return Text('${controller.count1.value}'); }, ), GetX( builder: (controller) { print("count 2 rebuild"); return Text('${controller.count2.value}'); }, ), GetX( builder: (controller) { print("count 3 rebuild"); return Text('${controller.sum}'); }, ), ``` If we increment `count1.value++` , it will print: * `count 1 rebuild` * `count 3 rebuild` because `count1` has a value of `1` , and `1 + 0 = 1` , changing the `sum` getter value. If we change `count2.value++` , it will print: * `count 2 rebuild` * `count 3 rebuild` because `count2.value` changed, and the result of the `sum` is now `2` . * NOTE: By default, the very first event will rebuild the widget, even if it is the same `value`. This behavior exists due to Boolean variables. Imagine you did this: ``` dart var isLogged = false.obs; ``` And then, you checked if a user is "logged in" to trigger an event in `ever` . ``` dart @override onInit() async { ever(isLogged, fireRoute); isLogged.value = await Preferences.hasToken(); } fireRoute(logged) { if (logged) { Get.off(Home()); } else { Get.off(Login()); } } ``` if `hasToken` was `false` , there would be no change to `isLogged` , so `ever()` would never be called. To avoid this type of behavior, the first change to an _observable_ will always trigger an event, even if it contains the same `.value` . You can remove this behavior if you want, using: `isLogged.firstRebuild = false;` ### Conditions to rebuild In addition, Get provides refined state control. You can condition an event (such as adding an object to a list), on a certain condition. ``` dart // First parameter: condition, must return true or false. // Second parameter: the new value to apply if the condition is true. list.addIf(item < limit, item); ``` Without decorations, without a code generator, without complications :smile: Do you know Flutter's counter app? Your Controller class might look like this: ``` dart class CountController extends GetxController { final count = 0.obs; } ``` With a simple: ``` dart controller.count.value++ ``` You could update the counter variable in your UI, regardless of where it is stored. ### Where .obs can be used You can transform anything on obs. Here are two ways of doing it: * You can convert your class values to obs ``` dart class RxUser { final name = "Camila".obs; final age = 18.obs; } ``` * or you can convert the entire class to be an observable ``` dart class User { User({String name, int age}); var name; var age; } // when instantianting: final user = User(name: "Camila", age: 18).obs; ``` ### Note about Lists Lists are completely observable as are the objects within it. That way, if you add a value to a list, it will automatically rebuild the widgets that use it. You also don't need to use ".value" with lists, the amazing dart api allowed us to remove that. Unfortunaly primitive types like String and int cannot be extended, making the use of .value mandatory, but that won't be a problem if you work with gets and setters for these. ``` dart // On the controller final String title = 'User Info:'.obs final list = List().obs; // on the view Text(controller.title.value), // String need to have .value in front of it ListView.builder ( itemCount: controller.list.length // lists don't need it ) ``` When you are making your own classes observable, there is a different way to update them: ``` dart // on the model file // we are going to make the entire class observable instead of each attribute class User() { User({this.name = '', this.age = 0}); String name; int age; } // on the controller file final user = User().obs; // when you need to update the user variable: user.update( (user) { // this parameter is the class itself that you want to update user.name = 'Jonny'; user.age = 18; }); // an alternative way of update the user variable: user(User(name: 'João', age: 35)); // on view: Obx(()=> Text("Name ${user.value.name}: Age: ${user.value.age}")) // you can also access the model values without the .value: user().name; // notice that is the user variable, not the class (variable has lowercase u) ``` You don't have to work with sets if you don't want to. you can use the "assign 'and" assignAll "api. The "assign" api will clear your list, and add a single object that you want to start there. The "assignAll" api will clear the existing list and add any iterable objects that you inject into it. ### Why i have to use .value We could remove the obligation to use 'value' to `String` and `int` with a simple decoration and code generator, but the purpose of this library is precisely avoid external dependencies. We want to offer an environment ready for programming, involving the essentials (management of routes, dependencies and states), in a simple, lightweight and performant way, without a need of an external package. You can literally add 3 letters to your pubspec (get) and a colon and start programming. All solutions included by default, from route management to state management, aim at ease, productivity and performance. The total weight of this library is less than that of a single state manager, even though it is a complete solution, and that is what you must understand. If you are bothered by `.value` , and like a code generator, MobX is a great alternative, and you can use it in conjunction with Get. For those who want to add a single dependency in pubspec and start programming without worrying about the version of a package being incompatible with another, or if the error of a state update is coming from the state manager or dependency, or still, do not want to worrying about the availability of controllers, whether literally "just programming", get is just perfect. If you have no problem with the MobX code generator, or have no problem with the BLoC boilerplate, you can simply use Get for routes, and forget that it has state manager. Get SEM and RSM were born out of necessity, my company had a project with more than 90 controllers, and the code generator simply took more than 30 minutes to complete its tasks after a Flutter Clean on a reasonably good machine, if your project it has 5, 10, 15 controllers, any state manager will supply you well. If you have an absurdly large project, and code generator is a problem for you, you have been awarded this solution. Obviously, if someone wants to contribute to the project and create a code generator, or something similar, I will link in this readme as an alternative, my need is not the need for all devs, but for now I say, there are good solutions that already do that, like MobX. ### Obx() Typing in Get using Bindings is unnecessary. you can use the Obx widget instead of GetX which only receives the anonymous function that creates a widget. Obviously, if you don't use a type, you will need to have an instance of your controller to use the variables, or use `Get.find()` .value or Controller.to.value to retrieve the value. ### Workers Workers will assist you, triggering specific callbacks when an event occurs. ``` dart /// Called every time `count1` changes. ever(count1, (_) => print("$_ has been changed")); /// Called only first time the variable $_ is changed once(count1, (_) => print("$_ was changed once")); /// Anti DDos - Called every time the user stops typing for 1 second, for example. debounce(count1, (_) => print("debouce$_"), time: Duration(seconds: 1)); /// Ignore all changes within 1 second. interval(count1, (_) => print("interval $_"), time: Duration(seconds: 1)); ``` All workers (except `debounce` ) have a `condition` named parameter, which can be a `bool` or a callback that returns a `bool` . This `condition` defines when the `callback` function executes. All workers returns a `Worker` instance, that you can use to cancel ( via `dispose()` ) the worker. * **`ever`** is called every time the _Rx_ variable emits a new value. * **`everAll`** Much like `ever` , but it takes a `List` of _Rx_ values Called every time its variable is changed. That's it. * **`once`** 'once' is called only the first time the variable has been changed. * **`debounce`** 'debounce' is very useful in search functions, where you only want the API to be called when the user finishes typing. If the user types "Jonny", you will have 5 searches in the APIs, by the letter J, o, n, n, and y. With Get this does not happen, because you will have a "debounce" Worker that will only be triggered at the end of typing. * **`interval`** 'interval' is different from the debouce. debouce if the user makes 1000 changes to a variable within 1 second, he will send only the last one after the stipulated timer (the default is 800 milliseconds). Interval will instead ignore all user actions for the stipulated period. If you send events for 1 minute, 1000 per second, debounce will only send you the last one, when the user stops strafing events. interval will deliver events every second, and if set to 3 seconds, it will deliver 20 events that minute. This is recommended to avoid abuse, in functions where the user can quickly click on something and get some advantage (imagine that the user can earn coins by clicking on something, if he clicked 300 times in the same minute, he would have 300 coins, using interval, you you can set a time frame for 3 seconds, and even then clicking 300 or a thousand times, the maximum he would get in 1 minute would be 20 coins, clicking 300 or 1 million times). The debounce is suitable for anti-DDos, for functions like search where each change to onChange would cause a query to your api. Debounce will wait for the user to stop typing the name, to make the request. If it were used in the coin scenario mentioned above, the user would only win 1 coin, because it is only executed, when the user "pauses" for the established time. * NOTE: Workers should always be used when starting a Controller or Class, so it should always be on onInit (recommended), Class constructor, or the initState of a StatefulWidget (this practice is not recommended in most cases, but it shouldn't have any side effects). ## Simple State Manager Get has a state manager that is extremely light and easy, which does not use ChangeNotifier, will meet the need especially for those new to Flutter, and will not cause problems for large applications. GetBuilder is aimed precisely at multiple state control. Imagine that you added 30 products to a cart, you click delete one, at the same time that the list is updated, the price is updated and the badge in the shopping cart is updated to a smaller number. This type of approach makes GetBuilder killer, because it groups states and changes them all at once without any "computational logic" for that. GetBuilder was created with this type of situation in mind, since for ephemeral change of state, you can use setState and you would not need a state manager for this. That way, if you want an individual controller, you can assign IDs for that, or use GetX. This is up to you, remembering that the more "individual" widgets you have, the more the performance of GetX will stand out, while the performance of GetBuilder should be superior, when there is multiple change of state. ### Advantages 1. Update only the required widgets. 2. Does not use changeNotifier, it is the state manager that uses less memory (close to 0mb). 3. Forget StatefulWidget! With Get you will never need it. With the other state managers, you will probably have to use a StatefulWidget to get the instance of your Provider, BLoC, MobX Controller, etc. But have you ever stopped to think that your appBar, your scaffold, and most of the widgets that are in your class are stateless? So why save the state of an entire class, if you can only save the state of the Widget that is stateful? Get solves that, too. Create a Stateless class, make everything stateless. If you need to update a single component, wrap it with GetBuilder, and its state will be maintained. 4. Organize your project for real! Controllers must not be in your UI, place your TextEditController, or any controller you use within your Controller class. 5. Do you need to trigger an event to update a widget as soon as it is rendered? GetBuilder has the property "initState", just like StatefulWidget, and you can call events from your controller, directly from it, no more events being placed in your initState. 6. Do you need to trigger an action like closing streams, timers and etc? GetBuilder also has the dispose property, where you can call events as soon as that widget is destroyed. 7. Use streams only if necessary. You can use your StreamControllers inside your controller normally, and use StreamBuilder also normally, but remember, a stream reasonably consumes memory, reactive programming is beautiful, but you shouldn't abuse it. 30 streams open simultaneously can be worse than changeNotifier (and changeNotifier is very bad). 8. Update widgets without spending ram for that. Get stores only the GetBuilder creator ID, and updates that GetBuilder when necessary. The memory consumption of the get ID storage in memory is very low even for thousands of GetBuilders. When you create a new GetBuilder, you are actually sharing the state of GetBuilder that has a creator ID. A new state is not created for each GetBuilder, which saves A LOT OF ram for large applications. Basically your application will be entirely Stateless, and the few Widgets that will be Stateful (within GetBuilder) will have a single state, and therefore updating one will update them all. The state is just one. 9. Get is omniscient and in most cases it knows exactly the time to take a controller out of memory. You should not worry about when to dispose of a controller, Get knows the best time to do this. ### Usage ``` dart // Create controller class and extends GetxController class Controller extends GetxController { int counter = 0; void increment() { counter++; update(); // use update() to update counter variable on UI when increment be called } } // On your Stateless/Stateful class, use GetBuilder to update Text when increment be called GetBuilder( init: Controller(), // INIT IT ONLY THE FIRST TIME builder: (_) => Text( '${_.counter}', ), ) //Initialize your controller only the first time. The second time you are using ReBuilder for the same controller, do not use it again. Your controller will be automatically removed from memory as soon as the widget that marked it as 'init' is deployed. You don't have to worry about that, Get will do it automatically, just make sure you don't start the same controller twice. ``` **Done!** * You have already learned how to manage states with Get. * Note: You may want a larger organization, and not use the init property. For that, you can create a class and extends Binding class, and within it mention the controllers that will be created within that route. Controllers will not be created at that time, on the contrary, this is just a statement, so that the first time you use a Controller, Get will know where to look. Get will remain lazyLoad, and will continue to dispose Controllers when they are no longer needed. See the pub.dev example to see how it works. If you navigate many routes and need data that was in your previously used controller, you just need to use GetBuilder Again (with no init): ``` dart class OtherClass extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( body: Center( child: GetBuilder( builder: (s) => Text('${s.counter}'), ), ), ); } ``` If you need to use your controller in many other places, and outside of GetBuilder, just create a get in your controller and have it easily. (or use `Get.find()` ) ``` dart class Controller extends GetxController { /// You do not need that. I recommend using it just for ease of syntax. /// with static method: Controller.to.increment(); /// with no static method: Get.find().increment(); /// There is no difference in performance, nor any side effect of using either syntax. Only one does not need the type, and the other the IDE will autocomplete it. static Controller get to => Get.find(); // add this line int counter = 0; void increment() { counter++; update(); } } ``` And then you can access your controller directly, that way: ``` dart FloatingActionButton( onPressed: () { Controller.to.increment(), } // This is incredibly simple! child: Text("${Controller.to.counter}"), ), ``` When you press FloatingActionButton, all widgets that are listening to the 'counter' variable will be updated automatically. ### How it handles controllers Let's say we have this: `Class a => Class B (has controller X) => Class C (has controller X)` In class A the controller is not yet in memory, because you have not used it yet (Get is lazyLoad). In class B you used the controller, and it entered memory. In class C you used the same controller as in class B, Get will share the state of controller B with controller C, and the same controller is still in memory. If you close screen C and screen B, Get will automatically take controller X out of memory and free up resources, because Class a is not using the controller. If you navigate to B again, controller X will enter memory again, if instead of going to class C, you return to class A again, Get will take the controller out of memory in the same way. If class C didn't use the controller, and you took class B out of memory, no class would be using controller X and likewise it would be disposed of. The only exception that can mess with Get, is if you remove B from the route unexpectedly, and try to use the controller in C. In this case, the creator ID of the controller that was in B was deleted, and Get was programmed to remove it from memory every controller that has no creator ID. If you intend to do this, add the "autoRemove: false" flag to class B's GetBuilder and use adoptID = true; in class C's GetBuilder. ### You won't need StatefulWidgets anymore Using StatefulWidgets means storing the state of entire screens unnecessarily, even because if you need to minimally rebuild a widget, you will embed it in a Consumer/Observer/BlocProvider/GetBuilder/GetX/Obx, which will be another StatefulWidget. The StatefulWidget class is a class larger than StatelessWidget, which will allocate more RAM, and this may not make a significant difference between one or two classes, but it will most certainly do when you have 100 of them! Unless you need to use a mixin, like TickerProviderStateMixin, it will be totally unnecessary to use a StatefulWidget with Get. You can call all methods of a StatefulWidget directly from a GetBuilder. If you need to call initState() or dispose() method for example, you can call them directly; ``` dart GetBuilder( initState: (_) => Controller.to.fetchApi(), dispose: (_) => Controller.to.closeStreams(), builder: (s) => Text('${s.username}'), ), ``` A much better approach than this is to use the onInit() and onClose() method directly from your controller. ``` dart @override void onInit() { fetchApi(); super.onInit(); } ``` * NOTE: If you want to start a method at the moment the controller is called for the first time, you DON'T NEED to use constructors for this, in fact, using a performance-oriented package like Get, this borders on bad practice, because it deviates from the logic in which the controllers are created or allocated (if you create an instance of this controller, the constructor will be called immediately, you will be populating a controller before it is even used, you are allocating memory without it being in use, this definitely hurts the principles of this library). The onInit() methods; and onClose(); were created for this, they will be called when the Controller is created, or used for the first time, depending on whether you are using Get.lazyPut or not. If you want, for example, to make a call to your API to populate data, you can forget about the old-fashioned method of initState/dispose, just start your call to the api in onInit, and if you need to execute any command like closing streams, use the onClose() for that. ### Why it exists The purpose of this package is precisely to give you a complete solution for navigation of routes, management of dependencies and states, using the least possible dependencies, with a high degree of decoupling. Get engages all high and low level Flutter APIs within itself, to ensure that you work with the least possible coupling. We centralize everything in a single package, to ensure that you don't have any kind of coupling in your project. That way, you can put only widgets in your view, and leave the part of your team that works with the business logic free, to work with the business logic without depending on any element of the View. This provides a much cleaner working environment, so that part of your team works only with widgets, without worrying about sending data to your controller, and part of your team works only with the business logic in its breadth, without depending on no element of the view. So to simplify this: You don't need to call methods in initState and send them by parameter to your controller, nor use your controller constructor for that, you have the onInit() method that is called at the right time for you to start your services. You do not need to call the device, you have the onClose() method that will be called at the exact moment when your controller is no longer needed and will be removed from memory. That way, leave views for widgets only, refrain from any kind of business logic from it. Do not call a dispose method inside GetxController, it will not do anything, remember that the controller is not a Widget, you should not "dispose" it, and it will be automatically and intelligently removed from memory by Get. If you used any stream on it and want to close it, just insert it into the close method. Example: ``` dart class Controller extends GetxController { StreamController user = StreamController(); StreamController name = StreamController(); /// close stream = onClose method, not dispose. @override void onClose() { user.close(); name.close(); super.onClose(); } } ``` Controller life cycle: * onInit() where it is created. * onClose() where it is closed to make any changes in preparation for the delete method * deleted: you do not have access to this API because it is literally removing the controller from memory. It is literally deleted, without leaving any trace. ### Other ways of using it You can use Controller instance directly on GetBuilder value: ``` dart GetBuilder( init: Controller(), builder: (value) => Text( '${value.counter}', //here ), ), ``` You may also need an instance of your controller outside of your GetBuilder, and you can use these approaches to achieve this: ``` dart class Controller extends GetxController { static Controller get to => Get.find(); [...] } // on you view: GetBuilder( init: Controller(), // use it only first time on each controller builder: (_) => Text( '${Controller.to.counter}', //here ) ), ``` or ``` dart class Controller extends GetxController { // static Controller get to => Get.find(); // with no static get [...] } // on stateful/stateless class GetBuilder( init: Controller(), // use it only first time on each controller builder: (_) => Text( '${Get.find().counter}', //here ), ), ``` * You can use "non-canonical" approaches to do this. If you are using some other dependency manager, like get_it, modular, etc., and just want to deliver the controller instance, you can do this: ``` dart Controller controller = Controller(); [...] GetBuilder( init: controller, //here builder: (_) => Text( '${controller.counter}', // here ), ), ``` ### Unique IDs If you want to refine a widget's update control with GetBuilder, you can assign them unique IDs: ``` dart GetBuilder( id: 'text' init: Controller(), // use it only first time on each controller builder: (_) => Text( '${Get.find().counter}', //here ), ), ``` And update it this form: ``` dart update(['text']); ``` You can also impose conditions for the update: ``` dart update(['text'], counter < 10); ``` GetX does this automatically and only reconstructs the widget that uses the exact variable that was changed, if you change a variable to the same as the previous one and that does not imply a change of state , GetX will not rebuild the widget to save memory and CPU cycles (3 is being displayed on the screen, and you change the variable to 3 again. In most state managers, this will cause a new rebuild, but with GetX the widget will only is rebuilt again, if in fact his state has changed). ## Mixing the two state managers Some people opened a feature request, as they wanted to use only one type of reactive variable, and the other mechanics, and needed to insert an Obx into a GetBuilder for this. Thinking about it MixinBuilder was created. It allows both reactive changes by changing ".obs" variables, and mechanical updates via update(). However, of the 4 widgets he is the one that consumes the most resources, since in addition to having a Subscription to receive change events from his children, he subscribes to the update method of his controller. Extending GetxController is important, as they have life cycles, and can "start" and "end" events in their onInit() and onClose() methods. You can use any class for this, but I strongly recommend you use the GetxController class to place your variables, whether they are observable or not. ## StateMixin Another way to handle your `UI` state is use the `StateMixin` . To implement it, use the `with` to add the `StateMixin` to your controller which allows a T model. ``` dart class Controller extends GetController with StateMixin{} ``` The `change()` method change the State whenever we want. Just pass the data and the status in this way: ```dart change(data, status: RxStatus.success()); ``` RxStatus allow these status: ``` dart RxStatus.loading(); RxStatus.success(); RxStatus.empty(); RxStatus.error('message'); ``` To represent it in the UI, use: ```dart class OtherClass extends GetView { @override Widget build(BuildContext context) { return Scaffold( body: controller.obx( (state)=>Text(state.name), // here you can put your custom loading indicator, but // by default would be Center(child:CircularProgressIndicator()) onLoading: CustomLoadingIndicator(), onEmpty: Text('No data found'), // here also you can set your own error widget, but by // default will be an Center(child:Text(error)) onError: (error)=>Text(error), ), ); } ``` ## GetBuilder vs GetX vs Obx vs MixinBuilder In a decade working with programming I was able to learn some valuable lessons. My first contact with reactive programming was so "wow, this is incredible" and in fact reactive programming is incredible. However, it is not suitable for all situations. Often all you need is to change the state of 2 or 3 widgets at the same time, or an ephemeral change of state, in which case reactive programming is not bad, but it is not appropriate. Reactive programming has a higher RAM consumption that can be compensated for by the individual workflow, which will ensure that only one widget is rebuilt and when necessary, but creating a list with 80 objects, each with several streams is not a good one idea. Open the dart inspect and check how much a StreamBuilder consumes, and you'll understand what I'm trying to tell you. With that in mind, I created the simple state manager. It is simple, and that is exactly what you should demand from it: updating state in blocks in a simple way, and in the most economical way. GetBuilder is very economical in RAM, and there is hardly a more economical approach than him (at least I can't imagine one, if it exists, please let us know). However, GetBuilder is still a mechanical state manager, you need to call update() just like you would need to call Provider's notifyListeners(). There are other situations where reactive programming is really interesting, and not working with it is the same as reinventing the wheel. With that in mind, GetX was created to provide everything that is most modern and advanced in a state manager. It updates only what is necessary and when necessary, if you have an error and send 300 state changes simultaneously, GetX will filter and update the screen only if the state actually changes. GetX is still more economical than any other reactive state manager, but it consumes a little more RAM than GetBuilder. Thinking about it and aiming to maximize the consumption of resources that Obx was created. Unlike GetX and GetBuilder, you will not be able to initialize a controller inside an Obx, it is just a Widget with a StreamSubscription that receives change events from your children, that's all. It is more economical than GetX, but loses to GetBuilder, which was to be expected, since it is reactive, and GetBuilder has the most simplistic approach that exists, of storing a widget's hashcode and its StateSetter. With Obx you don't need to write your controller type, and you can hear the change from multiple different controllers, but it needs to be initialized before, either using the example approach at the beginning of this readme, or using the Bindings class. ================================================ FILE: documentation/en_US/dependency_management.md ================================================ # Dependency Management - [Dependency Management](#dependency-management) - [Instancing methods](#instancing-methods) - [Get.put()](#getput) - [Get.lazyPut](#getlazyput) - [Get.putAsync](#getputasync) - [Get.create](#getcreate) - [Using instantiated methods/classes](#using-instantiated-methodsclasses) - [Specifying an alternate instance](#specifying-an-alternate-instance) - [Differences between methods](#differences-between-methods) - [Bindings](#bindings) - [Bindings class](#bindings-class) - [BindingsBuilder](#bindingsbuilder) - [SmartManagement](#smartmanagement) - [How to change](#how-to-change) - [SmartManagement.full](#smartmanagementfull) - [SmartManagement.onlyBuilder](#smartmanagementonlybuilder) - [SmartManagement.keepFactory](#smartmanagementkeepfactory) - [How bindings work under the hood](#how-bindings-work-under-the-hood) - [Notes](#notes) Get has a simple and powerful dependency manager that allows you to retrieve the same class as your Bloc or Controller with just 1 lines of code, no Provider context, no inheritedWidget: ```dart Controller controller = Get.put(Controller()); // Rather Controller controller = Controller(); ``` Instead of instantiating your class within the class you are using, you are instantiating it within the Get instance, which will make it available throughout your App. So you can use your controller (or Bloc class) normally - Note: If you are using Get's State Manager, pay more attention to the [Bindings](#bindings) api, which will make easier to connect your view to your controller. - Note²: Get dependency management is decloupled from other parts of the package, so if for example your app is already using a state manager (any one, it doesn't matter), you don't need to change that, you can use this dependency injection manager with no problems at all ## Instancing methods The methods and it's configurable parameters are: ### Get.put() The most common way of inserting a dependency. Good for the controllers of your views for example. ```dart Get.put(SomeClass()); Get.put(LoginController(), permanent: true); Get.put(ListItemController, tag: "some unique string"); ``` This is all options you can set when using put: ```dart Get.put( // mandatory: the class that you want to get to save, like a controller or anything // note: "S" means that it can be a class of any type S dependency // optional: this is for when you want multiple classess that are of the same type // since you normally get a class by using Get.find(), // you need to use tag to tell which instance you need // must be unique string String tag, // optional: by default, get will dispose instances after they are not used anymore (example, // the controller of a view that is closed), but you might need that the instance // to be kept there throughout the entire app, like an instance of sharedPreferences or something // so you use this // defaults to false bool permanent = false, // optional: allows you after using an abstract class in a test, replace it with another one and follow the test. // defaults to false bool overrideAbstract = false, // optional: allows you to create the dependency using function instead of the dependency itself. // this one is not commonly used InstanceBuilderCallback builder, ) ``` ### Get.lazyPut It is possible to lazyLoad a dependency so that it will be instantiated only when is used. Very useful for computational expensive classes or if you want to instantiate several classes in just one place (like in a Bindings class) and you know you will not gonna use that class at that time. ```dart /// ApiMock will only be called when someone uses Get.find for the first time Get.lazyPut(() => ApiMock()); Get.lazyPut( () { // ... some logic if needed return FirebaseAuth(); }, tag: Math.random().toString(), fenix: true ) Get.lazyPut( () => Controller() ) ``` This is all options you can set when using lazyPut: ```dart Get.lazyPut( // mandatory: a method that will be executed when your class is called for the first time InstanceBuilderCallback builder, // optional: same as Get.put(), it is used for when you want multiple different instance of a same class // must be unique String tag, // optional: It is similar to "permanent", the difference is that the instance is discarded when // is not being used, but when it's use is needed again, Get will recreate the instance // just the same as "SmartManagement.keepFactory" in the bindings api // defaults to false bool fenix = false ) ``` ### Get.putAsync If you want to register an asynchronous instance, you can use `Get.putAsync`: ```dart Get.putAsync(() async { final prefs = await SharedPreferences.getInstance(); await prefs.setInt('counter', 12345); return prefs; }); Get.putAsync( () async => await YourAsyncClass() ) ``` This is all options you can set when using putAsync: ```dart Get.putAsync( // mandatory: an async method that will be executed to instantiate your class AsyncInstanceBuilderCallback builder, // optional: same as Get.put(), it is used for when you want multiple different instance of a same class // must be unique String tag, // optional: same as in Get.put(), used when you need to maintain that instance alive in the entire app // defaults to false bool permanent = false ) ``` ### Get.create This one is tricky. A detailed explanation of what this is and the differences between the other one can be found on [Differences between methods:](#differences-between-methods) section ```dart Get.Create(() => SomeClass()); Get.Create(() => LoginController()); ``` This is all options you can set when using create: ```dart Get.create( // required: a function that returns a class that will be "fabricated" every // time `Get.find()` is called // Example: Get.create(() => YourClass()) FcBuilderFunc builder, // optional: just like Get.put(), but it is used when you need multiple instances // of a of a same class // Useful in case you have a list that each item need it's own controller // needs to be a unique string. Just change from tag to name String name, // optional: just like int`Get.put()`, it is for when you need to keep the // instance alive thoughout the entire app. The difference is in Get.create // permanent is true by default bool permanent = true ``` ## Using instantiated methods/classes Imagine that you have navigated through numerous routes, and you need a data that was left behind in your controller, you would need a state manager combined with the Provider or Get_it, correct? Not with Get. You just need to ask Get to "find" for your controller, you don't need any additional dependencies: ```dart final controller = Get.find(); // OR Controller controller = Get.find(); // Yes, it looks like Magic, Get will find your controller, and will deliver it to you. // You can have 1 million controllers instantiated, Get will always give you the right controller. ``` And then you will be able to recover your controller data that was obtained back there: ```dart Text(controller.textFromApi); ``` Since the returned value is a normal class, you can do anything you want: ```dart int count = Get.find().getInt('counter'); print(count); // out: 12345 ``` To remove an instance of Get: ```dart Get.delete(); //usually you don't need to do this because GetX already delete unused controllers ``` ## Specifying an alternate instance A currently inserted instance can be replaced with a similar or extended class instance by using the `replace` or `lazyReplace` method. This can then be retrieved by using the original class. ```dart abstract class BaseClass {} class ParentClass extends BaseClass {} class ChildClass extends ParentClass { bool isChild = true; } Get.put(ParentClass()); Get.replace(ChildClass()); final instance = Get.find(); print(instance is ChildClass); //true class OtherClass extends BaseClass {} Get.lazyReplace(() => OtherClass()); final instance = Get.find(); print(instance is ChildClass); // false print(instance is OtherClass); //true ``` ## Differences between methods First, let's of the `fenix` of Get.lazyPut and the `permanent` of the other methods. The fundamental difference between `permanent` and `fenix` is how you want to store your instances. Reinforcing: by default, GetX deletes instances when they are not in use. It means that: If screen 1 has controller 1 and screen 2 has controller 2 and you remove the first route from stack, (like if you use `Get.off()` or `Get.offNamed()`) the controller 1 lost its use so it will be erased. But if you want to opt for using `permanent:true`, then the controller will not be lost in this transition - which is very useful for services that you want to keep alive throughout the entire application. `fenix` in the other hand is for services that you don't worry in losing between screen changes, but when you need that service, you expect that it is alive. So basically, it will dispose the unused controller/service/class, but when you need it, it will "recreate from the ashes" a new instance. Proceeding with the differences between methods: - Get.put and Get.putAsync follows the same creation order, with the difference that the second uses an asynchronous method: those two methods creates and initializes the instance. That one is inserted directly in the memory, using the internal method `insert` with the parameters `permanent: false` and `isSingleton: true` (this isSingleton parameter only purpose is to tell if it is to use the dependency on `dependency` or if it is to use the dependency on `FcBuilderFunc`). After that, `Get.find()` is called that immediately initialize the instances that are on memory. - Get.create: As the name implies, it will "create" your dependency! Similar to `Get.put()`, it also calls the internal method `insert` to instancing. But `permanent` became true and `isSingleton` became false (since we are "creating" our dependency, there is no way for it to be a singleton instace, that's why is false). And because it has `permanent: true`, we have by default the benefit of not losing it between screens! Also, `Get.find()` is not called immediately, it wait to be used in the screen to be called. It is created this way to make use of the parameter `permanent`, since then, worth noticing, `Get.create()` was made with the goal of create not shared instances, but don't get disposed, like for example a button in a listView, that you want a unique instance for that list - because of that, Get.create must be used together with GetWidget. - Get.lazyPut: As the name implies, it is a lazy proccess. The instance is create, but it is not called to be used immediately, it remains waiting to be called. Contrary to the other methods, `insert` is not called here. Instead, the instance is inserted in another part of the memory, a part responsible to tell if the instance can be recreated or not, let's call it "factory". If we want to create something to be used later, it will not be mix with things been used right now. And here is where `fenix` magic enters: if you opt to leaving `fenix: false`, and your `smartManagement` are not `keepFactory`, then when using `Get.find` the instance will change the place in the memory from the "factory" to common instance memory area. Right after that, by default it is removed from the "factory". Now, if you opt for `fenix: true`, the instance continues to exist in this dedicated part, even going to the common area, to be called again in the future. ## Bindings One of the great differentials of this package, perhaps, is the possibility of full integration of the routes, state manager and dependency manager. When a route is removed from the Stack, all controllers, variables, and instances of objects related to it are removed from memory. If you are using streams or timers, they will be closed automatically, and you don't have to worry about any of that. In version 2.10 Get completely implemented the Bindings API. Now you no longer need to use the init method. You don't even have to type your controllers if you don't want to. You can start your controllers and services in the appropriate place for that. The Binding class is a class that will decouple dependency injection, while "binding" routes to the state manager and dependency manager. This allows Get to know which screen is being displayed when a particular controller is used and to know where and how to dispose of it. In addition, the Binding class will allow you to have SmartManager configuration control. You can configure the dependencies to be arranged when removing a route from the stack, or when the widget that used it is laid out, or neither. You will have intelligent dependency management working for you, but even so, you can configure it as you wish. ### Bindings class - Create a class and implements Binding ```dart class HomeBinding implements Bindings {} ``` Your IDE will automatically ask you to override the "dependencies" method, and you just need to click on the lamp, override the method, and insert all the classes you are going to use on that route: ```dart class HomeBinding implements Bindings { @override void dependencies() { Get.lazyPut(() => HomeController()); Get.put(()=> Api()); } } class DetailsBinding implements Bindings { @override void dependencies() { Get.lazyPut(() => DetailsController()); } } ``` Now you just need to inform your route, that you will use that binding to make the connection between route manager, dependencies and states. - Using named routes: ```dart getPages: [ GetPage( name: '/', page: () => HomeView(), binding: HomeBinding(), ), GetPage( name: '/details', page: () => DetailsView(), binding: DetailsBinding(), ), ]; ``` - Using normal routes: ```dart Get.to(Home(), binding: HomeBinding()); Get.to(DetailsView(), binding: DetailsBinding()) ``` There, you don't have to worry about memory management of your application anymore, Get will do it for you. The Binding class is called when a route is called, you can create an "initialBinding in your GetMaterialApp to insert all the dependencies that will be created. ```dart GetMaterialApp( initialBinding: SampleBind(), home: Home(), ); ``` ### BindingsBuilder The default way of creating a binding is by creating a class that implements Bindings. But alternatively, you can use `BindingsBuilder` callback so that you can simply use a function to instantiate whatever you desire. Example: ```dart getPages: [ GetPage( name: '/', page: () => HomeView(), binding: BindingsBuilder(() { Get.lazyPut(() => ControllerX()); Get.put(()=> Api()); }), ), GetPage( name: '/details', page: () => DetailsView(), binding: BindingsBuilder(() { Get.lazyPut(() => DetailsController()); }), ), ]; ``` That way you can avoid to create one Binding class for each route making this even simpler. Both ways of doing work perfectly fine and we want you to use what most suit your tastes. ### SmartManagement GetX by default disposes unused controllers from memory, even if a failure occurs and a widget that uses it is not properly disposed. This is what is called the `full` mode of dependency management. But if you want to change the way GetX controls the disposal of classes, you have `SmartManagement` class that you can set different behaviors. #### How to change If you want to change this config (which you usually don't need) this is the way: ```dart void main () { runApp( GetMaterialApp( smartManagement: SmartManagement.onlyBuilder //here home: Home(), ) ) } ``` #### SmartManagement.full It is the default one. Dispose classes that are not being used and were not set to be permanent. In the majority of the cases you will want to keep this config untouched. If you new to GetX then don't change this. #### SmartManagement.onlyBuilder With this option, only controllers started in `init:` or loaded into a Binding with `Get.lazyPut()` will be disposed. If you use `Get.put()` or `Get.putAsync()` or any other approach, SmartManagement will not have permissions to exclude this dependency. With the default behavior, even widgets instantiated with "Get.put" will be removed, unlike SmartManagement.onlyBuilder. #### SmartManagement.keepFactory Just like SmartManagement.full, it will remove it's dependencies when it's not being used anymore. However, it will keep their factory, which means it will recreate the dependency if you need that instance again. ### How bindings work under the hood Bindings creates transitory factories, which are created the moment you click to go to another screen, and will be destroyed as soon as the screen-changing animation happens. This happens so fast that the analyzer will not even be able to register it. When you navigate to this screen again, a new temporary factory will be called, so this is preferable to using SmartManagement.keepFactory, but if you don't want to create Bindings, or want to keep all your dependencies on the same Binding, it will certainly help you. Factories take up little memory, they don't hold instances, but a function with the "shape" of that class you want. This has a very low cost in memory, but since the purpose of this lib is to get the maximum performance possible using the minimum resources, Get removes even the factories by default. Use whichever is most convenient for you. ## Notes - DO NOT USE SmartManagement.keepFactory if you are using multiple Bindings. It was designed to be used without Bindings, or with a single Binding linked in the GetMaterialApp's initialBinding. - Using Bindings is completely optional, if you want you can use `Get.put()` and `Get.find()` on classes that use a given controller without any problem. However, if you work with Services or any other abstraction, I recommend using Bindings for a better organization. ================================================ FILE: documentation/en_US/route_management.md ================================================ - [Route Management](#route-management) - [How to use](#how-to-use) - [Navigation without named routes](#navigation-without-named-routes) - [Navigation with named routes](#navigation-with-named-routes) - [Send data to named Routes](#send-data-to-named-routes) - [Dynamic urls links](#dynamic-urls-links) - [Middleware](#middleware) - [Navigation without context](#navigation-without-context) - [SnackBars](#snackbars) - [Dialogs](#dialogs) - [BottomSheets](#bottomsheets) - [Nested Navigation](#nested-navigation) # Route Management This is the complete explanation of all there is to Getx when the matter is route management. ## How to use Add this to your pubspec.yaml file: ```yaml dependencies: get: ``` If you are going to use routes/snackbars/dialogs/bottomsheets without context, or use the high-level Get APIs, you need to simply add "Get" before your MaterialApp, turning it into GetMaterialApp and enjoy! ```dart GetMaterialApp( // Before: MaterialApp( home: MyHome(), ) ``` ## Navigation without named routes To navigate to a new screen: ```dart Get.to(NextScreen()); ``` To close snackbars, dialogs, bottomsheets, or anything you would normally close with Navigator.pop(context); ```dart Get.back(); ``` To go to the next screen and no option to go back to the previous screen (for use in SplashScreens, login screens and etc.) ```dart Get.off(NextScreen()); ``` To go to the next screen and cancel all previous routes (useful in shopping carts, polls, and tests) ```dart Get.offAll(NextScreen()); ``` To navigate to the next route, and receive or update data as soon as you return from it: ```dart var data = await Get.to(Payment()); ``` on other screen, send a data for previous route: ```dart Get.back(result: 'success'); ``` And use it: ex: ```dart if(data == 'success') madeAnything(); ``` Don't you want to learn our syntax? Just change the Navigator (uppercase) to navigator (lowercase), and you will have all the functions of the standard navigation, without having to use context Example: ```dart // Default Flutter navigator Navigator.of(context).push( context, MaterialPageRoute( builder: (BuildContext context) { return HomePage(); }, ), ); // Get using Flutter syntax without needing context navigator.push( MaterialPageRoute( builder: (_) { return HomePage(); }, ), ); // Get syntax (It is much better, but you have the right to disagree) Get.to(HomePage()); ``` ## Navigation with named routes - If you prefer to navigate by namedRoutes, Get also supports this. To navigate to nextScreen ```dart Get.toNamed("/NextScreen"); ``` To navigate and remove the previous screen from the tree. ```dart Get.offNamed("/NextScreen"); ``` To navigate and remove all previous screens from the tree. ```dart Get.offAllNamed("/NextScreen"); ``` To define routes, use GetMaterialApp: ```dart void main() { runApp( GetMaterialApp( initialRoute: '/', getPages: [ GetPage(name: '/', page: () => MyHomePage()), GetPage(name: '/second', page: () => Second()), GetPage( name: '/third', page: () => Third(), transition: Transition.zoom ), ], ) ); } ``` To handle navigation to non-defined routes (404 error), you can define an unknownRoute page in GetMaterialApp. ```dart void main() { runApp( GetMaterialApp( unknownRoute: GetPage(name: '/notfound', page: () => UnknownRoutePage()), initialRoute: '/', getPages: [ GetPage(name: '/', page: () => MyHomePage()), GetPage(name: '/second', page: () => Second()), ], ) ); } ``` ### Send data to named Routes Just send what you want for arguments. Get accepts anything here, whether it is a String, a Map, a List, or even a class instance. ```dart Get.toNamed("/NextScreen", arguments: 'Get is the best'); ``` on your class or controller: ```dart print(Get.arguments); //print out: Get is the best ``` ### Dynamic urls links Get offer advanced dynamic urls just like on the Web. Web developers have probably already wanted this feature on Flutter, and most likely have seen a package promise this feature and deliver a totally different syntax than a URL would have on web, but Get also solves that. ```dart Get.offAllNamed("/NextScreen?device=phone&id=354&name=Enzo"); ``` on your controller/bloc/stateful/stateless class: ```dart print(Get.parameters['id']); // out: 354 print(Get.parameters['name']); // out: Enzo ``` You can also receive NamedParameters with Get easily: ```dart void main() { runApp( GetMaterialApp( initialRoute: '/', getPages: [ GetPage( name: '/', page: () => MyHomePage(), ), GetPage( name: '/profile/', page: () => MyProfile(), ), //You can define a different page for routes with arguments, and another without arguments, but for that you must use the slash '/' on the route that will not receive arguments as above. GetPage( name: '/profile/:user', page: () => UserProfile(), ), GetPage( name: '/third', page: () => Third(), transition: Transition.cupertino ), ], ) ); } ``` Send data on route name ```dart Get.toNamed("/profile/34954"); ``` On second screen take the data by parameter ```dart print(Get.parameters['user']); // out: 34954 ``` or send multiple parameters like this ```dart Get.toNamed("/profile/34954?flag=true&country=italy"); ``` or ```dart var parameters = {"flag": "true","country": "italy",}; Get.toNamed("/profile/34954", parameters: parameters); ``` On second screen take the data by parameters as usually ```dart print(Get.parameters['user']); print(Get.parameters['flag']); print(Get.parameters['country']); // out: 34954 true italy ``` And now, all you need to do is use Get.toNamed() to navigate your named routes, without any context (you can call your routes directly from your BLoC or Controller class), and when your app is compiled to the web, your routes will appear in the url <3 ### Middleware If you want to listen Get events to trigger actions, you can to use routingCallback to it ```dart GetMaterialApp( routingCallback: (routing) { if(routing.current == '/second'){ openAds(); } } ) ``` If you are not using GetMaterialApp, you can use the manual API to attach Middleware observer. ```dart void main() { runApp( MaterialApp( onGenerateRoute: Router.generateRoute, initialRoute: "/", navigatorKey: Get.key, navigatorObservers: [ GetObserver(MiddleWare.observer), // HERE !!! ], ), ); } ``` Create a MiddleWare class ```dart class MiddleWare { static observer(Routing routing) { /// You can listen in addition to the routes, the snackbars, dialogs and bottomsheets on each screen. ///If you need to enter any of these 3 events directly here, ///you must specify that the event is != Than you are trying to do. if (routing.current == '/second' && !routing.isSnackbar) { Get.snackbar("Hi", "You are on second route"); } else if (routing.current =='/third'){ print('last route called'); } } } ``` Now, use Get on your code: ```dart class First extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( leading: IconButton( icon: Icon(Icons.add), onPressed: () { Get.snackbar("hi", "i am a modern snackbar"); }, ), title: Text('First Route'), ), body: Center( child: ElevatedButton( child: Text('Open route'), onPressed: () { Get.toNamed("/second"); }, ), ), ); } } class Second extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( leading: IconButton( icon: Icon(Icons.add), onPressed: () { Get.snackbar("hi", "i am a modern snackbar"); }, ), title: Text('second Route'), ), body: Center( child: ElevatedButton( child: Text('Open route'), onPressed: () { Get.toNamed("/third"); }, ), ), ); } } class Third extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Third Route"), ), body: Center( child: ElevatedButton( onPressed: () { Get.back(); }, child: Text('Go back!'), ), ), ); } } ``` ## Navigation without context ### SnackBars To have a simple SnackBar with Flutter, you must get the context of Scaffold, or you must use a GlobalKey attached to your Scaffold ```dart final snackBar = SnackBar( content: Text('Hi!'), action: SnackBarAction( label: 'I am a old and ugly snackbar :(', onPressed: (){} ), ); // Find the Scaffold in the widget tree and use // it to show a SnackBar. Scaffold.of(context).showSnackBar(snackBar); ``` With Get: ```dart Get.snackbar('Hi', 'i am a modern snackbar'); ``` With Get, all you have to do is call your Get.snackbar from anywhere in your code or customize it however you want! ```dart Get.snackbar( "Hey i'm a Get SnackBar!", // title "It's unbelievable! I'm using SnackBar without context, without boilerplate, without Scaffold, it is something truly amazing!", // message icon: Icon(Icons.alarm), shouldIconPulse: true, onTap:(){}, barBlur: 20, isDismissible: true, duration: Duration(seconds: 3), ); ////////// ALL FEATURES ////////// // Color colorText, // Duration duration, // SnackPosition snackPosition, // Widget titleText, // Widget messageText, // bool instantInit, // Widget icon, // bool shouldIconPulse, // double maxWidth, // EdgeInsets margin, // EdgeInsets padding, // double borderRadius, // Color borderColor, // double borderWidth, // Color backgroundColor, // Color leftBarIndicatorColor, // List boxShadows, // Gradient backgroundGradient, // TextButton mainButton, // OnTap onTap, // bool isDismissible, // bool showProgressIndicator, // AnimationController progressIndicatorController, // Color progressIndicatorBackgroundColor, // Animation progressIndicatorValueColor, // SnackStyle snackStyle, // Curve forwardAnimationCurve, // Curve reverseAnimationCurve, // Duration animationDuration, // double barBlur, // double overlayBlur, // Color overlayColor, // Form userInputForm /////////////////////////////////// ``` If you prefer the traditional snackbar, or want to customize it from scratch, including adding just one line (Get.snackbar makes use of a mandatory title and message), you can use `Get.rawSnackbar();` which provides the RAW API on which Get.snackbar was built. ### Dialogs To open dialog: ```dart Get.dialog(YourDialogWidget()); ``` To open default dialog: ```dart Get.defaultDialog( onConfirm: () => print("Ok"), middleText: "Dialog made in 3 lines of code" ); ``` To close the dialog and return a result use `Get.closeDialog` providing the `result` to return to the awaited `Get.dialog` call. ```dart Widget buttonWithResult({ required final String text, required final bool result, }) => TextButton( onPressed: () { Get.closeDialog(result: result); }, child: Text(text), ); bool? delete = await Get.dialog( AlertDialog( content: const Text('Are you sure you would like to delete?'), actions: [ buttonWithResult(text: 'No', result: false), buttonWithResult(text: 'Yes', result: true), ], ), ); if (delete != null && delete) { // Perform the deletion } ``` You can also use Get.generalDialog instead of showGeneralDialog. For all other Flutter dialog widgets, including cupertinos, you can use Get.overlayContext instead of context, and open it anywhere in your code. For widgets that don't use Overlay, you can use Get.context. These two contexts will work in 99% of cases to replace the context of your UI, except for cases where inheritedWidget is used without a navigation context. ### BottomSheets Get.bottomSheet is like showModalBottomSheet, but don't need of context. ```dart Get.bottomSheet( Container( child: Wrap( children: [ ListTile( leading: Icon(Icons.music_note), title: Text('Music'), onTap: () {} ), ListTile( leading: Icon(Icons.videocam), title: Text('Video'), onTap: () {}, ), ], ), ) ); ``` ## Nested Navigation Get made Flutter's nested navigation even easier. You don't need the context, and you will find your navigation stack by Id. - NOTE: Creating parallel navigation stacks can be dangerous. The ideal is not to use NestedNavigators, or to use sparingly. If your project requires it, go ahead, but keep in mind that keeping multiple navigation stacks in memory may not be a good idea for RAM consumption. See how simple it is: ```dart Navigator( key: Get.nestedKey(1), // create a key by index initialRoute: '/', onGenerateRoute: (settings) { if (settings.name == '/') { return GetPageRoute( page: () => Scaffold( appBar: AppBar( title: Text("Main"), ), body: Center( child: TextButton( color: Colors.blue, onPressed: () { Get.toNamed('/second', id:1); // navigate by your nested route by index }, child: Text("Go to second"), ), ), ), ); } else if (settings.name == '/second') { return GetPageRoute( page: () => Center( child: Scaffold( appBar: AppBar( title: Text("Main"), ), body: Center( child: Text("second") ), ), ), ); } } ), ``` ================================================ FILE: documentation/en_US/state_management.md ================================================ * [State Management](#state-management) + [Reactive State Manager](#reactive-state-manager) - [Advantages](#advantages) - [Maximum performance:](#maximum-performance) - [Declaring a reactive variable](#declaring-a-reactive-variable) - [Having a reactive state, is easy.](#having-a-reactive-state-is-easy) - [Using the values in the view](#using-the-values-in-the-view) - [Conditions to rebuild](#conditions-to-rebuild) - [Where .obs can be used](#where-obs-can-be-used) - [Note about Lists](#note-about-lists) - [Why i have to use .value](#why-i-have-to-use-value) - [Obx()](#obx) - [Workers](#workers) + [Simple State Manager](#simple-state-manager) - [Advantages](#advantages-1) - [Usage](#usage) - [How it handles controllers](#how-it-handles-controllers) - [You won't need StatefulWidgets anymore](#you-wont-need-statefulwidgets-anymore) - [Why it exists](#why-it-exists) - [Other ways of using it](#other-ways-of-using-it) - [Unique IDs](#unique-ids) + [Mixing the two state managers](#mixing-the-two-state-managers) + [GetBuilder vs GetX vs Obx vs MixinBuilder](#getbuilder-vs-getx-vs-obx-vs-mixinbuilder) # State Management GetX does not use Streams or ChangeNotifier like other state managers. Why? In addition to building applications for android, iOS, web, windows, macos and linux, with GetX you can build server applications with the same syntax as Flutter/GetX. In order to improve response time and reduce RAM consumption, we created GetValue and GetStream, which are low latency solutions that deliver a lot of performance, at a low operating cost. We use this base to build all of our resources, including state management. * _Complexity_: Some state managers are complex and have a lot of boilerplate. With GetX you don't have to define a class for each event, the code is highly clean and clear, and you do a lot more by writing less. Many people have given up on Flutter because of this topic, and they now finally have a stupidly simple solution for managing states. * _No code generators_: You spend half your development time writing your application logic. Some state managers rely on code generators to have minimally readable code. Changing a variable and having to run build_runner can be unproductive, and often the waiting time after a flutter clean will be long, and you will have to drink a lot of coffee. With GetX everything is reactive, and nothing depends on code generators, increasing your productivity in all aspects of your development. * _It does not depend on context_: You probably already needed to send the context of your view to a controller, making the View's coupling with your business logic high. You have probably had to use a dependency for a place that has no context, and had to pass the context through various classes and functions. This just doesn't exist with GetX. You have access to your controllers from within your controllers without any context. You don't need to send the context by parameter for literally nothing. * _Granular control_: most state managers are based on ChangeNotifier. ChangeNotifier will notify all widgets that depend on it when notifyListeners is called. If you have 40 widgets on one screen, which have a variable of your ChangeNotifier class, when you update one, all of them will be rebuilt. With GetX, even nested widgets are respected. If you have Obx watching your ListView, and another watching a checkbox inside the ListView, when changing the CheckBox value, only it will be updated, when changing the List value, only the ListView will be updated. * _It only reconstructs if its variable REALLY changes_: GetX has flow control, that means if you display a Text with 'Paola', if you change the observable variable to 'Paola' again, the widget will not be reconstructed. That's because GetX knows that 'Paola' is already being displayed in Text, and will not do unnecessary reconstructions. Most (if not all) current state managers will rebuild on the screen. ## Reactive State Manager Reactive programming can alienate many people because it is said to be complicated. GetX turns reactive programming into something quite simple: * You won't need to create StreamControllers. * You won't need to create a StreamBuilder for each variable * You will not need to create a class for each state. * You will not need to create a get for an initial value. Reactive programming with Get is as easy as using setState. Let's imagine that you have a name variable and want that every time you change it, all widgets that use it are automatically changed. This is your count variable: ``` dart var name = 'Jonatas Borges'; ``` To make it observable, you just need to add ".obs" to the end of it: ``` dart var name = 'Jonatas Borges'.obs; ``` That's all. It's *that* simple. From now on, we might refer to this reactive-".obs"(ervables) variables as _Rx_. What did we do under the hood? We created a `Stream` of `String` s, assigned the initial value `"Jonatas Borges"` , we notified all widgets that use `"Jonatas Borges"` that they now "belong" to this variable, and when the _Rx_ value changes, they will have to change as well. This is the **magic of GetX**, thanks to Dart's capabilities. But, as we know, a `Widget` can only be changed if it is inside a function, because static classes do not have the power to "auto-change". You will need to create a `StreamBuilder` , subscribe to this variable to listen for changes, and create a "cascade" of nested `StreamBuilder` if you want to change several variables in the same scope, right? No, you don't need a `StreamBuilder` , but you are right about static classes. Well, in the view, we usually have a lot of boilerplate when we want to change a specific Widget, that's the Flutter way. With **GetX** you can also forget about this boilerplate code. `StreamBuilder( … )` ? `initialValue: …` ? `builder: …` ? Nope, you just need to place this variable inside an `Obx()` Widget. ``` dart Obx (() => Text (controller.name)); ``` _What do you need to memorize?_ Only `Obx(() =>` . You are just passing that Widget through an arrow-function into an `Obx()` (the "Observer" of the _Rx_). `Obx` is pretty smart, and will only change if the value of `controller.name` changes. If `name` is `"John"` , and you change it to `"John"` ( `name.value = "John"` ), as it's the same `value` as before, nothing will change on the screen, and `Obx` , to save resources, will simply ignore the new value and not rebuild the Widget. **Isn't that amazing?** > So, what if I have 5 _Rx_ (observable) variables within an `Obx` ? It will just update when **any** of them changes. > And if I have 30 variables in a class, when I update one, will it update **all** the variables that are in that class? Nope, just the **specific Widget** that uses that _Rx_ variable. So, **GetX** only updates the screen, when the _Rx_ variable changes it's value. ``` final isOpen = false.obs; // NOTHING will happen... same value. void onButtonTap() => isOpen.value=false; ``` ### Advantages **GetX()** helps you when you need **granular** control over what's being updated. If you do not need `unique IDs` , because all your variables will be modified when you perform an action, then use `GetBuilder` , because it's a Simple State Updater (in blocks, like `setState()` ), made in just a few lines of code. It was made simple, to have the least CPU impact, and just to fulfill a single purpose (a _State_ rebuild) and spend the minimum resources possible. If you need a **powerful** State Manager, you can't go wrong with **GetX**. It doesn't work with variables, but __flows__, everything in it are `Streams` under the hood. You can use _rxDart_ in conjunction with it, because everything are `Streams`, you can listen to the `event` of each "_Rx_ variable", because everything in it are `Streams`. It is literally a _BLoC_ approach, easier than _MobX_, and without code generators or decorations. You can turn **anything** into an _"Observable"_ with just a `.obs` . ### Maximum performance: In addition to having a smart algorithm for minimal rebuilds, **GetX** uses comparators to make sure the State has changed. If you experience any errors in your app, and send a duplicate change of State, **GetX** will ensure it will not crash. With **GetX** the State only changes if the `value` change. That's the main difference between **GetX**, and using _ `computed` from MobX_. When joining two __observables__, and one changes; the listener of that _observable_ will change as well. With **GetX**, if you join two variables, `GetX()` (similar to `Observer()` ) will only rebuild if it implies a real change of State. ### Declaring a reactive variable You have 3 ways to turn a variable into an "observable". 1 - The first is using **`Rx{Type}`**. ``` dart // initial value is recommended, but not mandatory final name = RxString(''); final isLogged = RxBool(false); final count = RxInt(0); final balance = RxDouble(0.0); final items = RxList([]); final myMap = RxMap({}); ``` 2 - The second is to use **`Rx`** and use Darts Generics, `Rx` ``` dart final name = Rx(''); final isLogged = Rx(false); final count = Rx(0); final balance = Rx(0.0); final number = Rx(0); final items = Rx>([]); final myMap = Rx>({}); // Custom classes - it can be any class, literally final user = Rx(); ``` 3 - The third, more practical, easier and preferred approach, just add **`.obs`** as a property of your `value` : ``` dart final name = ''.obs; final isLogged = false.obs; final count = 0.obs; final balance = 0.0.obs; final number = 0.obs; final items = [].obs; final myMap = {}.obs; // Custom classes - it can be any class, literally final user = User().obs; ``` ##### Having a reactive state, is easy. As we know, _Dart_ is now heading towards _null safety_. To be prepared, from now on, you should always start your _Rx_ variables with an **initial value**. > Transforming a variable into an _observable_ + _initial value_ with **GetX** is the simplest, and most practical approach. You will literally add a " `.obs` " to the end of your variable, and **that’s it**, you’ve made it observable, and its `.value` , well, will be the _initial value_). ### Using the values in the view ``` dart // controller file final count1 = 0.obs; final count2 = 0.obs; int get sum => count1.value + count2.value; ``` ``` dart // view file GetX( builder: (controller) { print("count 1 rebuild"); return Text('${controller.count1.value}'); }, ), GetX( builder: (controller) { print("count 2 rebuild"); return Text('${controller.count2.value}'); }, ), GetX( builder: (controller) { print("count 3 rebuild"); return Text('${controller.sum}'); }, ), ``` If we increment `count1.value++` , it will print: * `count 1 rebuild` * `count 3 rebuild` because `count1` has a value of `1` , and `1 + 0 = 1` , changing the `sum` getter value. If we change `count2.value++` , it will print: * `count 2 rebuild` * `count 3 rebuild` because `count2.value` changed, and the result of the `sum` is now `2` . * NOTE: By default, the very first event will rebuild the widget, even if it is the same `value`. This behavior exists due to Boolean variables. Imagine you did this: ``` dart var isLogged = false.obs; ``` And then, you checked if a user is "logged in" to trigger an event in `ever` . ``` dart @override onInit() async { ever(isLogged, fireRoute); isLogged.value = await Preferences.hasToken(); } fireRoute(logged) { if (logged) { Get.off(Home()); } else { Get.off(Login()); } } ``` if `hasToken` was `false` , there would be no change to `isLogged` , so `ever()` would never be called. To avoid this type of behavior, the first change to an _observable_ will always trigger an event, even if it contains the same `.value` . You can remove this behavior if you want, using: `isLogged.firstRebuild = false;` ### Conditions to rebuild In addition, Get provides refined state control. You can condition an event (such as adding an object to a list), on a certain condition. ``` dart // First parameter: condition, must return true or false. // Second parameter: the new value to apply if the condition is true. list.addIf(item < limit, item); ``` Without decorations, without a code generator, without complications :smile: Do you know Flutter's counter app? Your Controller class might look like this: ``` dart class CountController extends GetxController { final count = 0.obs; } ``` With a simple: ``` dart controller.count.value++ ``` You could update the counter variable in your UI, regardless of where it is stored. ### Where .obs can be used You can transform anything on obs. Here are two ways of doing it: * You can convert your class values to obs ``` dart class RxUser { final name = "Camila".obs; final age = 18.obs; } ``` * or you can convert the entire class to be an observable ``` dart class User { User({String name, int age}); var name; var age; } // when instantianting: final user = User(name: "Camila", age: 18).obs; ``` ### Note about Lists Lists are completely observable as are the objects within it. That way, if you add a value to a list, it will automatically rebuild the widgets that use it. You also don't need to use ".value" with lists, the amazing dart api allowed us to remove that. Unfortunaly primitive types like String and int cannot be extended, making the use of .value mandatory, but that won't be a problem if you work with gets and setters for these. ``` dart // On the controller final String title = 'User Info:'.obs final list = List().obs; // on the view Text(controller.title.value), // String need to have .value in front of it ListView.builder ( itemCount: controller.list.length // lists don't need it ) ``` When you are making your own classes observable, there is a different way to update them: ``` dart // on the model file // we are going to make the entire class observable instead of each attribute class User() { User({this.name = '', this.age = 0}); String name; int age; } // on the controller file final user = User().obs; // when you need to update the user variable: user.update( (user) { // this parameter is the class itself that you want to update user.name = 'Jonny'; user.age = 18; }); // an alternative way of update the user variable: user(User(name: 'João', age: 35)); // on view: Obx(()=> Text("Name ${user.value.name}: Age: ${user.value.age}")) // you can also access the model values without the .value: user().name; // notice that is the user variable, not the class (variable has lowercase u) ``` You don't have to work with sets if you don't want to. you can use the "assign 'and" assignAll "api. The "assign" api will clear your list, and add a single object that you want to start there. The "assignAll" api will clear the existing list and add any iterable objects that you inject into it. ### Why i have to use .value We could remove the obligation to use 'value' to `String` and `int` with a simple decoration and code generator, but the purpose of this library is precisely avoid external dependencies. We want to offer an environment ready for programming, involving the essentials (management of routes, dependencies and states), in a simple, lightweight and performant way, without a need of an external package. You can literally add 3 letters to your pubspec (get) and a colon and start programming. All solutions included by default, from route management to state management, aim at ease, productivity and performance. The total weight of this library is less than that of a single state manager, even though it is a complete solution, and that is what you must understand. If you are bothered by `.value` , and like a code generator, MobX is a great alternative, and you can use it in conjunction with Get. For those who want to add a single dependency in pubspec and start programming without worrying about the version of a package being incompatible with another, or if the error of a state update is coming from the state manager or dependency, or still, do not want to worrying about the availability of controllers, whether literally "just programming", get is just perfect. If you have no problem with the MobX code generator, or have no problem with the BLoC boilerplate, you can simply use Get for routes, and forget that it has state manager. Get SEM and RSM were born out of necessity, my company had a project with more than 90 controllers, and the code generator simply took more than 30 minutes to complete its tasks after a Flutter Clean on a reasonably good machine, if your project it has 5, 10, 15 controllers, any state manager will supply you well. If you have an absurdly large project, and code generator is a problem for you, you have been awarded this solution. Obviously, if someone wants to contribute to the project and create a code generator, or something similar, I will link in this readme as an alternative, my need is not the need for all devs, but for now I say, there are good solutions that already do that, like MobX. ### Obx() Typing in Get using Bindings is unnecessary. you can use the Obx widget instead of GetX which only receives the anonymous function that creates a widget. Obviously, if you don't use a type, you will need to have an instance of your controller to use the variables, or use `Get.find()` .value or Controller.to.value to retrieve the value. ### Workers Workers will assist you, triggering specific callbacks when an event occurs. ``` dart /// Called every time `count1` changes. ever(count1, (_) => print("$_ has been changed")); /// Called only first time the variable $_ is changed once(count1, (_) => print("$_ was changed once")); /// Anti DDos - Called every time the user stops typing for 1 second, for example. debounce(count1, (_) => print("debouce$_"), time: Duration(seconds: 1)); /// Ignore all changes within 1 second. interval(count1, (_) => print("interval $_"), time: Duration(seconds: 1)); ``` All workers (except `debounce` ) have a `condition` named parameter, which can be a `bool` or a callback that returns a `bool` . This `condition` defines when the `callback` function executes. All workers returns a `Worker` instance, that you can use to cancel ( via `dispose()` ) the worker. * **`ever`** is called every time the _Rx_ variable emits a new value. * **`everAll`** Much like `ever` , but it takes a `List` of _Rx_ values Called every time its variable is changed. That's it. * **`once`** 'once' is called only the first time the variable has been changed. * **`debounce`** 'debounce' is very useful in search functions, where you only want the API to be called when the user finishes typing. If the user types "Jonny", you will have 5 searches in the APIs, by the letter J, o, n, n, and y. With Get this does not happen, because you will have a "debounce" Worker that will only be triggered at the end of typing. * **`interval`** 'interval' is different from the debouce. debouce if the user makes 1000 changes to a variable within 1 second, he will send only the last one after the stipulated timer (the default is 800 milliseconds). Interval will instead ignore all user actions for the stipulated period. If you send events for 1 minute, 1000 per second, debounce will only send you the last one, when the user stops strafing events. interval will deliver events every second, and if set to 3 seconds, it will deliver 20 events that minute. This is recommended to avoid abuse, in functions where the user can quickly click on something and get some advantage (imagine that the user can earn coins by clicking on something, if he clicked 300 times in the same minute, he would have 300 coins, using interval, you can set a time frame for 3 seconds, and even then clicking 300 or a thousand times, the maximum he would get in 1 minute would be 20 coins, clicking 300 or 1 million times). The debounce is suitable for anti-DDos, for functions like search where each change to onChange would cause a query to your api. Debounce will wait for the user to stop typing the name, to make the request. If it were used in the coin scenario mentioned above, the user would only win 1 coin, because it is only executed, when the user "pauses" for the established time. * NOTE: Workers should always be used when starting a Controller or Class, so it should always be on onInit (recommended), Class constructor, or the initState of a StatefulWidget (this practice is not recommended in most cases, but it shouldn't have any side effects). ## Simple State Manager Get has a state manager that is extremely light and easy, which does not use ChangeNotifier, will meet the need especially for those new to Flutter, and will not cause problems for large applications. GetBuilder is aimed precisely at multiple state control. Imagine that you added 30 products to a cart, you click delete one, at the same time that the list is updated, the price is updated and the badge in the shopping cart is updated to a smaller number. This type of approach makes GetBuilder killer, because it groups states and changes them all at once without any "computational logic" for that. GetBuilder was created with this type of situation in mind, since for ephemeral change of state, you can use setState and you would not need a state manager for this. That way, if you want an individual controller, you can assign IDs for that, or use GetX. This is up to you, remembering that the more "individual" widgets you have, the more the performance of GetX will stand out, while the performance of GetBuilder should be superior, when there is multiple change of state. ### Advantages 1. Update only the required widgets. 2. Does not use changeNotifier, it is the state manager that uses less memory (close to 0mb). 3. Forget StatefulWidget! With Get you will never need it. With the other state managers, you will probably have to use a StatefulWidget to get the instance of your Provider, BLoC, MobX Controller, etc. But have you ever stopped to think that your appBar, your scaffold, and most of the widgets that are in your class are stateless? So why save the state of an entire class, if you can only save the state of the Widget that is stateful? Get solves that, too. Create a Stateless class, make everything stateless. If you need to update a single component, wrap it with GetBuilder, and its state will be maintained. 4. Organize your project for real! Controllers must not be in your UI, place your TextEditController, or any controller you use within your Controller class. 5. Do you need to trigger an event to update a widget as soon as it is rendered? GetBuilder has the property "initState", just like StatefulWidget, and you can call events from your controller, directly from it, no more events being placed in your initState. 6. Do you need to trigger an action like closing streams, timers and etc? GetBuilder also has the dispose property, where you can call events as soon as that widget is destroyed. 7. Use streams only if necessary. You can use your StreamControllers inside your controller normally, and use StreamBuilder also normally, but remember, a stream reasonably consumes memory, reactive programming is beautiful, but you shouldn't abuse it. 30 streams open simultaneously can be worse than changeNotifier (and changeNotifier is very bad). 8. Update widgets without spending ram for that. Get stores only the GetBuilder creator ID, and updates that GetBuilder when necessary. The memory consumption of the get ID storage in memory is very low even for thousands of GetBuilders. When you create a new GetBuilder, you are actually sharing the state of GetBuilder that has a creator ID. A new state is not created for each GetBuilder, which saves A LOT OF ram for large applications. Basically your application will be entirely Stateless, and the few Widgets that will be Stateful (within GetBuilder) will have a single state, and therefore updating one will update them all. The state is just one. 9. Get is omniscient and in most cases it knows exactly the time to take a controller out of memory. You should not worry about when to dispose of a controller, Get knows the best time to do this. ### Usage ``` dart // Create controller class and extends GetxController class Controller extends GetxController { int counter = 0; void increment() { counter++; update(); // use update() to update counter variable on UI when increment be called } } // On your Stateless/Stateful class, use GetBuilder to update Text when increment be called GetBuilder( init: Controller(), // INIT IT ONLY THE FIRST TIME builder: (_) => Text( '${_.counter}', ), ) //Initialize your controller only the first time. The second time you are using ReBuilder for the same controller, do not use it again. Your controller will be automatically removed from memory as soon as the widget that marked it as 'init' is deployed. You don't have to worry about that, Get will do it automatically, just make sure you don't start the same controller twice. ``` **Done!** * You have already learned how to manage states with Get. * Note: You may want a larger organization, and not use the init property. For that, you can create a class and extends Binding class, and within it mention the controllers that will be created within that route. Controllers will not be created at that time, on the contrary, this is just a statement, so that the first time you use a Controller, Get will know where to look. Get will remain lazyLoad, and will continue to dispose Controllers when they are no longer needed. See the pub.dev example to see how it works. If you navigate many routes and need data that was in your previously used controller, you just need to use GetBuilder Again (with no init): ``` dart class OtherClass extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( body: Center( child: GetBuilder( builder: (s) => Text('${s.counter}'), ), ), ); } ``` If you need to use your controller in many other places, and outside of GetBuilder, just create a get in your controller and have it easily. (or use `Get.find()` ) ``` dart class Controller extends GetxController { /// You do not need that. I recommend using it just for ease of syntax. /// with static method: Controller.to.increment(); /// with no static method: Get.find().increment(); /// There is no difference in performance, nor any side effect of using either syntax. Only one does not need the type, and the other the IDE will autocomplete it. static Controller get to => Get.find(); // add this line int counter = 0; void increment() { counter++; update(); } } ``` And then you can access your controller directly, that way: ``` dart FloatingActionButton( onPressed: () { Controller.to.increment(), } // This is incredibly simple! child: Text("${Controller.to.counter}"), ), ``` When you press FloatingActionButton, all widgets that are listening to the 'counter' variable will be updated automatically. ### How it handles controllers Let's say we have this: `Class a => Class B (has controller X) => Class C (has controller X)` In class A the controller is not yet in memory, because you have not used it yet (Get is lazyLoad). In class B you used the controller, and it entered memory. In class C you used the same controller as in class B, Get will share the state of controller B with controller C, and the same controller is still in memory. If you close screen C and screen B, Get will automatically take controller X out of memory and free up resources, because Class a is not using the controller. If you navigate to B again, controller X will enter memory again, if instead of going to class C, you return to class A again, Get will take the controller out of memory in the same way. If class C didn't use the controller, and you took class B out of memory, no class would be using controller X and likewise it would be disposed of. The only exception that can mess with Get, is if you remove B from the route unexpectedly, and try to use the controller in C. In this case, the creator ID of the controller that was in B was deleted, and Get was programmed to remove it from memory every controller that has no creator ID. If you intend to do this, add the "autoRemove: false" flag to class B's GetBuilder and use adoptID = true; in class C's GetBuilder. ### You won't need StatefulWidgets anymore Using StatefulWidgets means storing the state of entire screens unnecessarily, even because if you need to minimally rebuild a widget, you will embed it in a Consumer/Observer/BlocProvider/GetBuilder/GetX/Obx, which will be another StatefulWidget. The StatefulWidget class is a class larger than StatelessWidget, which will allocate more RAM, and this may not make a significant difference between one or two classes, but it will most certainly do when you have 100 of them! Unless you need to use a mixin, like TickerProviderStateMixin, it will be totally unnecessary to use a StatefulWidget with Get. You can call all methods of a StatefulWidget directly from a GetBuilder. If you need to call initState() or dispose() method for example, you can call them directly; ``` dart GetBuilder( initState: (_) => Controller.to.fetchApi(), dispose: (_) => Controller.to.closeStreams(), builder: (s) => Text('${s.username}'), ), ``` A much better approach than this is to use the onInit() and onClose() method directly from your controller. ``` dart @override void onInit() { fetchApi(); super.onInit(); } ``` * NOTE: If you want to start a method at the moment the controller is called for the first time, you DON'T NEED to use constructors for this, in fact, using a performance-oriented package like Get, this borders on bad practice, because it deviates from the logic in which the controllers are created or allocated (if you create an instance of this controller, the constructor will be called immediately, you will be populating a controller before it is even used, you are allocating memory without it being in use, this definitely hurts the principles of this library). The onInit() methods; and onClose(); were created for this, they will be called when the Controller is created, or used for the first time, depending on whether you are using Get.lazyPut or not. If you want, for example, to make a call to your API to populate data, you can forget about the old-fashioned method of initState/dispose, just start your call to the api in onInit, and if you need to execute any command like closing streams, use the onClose() for that. ### Why it exists The purpose of this package is precisely to give you a complete solution for navigation of routes, management of dependencies and states, using the least possible dependencies, with a high degree of decoupling. Get engages all high and low level Flutter APIs within itself, to ensure that you work with the least possible coupling. We centralize everything in a single package, to ensure that you don't have any kind of coupling in your project. That way, you can put only widgets in your view, and leave the part of your team that works with the business logic free, to work with the business logic without depending on any element of the View. This provides a much cleaner working environment, so that part of your team works only with widgets, without worrying about sending data to your controller, and part of your team works only with the business logic in its breadth, without depending on no element of the view. So to simplify this: You don't need to call methods in initState and send them by parameter to your controller, nor use your controller constructor for that, you have the onInit() method that is called at the right time for you to start your services. You do not need to call the device, you have the onClose() method that will be called at the exact moment when your controller is no longer needed and will be removed from memory. That way, leave views for widgets only, refrain from any kind of business logic from it. Do not call a dispose method inside GetxController, it will not do anything, remember that the controller is not a Widget, you should not "dispose" it, and it will be automatically and intelligently removed from memory by Get. If you used any stream on it and want to close it, just insert it into the close method. Example: ``` dart class Controller extends GetxController { StreamController user = StreamController(); StreamController name = StreamController(); /// close stream = onClose method, not dispose. @override void onClose() { user.close(); name.close(); super.onClose(); } } ``` Controller life cycle: * onInit() where it is created. * onClose() where it is closed to make any changes in preparation for the delete method * deleted: you do not have access to this API because it is literally removing the controller from memory. It is literally deleted, without leaving any trace. ### Other ways of using it You can use Controller instance directly on GetBuilder value: ``` dart GetBuilder( init: Controller(), builder: (value) => Text( '${value.counter}', //here ), ), ``` You may also need an instance of your controller outside of your GetBuilder, and you can use these approaches to achieve this: ``` dart class Controller extends GetxController { static Controller get to => Get.find(); [...] } // on you view: GetBuilder( init: Controller(), // use it only first time on each controller builder: (_) => Text( '${Controller.to.counter}', //here ) ), ``` or ``` dart class Controller extends GetxController { // static Controller get to => Get.find(); // with no static get [...] } // on stateful/stateless class GetBuilder( init: Controller(), // use it only first time on each controller builder: (_) => Text( '${Get.find().counter}', //here ), ), ``` * You can use "non-canonical" approaches to do this. If you are using some other dependency manager, like get_it, modular, etc., and just want to deliver the controller instance, you can do this: ``` dart Controller controller = Controller(); [...] GetBuilder( init: controller, //here builder: (_) => Text( '${controller.counter}', // here ), ), ``` ### Unique IDs If you want to refine a widget's update control with GetBuilder, you can assign them unique IDs: ``` dart GetBuilder( id: 'text' init: Controller(), // use it only first time on each controller builder: (_) => Text( '${Get.find().counter}', //here ), ), ``` And update it this form: ``` dart update(['text']); ``` You can also impose conditions for the update: ``` dart update(['text'], counter < 10); ``` GetX does this automatically and only reconstructs the widget that uses the exact variable that was changed, if you change a variable to the same as the previous one and that does not imply a change of state , GetX will not rebuild the widget to save memory and CPU cycles (3 is being displayed on the screen, and you change the variable to 3 again. In most state managers, this will cause a new rebuild, but with GetX the widget will only is rebuilt again, if in fact his state has changed). ## Mixing the two state managers Some people opened a feature request, as they wanted to use only one type of reactive variable, and the other mechanics, and needed to insert an Obx into a GetBuilder for this. Thinking about it MixinBuilder was created. It allows both reactive changes by changing ".obs" variables, and mechanical updates via update(). However, of the 4 widgets he is the one that consumes the most resources, since in addition to having a Subscription to receive change events from his children, he subscribes to the update method of his controller. Extending GetxController is important, as they have life cycles, and can "start" and "end" events in their onInit() and onClose() methods. You can use any class for this, but I strongly recommend you use the GetxController class to place your variables, whether they are observable or not. ## StateMixin Another way to handle your `UI` state is use the `StateMixin` . To implement it, use the `with` to add the `StateMixin` to your controller which allows a T model. ``` dart class Controller extends GetController with StateMixin{} ``` The `change()` method change the State whenever we want. Just pass the data and the status in this way: ```dart change(data, status: RxStatus.success()); ``` RxStatus allow these status: ``` dart RxStatus.loading(); RxStatus.success(); RxStatus.empty(); RxStatus.error('message'); ``` To represent it in the UI, use: ```dart class OtherClass extends GetView { @override Widget build(BuildContext context) { return Scaffold( body: controller.obx( (state)=>Text(state.name), // here you can put your custom loading indicator, but // by default would be Center(child:CircularProgressIndicator()) onLoading: CustomLoadingIndicator(), onEmpty: Text('No data found'), // here also you can set your own error widget, but by // default will be an Center(child:Text(error)) onError: (error)=>Text(error), ), ); } ``` ## GetBuilder vs GetX vs Obx vs MixinBuilder In a decade working with programming I was able to learn some valuable lessons. My first contact with reactive programming was so "wow, this is incredible" and in fact reactive programming is incredible. However, it is not suitable for all situations. Often all you need is to change the state of 2 or 3 widgets at the same time, or an ephemeral change of state, in which case reactive programming is not bad, but it is not appropriate. Reactive programming has a higher RAM consumption that can be compensated for by the individual workflow, which will ensure that only one widget is rebuilt and when necessary, but creating a list with 80 objects, each with several streams is not a good one idea. Open the dart inspect and check how much a StreamBuilder consumes, and you'll understand what I'm trying to tell you. With that in mind, I created the simple state manager. It is simple, and that is exactly what you should demand from it: updating state in blocks in a simple way, and in the most economical way. GetBuilder is very economical in RAM, and there is hardly a more economical approach than him (at least I can't imagine one, if it exists, please let us know). However, GetBuilder is still a mechanical state manager, you need to call update() just like you would need to call Provider's notifyListeners(). There are other situations where reactive programming is really interesting, and not working with it is the same as reinventing the wheel. With that in mind, GetX was created to provide everything that is most modern and advanced in a state manager. It updates only what is necessary and when necessary, if you have an error and send 300 state changes simultaneously, GetX will filter and update the screen only if the state actually changes. GetX is still more economical than any other reactive state manager, but it consumes a little more RAM than GetBuilder. Thinking about it and aiming to maximize the consumption of resources that Obx was created. Unlike GetX and GetBuilder, you will not be able to initialize a controller inside an Obx, it is just a Widget with a StreamSubscription that receives change events from your children, that's all. It is more economical than GetX, but loses to GetBuilder, which was to be expected, since it is reactive, and GetBuilder has the most simplistic approach that exists, of storing a widget's hashcode and its StateSetter. With Obx you don't need to write your controller type, and you can hear the change from multiple different controllers, but it needs to be initialized before, either using the example approach at the beginning of this readme, or using the Bindings class. ================================================ FILE: documentation/es_ES/dependency_management.md ================================================ - [Gestión de dependencias](#gestión-de-dependencias) - [Simple Instance Manager](#simple-instance-manager) - [Options](#options) - [Bindings](#bindings) - [Cómo utilizar](#cómo-utilizar) - [SmartManagement](#smartmanagement) # Gestión de dependencias ## Simple Instance Manager - Nota: si está utilizando el gestor de estado de GetX, no tiene que preocuparse por esto, solo lea para obtener información, pero preste más atención a la API de bindings, que hará todo esto automáticamente por usted. ¿Ya estás utilizando GetX y quieres que tu proyecto sea lo más ágil posible? GetX tiene un gestor de dependencias simple y poderoso que le permite recuperar la misma clase que su BLoC o Controller con solo una líneas de código, sin contexto de Provider, sin inheritedWidget: ```dart Controller controller = Get.put(Controller()); // Rather Controller controller = Controller(); ``` En lugar de crear una instancia de su clase dentro de la clase que está utilizando, la está creando dentro de la instancia GetX, que la hará disponible en toda su aplicación. Entonces puede usar su Controller (o BLoC) normalmente. ```dart controller.fetchApi(); ``` Imagine que ha navegado a través de numerosas rutas y necesita datos que quedaron en su controlador, necesitaría un gestor de estado combinado con Providere o Get_it, ¿correcto? No con GetX. Solo necesita pedirle a GetX que "encuentre" su controlador, no necesita dependencias adicionales: ```dart Controller controller = Get.find(); //Yes, it looks like Magic, Get will find your controller, and will deliver it to you. You can have 1 million controllers instantiated, Get will always give you the right controller. ``` Y luego podrá recuperar los datos de su controlador que se obtuvieron allí: ```dart Text(controller.textFromApi); ``` ¿Buscando lazy loading? Puede declarar todos sus controladores, y se llamará solo cuando alguien lo necesite. Puedes hacer esto con: ```dart Get.lazyPut(()=> ApiMock()); /// ApiMock will only be called when someone uses Get.find for the first time ``` Si desea registrar una instancia asincrónica, puede usar Get.putAsync. ```dart Get.putAsync(() async { final prefs = await SharedPreferences.getInstance(); await prefs.setInt('counter', 12345); return prefs; }); ``` uso: ```dart int count = Get.find().getInt('counter'); print(count); // out: 12345 } ``` Para eliminar una instancia de GetX: ```dart Get.delete(); ``` ## Instancing methods Although Getx already delivers very good settings for use, it is possible to refine them even more so that it become more useful to the programmer. The methods and it's configurable parameters are: - Get.put(): ```dart Get.put( // mandatory: the class that you want to get to save, like a controller or anything // note: that "S" means that it can be anything S dependency // optional: this is for when you want multiple classess that are of the same type // since you normally get a class by using Get.find(), // you need to use tag to tell which instance you need // must be unique string String tag, // optional: by default, get will dispose instances after they are not used anymore (example, // the controller of a view that is closed), but you might need that the instance // to be kept there throughout the entire app, like an instance of sharedPreferences or something // so you use this // defaults to false bool permanent = false, // optional: allows you after using an abstract class in a test, replace it with another one and follow the test. // defaults to false bool overrideAbstract = false, // optional: allows you to create the dependency using function instead of the dependency itself. InstanceBuilderCallback builder, ) ``` - Get.lazyPut: ```dart Get.lazyPut( // mandatory: a method that will be executed when your class is called for the first time // Example: Get.lazyPut( () => Controller() ) InstanceBuilderCallback builder, // optional: same as Get.put(), it is used for when you want multiple different instance of a same class // must be unique String tag, // optional: It is similar to "permanent", the difference is that the instance is discarded when // is not being used, but when it's use is needed again, Get will recreate the instance // just the same as "SmartManagement.keepFactory" in the bindings api // defaults to false bool fenix = false ) ``` - Get.putAsync: ```dart Get.putAsync( // mandatory: an async method that will be executed to instantiate your class // Example: Get.putAsync( () async => await YourAsyncClass() ) AsyncInstanceBuilderCallback builder, // optional: same as Get.put(), it is used for when you want multiple different instance of a same class // must be unique String tag, // optional: same as in Get.put(), used when you need to maintain that instance alive in the entire app // defaults to false bool permanent = false ``` - Get.create: ```dart Get.create( // required: a function that returns a class that will be "fabricated" every // time `Get.find()` is called // Example: Get.create(() => YourClass()) FcBuilderFunc builder, // optional: just like Get.put(), but it is used when you need multiple instances // of a of a same class // Useful in case you have a list that each item need it's own controller // needs to be a unique string. Just change from tag to name String name, // optional: just like int`Get.put()`, it is for when you need to keep the // instance alive thoughout the entire app. The difference is in Get.create // permanent is true by default bool permanent = true ``` ### Diferences between methods: First, let's of the `fenix` of Get.lazyPut and the `permanent` of the other methods. The fundamental difference between `permanent` and `fenix` is how you want to store your instances. Reinforcing: by default, GetX deletes instances when they are not is use. It means that: If screen 1 has controller 1 and screen 2 has controller 2 and you remove the first route from stack, (like if you use `Get.off()` or `Get.offName()`) the controller 1 lost it's use so it will be erased. But if you want to opt to `permanent:true`, then the controller will not be lost in this transition - which is very usefult for services that you want to keep alive thoughout the entire application. `fenix` in the other hand is for services that you don't worry in losing between screen changes, but when you need that service, you expect that it is alive. So basically, it will dispose the unused controller/service/class, but when you need that, it will "recreate from the ashes" a new instance. Proceeding with the differences between methods: - Get.put and Get.putAsync follow the same creation order, with the difference that asyn opt for applying a asynchronous method: those two methods create and initialize the instance. That one is inserted directly in the memory, using the internal method `insert` with the parameters `permanent: false` and `isSingleton: true` (this isSingleton parameter only porpuse is to tell if it is to use the dependency on `dependency` or if it is to use the dependency on `FcBuilderFunc`). After that, `Get.find()` is called that immediately initialize the instances that are on memory. - Get.create: As the name implies, it will "create" your dependency! Similar to `Get.put()`, it also call the internal method `insert` to instancing. But `permanent` became true and `isSingleton` became false (since we are "creating" our dependency, there is no way for it to be a singleton instace, that's why is false). And because it has `permanent: true`, we have by default the benefit of not losing it between screens! Also, `Get.find()` is not called immediately, it wait to be used in the screen to be called. It is created this way to make use of the parameter `permanent`, since then, worth noticing, `Get.create()` was made with the goal of create not shared instances, but don't get disposed, like for example a button in a listView, that you want a unique instance for that list - because of that, Get.create must be used together with GetWidget. - Get.lazyPut: As the name implies, it is a lazy proccess. The instance is create, but it is not called to be used immediately, it remains waiting to be called. Contrary to the other methods, `insert` is not called here. Instead, the instance is inserted in another part of the memory, a part responsable to tell if the instance can be recreated or not, let's call it "factory". If we want to create something to be used later, it will not be mix with things been used right now. And here is where `fenix` magic enters: if you opt to leaving `fenix: false`, and your `smartManagement` are not `keepFactory`, then when using `Get.find` the instance will change the place in the memory from the "factory" to common instance memory area. Right after that, by default it is removed from the "factory". Now, if you opt for `fenix: true`, the instance continues to exist in this dedicated part, even going to the common area, to be called again in the future. ## Bindings Una de las grandes diferencias de este paquete, tal vez, es la posibilidad de una integración completa de las rutas, gestor de estado y dependencias. Cuando se elimina una ruta del stack, todos los controladores, variables e instancias de objetos relacionados con ella se eliminan de la memoria. Si está utilizando stream o timers, se cerrarán automáticamente y no tendrá que preocuparse por nada de eso. En la versión 2.10 GetX se implementó por completo la API de bindings. Ahora ya no necesita usar el método init. Ni siquiera tiene que escribir sus controladores si no lo desea. Puede iniciar sus controladores y servicios en un lugar apropiado para eso. La clase Binding es una clase que desacoplará la inyección de dependencia, al tiempo que vinculará rutas con el gestor de estado y el gestor de dependencias. Esto permite conocer qué pantalla se muestra cuando se utiliza un controlador en particular y saber dónde y cómo descartarlo. Además, la clase Binding le permitirá tener el control de configuración SmartManager. Puede configurar las dependencias que se organizarán al eliminar una ruta de la pila, o cuando el widget que lo usó se presenta, o ninguno de los dos. Tendrá una gestión inteligente de dependencias que funcione para usted, pero aun así, puede configurarla como desee. ### Cómo utilizar - Crea una clase e implementa Binding ```dart class HomeBinding implements Bindings{ ``` Su IDE le pedirá automáticamente que anule el método de "dependencies", y solo necesita hacer clic en la lámpara, anular el método e insertar todas las clases que va a utilizar en esa ruta: ```dart class HomeBinding implements Bindings{ @override void dependencies() { Get.lazyPut(() => ControllerX()); Get.lazyPut(()=> Api()); } } ``` Ahora solo necesita informar su ruta, que utilizará ese binding para establecer la conexión entre el gestor de rutas, las dependencias y los estados. - Uso de rutas nombradas: ```dart getPages: [ GetPage(name: '/', page: () => Home(), binding: HomeBinding()), ] ``` - Usando rutas normales: ```dart Get.to(Home(), binding: HomeBinding()); ``` Allí, ya no tiene que preocuparse por la administración de memoria de su aplicación, GetX lo hará por usted. La clase Binding se llama cuando se llama una ruta, puede crear un initialBinding en su GetMaterialApp para insertar todas las dependencias que se crearán. ```dart GetMaterialApp( initialBinding: SampleBind(), home: Home(), ); ``` ## SmartManagement Siempre prefiera usar SmartManagement estándar (full), no necesita configurar nada para eso, GetX ya se lo proporciona de forma predeterminada. Seguramente eliminará todos los controladores en desuso de la memoria, ya que su control refinado elimina la dependencia, incluso si se produce un error y un widget que lo utiliza no se elimina correctamente. El modo "full" también es lo suficientemente seguro como para usarlo con StatelessWidget, ya que tiene numerosos callbacks de seguridad que evitarán que un controlador permanezca en la memoria si ningún widget lo está utilizando, y los disposers no son importante aquí. Sin embargo, si le molesta el comportamiento predeterminado, o simplemente no quiere que suceda, GetX ofrece otras opciones más indulgentes para la administración inteligente de la memoria, como SmartManagement.onlyBuilders, que dependerá de la eliminación efectiva de los widgets que estén usando el controller para eliminarlo, y puede evitar que se implemente un controlador usando "autoRemove: false" en su GetBuilder/GetX. Con esta opción, solo se eliminarán los controladores iniciados en "init:" o cargados en un enlace con "Get.lazyPut"; si usa Get.put o cualquier otro enfoque, SmartManagement no tendrá permisos para excluir esta dependencia. Con el comportamiento predeterminado, incluso los widgets instanciados con "Get.put" se eliminarán, a diferencia de SmartManagement.onlyBuilders. SmartManagement.keepFactory es como SmartManagement.full, con una diferencia. SmartManagement.full purga los factories de las premises, de modo que Get.lazyPut() solo podrá llamarse una vez y su factory y sus referencias se autodestruirán. SmartManagement.keepFactory eliminará sus dependencias cuando sea necesario, sin embargo, mantendrá la "forma" de estas, para hacer una igual si necesita una instancia de eso nuevamente. En lugar de usar SmartManagement.keepFactory, puede usar Bindings. Bindings crea factories transitorios, que se crean en el momento en que hace clic para ir a otra pantalla, y se destruirán tan pronto como ocurra la animación de cambio de pantalla. Es tan poco tiempo que el analizador ni siquiera podrá registrarlo. Cuando navegue de nuevo a esta pantalla, se llamará a una nueva factory temporal, por lo que es preferible usar SmartManagement.keepFactory, pero si no desea crear enlaces o desea mantener todas sus dependencias en el mismo enlace, sin duda te ayudará. Las factories ocupan poca memoria, no tienen instancias, sino una función con la "forma" de esa clase que desea. Esto es muy poco, pero dado que el propósito de esta lib es obtener el máximo rendimiento posible utilizando los recursos mínimos, GetX elimina incluso las factories por defecto. Use el que sea más conveniente para usted. - NOTA: NO USE SmartManagement.keepFactory si está utilizando bindings múltiples. Fue diseñado para usarse sin bindings, o con uno único vinculado en el binding inicial de GetMaterialApp. - NOTA2: El uso de bindings es completamente opcional, puede usar Get.put() y Get.find() en clases que usan un controlador dado sin ningún problema. Sin embargo, si trabaja con Servicios o cualquier otra abstracción, le recomiendo usar binding para una organización más grande. ================================================ FILE: documentation/es_ES/route_management.md ================================================ - [Gestión de Rutas](#gestión-de-rutas) - [¿Cómo utilizarlo](#cómo-utilizarlo) - [Navegación sin rutas nombradas](#navegación-sin-rutas-nombradas) - [Navegación con rutas nombradas](#navegación-con-rutas-nombradas) - [Enviar datos a rutas nombradas](#enviar-datos-a-rutas-nombradas) - [Enlaces de URL dinámicos](#enlaces-de-url-dinámicos) - [Middleware](#middleware) - [Navegación sin contexto](#navegación-sin-contexto) - [SnackBars](#snackbars) - [Diálogos](#diálogos) - [BottomSheets](#bottomsheets) - [Navegación anidada](#navegación-anidada) # Gestión de Rutas Cualquier contribución es bienvenida! ## ¿Cómo utilizarlo Agregue esto a su archivo pubspec.yaml: ```yaml dependencies: get: ``` Si va a utilizar rutas/snackbars/dialogs/bottomsheets sin contexto, o las APIs de GetX de alto nivel, simplemente debe agregar "Get" antes de su MaterialApp, ¡convertirlo en GetMaterialApp y disfrutar! ```dart GetMaterialApp( // Before: MaterialApp( home: MyHome(), ) ``` ## Navegación sin rutas nombradas Para navegar a una nueva pantalla: ```dart Get.to(NextScreen()); ``` Para cerrar snackbars, dialogs, bottomsheets o cualquier cosa que normalmente cierre con Navigator.pop(contexto); ```dart Get.back(); ``` Para ir a la siguiente pantalla, sin opción a volver (util por ejemplo en SplashScreens, LoginScreen, etc.) ```dart Get.off(NextScreen()); ``` Para ir a la siguiente pantalla y cancelar todas las rutas anteriores (útil en carritos de compras, encuestas y exámenes) ```dart Get.offAll(NextScreen()); ``` Para navegar a la siguiente ruta y recibir o actualizar datos tan pronto como se regrese de ella: ```dart var data = await Get.to(Payment()); ``` en la otra pantalla, envíe los datos para la ruta anterior: ```dart Get.back(result: 'success'); ``` Y luego usarlo: ej: ```dart if(data == 'success') madeAnything(); ``` ¿No quieres aprender nuestra sintaxis? Simplemente cambie Navigator (mayúsculas) a navigator (minúsculas), y tendrá todas las funciones de la navegación estándar, pero sin tener que usar el contexto. Ejemplo: ```dart // Default Flutter navigator Navigator.of(context).push( context, MaterialPageRoute( builder: (BuildContext context) { return HomePage(); }, ), ); // Get using Flutter syntax without needing context navigator.push( MaterialPageRoute( builder: (_) { return HomePage(); }, ), ); // Get syntax (It is much better, but you have the right to disagree) Get.to(HomePage()); ``` ## Navegación con rutas nombradas - Si prefiere navegar con rutas nombradas, con GetX también es posible. Para navegar a la siguiente pantalla ```dart Get.toNamed("/NextScreen"); ``` Para navegar y eliminar la pantalla anterior del árbol. ```dart Get.offNamed("/NextScreen"); ``` Para navegar y eliminar todas las pantallas anteriores del árbol. ```dart Get.offAllNamed("/NextScreen"); ``` Para definir rutas, use GetMaterialApp: ```dart void main() { runApp( GetMaterialApp( initialRoute: '/', getPages: [ GetPage(name: '/', page: () => MyHomePage()), GetPage(name: '/second', page: () => Second()), GetPage( name: '/third', page: () => Third(), transition: Transition.zoom ), ], ) ); } ``` Para manejar la navegación a rutas no definidas (error 404), puede definir una página de ruta desconocida en GetMaterialApp. ```dart void main() { runApp( GetMaterialApp( unknownRoute: GetPage(name: '/notfound', page: () => UnknownRoutePage()), initialRoute: '/', getPages: [ GetPage(name: '/', page: () => MyHomePage()), GetPage(name: '/second', page: () => Second()), ], ) ); } ``` ### Enviar datos a rutas nombradas Envía lo que quieras usando el parámetro arguments. GetX acepta cualquier cosa aquí, ya sea un String, Map, List o incluso una instancia de clase. ```dart Get.toNamed("/NextScreen", arguments: 'Get is the best'); ``` luego en su clase o controlador: ```dart print(Get.arguments); //print out: Get is the best ``` ### Enlaces de URL dinámicos GetX ofrece URLs dinámicas avanzadas como en la Web. Los desarrolladores web probablemente ya quisieron esta característica en Flutter, y lo más probable es que hayan visto un paquete que promete esta característica y pero que ofrece una sintaxis totalmente diferente a la que una URL tendría en la web, pero GetX lo resuelve. ```dart Get.offAllNamed("/NextScreen?device=phone&id=354&name=Enzo"); ``` y luego en su clase controller/bloc/stateful/stateless: ```dart print(Get.parameters['id']); // out: 354 print(Get.parameters['name']); // out: Enzo ``` También puede recibir parámetros nombrados fácilmente: ```dart void main() { runApp( GetMaterialApp( initialRoute: '/', getPages: [ GetPage( name: '/', page: () => MyHomePage(), ), GetPage( name: '/profile/', page: () => MyProfile(), ), //You can define a different page for routes with arguments, and another without arguments, but for that you must use the slash '/' on the route that will not receive arguments as above. GetPage( name: '/profile/:user', page: () => UserProfile(), ), GetPage( name: '/third', page: () => Third(), transition: Transition.cupertino ), ], ) ); } ``` Enviar datos sobre el nombre de la ruta ```dart Get.toNamed("/second/34954"); ``` Y en la segunda pantalla tome los datos por parámetro ```dart print(Get.parameters['user']); // salida: 34954 ``` o envie multiples parametros de la siguiente manera ```dart Get.toNamed("/profile/34954?flag=true"); ``` En la segunda pantalla tome los parametros como lo haria normalmente ```dart print(Get.parameters['user']); print(Get.parameters['flag']); // salida: 34954 true ``` Y ahora, todo lo que necesita hacer es usar Get.toNamed() para navegar por sus rutas nombradas, sin ningún contexto (puede llamar a sus rutas directamente desde su clase BLoC o Controller), y cuando su aplicación se compila para web, sus rutas aparecerán en la url del navegador <3 ### Middleware Si desea escuchar eventos de GetX para activar acciones, puede usar el routingCallback: ```dart GetMaterialApp( routingCallback: (routing) { if(routing.current == '/second'){ openAds(); } } ) ``` Si no está usando GetMaterialApp, puede usar la API para adjuntar el observador de Middleware. ```dart void main() { runApp( MaterialApp( onGenerateRoute: Router.generateRoute, initialRoute: "/", navigatorKey: Get.key, navigatorObservers: [ GetObserver(MiddleWare.observer), // HERE !!! ], ), ); } ``` Crear la clase MiddleWare: ```dart class MiddleWare { static observer(Routing routing) { /// You can listen in addition to the routes, the snackbars, dialogs and bottomsheets on each screen. ///If you need to enter any of these 3 events directly here, ///you must specify that the event is != Than you are trying to do. if (routing.current == '/second' && !routing.isSnackbar) { Get.snackbar("Hi", "You are on second route"); } else if (routing.current =='/third'){ print('last route called'); } } } ``` Ahora, usa GetX en tu código: ```dart class First extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( leading: IconButton( icon: Icon(Icons.add), onPressed: () { Get.snackbar("hi", "i am a modern snackbar"); }, ), title: Text('First Route'), ), body: Center( child: ElevatedButton( child: Text('Open route'), onPressed: () { Get.toNamed("/second"); }, ), ), ); } } class Second extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( leading: IconButton( icon: Icon(Icons.add), onPressed: () { Get.snackbar("hi", "i am a modern snackbar"); }, ), title: Text('second Route'), ), body: Center( child: ElevatedButton( child: Text('Open route'), onPressed: () { Get.toNamed("/third"); }, ), ), ); } } class Third extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Third Route"), ), body: Center( child: ElevatedButton( onPressed: () { Get.back(); }, child: Text('Go back!'), ), ), ); } } ``` ## Navegación sin contexto ### SnackBars Para tener simple SnackBar con Flutter, debe obtener el contexto de Scaffold, o debe utilizar una GlobalKey: ```dart final snackBar = SnackBar( content: Text('Hi!'), action: SnackBarAction( label: 'I am a old and ugly snackbar :(', onPressed: (){} ), ); // Find the Scaffold in the widget tree and use // it to show a SnackBar. Scaffold.of(context).showSnackBar(snackBar); ``` Con GetX esto se resume en: ```dart Get.snackbar('Hi', 'i am a modern snackbar'); ``` Todo lo que tiene que hacer es llamar a Get.snackbar desde cualquier parte de su código y personalizarlo como desee: ```dart Get.snackbar( "Hey i'm a Get SnackBar!", // title "It's unbelievable! I'm using SnackBar without context, without boilerplate, without Scaffold, it is something truly amazing!", // message icon: Icon(Icons.alarm), shouldIconPulse: true, onTap:(){}, barBlur: 20, isDismissible: true, duration: Duration(seconds: 3), ); ////////// ALL FEATURES ////////// // Color colorText, // Duration duration, // SnackPosition snackPosition, // Widget titleText, // Widget messageText, // bool instantInit, // Widget icon, // bool shouldIconPulse, // double maxWidth, // EdgeInsets margin, // EdgeInsets padding, // double borderRadius, // Color borderColor, // double borderWidth, // Color backgroundColor, // Color leftBarIndicatorColor, // List boxShadows, // Gradient backgroundGradient, // TextButton mainButton, // OnTap onTap, // bool isDismissible, // bool showProgressIndicator, // AnimationController progressIndicatorController, // Color progressIndicatorBackgroundColor, // Animation progressIndicatorValueColor, // SnackStyle snackStyle, // Curve forwardAnimationCurve, // Curve reverseAnimationCurve, // Duration animationDuration, // double barBlur, // double overlayBlur, // Color overlayColor, // Form userInputForm /////////////////////////////////// ``` Si prefiere el snackbar tradicional, o desea personalizarlo desde cero, inclyendo reducirlo a una sola línea de código (dado que Get.snackbar utiliza al menos un título y un mensaje obligatorios), puede usar `Get.rawSnackbar();` que proporciona la API en la que se creó el Get.snackbar. ### Diálogos Para abrir el dialog: ```dart Get.dialog(YourDialogWidget()); ``` Para abrir un dialog predeterminado: ```dart Get.defaultDialog( onConfirm: () => print("Ok"), middleText: "Dialog made in 3 lines of code" ); ``` También puede usar Get.generalDialog en lugar de showGeneralDialog. Para todos los demás dialogs de Flutter, incluidos los cupertinos, puede usar Get.overlayContext en lugar de context, y abrirlo en cualquier parte de su código. Para los widgets que no usan Overlay, puede usar Get.context. Estos dos contexts funcionarán en el 99% de los casos para reemplazar el context de su UI, excepto en los casos donde inheritedWidget es usado sin un contexto de navegación. ### BottomSheets Get.bottomSheet es como showModalBottomSheet, pero no necesita contexto. ```dart Get.bottomSheet( Container( child: Wrap( children: [ ListTile( leading: Icon(Icons.music_note), title: Text('Music'), onTap: () {} ), ListTile( leading: Icon(Icons.videocam), title: Text('Video'), onTap: () {}, ), ], ), ) ); ``` ## Navegación anidada GetX hizo la navegación anidada de Flutter aún más fácil. No necesita el contexto, y encontrará su pila de navegación por Id. - NOTA: Crear pilas de navegación paralelas puede ser peligroso. Lo ideal es no usar NestedNavigators o hacerlo con moderación. Si su proyecto lo requiere, continúe, pero tenga en cuenta que mantener múltiples pilas de navegación en la memoria puede no ser una buena idea para el consumo de RAM. Mira qué simple es: ```dart Navigator( key: Get.nestedKey(1), // create a key by index initialRoute: '/', onGenerateRoute: (settings) { if (settings.name == '/') { return GetPageRoute( page: () => Scaffold( appBar: AppBar( title: Text("Main"), ), body: Center( child: TextButton( color: Colors.blue, onPressed: () { Get.toNamed('/second', id:1); // navigate by your nested route by index }, child: Text("Go to second"), ), ), ), ); } else if (settings.name == '/second') { return GetPageRoute( page: () => Center( child: Scaffold( appBar: AppBar( title: Text("Main"), ), body: Center( child: Text("second") ), ), ), ); } } ), ``` ================================================ FILE: documentation/es_ES/state_management.md ================================================ - [Gestión del Estado](#gestión-del-estado) - [Gestor de Estado Simple](#gestor-de-estado-simple) - [Ventajas](#ventajas) - [Uso](#uso) - [Cómo maneja los Controllers](#cómo-maneja-los-controllers) - [Ya no necesitará StatefulWidgets](#ya-no-necesitará-statefulwidgets) - [Por qué existe](#por-qué-existe) - [Otras formas de usarlo](#otras-formas-de-usarlo) - [ID únicos](#id-únicos) - [Reactivo STATE_MANAGER](#reactivo-state_manager) - [Ventajas](#ventajas-1) - [Uso](#uso-1) - [Donde se pueden usar .obs](#donde-se-pueden-usar-obs) - [Nota sobre listas](#nota-sobre-listas) - [¿Por qué tengo que usar .value](#por-qué-tengo-que-usar-value) - [Obx()](#obx) - [Workers](#workers) - [Mezclando los dos State Managers](#mezclando-los-dos-state-managers) - [GetBuilder vs GetX vs Obx vs MixinBuilder](#getbuilder-vs-getx-vs-obx-vs-mixinbuilder) # Gestión del Estado Actualmente hay varios State Managers para Flutter. Sin embargo, con la mayoría de ellos implica utilizar ChangeNotifier para actualizar widgets y este es un enfoque malo y muy malo para el rendimiento de aplicaciones medianas o grandes. Puede verificar en la documentación oficial de Flutter que [ChangeNotifier debe usarse con 1 o un máximo de 2 listeners](https://api.Flutter.dev/Flutter/foundation/ChangeNotifier-class.html), por lo que es prácticamente inutilizable para cualquier aplicación mediana o grande. Otros state managers son buenos, pero tienen sus matices: - BLoC es muy seguro y eficiente, pero es muy complejo para principiantes, lo que ha impedido que las personas se desarrollen con Flutter. - MobX es más fácil que BLoC y reactivo, casi perfecto, diría, pero necesita usar un generador de código, que para aplicaciones grandes, reduce la productividad, ya que necesitará beber muchos cafés hasta que su código esté listo nuevamente después de un `flutter clean` (¡Y esto no es culpa de MobX, sino del codegen que es realmente lento!). - Provider usa InheritedWidget para entregar el mismo listener, como una forma de resolver el problema mencionado anteriormente con ChangeNotifier, lo que implica que cualquier acceso a su clase ChangeNotifier debe estar dentro del árbol de widgets debido al contexto necesario para acceder. GetX no es mejor ni peor que cualquier otro gestor de estado, pero debe analizar estos puntos, así como los puntos que se mencionan a continuación, para elegir entre usar GetX en forma pura (vanilla) o usarlo junto con otro gestor de estado. Definitivamente, GetX no es enemigo de ningún otro gestor de estado, porque GetX es más bien un microframework, no solo un gestor de estado, y se puede usar solo o en combinación con ellos. ## Gestor de Estado Simple GetX tiene un gestor de estado que es extremadamente ligero y fácil de implementar, especialmente para aquellos nuevos en Flutter, que no utiliza ChangeNotifier, y satisface la necesidad, y no causará problemas en aplicaciones grandes. ### Ventajas 1. Actualiza solo los widgets necesarios. 2. No usa changeNotifier, es el gestor de estados que usa menos memoria (cerca de 0mb). 3. ¡Olvídate de StatefulWidget! Con GetX nunca lo necesitarás. Con los otros state managers, probablemente tendrá que usar un StatefulWidget para obtener la instancia de su Provider,BLoC,MobX Controller, etc. Pero alguna vez se detuvo para pensar que su appBar, su Scaffold y la mayoría de los widgets que están en tu clase son Stateless? Entonces, ¿por qué guardar el estado de una clase completa, si puede guardar solo el estado del widget que es Stateful? GetX también resuelve eso. Crea una clase Stateless, haz todo Stateless. Si necesita actualizar un solo componente, envuélvalo con GetBuilder, y se mantendrá su estado. 4. ¡Organiza tu proyecto de verdad! Los Controllers no deben estar en su UI, colocar su TextEditController o cualquier controller que utilice dentro de su clase Controller. 5. ¿Necesita activar un evento para actualizar un widget tan pronto como este se dibuje? GetBuilder tiene la propiedad "initState", al igual que en un StatefulWidget, y puede llamar a eventos desde su Controller, directamente desde él, sin que se coloquen más eventos en su initState. 6. ¿Necesita activar una acción como cerrar streams, timers, etc.? GetBuilder también tiene la propiedad dispose, donde puede llamar a eventos tan pronto como se destruya ese widget. 7. Use streams solo si es necesario. Puede usar sus StreamControllers dentro de su Controller normalmente, y usar StreamBuilder también normalmente, pero recuerde, un stream consume una cantidad azonablemente de memoria, la programación reactiva es hermosa, pero no debe abusar de ella. 30 streams abiertos simultáneamente pueden ser peores que un changeNotifier (y changeNotifier es muy malo). 8. Actualice los widgets sin consumir ram por eso. GetX almacena solo el creator ID de GetBuilder y lo actualiza cuando es necesario. El consumo de memoria del almacenamiento del Get ID en la memoria es muy bajo, incluso para miles de GetBuilders. Cuando crea un nuevo GetBuilder, en realidad está compartiendo el estado de GetBuilder que tiene un creator ID. No se crea un nuevo estado para cada GetBuilder, lo que ahorra MUCHA RAM para aplicaciones grandes. Básicamente, su aplicación será completamente Stateless, y los pocos Widgets que serán Stateful (dentro de GetBuilder) tendrán un solo estado y por lo tanto actualizar uno los actualizará a todos. El estado es solo uno. 9. GetX es omnisciente y, en la mayoría de los casos, sabe exactamente el momento de sacar un Controller de la memoria. No debe preocuparse por eso, GetX conoce el mejor momento para hacerlo. ### Uso ```dart // Create controller class and extends GetXController class Controller extends GetXController { int counter = 0; void increment() { counter++; update(); // use update() to update counter variable on UI when increment be called } } // On your Stateless/Stateful class, use GetBuilder to update Text when increment be called GetBuilder( init: Controller(), // INIT IT ONLY THE FIRST TIME builder: (_) => Text( '${_.counter}', ), ) //Initialize your controller only the first time. The second time you are using ReBuilder for the same controller, do not use it again. Your controller will be automatically removed from memory as soon as the widget that marked it as 'init' is deployed. You don't have to worry about that, Get will do it automatically, just make sure you don't start the same controller twice. ``` **¡Listo!** - Ya has aprendido a gestionar estados con GetX. - Nota: es posible que desee una organización más grande y no usar la propiedad init. Para eso, puede crear una clase y extender la clase Bindings, dentro de ella mencionar los Controllers que necesita crear dentro de esa ruta. Pero los Controllers no se crearán en ese momento, por el contrario, esto será solo una declaración, por lo que, la primera vez que use un Controller, GetX sabrá dónde buscarlo. GetX seguirá siendo lazyLoad y se ocupará de eliminar los controllers cuando ya no sean necesarios. Vea el ejemplo pub.dev para ver cómo funciona. Si navega por muchas rutas y necesita datos que estaban en su Controller utilizado previamente, solo necesita usar nuevamente GetBuilder (sin init): ```dart class OtherClass extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( body: Center( child: GetBuilder( builder: (s) => Text('${s.counter}'), ), ), ); } ``` Si necesita usar su Controller en muchos otros lugares y fuera de GetBuilder, simplemente cree un get en su Controller y obténgalo fácilmente. (o use `Get.find ()`) ```dart class Controller extends GetXController { /// You do not need that. I recommend using it just for ease of syntax. /// with static method: Controller.to.increment(); /// with no static method: Get.find().increment(); /// There is no difference in performance, nor any side effect of using either syntax. Only one does not need the type, and the other the IDE will autocomplete it. static Controller get to => Get.find(); // add this line int counter = 0; void increment() { counter++; update(); } } ``` Y luego puede acceder a su Controller directamente, de esa manera: ```dart FloatingActionButton( onPressed: () { Controller.to.increment(), } // This is incredibly simple! child: Text("${Controller.to.counter}"), ), ``` Cuando presiona FloatingActionButton, todos los widgets que escuchan la variable 'counter' se actualizarán automáticamente. ### Cómo maneja los Controllers Digamos que tenemos esto: `Class a => Class B (has controller X) => Class C (has controller X)` En la clase A, el Controller aún no está en la memoria, porque aún no lo ha utilizado (GetX es lazyLoad). En la clase B usaste el Controller y entró en la memoria. En la clase C usó el mismo Controller que en la clase B, GetX compartirá el estado del Controller B con el Controller C, y el mismo Controller todavía está en la memoria. Si cierra la pantalla C y la pantalla B, GetX eliminará automáticamente Controller X de la memoria y liberará recursos, porque la clase A no está utilizando Controller. Si navega nuevamente hacia B, Controller X ingresará nuevamente a la memoria, si en lugar de ir a la clase C, regresa nuevamente a la clase A, GetX eliminará el Controller de la misma manera. Si la clase C no usó el Controller, y usted sacó la clase B de la memoria, ninguna clase estaría usando Controller X y de la misma manera se eliminaría. La única excepción que puede interferir con GetX es si elimina B de la ruta de forma inesperada e intenta utilizar el Controller en C. En este caso, se eliminó el creator ID del Controller que estaba en B y GetX se programó para eliminar de la memoria cada controller que no tiene creator ID. Si tiene la intención de hacer esto, agregue el indicador "autoRemove: false" al GetBuilder de clase B y use "adoptID = true;" en GetBuilder de la clase C. ### Ya no necesitará StatefulWidgets Usar StatefulWidgets significa almacenar el estado de pantallas enteras innecesariamente, incluso si necesita reconstruir mínimamente un widget, lo incrustará en un Consumer/Observer/BlocProvider/GetBuilder/GetX/Obx, que será será también otro StatefulWidget. La clase StatefulWidget es una clase más grande que StatelessWidget, que asignará más RAM, y esto puede no hacer una diferencia significativa entre una o dos clases, ¡pero sin duda lo hará cuando tenga 100 de ellas! A menos que necesite usar un mixin, como TickerProviderStateMixin, será totalmente innecesario usar un StatefulWidget con GetX. Puede llamar a todos los métodos de un StatefulWidget directamente desde un GetBuilder. Si necesita llamar al método initState() o dispose(), por ejemplo, puede llamarlos directamente; ```dart GetBuilder( initState: (_) => Controller.to.fetchApi(), dispose: (_) => Controller.to.closeStreams(), builder: (s) => Text('${s.username}'), ), ``` Un enfoque mucho mejor que esto es utilizar el método onInit() y onClose() directamente desde su Controller. ```dart @override void onInit() { fetchApi(); super.onInit(); } ``` - NOTA: Si desea iniciar un método en el momento en que se llama al Controller por primera vez, NO NECESITA usar constructores para esto, de hecho, usando un paquete orientado al rendimiento como GetX, esto sería casi una mala práctica, debido a que se desvía de la lógica en la que los controllers son creados o asignados (si crea una instancia de este controller, se llamará al constructor inmediatamente, completará un Controller antes de que se use, estará asignando memoria sin ser usado, esto definitivamente perjudica los principios de esta biblioteca). Los métodos onInit(); y onClose(); fueron creados para esto, se los llamará cuando se cree el controller, o se use por primera vez, dependiendo de si está utilizando GetX.lazyPut o no. Si desea, por ejemplo, hacer una llamada a su API para llenar datos, puede olvidarse del viejo método initState/dispose, simplemente inicie su llamada a la API en onInit y si necesita ejecutar algún comando como cerrar streams, use onClose() para eso. ### Por qué existe El propósito de este paquete es precisamente brindarle una solución completa para la navegación de rutas, la gestión de dependencias y de estados, utilizando la menor cantidad de dependencias posibles, con un alto grado de desacoplamiento. GetX se acopla internamente a todas las API de Flutter de alto y bajo nivel, para garantizar que trabaje con el menor acoplamiento posible. Centralizamos todo en un solo paquete, para garantizar que no tenga ningún otro tipo de acoplamiento en su proyecto. De esa manera, puede poner solo widgets en su vista y dejar libre la parte de su equipo que trabaja con la lógica de negocios, sin depender de ningún elemento de la vista. Esto proporciona un entorno de trabajo mucho más limpio y ordenado, de modo tal que parte de su equipo trabajará solo con widgets, sin preocuparse por tener que enviar datos a su Controller, mientras la otra parte de su equipo trabajará solo con lógica de negocios, sin depender de ningún elemento de la UI. Entonces, para simplificar esto: No necesita llamar a métodos en initState y enviarlos por parámetro a su Controller, ni usar un constructor Controller. Para eso tiene el método onInit() que se llamará en el momento adecuado para que sus servicios sean iniciados. No necesita llamar a dispose(), dado que dispone del método onClose() que se llamará en el momento exacto en que su Controller ya no se necesita, y se eliminará de la memoria. De esa manera, deje las vistas solo para widgets, y abstenerse de incluír cualquier tipo de lógica de negocios. No llame a un método dispose() dentro de GetXController, no hará nada, recuerde que Controller no es un widget, no debe "eliminarlo" y GetX lo eliminará de forma automática e inteligente de la memoria. Si utilizó algún stream en él y desea cerrarlo, simplemente insértelo en el método de cierre. Ejemplo: ```dart class Controller extends GetXController { StreamController user = StreamController(); StreamController name = StreamController(); /// close stream = onClose method, not dispose. @override void onClose() { user.close(); name.close(); super.onClose(); } } ``` Ciclo de vida del Controller: - onInit() donde se crea. - onClose() donde está cerrado para cualquier tipo de modificación en preparación para ser removido. - deleted: no tiene acceso a esta API porque literalmente el Controller está eliminando de la memoria, sin dejar rastro (literal). ### Otras formas de usarlo Puede usar la instancia de Controller directamente en el value de GetBuilder: ```dart GetBuilder( init: Controller(), builder: (value) => Text( '${value.counter}', //here ), ), ``` También puede necesitar una instancia de su Controller fuera de su GetBuilder, y puede usar estos enfoques para lograr esto: ```dart class Controller extends GetXController { static Controller get to => Get.find(); [...] } // on you view: GetBuilder( init: Controller(), // use it only first time on each controller builder: (_) => Text( '${Controller.to.counter}', //here ) ), ``` o ```dart class Controller extends GetXController { // static Controller get to => Get.find(); // with no static get [...] } // on stateful/stateless class GetBuilder( init: Controller(), // use it only first time on each controller builder: (_) => Text( '${Get.find().counter}', //here ), ), ``` - Puede utilizar enfoques "no canónicos" para hacer esto. Si está utilizando algún otro gestor de dependencias, como get_it, modular, etc., y solo desea entregar la instancia de Controller, puede hacer esto: ```dart Controller controller = Controller(); [...] GetBuilder( init: controller, //here builder: (_) => Text( '${controller.counter}', // here ), ), ``` ### ID únicos Si desea refinar el control de actualización de widgets con GetBuilder, puede asignarles ID únicos: ```dart GetBuilder( id: 'text' init: Controller(), // use it only first time on each controller builder: (_) => Text( '${Get.find().counter}', //here ), ), ``` Y actualízalo de esta forma: ```dart update(['text']); ``` También puede imponer condiciones para la actualización: ```dart update(['text'], counter < 10); ``` GetX hace esto automáticamente y solo reconstruye el widget que usa la variable exacta que se modificó, si cambia una variable a la misma que la anterior y eso no implica un cambio de estado, GetX no reconstruirá el widget para ahorrar memoria y ciclos de CPU (ej. si se muestra 3 en pantalla y la variable cambia a 3 nuevamente, en la mayoría de los gestores de estados, esto provocará una nueva reconstrucción, pero con GetX el widget solo se reconstruirá si efectivamente su estado ha sido modificado). ## Reactivo STATE_MANAGER La programación reactiva puede alienar a muchas personas porque se dice que es complicada. GetX convierte la programación reactiva en algo tan simple que puede ser aprendido y utilizado por aquellos que comenzaron en ese mismo momento en Flutter. No, no necesitará crear StreamControllers. Tampoco necesitará crear un StreamBuilder para cada variable. No necesitará crear una clase para cada estado. No necesitará crear un get para un valor inicial. La programación reactiva con GetX es tan fácil como usar setState (¡o incluso más fácil!). Imaginemos que tiene una variable "name" y desea que cada vez que la modifique, todos los widgets que la usan cambien automáticamente. Ej. esta es tu variable "name": ```dart var name = 'Jonatas Borges'; ``` Para que sea observable, solo necesita agregar ".obs" al final: ```dart var name = 'Jonatas Borges'.obs; ``` Esto raya en lo absurdo cuando se trata de practicidad. ¿Qué hicimos, debajo del capó? Creamos un stream de Strings, asignamos el valor inicial "Jonatas Borges", advertimos a todos los widgets que usan "Jonatas Borges" que ahora pertenecen a esta variable, y cuando se modifica, ellos también lo harán. Esta es la magia de GetX, que solo Dart nos permite hacer. De acuerdo, pero como sabemos, un widget solo se puede modificar si está dentro de una función, porque las clases estáticas no tienen el poder de "auto-change". Tendrá que crear un StreamBuilder, suscribirse para escuchar esta variable y crear una "cascada" de StreamBuilder si desea cambiar multiples variables en el mismo scope, ¿verdad? No, no necesitas un StreamBuilder, pero tienes razón sobre las clases estáticas. Bueno, en la vista, generalmente tenemos mucho boilerplate cuando queremos cambiar un widget específico. Con GetX también puedes olvidarte de esto. ¿StreamBuilder? ¿initialValue? ¿builder? No, solo necesitas jugar con esta variable dentro de un widget Obx. ```dart Obx(() => Text (controller.name)); ``` ¿Qué necesitas memorizar? "Obx(() =>" Simplemente está pasando ese widget a través de una función de flecha en un Obx. Obx es inteligente, y solo se cambiará si se modifica el valor de "name". Si el nombre es "John" y usted lo cambia "John", no tendrá ningún cambio en la pantalla, y Obx simplemente ignorará este cambio y no reconstruirá ningún widget, para ahorrar recursos. ¿No es increíble? ¿Qué pasa si tengo 5 variables observables dentro de un Obx? Se actualizará cuando se modifique alguna de ellos. Y si tengo 30 variables en una clase, cuando actualizo una, ¿actualizará todas las variables que están en esa clase? No, solo el widget específico que usa esa variable. Y si ametrallo mi variable observable mil millones de veces con el mismo valor, ¿me congelaré en la pantalla para reconstrucciones innecesarias? No, GetX solo actualiza la pantalla cuando la variable cambia en la pantalla, si la pantalla sigue siendo la misma, no reconstruirá nada. ### Ventajas GetBuilder está dirigido precisamente al control de múltiples estados. Imagine que agregó 30 productos a un carrito, hace clic en eliminar uno, al mismo tiempo que se actualiza la lista, se actualiza el precio y la insignia en el carrito de compras a un número menor. Este tipo de enfoque hace que GetBuilder sea superior, porque agrupa estados y los cambia todos a la vez sin ninguna "lógica computacional" para eso. GetBuilder se creó con este tipo de situación en mente, ya que para un cambio de estado efímero, puede usar setState y no necesitaría un gestor de estado. Sin embargo, hay situaciones en las que solo desea que el widget donde una determinada variable ha sido modificada sea reconstruida, y esto es lo que GetX hace con un dominio nunca antes visto. De esa manera, si desea un controlador individual, puede asignar un ID o usar GetX. Esto depende de usted, recordando que cuantos más widgets "individuales" tenga, más se destacará el rendimiento de GetX, mientras que el rendimiento de GetBuilder debería ser superior cuando haya un cambio múltiple de estado. Puede usar ambos en cualquier situación, pero si desea ajustar su aplicación al máximo rendimiento posible, diría que: - si sus variables se cambian en diferentes momentos, use GetX, porque no hay competencia para ello cuando el tema es para reconstruir solo lo que es necesario, - si no necesita ID únicos, porque todas sus variables cambiarán cuando realice una acción, use GetBuilder, porque es un simple actualizador de estado en bloques, hecho en unas pocas líneas de código, para que haga exactamente lo que promete hacer: actualizar el estado en bloques. No hay forma de comparar RAM, CPU o cualquier otra cosa, desde un gestor de estado gigante a un simple StatefulWidget (como GetBuilder) que se actualiza cuando se llama a update(). Se hizo de una manera simple, para involucrar la menor lógica computacional, para cumplir con un solo propósito y consumiendo la menor cantidad de recursos posibles. Si quieres un poderoso gestor de estado, puedes ir sin temor a GetX. No funciona con variables, pero fluye, todo lo que contiene son streams bajo el capó. Puede usar rxDart junto con él, porque todo es streams, puede escuchar el evento de cada "variable", porque todo en él es streams, es literalmente BLoC, más fácil que MobX, y sin generador de código o decoraciones. Si quieres poder, GetX te ofrece el gestor de estado más avanzado que puedas tener. GetX fue construido 100% basado en Streams, y le brinda toda la potencia de fuego que BLoC le brindó, con una instalación más fácil que al de MobX. Sin decoraciones, puede convertir cualquier cosa en Observable con solo un ".obs". Máximo rendimiento: además de tener un algoritmo inteligente para una reconstrucción mínima, GetX utiliza comparadores para asegurarse de que el estado realmente haya cambiado. Si experimenta algún error en su aplicación y envía un cambio de estado duplicado, GetX se asegurará de que su aplicación no se colapse. El estado solo cambia si los valores son modificados. Esa es la principal diferencia entre GetX y usar Computed de MobX. Al unir dos observables, cuando se cambia uno, la audiencia de ese observable cambiará. Con GetX, si une dos variables (lo cual sería innecesario), GetX (similar a Observer) solo cambiará si implica un cambio de estado real. ### Uso Tienes 3 formas de convertir una variable en observable. El primero es usar Rx{Type}. ```dart var count = RxString(); ``` El segundo es usar Rx y escribirlo con `Rx` ```dart var count = Rx(); ``` El tercer enfoque, más práctico, fácil e increíble, es simplemente agregar un .obs a su variable. ```dart var count = 0.obs; // or Rxint count = 0.obs; // or Rx count = 0.obs; ``` Como sabemos, Dart ahora se dirige hacia el null safety. Con eso es una buena idea, de ahora en adelante, que comience a usar sus variables siempre con un valor inicial. Transformar una variable en observable con un valor inicial con GetX es el enfoque más simple y práctico que existe actualmente en Flutter. Literalmente agregará un ".obs" al final de su variable, y eso es todo, lo ha hecho observable, y su valor será el valor inicial, ¡esto es fantástico! Puede agregar variables, y si desea escribir un widget que le permita obtener su controlador dentro, solo necesita usar el widget GetX en lugar de Obx ```dart final count1 = 0.obs; final count2 = 0.obs; int get sum => count1.value + count2.value; ``` ```dart GetX( builder: (value) { print("count 1 rebuild"); return Text('${value.count1.value}'); }, ), GetX( builder: (_) { print("count 2 rebuild"); return Text('${_.count2.value}'); }, ), GetX( builder: (_) { print("count 3 rebuild"); return Text('${_.sum}'); }, ), ``` Si incrementamos el número de counter1, solo se reconstruyen el counter1 y el counter3, porque el counter1 ahora tiene un valor de 1 y 1 + 0 = 1, cambiando el valor de la suma. Si cambiamos el counter2, solo se reconstruyen el counter2 y 3, porque el valor de 2 ha cambiado y el resultado de la suma ahora es 2. Si agregamos el número 1 para counter1, que ya contiene 1, no se reconstruye ningún widget. Si agregamos un valor de 1 para el counter1 y un valor de 2 para el counter2, solo se reconstruirán 2 y 3, porque GetX además de cambiar solo lo que es necesario, evita la duplicación de eventos. - NOTA: Por defecto, el primer evento permitirá la reconstrucción incluso si es igual. Creamos este comportamiento debido a variables dualistas, como Boolean. Imaginemos que hiciste esto: ```dart var isLogged = false.obs; ``` y luego verifica si un usuario ha iniciado sesión para activar un evento en "ever". ```dart onInit(){ ever(isLogged, fireRoute); isLogged.value = await Preferences.hasToken(); } fireRoute(logged) { if (logged) { Get.off(Home()); } else { Get.off(Login()); } } ``` si hasToken fuera falso, no habría cambios en isLogged, por lo que nunca se llamaría. Para evitar este tipo de comportamiento, el primer cambio a un observable siempre desencadenará un evento, incluso si es el mismo. Puede eliminar este comportamiento si lo desea, utilizando: `isLogged.firstRebuild = false;` Además, GetX proporciona control de estado refinado. Puede condicionar un evento (como agregar un objeto a una lista), en una determinada condición: ```dart list.addIf(item < limit, item); ``` Sin decoraciones, sin un generador de código, sin complicaciones, GetX cambiará la forma en que administra sus estados en Flutter, y eso no es una promesa, ¡es una certeza! ¿Conoces el counter de Flutter? Su clase de controlador podría verse así: ```dart class CountCtl extends GetxController { final count = 0.obs; } ``` Con un simple: ```dart ctl.count.value++ ``` Puede actualizar la variable de contador en su IU, independientemente de dónde esté almacenada. ### Donde se pueden usar .obs Puedes transformar cualquier cosa en obs: ```dart class RxUser { final name = "Camila".obs; final age = 18.obs; } class User { User({String name, int age}); final rx = RxUser(); String get name => rx.name.value; set name(String value) => rx.name.value = value; int get age => rx.age.value; set age(int value) => rx.age.value = value; } ``` ```dart void main() { final user = User(); print(user.name); user.age = 23; user.rx.age.listen((int age) => print(age)); user.age = 24; user.age = 25; } ___________ out: Camila 23 24 25 ``` ### Nota sobre listas Trabajar con listas usando GetX es lo mejor y lo más divertido del mundo. Son completamente observables como lo son los objetos dentro de él. De esa manera, si agrega un valor a una lista, reconstruirá automáticamente los widgets que lo usan. Tampoco necesita usar ".value" con las listas, la increíble api de Dart nos permitió eliminar eso, los tipos primitivos desafortunados como String e int no se pueden extender, haciendo que el uso de .value sea obligatorio, pero eso no será un problema si trabajas con gets y setters para estos. ```dart final list = List().obs; ``` ```dart ListView.builder ( itemCount: list.length ) ``` No tiene que trabajar con Sets si no lo desea. puede usar la api "assign" y "assignAll". La API "assign" borrará su lista y agregará un solo objeto, con el que quiere comenzar allí. La API "assignAll" borrará la lista existente y agregará cualquier objeto iterable que le inyecte. ### ¿Por qué tengo que usar .value Podríamos eliminar la obligación de usar 'value' para String e int con una simple decoración y generador de código, pero el propósito de esta biblioteca es, precisamente, no necesitar ninguna dependencia externa. Ofrecer un entorno listo para la programación, que incluya lo esencial (gestión de rutas, dependencias y estados), de una manera simple, ligera y de gran rendimiento sin necesidad de ningún otro paquete externo. Literalmente, agrega GetX a su pubspec y puede comenzar a programar. Todas las soluciones incluidas por defecto, desde gestión de rutas a gestión de estádo, apuntan a la facilidad, la productividad y el rendimiento. El peso total de esta biblioteca es menor que el de un solo gestor de estado, aunque es una solución completa, y eso es lo que debe comprender. Si le molesta el ".value" y, como además si se trata de un generador de código, MobX ya es una gran alternativa, puede simplemente usarlo junto con GetX. Para aquellos que desean agregar una sola dependencia en pubspec y comenzar a programar sin preocuparse de que la versión de un paquete sea incompatible con otra, o si el error de una actualización de estado proviene de gestor de estado o dependencia, o aún, no quieren preocuparse por la disponibilidad de controladores, ya sea literalmente "solo programación", GetX es simplemente perfecto. Si no tiene ningún problema con el generador de código de MobX, o no tiene ningún problema con el boilerplate de BLoC, simplemente puede usar GetX para las rutas y olvidar que incluye un gestor de estado. GetX, SEM y RSM nacieron por necesidad, mi empresa tenía un proyecto con más de 90 controladores, y el generador de código tardó más de 30 minutos en completar sus tareas después de un Flutter Clean en una máquina razonablemente buena. Si su proyecto tiene 5, 10, 15 controladores, cualquier gestor de estado te vendrá bien. Si tiene un proyecto absurdamente grande y el generador de código es un problema para usted, se le ha otorgado esta solución. Obviamente, si alguien quiere contribuir al proyecto y crear un generador de código, o algo similar, lo añadiré a este archivo como una alternativa, mi necesidad no es la necesidad de todos los desarrolladores, pero lo que ahora digo es que hay buenas soluciones que ya hacen eso, como MobX. ### Obx() El Typing en GetX usando Bindings es innecesario. Puede usar el widget Obx (en lugar de GetX), que solo recibe la función anónima que crea un widget. Obviamente, si no usa un tipo, necesitará tener una instancia de su controlador para usar las variables, o usar `Get.find ()` .value o Controller.to.value para recuperar el valor. ### Workers Los workers lo ayudarán, activando callbacks específicos cuando ocurra un evento. ```dart /// Called every time the variable $_ is changed ever(count1, (_) => print("$_ has been changed")); /// Called only first time the variable $_ is changed once(count1, (_) => print("$_ was changed once")); /// Anti DDos - Called every time the user stops typing for 1 second, for example. debounce(count1, (_) => print("debouce$_"), time: Duration(seconds: 1)); /// Ignore all changes within 1 second. interval(count1, (_) => print("interval $_"), time: Duration(seconds: 1)); ``` - ever: se llama cada vez que se cambia su variable. Eso es. - once: se llama solo la primera vez que se ha cambiado la variable. - debounce: es muy útil en las funciones de búsqueda, donde solo desea que se llame a la API cuando el usuario termina de escribir. Si el usuario escribe "JONNY", tendrá 5 búsquedas en las API, por la letra J, O, N, N e Y. Con GetX esto no sucede, porque tendrá un worker "debounce" que solo se activará al final de la escritura. - interval: es diferente del debouce. Con debouce si el usuario realiza 1000 cambios en una variable dentro de 1 segundo, enviará solo el último después del timer estipulado (el valor predeterminado es 800 milisegundos). En cambio, el interval ignorará todas las acciones del usuario durante el período estipulado. Si envía eventos durante 1 minuto, 1000 por segundo, la función antirrebote solo le enviará el último, cuando el usuario deje de enviar eventos. Interval entregará eventos cada segundo, y si se establece en 3 segundos, entregará 20 eventos ese minuto. Esto se recomienda para evitar abusos, en funciones en las que el usuario puede hacer clic rápidamente en algo y obtener alguna ventaja (imagine que el usuario puede ganar monedas haciendo clic en algo, si hace clic 300 veces en el mismo minuto, tendría 300 monedas, usando el interval, puede establecer un time frame de 3 segundos, e incluso luego hacer clic 300 o mil veces, el máximo que obtendría en 1 minuto sería 20 monedas, haciendo clic 300 o 1 millón de veces). El debouce es adecuado para anti-DDos, para funciones como la búsqueda donde cada cambio en onChange provocaría una consulta en su API. Debounce esperará a que el usuario deje de escribir el nombre para realizar la solicitud. Si se usara en el escenario de monedas mencionado anteriormente, el usuario solo ganaría 1 moneda, ya que solo se ejecuta cuando el usuario "hace una pausa" durante el tiempo establecido. - NOTE: Los Workers siempre deben usarse al iniciar un controlador o clase, por lo que siempre debe estar en onInit (recomendado), Class Constructor o initState de un StatefulWidget (esta práctica no se recomienda en la mayoría de los casos, pero no debería tener efectos secundarios). ## Mezclando los dos State Managers Algunas personas abrieron una feature request, ya que querían usar solo un tipo de variable reactiva, y la otra mecánica, y necesitaban insertar un Obx en un GetBuilder para esto. Pensando en ello, se creó MixinBuilder. Permite cambios reactivos cambiando las variables ".obs" y actualizaciones mecánicas a través de update(). Sin embargo, de los 4 widgets, es el que consume más recursos, ya que además de tener una suscripción para recibir eventos de cambio de sus hijos, se suscribe al método update de su controlador. Extender GetxController es importante, ya que tienen ciclos de vida y pueden "iniciar" y "finalizar" eventos en sus métodos onInit() y onClose(). Puede usar cualquier clase para esto, pero le recomiendo que use la clase GetxController para colocar sus variables, sean observables o no. ## GetBuilder vs GetX vs Obx vs MixinBuilder En una década trabajando con programación pude aprender algunas lecciones valiosas. Mi primer contacto con la programación reactiva fue tan "guau, esto es increíble" y, de hecho, la programación reactiva es increíble. Sin embargo, no es adecuado para todas las situaciones. A menudo, todo lo que necesita es cambiar el estado de 2 o 3 widgets al mismo tiempo, o un cambio de estado efímero, en cuyo caso la programación reactiva no es mala, pero no es apropiada. La programación reactiva tiene un mayor consumo de RAM que se puede compensar con el workflow individual, lo que garantizará que solo se reconstruya un widget y cuando sea necesario, pero crear una lista con 80 objetos, cada uno con varios streams no es una buena idea. Abra el dart inspector y compruebe cuánto consume un StreamBuilder, y comprenderá lo que estoy tratando de decirle. Con eso en mente, creé el Simple State Manager. Es simple, y eso es exactamente lo que debe exigirle: actualizar el estado en bloques de una manera simple y de la manera más económica. GetBuilder es muy económico en RAM, y es difícil que haya un enfoque más económico que él (al menos no puedo imaginar uno, si existe, háganoslo saber). Sin embargo, GetBuilder sigue siendo un gestor de estado mecánico, debe llamar a update() al igual que necesitaría llamar a notifyListeners() con Provider. Hay otras situaciones, en las que la programación reactiva es realmente interesante, y no trabajar con ella es lo mismo que reinventar la rueda. Con eso en mente, GetX fue creado para proporcionar todo lo más moderno y avanzado en un gestor de estado. Actualiza solo lo necesario y solo si es necesario, si tiene un error y envía 300 cambios de estado simultáneamente, GetX lo filtrará y actualizará la pantalla solo si el estado realmente fue modificado. GetX es aún más económico que cualquier otro gestor de estado reactivo, pero consume un poco más de RAM que GetBuilder. Pensando en ello y con el objetivo de maximizar el consumo de recursos es que se creó Obx. A diferencia de GetX y GetBuilder, no podrá inicializar un controlador dentro de un Obx, es solo un Widget con una stream suscription que recibe eventos de cambio de sus children, eso es todo. Es más económico que GetX, pero pierde con GetBuilder, lo que era de esperar, ya que es reactivo, y GetBuilder tiene el enfoque más simplista que existe, de almacenar el código hash de un widget y su StateSetter. Con Obx no necesita escribir su tipo de controlador, y puede escuchar el cambio desde varios controladores diferentes, pero debe inicializarse antes, ya sea utilizando el enfoque de ejemplo al comienzo de este archivo o utilizando la clase Bindings. ================================================ FILE: documentation/fr_FR/dependency_management.md ================================================ # Gestion des dépendances - [Gestion des dépendances](#Gestion-des-dépendances) - [Instanciation des methodes](#Instanciation-des-methodes) - [Get.put()](#getput) - [Get.lazyPut](#getlazyput) - [Get.putAsync](#getputasync) - [Get.create](#getcreate) - [Utilisation de méthodes/classes instanciées](#utilisation-de-mthodes-classes-instancies) - [Différences entre les méthodes](#differences-entre-les-methodes) - [Bindings](#bindings) - [Classe Bindings](#classe-bindings) - [BindingsBuilder](#bindingsbuilder) - [SmartManagement](#smartmanagement) - [Comment changer](#comment-changer) - [SmartManagement.full](#smartmanagementfull) - [SmartManagement.onlyBuilders](#smartmanagementonlybuilders) - [SmartManagement.keepFactory](#smartmanagementkeepfactory) - [Comment Bindings fonctionne sous le capot](#comment-bindings-fonctionne-sous-le-capot) - [Notes](#notes) Get a un gestionnaire de dépendances simple et puissant qui vous permet de récupérer la même classe que votre Bloc ou Controller avec une seule ligne de code, pas de context Provider, pas d' inheritedWidget: ```dart Controller controller = Get.put(Controller()); // Au lieu de Controller controller = Controller(); ``` Au lieu d'instancier votre classe dans la classe que vous utilisez, vous l'instanciez dans l'instance Get, qui la rendra disponible dans toute votre application. Vous pouvez donc utiliser votre contrôleur (ou classe Bloc) normalement - Note: Si vous utilisez le gestionnaire d'état de Get, faites plus attention à l'API [Bindings] (# bindings), qui facilitera la connexion de votre vue à votre contrôleur. - Note²: La gestion des dépendances est découplée des autres parties du package, donc si, par exemple, votre application utilise déjà un gestionnaire d'état (n'importe lequel, peu importe), vous n'avez pas besoin de changer cela, vous pouvez utiliser ce manager d'injection de dépendance sans aucun problème. ## Instanciation des methodes Les méthodes et leurs paramètres configurables sont: ### Get.put() La manière la plus courante d'insérer une dépendance. Bon pour les contrôleurs de vos vues par exemple. ```dart Get.put(SomeClass()); Get.put(LoginController(), permanent: true); Get.put(ListItemController, tag: "un String unique"); ``` Ce sont toutes les options que vous pouvez définir lorsque vous utilisez put: ```dart Get.put( // obligatoire: la classe que vous voulez que get enregistre, comme un 'controler' ou autre // note: "S" signifie que ca peut etre une classe de n'importe quel type S dependency // optionnel: c'est pour quand vous voulez plusieurs classes qui sont du même type // puisque vous obtenez normalement une classe en utilisant Get.find(), // vous devez utiliser ce tag pour indiquer de quelle instance vous avez besoin // doit être un String unique String tag, // optionnel: par défaut, get supprimera les instances une fois qu'elles ne seront plus utilisées (exemple, // le contrôleur d'une vue qui est fermée), mais vous pourriez avoir besoin que l'instance // soit conservée dans toute l'application, comme une instance de sharedPreferences ou quelque chose du genre // donc vous utilisez ceci // équivaut à false par défaut bool permanent = false, // facultatif: permet après avoir utilisé une classe abstraite dans un test, de la remplacer par une autre et de suivre le test. // équivaut à false par défaut bool overrideAbstract = false, // facultatif: vous permet de créer la dépendance en utilisant la fonction au lieu de la dépendance elle-même. // ce n'est pas couramment utilisé InstanceBuilderCallback builder, ) ``` ### Get.lazyPut Il est possible de lazyLoad une dépendance afin qu'elle ne soit instanciée que lorsqu'elle est utilisée. Très utile pour les classes qui demandent beaucoup de ressources ou si vous souhaitez instancier plusieurs classes en un seul endroit (comme dans une classe Bindings) et que vous savez que vous n'utiliserez pas cette classe à ce moment-là. ```dart /// ApiMock ne sera appelé que lorsque quelqu'un utilise Get.find pour la première fois Get.lazyPut(() => ApiMock()); Get.lazyPut( () { // ... some logic if needed return FirebaseAuth(); }, tag: Math.random().toString(), fenix: true ) Get.lazyPut(() => Controller() ) ``` Ce sont toutes les options que vous pouvez définir lors de l'utilisation de lazyPut: ```dart Get.lazyPut( // obligatoire: une méthode qui sera exécutée lorsque votre classe sera appelée pour la première fois InstanceBuilderCallback builder, // facultatif: identique à Get.put(), il est utilisé lorsque vous voulez plusieurs instances différentes d'une même classe // doit être unique String tag, // facultatif: cela est similaire à "permanent", la différence est que l'instance est supprimée lorsqu'elle // n'est pas utilisée, mais lorsqu'elle est à nouveau nécessaire, Get recrée l'instance // identique à "SmartManagement.keepFactory" dans l'API Bindings // vaut false par défaut bool fenix = false ) ``` ### Get.putAsync Si vous souhaitez enregistrer une instance async, vous pouvez utiliser `Get.putAsync`: ```dart Get.putAsync(() async { final prefs = await SharedPreferences.getInstance(); await prefs.setInt('counter', 12345); return prefs; }); Get.putAsync(() async => await YourAsyncClass()) ``` Ce sont toutes les options que vous pouvez définir lors de l'utilisation de putAsync: ```dart Get.putAsync( // obligatoire: une méthode async qui sera exécutée pour instancier votre classe AsyncInstanceBuilderCallback builder, // facultatif: identique à Get.put(), il est utilisé lorsque vous voulez plusieurs instances différentes d'une même classe // doit être unique String tag, // facultatif: identique à Get.put(), utilisé lorsque vous devez maintenir cette instance active dans l'ensemble de l'application // vaut false par défaut bool permanent = false ) ``` ### Get.create Celui-ci est délicat. Une explication détaillée de ce que c'est et des différences d'avec les autres peut être trouvée dans la section [Différences entre les méthodes:](#differences-entre-les-methodes). ```dart Get.Create(() => SomeClass()); Get.Create(() => LoginController()); ``` Ce sont toutes les options que vous pouvez définir lors de l'utilisation de create: ```dart Get.create( // requis: une fonction qui renvoie une classe qui sera "fabriquée" chaque // fois que `Get.find()` est appelé // Exemple: Get.create(() => YourClass()) FcBuilderFunc builder, // facultatif: comme Get.put(), mais il est utilisé lorsque vous avez besoin de plusieurs instances // d'une même classe // Utile dans le cas où vous avez une liste oú chaque élément a besoin de son propre contrôleur // doit être une String unique. Changez simplement de 'tag' en 'name' String name, // optionnel: tout comme dans `Get.put()`, c'est pour quand vous devez garder l' // instance vivante dans toute l'application. La différence est que dans Get.create, // permanent est 'true' par défaut bool permanent = true ``` ## Utilisation de méthodes/classes instanciées Imaginez que vous ayez parcouru de nombreuses routes et que vous ayez besoin d'une donnée qui a été laissée dans votre contrôleur, vous auriez besoin d'un gestionnaire d'état combiné avec le 'Provider' ou Get_it, n'est-ce pas? Pas avec Get. Il vous suffit de demander à Get de "find" (trouver) votre contrôleur, vous n'avez pas besoin de dépendances supplémentaires: ```dart final controller = Get.find(); // OR Controller controller = Get.find(); // Oui, cela ressemble à Magic, Get trouvera votre contrôleur et vous le livrera. // Vous pouvez avoir 1 million de contrôleurs instanciés, Get vous trouvera toujours le bon contrôleur. ``` Et puis vous pourrez récupérer les données de votre contrôleur qui ont été obtenues là-bas: ```dart Text(controller.textFromApi); ``` La valeur renvoyée étant une classe normale, vous pouvez faire tout ce que vous voulez: ```dart int count = Get.find().getInt('counter'); print(count); // donne: 12345 ``` Pour supprimer une instance de Get: ```dart Get.delete(); //généralement, vous n'avez pas besoin de le faire car GetX supprime déjà les contrôleurs inutilisés- ``` ## Differences entre les methodes Commençons par le `fenix` de Get.lazyPut et le `permanent` des autres méthodes. La différence fondamentale entre `permanent` et `fenix` réside dans la manière dont vous souhaitez stocker vos instances. Renforcement: par défaut, GetX supprime les instances lorsqu'elles ne sont pas utilisées. Cela signifie que: Si l'écran 1 a le contrôleur 1 et l'écran 2 a le contrôleur 2 et que vous supprimez la première route du Stack, (comme si vous utilisez `Get.off()` ou `Get.offNamed()`) le contrôleur 1 a perdu son utilisation, il sera donc effacé. Mais si vous optez pour l'utilisation de `permanent: true`, alors le contrôleur ne sera pas perdu dans cette transition - ce qui est très utile pour les services que vous souhaitez maintenir actif dans toute l'application. `fenix`, quant à lui, est destiné aux services que vous ne craignez pas de perdre entre les changements d'écran, mais lorsque vous avez besoin de ce service, vous vous attendez à ce qu'il soit vivant. Donc, fondamentalement, il supprimera le contrôleur / service / classe inutilisé, mais lorsque vous en aurez besoin, il "recréera à partir de ses cendres" une nouvelle instance. Différences entre les méthodes: - Get.put et Get.putAsync suivent le même ordre de création, à la différence que la seconde utilise une méthode asynchrone: ces deux méthodes créent et initialisent l'instance. Celle-ci est insérée directement dans la mémoire, en utilisant la méthode interne `insert` avec les paramètres` permanent: false` et `isSingleton: true` (ce paramètre isSingleton a pour seul but de dire s'il faut utiliser la dépendance sur` dependency` ou s'il doit utiliser la dépendance sur `FcBuilderFunc`). Après cela, `Get.find()` est appelé pour initialiser immédiatement les instances qui sont en mémoire. - Get.create: Comme son nom l'indique, il "créera" votre dépendance! Similaire à `Get.put()`, il appelle également la méthode interne `insert` pour l'instanciation. Mais `permanent` devient vrai et` isSingleton` devient faux (puisque nous "créons" notre dépendance, il n'y a aucun moyen pour que ce soit une instance singleton, c'est pourquoi il est faux). Et comme il a `permanent: true`, nous avons par défaut l'avantage de ne pas le perdre entre les écrans! De plus, `Get.find()` n'est pas appelé immédiatement, il attend d'être utilisé dans l'écran pour être appelé. Il est créé de cette façon pour utiliser le paramètre `permanent`, depuis lors, il convient de noter que` Get.create() `a été créé dans le but de créer des instances non partagées, mais qui ne sont pas supprimées, comme par exemple un bouton dans un listView, pour lequel vous voulez une instance unique pour cette liste - à cause de cela, Get.create doit être utilisé avec GetWidget. - Get.lazyPut: Comme son nom l'indique, il s'agit d'un processus 'paresseux'. L'instance est créée, mais elle n'est pas appelée pour être utilisée immédiatement, elle reste en attente d'être appelée. Contrairement aux autres méthodes, `insert` n'est pas appelé ici. Au lieu de cela, l'instance est insérée dans une autre partie de la mémoire, une partie chargée de dire si l'instance peut être recréée ou non, appelons-la "factory". Si nous voulons créer quelque chose pour être utilisé plus tard, il ne sera pas mélangé avec les choses actuellement utilisées. Et voici où la magie de `fenix` apparaît: si vous choisissez de laisser` fenix: false`, et que votre `smartManagement` n'est pas` keepFactory`, alors lors de l'utilisation de `Get.find`, l'instance changera la place dans la mémoire de la "factory" à la zone de mémoire d'instance commune. Juste après cela, par défaut, il est retiré de `la factory`. Maintenant, si vous optez pour `fenix: true`, l'instance continue d'exister dans cette partie dédiée, allant même vers la zone commune, pour être appelée à nouveau dans le futur. ## Bindings L'une des grandes différences de ce package, peut-être, est la possibilité d'une intégration complète des routes, du gestionnaire d'état et du gestionnaire de dépendances. Lorsqu'une route est supprimée de la pile, tous les contrôleurs, variables et instances d'objets qui lui sont associés sont supprimés de la mémoire. Si vous utilisez des streams ou timers, ils seront fermés automatiquement et vous n'aurez à vous soucier de rien de tout cela. Dans la version 2.10, Get a complètement implémenté l'API Bindings. Vous n'avez plus besoin d'utiliser la méthode init. Vous n'avez même pas besoin de `typer`(declaration de type) vos contrôleurs si vous ne le souhaitez pas. Vous pouvez démarrer vos contrôleurs et services à l'endroit approprié pour cela. La classe Binding est une classe qui découplera l'injection de dépendances, en faisant du "binding" des routes entre le gestionnaire d'état et le gestionnaire de dépendances. Cela permet à Get de savoir quel écran est affiché lorsqu'un contrôleur particulier est utilisé et de savoir où et comment s'en débarrasser. De plus, la classe Binding vous permettra d'avoir le contrôle de la configuration de SmartManager. Vous pouvez configurer les dépendances à organiser lors de la suppression d'une route du Stack, ou lorsque le widget qui l'utilisait est disposé, ou ni l'un ni l'autre. Vous disposerez d'une gestion intelligente des dépendances qui fonctionnera pour vous, mais vous pourrez malgré tout la configurer comme vous le souhaitez. ### Classe Bindings - Créer une classe et implémenter Bindings ```dart class HomeBinding implements Bindings {} ``` Votre IDE vous demandera automatiquement de remplacer la méthode "dependencies", et il vous suffit de cliquer sur la lampe, de remplacer la méthode et d'insérer toutes les classes que vous allez utiliser sur cette route: ```dart class HomeBinding implements Bindings { @override void dependencies() { Get.lazyPut(() => HomeController()); Get.put(()=> Api()); } } class DetailsBinding implements Bindings { @override void dependencies() { Get.lazyPut(() => DetailsController()); } } ``` Il vous suffit maintenant d'informer votre route, que vous utiliserez ce Binding pour établir la connexion entre le gestionnaire de routes, les dépendances et les états. - En utilisant les routes nommées: ```dart getPages: [ GetPage( name: '/', page: () => HomeView(), binding: HomeBinding(), ), GetPage( name: '/details', page: () => DetailsView(), binding: DetailsBinding(), ), ]; ``` - En utilisant les routes normales: ```dart Get.to(Home(), binding: HomeBinding()); Get.to(DetailsView(), binding: DetailsBinding()) ``` Là, vous n'avez plus à vous soucier de la gestion de la mémoire de votre application, Get le fera pour vous. La classe Binding est appelée lorsqu'une route est appelée, vous pouvez créer un "initialBinding dans votre GetMaterialApp pour insérer toutes les dépendances qui seront créées. ```dart GetMaterialApp( initialBinding: SampleBind(), home: Home(), ); ``` ### BindingsBuilder La manière par défaut de créer un binding est de créer une classe qui implémente Bindings. Mais alternativement, vous pouvez utiliser le callback `BindingsBuilder` afin de pouvoir simplement utiliser une fonction pour instancier ce que vous désirez. Exemple: ```dart getPages: [ GetPage( name: '/', page: () => HomeView(), binding: BindingsBuilder(() { Get.lazyPut(() => ControllerX()); Get.put(()=> Api()); }), ), GetPage( name: '/details', page: () => DetailsView(), binding: BindingsBuilder(() { Get.lazyPut(() => DetailsController()); }), ), ]; ``` De cette façon, vous pouvez éviter de créer une classe Binding pour chaque route, ce qui est encore plus simple. Les deux méthodes fonctionnent parfaitement bien et nous voulons que vous utilisiez ce qui correspond le mieux à vos goûts. ### SmartManagement GetX par défaut supprime les contrôleurs inutilisés de la mémoire, même si un échec se produit et qu'un widget qui l'utilise n'est pas correctement supprimé. C'est ce qu'on appelle le mode `full` de gestion des dépendances. Mais si vous voulez changer la façon dont GetX contrôle la suppression des classes, vous avez la classe `SmartManagement` pour définir différents comportements. #### Comment changer Si vous souhaitez modifier cette configuration (dont vous n'avez généralement pas besoin), procédez comme suit: ```dart void main () { runApp( GetMaterialApp( smartManagement: SmartManagement.onlyBuilders //Ici home: Home(), ) ) } ``` #### SmartManagement.full C'est celui par défaut. Supprime les classes qui ne sont pas utilisées et qui n'ont pas été définies pour être permanentes. Dans la majorité des cas, vous voudrez garder cette configuration intacte. Si vous débutez avec GetX, ne changez pas cela. #### SmartManagement.onlyBuilders Avec cette option, seuls les contrôleurs démarrés dans `init:` ou chargés dans un Binding avec `Get.lazyPut()` seront supprimés. Si vous utilisez `Get.put()` ou `Get.putAsync()` ou toute autre approche, SmartManagement n'aura pas les autorisations pour exclure cette dépendance. Avec le comportement par défaut, même les widgets instanciés avec "Get.put" seront supprimés, contrairement à SmartManagement.onlyBuilders. #### SmartManagement.keepFactory Tout comme SmartManagement.full, il supprimera ses dépendances lorsqu'elles ne seront plus utilisées. Cependant, il conservera leur factory, ce qui signifie qu'il recréera la dépendance si vous avez à nouveau besoin de cette instance. ### Comment Bindings fonctionne sous le capot Bindings crée des `'factories' transitoires`, qui sont créées au moment où vous cliquez pour aller à un autre écran, et seront détruites dès que l'animation de changement d'écran se produit. Cela arrive si vite que l'analyseur ne pourra même pas l'enregistrer. Lorsque vous accédez à nouveau à cet écran, une nouvelle fabrique temporaire sera appelée, c'est donc préférable à l'utilisation de SmartManagement.keepFactory, mais si vous ne voulez pas créer de Bindings, ou si vous voulez garder toutes vos dépendances sur le même Binding , cela vous aidera certainement. Les factories prennent peu de mémoire, elles ne contiennent pas d'instances, mais une fonction avec la "forme" de cette classe que vous voulez. Cela a un très faible coût en mémoire, mais comme le but de cette bibliothèque est d'obtenir le maximum de performances possible en utilisant le minimum de ressources, Get supprime même les factories par défaut. Utilisez celui qui vous convient le mieux. ## Notes - N'UTILISEZ PAS SmartManagement.keepFactory si vous utilisez plusieurs Bindings. Il a été conçu pour être utilisé sans Bindings ou avec une seule Binding liée dans le fichier initialBinding de GetMaterialApp. - L'utilisation de Bindings est complètement facultative, si vous le souhaitez, vous pouvez utiliser `Get.put()` et `Get.find()` sur les classes qui utilisent un contrôleur donné sans aucun problème. Cependant, si vous travaillez avec des services ou toute autre abstraction, je vous recommande d'utiliser Bindings pour une meilleure organisation. ================================================ FILE: documentation/fr_FR/route_management.md ================================================ - [Gestion de route](#gestion-de-route) - [Utilisation](#utilisation) - [Navigation sans nom](#navigation-sans-nom) - [Navigation par nom](#navigation-par-nom) - [Envoyer des données aux routes nommées](#envoyer-des-donnes-aux-routes-nommes) - [Liens URL dynamiques](#liens-url-dynamiques) - [Middleware](#middleware) - [Navigation sans context](#navigation-sans-context) - [SnackBars](#snackbars) - [Dialogs](#dialogs) - [BottomSheets](#bottomsheets) - [Nested Navigation](#nested-navigation) # Gestion de route C'est l'explication complète de tout ce qu'il y a à savoir sur Getx quand il s'agit de la gestion des routes. ## Utilisation Ajoutez ceci à votre fichier pubspec.yaml: ```yaml dependencies: get: ``` Si vous allez utiliser des routes/snackbars/dialogs/bottomsheets sans contexte, ou utiliser les API Get de haut niveau, vous devez simplement ajouter "Get" avant votre MaterialApp, en le transformant en GetMaterialApp et en profiter! ```dart GetMaterialApp( // Avant: MaterialApp( home: MyHome(), ) ``` ## Navigation sans nom Pour accéder à un nouvel écran: ```dart Get.to(NextScreen()); ``` Pour fermer les snackbars, dialogs, bottomsheets ou tout ce que vous fermez normalement avec Navigator.pop(context); ```dart Get.back(); ``` Pour aller à l'écran suivant et aucune option pour revenir à l'écran précédent (pour une utilisation dans SplashScreens, écrans de connexion, etc.) ```dart Get.off(NextScreen()); ``` Pour aller à l'écran suivant et annuler toutes les routes précédents (utile dans les paniers d'achat e-commerce, les sondages et les tests) ```dart Get.offAll(NextScreen()); ``` Pour naviguer vers l'écran suivant et recevoir ou mettre à jour des données dès que vous en revenez: ```dart var data = await Get.to(Payment()); ``` sur l'autre écran, envoyez les données pour l'écran précédent: ```dart Get.back(result: 'success'); ``` Et utilisez-les: ex: ```dart if(data == 'success') madeAnything(); ``` Vous ne voulez pas apprendre notre syntaxe? Changez simplement le Navigateur (majuscule) en navigateur (minuscule), et vous aurez toutes les fonctions de la navigation standard, sans avoir à utiliser 'context'. Exemple: ```dart // Navigateur Flutter par défaut Navigator.of(context).push( context, MaterialPageRoute( builder: (BuildContext context) { return HomePage(); }, ), ); // Utilisez la syntaxe Flutter sans avoir besoin de 'context' navigator.push( MaterialPageRoute( builder: (_) { return HomePage(); }, ), ); // Syntaxe Get (c'est beaucoup mieux, mais vous avez le droit d'être en désaccord) Get.to(HomePage()); ``` ## Navigation Par Nom - Si vous préférez naviguer par namedRoutes, Get prend également en charge cela. Pour aller à nextScreen ```dart Get.toNamed("/NextScreen"); ``` Pour naviguer et supprimer l'écran précédent du stack. ```dart Get.offNamed("/NextScreen"); ``` Pour naviguer et supprimer tous les écrans précédents du stack. ```dart Get.offAllNamed("/NextScreen"); ``` Pour définir des routes, utilisez GetMaterialApp: ```dart void main() { runApp( GetMaterialApp( initialRoute: '/', getPages: [ GetPage(name: '/', page: () => MyHomePage()), GetPage(name: '/second', page: () => Second()), GetPage( name: '/third', page: () => Third(), transition: Transition.zoom ), ], ) ); } ``` Pour gérer la navigation vers des routes non définies (erreur 404), vous pouvez définir une page 'unknownRoute' dans GetMaterialApp. ```dart void main() { runApp( GetMaterialApp( unknownRoute: GetPage(name: '/notfound', page: () => UnknownRoutePage()), initialRoute: '/', getPages: [ GetPage(name: '/', page: () => MyHomePage()), GetPage(name: '/second', page: () => Second()), ], ) ); } ``` ### Envoyer des données aux routes nommées Envoyez simplement ce que vous voulez comme arguments. Get accepte n'importe quoi ici, qu'il s'agisse d'une String, d'une Map, d'une List ou même d'une instance de classe. ```dart Get.toNamed("/NextScreen", arguments: 'Get is the best'); ``` dans votre classe ou contrôleur: ```dart print(Get.arguments); //montre: Get is the best ``` ### Liens URL dynamiques Get propose des URL dynamiques avancées, tout comme sur le Web. Les développeurs Web ont probablement déjà voulu cette fonctionnalité sur Flutter, et ont très probablement vu un package promettre cette fonctionnalité et fournir une syntaxe totalement différente de celle d'une URL sur le Web, mais Get résout également cela. ```dart Get.offAllNamed("/NextScreen?device=phone&id=354&name=Enzo"); ``` sur votre classe controller/bloc/stateful/stateless: ```dart print(Get.parameters['id']); // donne: 354 print(Get.parameters['name']); // donne: Enzo ``` Vous pouvez également recevoir facilement des paramètres nommés avec Get: ```dart void main() { runApp( GetMaterialApp( initialRoute: '/', getPages: [ GetPage( name: '/', page: () => MyHomePage(), ), GetPage( name: '/profile/', page: () => MyProfile(), ), //Vous pouvez définir une page différente pour les routes avec arguments, et une autre sans arguments, mais pour cela vous devez utiliser la barre oblique '/' sur la route qui ne recevra pas d'arguments comme ci-dessus. GetPage( name: '/profile/:user', page: () => UserProfile(), ), GetPage( name: '/third', page: () => Third(), transition: Transition.cupertino ), ], ) ); } ``` Envoyer des données sur le nom de la route ```dart Get.toNamed("/profile/34954"); ``` Sur le deuxième écran, recevez les données par paramètre ```dart print(Get.parameters['user']); // donne: 34954 ``` ou envoyer plusieurs paramètres comme celui-ci ```dart Get.toNamed("/profile/34954?flag=true"); ``` Sur le deuxième écran, prenez les données par paramètres comme d'habitude ```dart print(Get.parameters['user']); print(Get.parameters['flag']); // donne: 34954 true ``` Et maintenant, tout ce que vous avez à faire est d'utiliser Get.toNamed() pour parcourir vos routes nommées, sans aucun contexte (vous pouvez appeler vos routes directement à partir de votre classe BLoC ou Controller), et lorsque votre application est compilée sur le Web, vos routes apparaîtront dans l'url <3 ### Middleware Si vous souhaitez écouter les événements Get pour déclencher des actions, vous pouvez utiliser routingCallback pour le faire: ```dart GetMaterialApp( routingCallback: (routing) { if(routing.current == '/second'){ openAds(); } } ) ``` Si vous n'utilisez pas GetMaterialApp, vous pouvez utiliser l'API manuelle pour attacher l'observateur Middleware. ```dart void main() { runApp( MaterialApp( onGenerateRoute: Router.generateRoute, initialRoute: "/", navigatorKey: Get.key, navigatorObservers: [ GetObserver(MiddleWare.observer), // ICI !!! ], ), ); } ``` Créez une classe MiddleWare ```dart class MiddleWare { static observer(Routing routing) { /// Vous pouvez écouter en plus des routes, des snackbars, des dialogs et des bottomsheets sur chaque écran. /// Si vous devez saisir l'un de ces 3 événements directement ici, /// vous devez spécifier que l'événement est != Ce que vous essayez de faire. if (routing.current == '/second' && !routing.isSnackbar) { Get.snackbar("Hi", "You are on second route"); } else if (routing.current =='/third'){ print('dernière route'); } } } ``` Maintenant, utilisez Get sur votre code: ```dart class First extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( leading: IconButton( icon: Icon(Icons.add), onPressed: () { Get.snackbar("hi", "i am a modern snackbar"); }, ), title: Text('First Route'), ), body: Center( child: ElevatedButton( child: Text('Open route'), onPressed: () { Get.toNamed("/second"); }, ), ), ); } } class Second extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( leading: IconButton( icon: Icon(Icons.add), onPressed: () { Get.snackbar("hi", "i am a modern snackbar"); }, ), title: Text('second Route'), ), body: Center( child: ElevatedButton( child: Text('Open route'), onPressed: () { Get.toNamed("/third"); }, ), ), ); } } class Third extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Third Route"), ), body: Center( child: ElevatedButton( onPressed: () { Get.back(); }, child: Text('Go back!'), ), ), ); } } ``` ## Navigation sans context ### SnackBars Pour avoir un simple SnackBar avec Flutter, vous devez obtenir le 'context' de Scaffold, ou vous devez utiliser un GlobalKey attaché à votre Scaffold ```dart final snackBar = SnackBar( content: Text('Hi!'), action: SnackBarAction( label: 'I am a old and ugly snackbar :(', onPressed: (){} ), ); // Trouvez le scaffold dans l'arborescence des widgets et utilisez-le pour afficher un SnackBar. Scaffold.of(context).showSnackBar(snackBar); ``` Avec Get: ```dart Get.snackbar('Hi', 'i am a modern snackbar'); ``` Avec Get, tout ce que vous avez à faire est d'appeler votre Get.snackbar à partir de n'importe où dans votre code ou de le personnaliser comme vous le souhaitez! ```dart Get.snackbar( "Hey i'm a Get SnackBar!", // title "C'est incroyable! J'utilise SnackBar sans context, sans code standard, sans Scaffold, c'est quelque chose de vraiment incroyable!", // message icon: Icon(Icons.alarm), shouldIconPulse: true, onTap:(){}, barBlur: 20, isDismissible: true, duration: Duration(seconds: 3), ); ////////// TOUTES LES FONCTIONNALITÉS ////////// // Color colorText, // Duration duration, // SnackPosition snackPosition, // Widget titleText, // Widget messageText, // bool instantInit, // Widget icon, // bool shouldIconPulse, // double maxWidth, // EdgeInsets margin, // EdgeInsets padding, // double borderRadius, // Color borderColor, // double borderWidth, // Color backgroundColor, // Color leftBarIndicatorColor, // List boxShadows, // Gradient backgroundGradient, // TextButton mainButton, // OnTap onTap, // bool isDismissible, // bool showProgressIndicator, // AnimationController progressIndicatorController, // Color progressIndicatorBackgroundColor, // Animation progressIndicatorValueColor, // SnackStyle snackStyle, // Curve forwardAnimationCurve, // Curve reverseAnimationCurve, // Duration animationDuration, // double barBlur, // double overlayBlur, // Color overlayColor, // Form userInputForm /////////////////////////////////// ``` Si vous préférez le snack-bar traditionnel, ou souhaitez le personnaliser à partir de zéro, y compris en ajoutant une seule ligne (Get.snackbar utilise un titre et un message obligatoires), vous pouvez utiliser `Get.rawSnackbar ();` qui fournit l'API brute sur laquelle Get.snackbar a été construit. ### Dialogs Pour ouvrir un 'dialog': ```dart Get.dialog(VotreDialogWidget()); ``` Pour ouvrir le 'dialog' par défaut: ```dart Get.defaultDialog( onConfirm: () => print("Ok"), middleText: "Dialog made in 3 lines of code" ); ``` Vous pouvez également utiliser Get.generalDialog au lieu de showGeneralDialog. Pour tous les autres widgets de la boîte de dialogue Flutter, y compris cupertinos, vous pouvez utiliser Get.overlayContext au lieu du context et l'ouvrir n'importe où dans votre code. Pour les widgets qui n'utilisent pas Overlay, vous pouvez utiliser Get.context. Ces deux contextes fonctionneront dans 99% des cas pour remplacer le context de votre interface utilisateur, sauf dans les cas où inheritedWidget est utilisé sans context de navigation. ### BottomSheets Get.bottomSheet est comme showModalBottomSheet, mais n'a pas besoin de 'context'. ```dart Get.bottomSheet( Container( child: Wrap( children: [ ListTile( leading: Icon(Icons.music_note), title: Text('Music'), onTap: () {} ), ListTile( leading: Icon(Icons.videocam), title: Text('Video'), onTap: () {}, ), ], ), ) ); ``` ## Nested Navigation Getx a rendu la navigation imbriquée de Flutter encore plus facile. Vous n'avez pas besoin de 'context' et vous trouverez votre stack de navigation par ID. - NOTE: La création de stacks de navigation parallèles peut être dangereuse. L'idéal est de ne pas utiliser NestedNavigators, ou de l'utiliser avec parcimonie. Si votre projet l'exige, allez-y, mais gardez à l'esprit que conserver plusieurs stacks de navigation en mémoire n'est peut-être pas une bonne idée pour la consommation de RAM. Voyez comme c'est simple: ```dart Navigator( key: Get.nestedKey(1), // créez une clé par index initialRoute: '/', onGenerateRoute: (settings) { if (settings.name == '/') { return GetPageRoute( page: () => Scaffold( appBar: AppBar( title: Text("Main"), ), body: Center( child: TextButton( color: Colors.blue, onPressed: () { Get.toNamed('/second', id:1); // naviguer votre itinéraire imbriqué par index }, child: Text("Go to second"), ), ), ), ); } else if (settings.name == '/second') { return GetPageRoute( page: () => Center( child: Scaffold( appBar: AppBar( title: Text("Main"), ), body: Center( child: Text("second") ), ), ), ); } } ), ``` ================================================ FILE: documentation/fr_FR/state_management.md ================================================ - [Gestion d'État](#gestion-d-etat) - [Gestionnaire d'état réactif](#gestionnaire-d-etat-reactif) - [Avantages](#avantages) - [Performance maximale:](#performance-maximale) - [Déclaration d'une variable réactive](#declaration-d-une-variable-reactive) - [Avoir un état réactif, c'est facile.](#avoir-un-etat-reactif-c-est-facile) - [Utilisation des valeurs dans la Vue](#utilisation-des-valeurs-dans-la-vue) - [Conditions pour reconstruire](#conditions-pour-reconstruire) - [Quand utiliser .obs](#quand-utiliser-obs) - [Remarque sur List](#remarque-sur-list) - [Pourquoi je dois utiliser .value](#pourquoi-je-dois-utiliser-value) - [Obx()](#obx) - [Workers](#workers) - [Gestionnaire d'état simple](#gestionnaire-d-etat-simple) - [Atouts](#atouts) - [Utilisation](#utilisation) - [Comment il gère les contrôleurs](#comment-il-gre-les-contrleurs) - [Vous n'aurez plus besoin de StatefulWidgets](#vous-naurez-plus-besoin-de-statefulwidgets) - [Pourquoi ça existe](#pourquoi-ca-existe) - [Autres façons de l'utiliser](#autres-formes-d-utilisation) - [IDs Uniques](#ids-uniques) - [Mélanger les deux gestionnaires d'état](#mixing-the-two-state-managers) - [GetBuilder vs GetX vs Obx vs MixinBuilder](#getbuilder-vs-getx-vs-obx-vs-mixinbuilder) # Gestion d Etat GetX n'utilise pas Streams ou ChangeNotifier comme les autres gestionnaires d'état. Pourquoi? En plus de créer des applications pour Android, iOS, Web, Linux, MacOS et Linux, GetX vous permet de créer des applications serveur avec la même syntaxe que Flutter / GetX. Afin d'améliorer le temps de réponse et de réduire la consommation de RAM, nous avons créé GetValue et GetStream, des solutions à faible latence qui offrent beaucoup de performances, à un faible coût d'exploitation. Nous utilisons cette base pour construire toutes nos ressources, y compris la gestion d'état. - _Complexité_: Certains gestionnaires d'État sont complexes et ont beaucoup de code standard. Avec GetX, vous n'avez pas à définir une classe pour chaque événement, le code est très propre et clair, et vous faites beaucoup plus en écrivant moins. Beaucoup de gens ont abandonné Flutter à cause de ce sujet, et ils ont enfin une solution stupidement simple pour gérer les états. - _Aucun générateur de code_: Vous passez la moitié de votre temps de développement à écrire la logique de votre application. Certains gestionnaires d'état s'appuient sur des générateurs de code pour avoir un code lisible minimal. Changer une variable et avoir à exécuter build_runner peut être improductif, et souvent le temps d'attente après un redémarrage sera long, et vous devrez boire beaucoup de café. Avec GetX, tout est réactif, et rien ne dépend des générateurs de code, augmentant votre productivité dans tous les aspects de votre développement. - _Cela ne dépend pas de 'context'_: Vous avez probablement déjà eu besoin d'envoyer le contexte de votre vue à un contrôleur, ce qui rend le couplage de la vue avec votre logique métier élevé. Vous avez probablement dû utiliser une dépendance dans un endroit qui n'a pas de contexte, et avez dû passer le contexte à travers différentes classes et fonctions. Cela n'existe tout simplement pas avec GetX. Vous avez accès à vos contrôleurs depuis vos contrôleurs sans aucun contexte. Vous n'avez pas besoin d'envoyer le contexte par paramètre pour rien. - _Contrôle granulaire_: la plupart des gestionnaires d'état sont basés sur ChangeNotifier. ChangeNotifier notifiera tous les widgets qui en dépendent lors de l'appel de notifyListeners. Si vous avez 40 widgets sur un écran, qui ont une variable de votre classe ChangeNotifier, lorsque vous en mettez un à jour, tous seront reconstruits. Avec GetX, même les widgets imbriqués sont respectés. Si Obx gère votre ListView et un autre gère une case à cocher dans ListView, lors de la modification de la valeur CheckBox, il ne sera mis à jour que, lors de la modification de la valeur List, seul le ListView sera mis à jour. - _Il ne reconstruit que si sa variable change VRAIMENT_: GetX a un contrôle de flux, cela signifie que si vous affichez un texte avec 'Paola', si vous changez à nouveau la variable observable en 'Paola', le widget ne sera pas reconstruit. C'est parce que GetX sait que `Paola` est déjà affiché dans Text et ne fera pas de reconstructions inutiles. La plupart (sinon tous) les gestionnaires d'état actuels se reconstruiront à l'écran. ## Gestionnaire d etat reactif La programmation réactive peut aliéner de nombreuses personnes car on dit qu'elle est compliquée. GetX transforme la programmation réactive en quelque chose d'assez simple: - Vous n'aurez pas besoin de créer des StreamControllers. - Vous n'aurez pas besoin de créer un StreamBuilder pour chaque variable - Vous n'aurez pas besoin de créer une classe pour chaque état. - Vous n'aurez pas besoin de créer un 'get' pour une valeur initiale. La programmation réactive avec Get est aussi simple que d'utiliser setState. Imaginons que vous ayez une variable de 'name' et que vous souhaitiez que chaque fois que vous la modifiez, tous les widgets qui l'utilisent soient automatiquement modifiés. Voici votre variable: ```dart var name = 'Jonatas Borges'; ``` Pour la rendre observable, il vous suffit d'ajouter ".obs" à la fin: ```dart var name = 'Jonatas Borges'.obs; ``` C'est *tout*. Si simple que ca. A partir de maintenant, nous pourrions désigner ces variables réactives - ". Obs" (ervables) comme _Rx_. Qu'est ce qui s'est passé derrière les rideaux? Nous avons créé un `Stream` de` String`s, assigné la valeur initiale `" Jonatas Borges "`, nous avons notifié tous les widgets qui utilisent `" Jonatas Borges "` qu'ils "appartiennent" maintenant à cette variable, et quand la valeur _Rx_ changements, ils devront également changer. C'est la **magie de GetX**, grâce aux performances de Dart. Mais, comme nous le savons, un `Widget` ne peut être changé que s'il est à l'intérieur d'une fonction, car les classes statiques n'ont pas le pouvoir de" changer automatiquement ". Vous devrez créer un `StreamBuilder`, vous abonner à cette variable pour écouter les changements et créer une" cascade "de` StreamBuilder` imbriqués si vous voulez changer plusieurs variables dans la même portée, non? Non, vous n'avez pas besoin d'un `StreamBuilder`, mais vous avez raison pour les classes statiques. Eh bien, dans la vue, nous avons généralement beaucoup de code standard lorsque nous voulons changer un widget spécifique, c'est la manière Flutter. Avec **GetX**, vous pouvez également oublier ce code passe-partout. `StreamBuilder( … )`? `initialValue: …`? `builder: …`? Non, il vous suffit de placer cette variable dans un widget `Obx()`. ```dart Obx (() => Text (controller.name)); ``` _Que devez-vous mémoriser?_ Seulement `Obx(() =>`. Vous passez simplement ce Widget via une fonction dans un `Obx()` (l' "Observateur" du _Rx_). `Obx` est assez intelligent et ne changera que si la valeur de` controller.name` change. Si `name` est` "John" `, et que vous le changez en` "John" `(` name.value = "John" `), comme c'est la même` valeur` qu'avant, rien ne changera à l'écran, et `Obx`, pour économiser les ressources, ignorera simplement la nouvelle valeur et ne reconstruira pas le widget. **N'est-ce pas incroyable?** > Alors, que faire si j'ai 5 variables _Rx_ (observables) dans un `Obx`? Il sera simplement mis à jour lorsque **l'un d'entre eux** change. > Et si j'ai 30 variables dans une classe, lorsque j'en mets une à jour, est-ce que cela va mettre à jour **toutes** les variables qui sont dans cette classe? Non, juste le **Widget spécifique** qui utilise cette variable _Rx_. Ainsi, **GetX** ne met à jour l'écran que lorsque la variable _Rx_ change sa valeur. ``` final isOpen = false.obs; // Rien de ne change... valeur identique. void onButtonTap() => isOpen.value=false; ``` ### Avantages **GetX()** vous aide lorsque vous avez besoin d'un contrôle **granulaire** sur ce qui est mis à jour. Si vous n'avez pas besoin d'ID uniques, car toutes vos variables seront modifiées lorsque vous effectuez une action, utilisez `GetBuilder`, parce que c'est un Simple State Updater (en blocs, comme `setState()`), fait en seulement quelques lignes de code. Il a été rendu simple, pour avoir le moins d'impact sur le processeur, et juste pour remplir un seul objectif (une reconstruction de _l'état_) et dépenser le minimum de ressources possible. Si vous avez besoin d'un State Manager **puissant** , vous ne pouvez pas vous tromper avec **GetX**. Cela ne fonctionne pas avec les variables, mais __flows__, tout ce qu'il contient sont des `Streams` en réalité. Vous pouvez utiliser _rxDart_ en conjonction avec lui, car tout est `Streams`. Vous pouvez écouter les changements de chaque "variable _Rx_", parce que tout ce qui se trouve dedans est un `Streams`. C'est littéralement une approche _BLoC_, plus facile que _MobX_, et sans générateurs de code ni décorations. Vous pouvez transformer **n'importe quoi** en un _"Observable"_ avec juste un `.obs`. ### Performance maximale: En plus d'avoir un algorithme intelligent pour des reconstructions minimales, **GetX** utilise des comparateurs pour s'assurer que l'État a changé. Si vous rencontrez des erreurs dans votre application et envoyez un changement d'état en double, **GetX** garantira qu'il ne plantera pas. Avec **GetX**, l'état ne change que si la `valeur` change. C'est la principale différence entre **GetX** et l'utilisation de _`computed` de MobX_. Lors de la jonction de deux __observables__, si l'une change; le listener de cet _observable_ changera également. Avec **GetX**, si vous joignez deux variables, `GetX()` (similaire à `Observer()`), ne se reconstruira que si cela implique un réel changement d'état. ### Declaration d une variable reactive Vous avez 3 façons de transformer une variable en "observable". 1 - La première est d'utiliser **`Rx{Type}`**. ```dart // la valeur initiale est recommandée, mais pas obligatoire final name = RxString(''); final isLogged = RxBool(false); final count = RxInt(0); final balance = RxDouble(0.0); final items = RxList([]); final myMap = RxMap({}); ``` 2 - La seconde consiste à utiliser **`Rx`** et à utiliser les types `Rx` Génériques Darts ```dart final name = Rx(''); final isLogged = Rx(false); final count = Rx(0); final balance = Rx(0.0); final number = Rx(0); final items = Rx>([]); final myMap = Rx>({}); // Classes personnalisées - il peut s'agir de n'importe quelle classe, littéralement final user = Rx(); ``` 3 - La troisième approche, plus pratique, plus facile et préférée, ajoutez simplement **`.obs`** comme propriété de votre` valeur`: ```dart final name = ''.obs; final isLogged = false.obs; final count = 0.obs; final balance = 0.0.obs; final number = 0.obs; final items = [].obs; final myMap = {}.obs; // Classes personnalisées - il peut s'agir de n'importe quelle classe, littéralement final user = User().obs; ``` ##### Avoir un etat reactif, c est facile. Comme nous le savons, _Dart_ se dirige maintenant vers _null safety_. Pour être prêt, à partir de maintenant, vous devez toujours commencer vos variables _Rx_ avec une **valeur initiale**. > Transformer une variable en _observable_ + _valeurInitiale_ avec **GetX** est l'approche la plus simple et la plus pratique. Vous allez littéralement ajouter un "".obs"" à la fin de votre variable, et **c'est tout**, vous l'avez rendue observable, et sa `.value`, eh bien, sera la _valeurInitiale_. ### Utilisation des valeurs dans la Vue ```dart // dans le controlleur final count1 = 0.obs; final count2 = 0.obs; int get sum => count1.value + count2.value; ``` ```dart // dans la vue GetX( builder: (controller) { print("count 1 reconstruction"); return Text('${controller.count1.value}'); }, ), GetX( builder: (controller) { print("count 2 reconstruction"); return Text('${controller.count2.value}'); }, ), GetX( builder: (controller) { print("count 3 reconstruction"); return Text('${controller.sum}'); }, ), ``` Si nous incrémentons `count1.value++`, cela affichera: - `count 1 reconstruction` - `count 3 reconstruction` parce que `count1` a une valeur de `1`, et `1 + 0 = 1`, changeant la valeur du getter `sum`. Si nous incrémentons `count2.value++`, cela affichera: - `count 2 reconstruction` - `count 3 reconstruction` parce que `count2.value` a changé et que le résultat de `sum` est maintenant `2`. - NOTE: Par défaut, le tout premier événement reconstruira le widget, même s'il s'agit de la même `valeur`. Ce comportement existe en raison de variables booléennes. Imaginez que vous fassiez ceci: ```dart var isLogged = false.obs; ``` Et puis, vous vérifiez si un utilisateur est "connecté" pour déclencher un événement dans `ever`. ```dart @override onInit(){ ever(isLogged, fireRoute); isLogged.value = await Preferences.hasToken(); } fireRoute(logged) { if (logged) { Get.off(Home()); } else { Get.off(Login()); } } ``` si `hasToken` était `false`, il n'y aurait pas de changement à `isLogged`, donc` ever() `ne serait jamais appelé. Pour éviter ce type de comportement, la première modification d'un _observable_ déclenchera toujours un événement, même s'il contient la même `.value`. Vous pouvez supprimer ce comportement si vous le souhaitez, en utilisant: `isLogged.firstRebuild = false;` ### Conditions pour reconstruire En outre, Get fournit un contrôle d'état raffiné. Vous pouvez conditionner un événement (comme l'ajout d'un objet à une liste), à ​​une certaine condition. ```dart // Premier paramètre: condition, doit retourner vrai ou faux. // Deuxième paramètre: la nouvelle valeur à appliquer si la condition est vraie. list.addIf(item < limit, item); ``` Sans décorations, sans générateur de code, sans complications :smile: Connaissez-vous l'application 'counter' de Flutter? Votre classe Controller pourrait ressembler à ceci: ```dart class CountController extends GetxController { final count = 0.obs; } ``` Avec un simple: ```dart controller.count.value++ ``` Vous pouvez mettre à jour la variable de compteur dans votre interface utilisateur, quel que soit l'endroit où elle est stockée. ### Quand utiliser .obs Vous pouvez tout transformer sur obs. Voici deux façons de procéder: * Vous pouvez convertir vos valeurs de classe en obs ```dart class RxUser { final name = "Camila".obs; final age = 18.obs; } ``` * ou vous pouvez convertir la classe entière en un observable: ```dart class User { User({String name, int age}); var name; var age; } // en instanciant: final user = User(name: "Camila", age: 18).obs; ``` ### Remarque sur List Les listes sont complètement observables, tout comme les objets qu'elles contiennent. De cette façon, si vous ajoutez une valeur à une liste, cela reconstruira automatiquement les widgets qui l'utilisent. Vous n'avez pas non plus besoin d'utiliser ".value" avec des listes, l'incroyable api de Dart nous a permis de supprimer cela. Malheureusement, les types primitifs comme String et int ne peuvent pas être étendus, ce qui rend l'utilisation de .value obligatoire, mais ce ne sera pas un problème si vous travaillez avec des getters et des setters pour ceux-ci. ```dart // Dans le controlleur final String title = 'User Info:'.obs; final list = List().obs; // Dans la vue Text(controller.title.value), // La String doit avoir .value devant elle ListView.builder ( itemCount: controller.list.length // pas besoin pour List ) ``` Lorsque vous rendez vos propres classes observables, il existe une manière différente de les mettre à jour: ```dart // sur le fichier modèle // nous allons rendre la classe entière observable au lieu de chaque attribut class User() { User({this.name = '', this.age = 0}); String name; int age; } // Dans le controlleur final user = User().obs; // lorsque vous devez mettre à jour la variable utilisateur: user.update( (user) { // ce paramètre est la classe même que vous souhaitez mettre à jour user.name = 'Jonny'; user.age = 18; }); // une autre manière de mettre à jour la variable user: user(User(name: 'João', age: 35)); // Dans la vue: Obx(()=> Text("Name ${user.value.name}: Age: ${user.value.age}")) // vous pouvez également accéder aux valeurs du modèle sans le .value: user().name; //notez que c'est la variable utilisateur, pas la classe (la variable a un u minuscule) ``` Vous n'êtes pas obligé de travailler avec des setters si vous ne le souhaitez pas. vous pouvez utiliser les API `assign` et `assignAll`. L'API `assign` effacera votre liste et ajoutera un seul objet que vous souhaitez. L'API `assignAll` effacera la liste existante et ajoutera tous les objets itérables que vous y injecterez. ### Pourquoi je dois utiliser .value Nous pourrions supprimer l'obligation d'utiliser 'value' pour `String` et` int` avec une simple décoration et un générateur de code, mais le but de cette bibliothèque est précisément d'éviter les dépendances externes. Nous souhaitons proposer un environnement prêt à la programmation, impliquant l'essentiel (gestion des routes, des dépendances et des états), de manière simple, légère et performante, sans avoir besoin d'un package externe. Vous pouvez littéralement ajouter 3 lettres à votre pubspec (get) et un signe deux-points et commencer la programmation. Toutes les solutions incluses par défaut, de la gestion des routes à la gestion des états, visent la facilité, la productivité et la performance. Le poids total de cette bibliothèque est inférieur à celui d'un seul gestionnaire d'état, bien qu'il s'agisse d'une solution complète, et c'est ce que vous devez comprendre. Si vous êtes dérangé par `.value`, et comme un générateur de code, MobX est une excellente alternative, et vous pouvez l'utiliser en conjonction avec Get. Pour ceux qui veulent ajouter une seule dépendance dans pubspec et commencer à programmer sans se soucier de l'incompatibilité de la version d'un package avec un autre, ou si l'erreur d'une mise à jour d'état vient du gestionnaire d'état ou de la dépendance, ou encore, ne veulent pas s'inquiéter de la disponibilité des contrôleurs, que ce soit littéralement "juste de la programmation", get est tout simplement parfait. Si vous n'avez aucun problème avec le générateur de code MobX, ou si vous n'avez aucun problème avec le code standard BLoC, vous pouvez simplement utiliser Get pour les routes et oublier qu'il a un gestionnaire d'état. Get SEM et RSM sont nés par nécessité, mon entreprise avait un projet avec plus de 90 contrôleurs, et le générateur de code a simplement pris plus de 30 minutes pour terminer ses tâches après un Flutter Clean sur une machine raisonnablement bonne, si votre projet il a 5, 10, 15 contrôleurs, n'importe quel gestionnaire d'état vous suffira bien. Si vous avez un projet d'une taille absurde et que le générateur de code est un problème pour vous, cette solution vous a été attribuée. Évidemment, si quelqu'un veut contribuer au projet et créer un générateur de code, ou quelque chose de similaire, je vais créer un lien dans ce readme comme alternative, mon besoin n'est pas le besoin de tous les développeurs, mais pour l'instant je dis q'il y a de bonnes solutions qui font déjà cela, comme MobX. ### Obx() Les types dans Get à l'aide de Bindings ne sont pas nécessaires. Vous pouvez utiliser le widget Obx, au lieu de GetX, qui ne reçoit que la fonction anonyme qui crée un widget. Évidemment, si vous n'utilisez pas de type, vous devrez avoir une instance de votre contrôleur pour utiliser les variables, ou utiliser `Get.find()` .value ou Controller.to.value pour récupérer la valeur . ### Workers Les 'workers' vous assisteront, déclenchant des callbacks spécifiques lorsqu'un événement se produit. ```dart /// Appelée à chaque fois que `count1` change. ever(count1, (_) => print("$_ a été modifié")); /// Appelée uniquement la première fois que la variable est modifiée once(count1, (_) => print("$_ a été changé une fois")); /// Anti DDos - Appelée chaque fois que l'utilisateur arrête de taper pendant 1 seconde, par exemple. debounce(count1, (_) => print("debouce$_"), time: Duration(seconds: 1)); /// Ignore toutes les modifications pendant 1 seconde. interval(count1, (_) => print("interval $_"), time: Duration(seconds: 1)); ``` Tous les workers (sauf `debounce`) ont un paramètre nommé `condition`, qui peut etre un `bool` ou un callback qui retourne un `bool`. Cette `condition` definit quand la fonction `callback` est executée. Tous les workers renvoyent un objet `Worker`, qui peut être utilisé pour annuler ( via `dispose()` ) le `worker`. - **`ever`** est appelée chaque fois que la variable _Rx_ émet une nouvelle valeur. - **`everAll`** Un peu comme `ever`, mais il prend une` List` de valeurs _Rx_. Appelée chaque fois que sa variable est changée. C'est tout. - **`once`** 'once' est appelée uniquement la première fois que la variable a été modifiée. - **`debounce`** 'debounce' est très utile dans les fonctions de recherche, où vous souhaitez que l'API ne soit appelée que lorsque l'utilisateur a fini de taper. Si l'utilisateur tape "Jonny", vous aurez 5 recherches dans les API, par la lettre J, o, n, n et y. Avec Get, cela ne se produit pas, car vous aurez un Worker "anti-rebond" qui ne sera déclenché qu'à la fin de la saisie. - **`interval`** 'interval' est différent de 'debounce'. Avec `debounce` si l'utilisateur fait 1000 changements à une variable en 1 seconde, il n'enverra que le dernier après le temporisateur stipulé (la valeur par défaut est 800 millisecondes). 'Interval' ignorera à la place toutes les actions de l'utilisateur pour la période stipulée. Si vous envoyez des événements pendant 1 minute, 1000 par seconde, debounce ne vous enverra que le dernier, lorsque l'utilisateur arrête de mitrailler les événements. interval délivrera des événements toutes les secondes, et s'il est réglé sur 3 secondes, il fournira 20 événements cette minute. Ceci est recommandé pour éviter les abus, dans des fonctions où l'utilisateur peut rapidement cliquer sur quelque chose et obtenir un avantage (imaginez que l'utilisateur puisse gagner des pièces en cliquant sur quelque chose, s'il cliquait 300 fois dans la même minute, il aurait 300 pièces, en utilisant l'intervalle, vous pouvez définir une période de 3 secondes, et même en cliquant 300 ou mille fois, le maximum qu'il obtiendrait en 1 minute serait de 20 pièces, en cliquant 300 ou 1 million de fois). Le 'debounce' convient aux anti-DDos, pour des fonctions comme la recherche où chaque changement de onChange entraînerait une requête à votre api. Debounce attendra que l'utilisateur arrête de taper le nom, pour faire la demande. S'il était utilisé dans le scénario de pièces mentionné ci-dessus, l'utilisateur ne gagnerait qu'une pièce, car il n'est exécuté que lorsque l'utilisateur "fait une pause" pendant le temps établi. - NOTE: Les 'workers' doivent toujours être utilisés lors du démarrage d'un contrôleur ou d'une classe, il doit donc toujours être dans onInit (recommandé), le constructeur de classe ou l'initState d'un StatefulWidget (cette pratique n'est pas recommandée dans la plupart des cas, mais cela ne devrait poser aucun problème). ## Gestionnaire d etat simple Get a un gestionnaire d'état extrêmement léger et facile, qui n'utilise pas ChangeNotifier, répondra aux besoins en particulier des nouveaux utilisateurs de Flutter et ne posera pas de problèmes pour les applications volumineuses. GetBuilder vise précisément le contrôle de plusieurs états. Imaginez que vous avez ajouté 30 produits à un panier, que vous cliquez sur supprimer un, en même temps que la liste est mise à jour, le prix est mis à jour et le badge dans le panier est mis à jour avec un nombre plus petit. Ce type d'approche fait de GetBuilder un tueur, car il regroupe les états et les modifie tous à la fois sans aucune "logique de calcul" pour cela. GetBuilder a été créé avec ce type de situation à l'esprit, car pour un changement d'état éphémère, vous pouvez utiliser setState et vous n'aurez pas besoin d'un gestionnaire d'état pour cela. De cette façon, si vous voulez un contrôleur individuel, vous pouvez lui attribuer des ID ou utiliser GetX. Cela dépend de vous, en vous rappelant que plus vous avez de widgets "individuels", plus les performances de GetX se démarqueront, tandis que les performances de GetBuilder devraient être supérieures, en cas de changement d'état multiple. ### Atouts 1. Met à jour uniquement les widgets requis. 2. N'utilise pas changeNotifier, c'est le gestionnaire d'état qui utilise le moins de mémoire (proche de 0 Mo). 3. Oubliez StatefulWidget! Avec Get, vous n'en aurez jamais besoin. Avec les autres gestionnaires d'états, vous devrez probablement utiliser un StatefulWidget pour obtenir l'instance de votre fournisseur, BLoC, MobX Controller, etc. Mais vous êtes-vous déjà arrêté pour penser que votre appBar, votre 'scaffold', et la plupart des les widgets de votre classe sont sans état (stateless)? Alors pourquoi sauvegarder l'état d'une classe entière, si vous pouvez sauvegarder l'état du widget qui est `avec état` (statefull)? Get résout cela aussi. Créez une classe sans état, rendez tout `sans état`. Si vous devez mettre à jour un seul composant, enveloppez-le avec GetBuilder et son état sera conservé. 4. Organisez votre projet pour de vrai! Les contrôleurs ne doivent pas être dans votre interface utilisateur, placer votre TextEditController ou tout contrôleur que vous utilisez dans votre classe Controller. 5. Avez-vous besoin de déclencher un événement pour mettre à jour un widget dès son rendu? GetBuilder a la propriété "initState", tout comme StatefulWidget, et vous pouvez appeler des événements depuis votre contrôleur, directement depuis celui-ci, aucun événement n'étant placé dans votre initState. 6. Avez-vous besoin de déclencher une action comme la fermeture de stream, de timers, etc.? GetBuilder a également la propriété dispose(), où vous pouvez appeler des événements dès que ce widget est détruit. 7. N'utilisez les streams que si nécessaire. Vous pouvez utiliser vos StreamControllers à l'intérieur de votre contrôleur normalement, et utiliser StreamBuilder également normalement, mais rappelez-vous qu'un stream consomme raisonnablement de la mémoire, la programmation réactive est belle, mais vous ne devriez pas en abuser. 30 streams ouverts simultanément peuvent être pires que changeNotifier (et changeNotifier est très mauvais). 8. Mettez à jour les widgets sans dépenser de RAM pour cela. Get stocke uniquement l'ID de créateur GetBuilder et met à jour ce GetBuilder si nécessaire. La consommation de mémoire du stockage get ID en mémoire est très faible, même pour des milliers de GetBuilders. Lorsque vous créez un nouveau GetBuilder, vous partagez en fait l'état de GetBuilder qui a un ID de créateur. Un nouvel état n'est pas créé pour chaque GetBuilder, ce qui économise BEAUCOUP de RAM pour les applications volumineuses. Fondamentalement, votre application sera entièrement sans état (stateless), et les quelques widgets qui seront stateful (dans GetBuilder) auront un seul état, et par conséquent, la mise à jour d'un seul les mettra tous à jour. L'état est unique. 9. Get est omniscient et, dans la plupart des cas, il sait exactement quand sortir de mémoire un contrôleur. Vous ne devez pas vous soucier du moment de vous débarrasser d'un contrôleur, Get connaît le meilleur moment pour le faire. ### Utilisation ```dart // Créez la classe controller qui 'extends' GetxController class Controller extends GetxController { int counter = 0; void increment() { counter++; update(); // utilisez update() pour mettre à jour la variable de compteur sur l'interface utilisateur lorsque incrément() est appelé } } // Sur votre classe Stateless / Stateful, utilisez GetBuilder pour mettre à jour le texte lorsque incrément() est appelé GetBuilder( init: Controller(), // INITIER CA UNIQUEMENT LA PREMIÈRE FOIS builder: (_) => Text( '${_.counter}', ), ) //Initialisez votre contrôleur uniquement la première fois. La deuxième fois que vous utilisez ReBuilder pour le même contrôleur, ne recommencez pas. Votre contrôleur sera automatiquement supprimé de la mémoire dès que le widget qui l'a marqué comme `init` sera déployé. Vous n'avez pas à vous en soucier, Get le fera automatiquement, assurez-vous simplement de ne pas démarrer deux fois le même contrôleur. ``` **Fait!** - Vous avez déjà appris à gérer les états avec Get. - Note: Vous pouvez souhaiter une organisation plus grande et ne pas utiliser la propriété init. Pour cela, vous pouvez créer une classe et étendre la classe Bindings, et y mentionner les contrôleurs qui seront créés dans cette route. Les contrôleurs ne seront pas créés à ce moment-là, au contraire, il ne s'agit que d'une déclaration, de sorte que la première fois que vous utilisez un contrôleur, Get saura où chercher. Get restera lazyLoad et continuera à supprimer les contrôleurs lorsqu'ils ne seront plus nécessaires. Voir l'exemple pub.dev pour voir comment cela fonctionne. Si vous parcourez de nombreuses routes et avez besoin de données qui se trouvaient dans votre contrôleur précédemment utilisé, il vous suffit de réutiliser GetBuilder (sans init): ```dart class OtherClass extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( body: Center( child: GetBuilder( builder: (s) => Text('${s.counter}'), ), ), ); } ``` Si vous devez utiliser votre contrôleur dans de nombreux autres endroits, et en dehors de GetBuilder, créez simplement un get dans votre contrôleur et ayez-le facilement. (ou utilisez `Get.find()`) ```dart class Controller extends GetxController { /// Vous n'en avez pas besoin. Je recommande de l'utiliser uniquement pour faciliter la syntaxe. /// avec la méthode statique: Controller.to.counter(); /// sans méthode statique: Get.find() .counter(); /// Il n'y a aucune différence de performances, ni aucun effet secondaire de l'utilisation de l'une ou l'autre syntaxe. Un seul n'a pas besoin du type, et l'autre l'EDI le complétera automatiquement. static Controller get to => Get.find(); // Ajouter cette ligne int counter = 0; void increment() { counter++; update(); } } ``` Et puis vous pouvez accéder directement à votre contrôleur, de cette façon: ```dart FloatingActionButton( onPressed:() { Controller.to.increment(), } // This is incredibly simple! child: Text("${Controller.to.counter}"), ), ``` Lorsque vous appuyez sur FloatingActionButton, tous les widgets qui écoutent la variable `counter` seront mis à jour automatiquement. ### Comment il gère les contrôleurs Disons que nous avons ceci: `Class a => Class B (has controller X) => Class C (has controller X)` Dans la classe A, le contrôleur n'est pas encore en mémoire, car vous ne l'avez pas encore utilisé (Get est lazyLoad). Dans la classe B, vous avez utilisé le contrôleur et il est entré en mémoire. Dans la classe C, vous avez utilisé le même contrôleur que dans la classe B, Get partagera l'état du contrôleur B avec le contrôleur C, et le même contrôleur est toujours en mémoire. Si vous fermez l'écran C et l'écran B, Get retirera automatiquement le contrôleur X de la mémoire et libèrera des ressources, car la classe A n'utilise pas le contrôleur. Si vous naviguez à nouveau vers B, le contrôleur X entrera à nouveau en mémoire, si au lieu de passer à la classe C, vous revenez en classe A, Get retirera le contrôleur de la mémoire de la même manière. Si la classe C n'utilisait pas le contrôleur et que vous retiriez la classe B de la mémoire, aucune classe n'utiliserait le contrôleur X et de même, elle serait éliminée. La seule exception qui peut gâcher Get, est si vous supprimez B de l'itinéraire de manière inattendue et essayez d'utiliser le contrôleur dans C.Dans ce cas, l'ID de créateur du contrôleur qui était dans B a été supprimé et Get a été programmé pour supprimer de la mémoire tous les contrôleurs qui n'ont pas d'ID de créateur. Si vous avez l'intention de faire cela, ajoutez l'indicateur "autoRemove: false" au GetBuilder de la classe B et utilisez adoptID = true; dans GetBuilder de la classe C. ### Vous n'aurez plus besoin de StatefulWidgets Utiliser StatefulWidgets signifie stocker inutilement l'état d'écrans entiers, même parce que si vous avez besoin de reconstruire au minimum un widget, vous l'intègrerez dans un Consumer / Observer / BlocProvider / GetBuilder / GetX / Obx, qui sera un autre StatefulWidget. La classe StatefulWidget est une classe plus grande que StatelessWidget, qui allouera plus de RAM, et cela ne fera peut-être pas une différence significative entre une ou deux classes, mais cela le fera très certainement lorsque vous en aurez 100! À moins que vous n'ayez besoin d'utiliser un mixin, comme TickerProviderStateMixin, il sera totalement inutile d'utiliser un StatefulWidget avec Get. Vous pouvez appeler toutes les méthodes d'un StatefulWidget directement à partir d'un GetBuilder. Si vous devez appeler la méthode initState() ou dispose() par exemple, vous pouvez les appeler directement: ```dart GetBuilder( initState: (_) => Controller.to.fetchApi(), dispose: (_) => Controller.to.closeStreams(), builder: (s) => Text('${s.username}'), ), ``` Une bien meilleure approche que celle-ci consiste à utiliser les méthodes onInit() et onClose() directement à partir de votre contrôleur. ```dart @override void onInit() { fetchApi(); super.onInit(); } ``` - NOTE: Si vous voulez démarrer une méthode au moment où le contrôleur est appelé pour la première fois, vous N'AVEZ PAS BESOIN d'utiliser des constructeurs pour cela, en fait, en utilisant un package orienté performance comme Get, cela frôle la mauvaise pratique, car il s'écarte de la logique dans laquelle les contrôleurs sont créés ou alloués (si vous créez une instance de ce contrôleur, le constructeur sera appelé immédiatement, vous remplirez un contrôleur avant même qu'il ne soit utilisé, vous allouez de la mémoire sans qu'elle ne soit utilisée , cela nuit définitivement aux principes de cette bibliothèque). Les méthodes onInit(); et onClose(); ont été créés pour cela, ils seront appelés lors de la création du Controller, ou lors de sa première utilisation, selon que vous utilisez Get.lazyPut ou non. Si vous voulez, par exemple, faire un appel à votre API pour remplir des données, vous pouvez oublier la méthode à l'ancienne de initState / dispose, lancez simplement votre appel à l'API dans onInit, et si vous devez exécuter une commande comme la fermeture des flux, utilisez onClose() pour cela. ### Pourquoi ca existe Le but de ce package est précisément de vous donner une solution complète pour la navigation des routes, la gestion des dépendances et des états, en utilisant le moins de dépendances possible, avec un haut degré de découplage. Get engage toutes les API Flutter de haut et bas niveau en lui-même, pour vous assurer de travailler avec le moins de couplage possible. Nous centralisons tout dans un seul package, pour vous assurer que vous n'avez aucun type de couplage dans votre projet. De cette façon, vous pouvez mettre uniquement des widgets dans votre vue et laisser la partie de votre équipe qui travaille avec la `business logique` libre, pour travailler avec la business logique sans dépendre d'aucun élément de la vue. Cela fournit un environnement de travail beaucoup plus propre, de sorte qu'une partie de votre équipe ne travaille qu'avec des widgets, sans se soucier d'envoyer des données à votre contrôleur, et une partie de votre équipe ne travaille qu'avec la business logique dans toute son ampleur, sans dépendre d'aucun élément de la Vue. Donc, pour simplifier cela: Vous n'avez pas besoin d'appeler des méthodes dans initState et de les envoyer par paramètre à votre contrôleur, ni d'utiliser votre constructeur de contrôleur pour cela, vous avez la méthode onInit() qui est appelée au bon moment pour démarrer vos services. Vous n'avez pas besoin d'appeler l'appareil, vous avez la méthode onClose() qui sera appelée au moment exact où votre contrôleur n'est plus nécessaire et sera supprimé de la mémoire. De cette façon, ne laissez les vues que pour les widgets, abstenez-vous d'y mettre tout type de business logique. N'appelez pas une méthode dispose() dans GetxController, cela ne fera rien, rappelez-vous que le contrôleur n'est pas un Widget, vous ne devez pas le `supprimer`, et il sera automatiquement et intelligemment supprimé de la mémoire par Get. Si vous avez utilisé un Stream et que vous souhaitez le fermer, insérez-le simplement dans la méthode close(). Exemple: ```dart class Controller extends GetxController { StreamController user = StreamController(); StreamController name = StreamController(); /// pour fermer stream = méthode onClose(), pas dispose(). @override void onClose() { user.close(); name.close(); super.onClose(); } } ``` Cycle de vie du controlleur: - onInit() quand il est créé. - onClose() quand il est fermé pour apporter des modifications en préparation de la méthode delete. - deleted: vous n'avez pas accès à cette API car elle supprime littéralement le contrôleur de la mémoire. Il est littéralement supprimé, sans laisser de trace. ### Autres formes d utilisation Vous pouvez utiliser l'instance Controller directement sur la valeur GetBuilder: ```dart GetBuilder( init: Controller(), builder: (value) => Text( '${value.counter}', // ici ), ), ``` Vous pouvez également avoir besoin d'une instance de votre contrôleur en dehors de votre GetBuilder, et vous pouvez utiliser ces approches pour y parvenir: ```dart class Controller extends GetxController { static Controller get to => Get.find(); [...] } // Dans la vue: GetBuilder( init: Controller(), // utilisez-le seulement la première fois sur chaque contrôleur builder: (_) => Text( '${Controller.to.counter}', // ici ) ), ``` ou encore ```dart class Controller extends GetxController { // static Controller get to => Get.find(); // sans static get [...] } // Dans la classe stateful/stateless GetBuilder( init: Controller(), // utilisez-le seulement la première fois sur chaque contrôleur builder: (_) => Text( '${Get.find().counter}', // ici ), ), ``` - Vous pouvez utiliser des approches `non canoniques` pour ce faire. Si vous utilisez un autre gestionnaire de dépendances, comme get_it, modular, etc., et que vous souhaitez simplement fournir l'instance de contrôleur, vous pouvez le faire: ```dart Controller controller = Controller(); [...] GetBuilder( init: controller, // ici builder: (_) => Text( '${controller.counter}', // ici ), ), ``` ### IDs Uniques Si vous souhaitez affiner le contrôle de mise à jour d'un widget avec GetBuilder, vous pouvez leur attribuer des ID uniques: ```dart GetBuilder( id: 'text' init: Controller(), // utilisez-le seulement la première fois sur chaque contrôleur builder: (_) => Text( '${Get.find().counter}', // ici ), ), ``` Et mettez-le à jour de cette façon: ```dart update(['text']); ``` Vous pouvez également imposer des conditions pour la mise à jour: ```dart update(['text'], counter < 10); ``` GetX le fait automatiquement et ne reconstruit que le widget qui utilise la variable exacte qui a été modifiée, si vous remplacez une variable par la même que la précédente et que cela n'implique pas un changement d'état, GetX ne reconstruira pas le widget pour économiser de la mémoire et Cycles CPU (3 est affiché à l'écran, et vous changez à nouveau la variable à 3. Dans la plupart des gestionnaires d'états, cela entraînera une nouvelle reconstruction, mais avec GetX, le widget ne sera reconstruit qu'à nouveau, si en fait son état a changé ). ## Mélanger les deux gestionnaires d'état Certaines personnes ont ouvert une demande de fonctionnalité, car elles ne voulaient utiliser qu'un seul type de variable réactive, et les autres mécanismes, et devaient insérer un Obx dans un GetBuilder pour cela. En y réfléchissant, MixinBuilder a été créé. Il permet à la fois des changements réactifs en changeant les variables ".obs" et des mises à jour mécaniques via update(). Cependant, des 4 widgets c'est celui qui consomme le plus de ressources, car en plus d'avoir un Abonnement pour recevoir les événements de changement de ses enfants, il souscrit à la méthode de mise à jour de son contrôleur. L'extension de GetxController est importante, car ils ont des cycles de vie et peuvent `démarrer` et `terminer` des événements dans leurs méthodes onInit() et onClose(). Vous pouvez utiliser n'importe quelle classe pour cela, mais je vous recommande fortement d'utiliser la classe GetxController pour placer vos variables, qu'elles soient observables ou non. ## GetBuilder vs GetX vs Obx vs MixinBuilder En une décennie de travail avec la programmation, j'ai pu apprendre de précieuses leçons. Mon premier contact avec la programmation réactive a été tellement "wow, c'est incroyable" et en fait la programmation réactive est incroyable. Cependant, elle ne convient pas à toutes les situations. Souvent, il suffit de changer l'état de 2 ou 3 widgets en même temps, ou d'un changement d'état éphémère, auquel cas la programmation réactive n'est pas mauvaise, mais elle n'est pas appropriée. La programmation réactive a une consommation de RAM plus élevée qui peut être compensée par le flux de travail individuel, ce qui garantira qu'un seul widget est reconstruit et si nécessaire, mais créer une liste avec 80 objets, chacun avec plusieurs flux n'est pas une bonne idée . Ouvrez le 'dart inspect' et vérifiez combien un StreamBuilder consomme, et vous comprendrez ce que j'essaie de vous dire. Dans cet esprit, j'ai créé le gestionnaire d'état simple. C'est simple, et c'est exactement ce que vous devriez lui demander: mettre à jour l'état par blocs de manière simple et de la manière la plus économique. GetBuilder est très économique en RAM, et il n'y a guère d'approche plus économique que lui (du moins je ne peux pas en imaginer une, si elle existe, merci de nous le faire savoir). Cependant, GetBuilder est toujours un gestionnaire d'état mécanique, vous devez appeler update() comme vous auriez besoin d'appeler les notifyListeners() de Provider. Il y a d'autres situations où la programmation réactive est vraiment intéressante, et ne pas travailler avec elle revient à réinventer la roue. Dans cet esprit, GetX a été créé pour fournir tout ce qui est le plus moderne et le plus avancé dans un gestionnaire d'état. Il met à jour uniquement ce qui est nécessaire et si nécessaire, si vous avez une erreur et envoyez 300 changements d'état simultanément, GetX filtrera et mettra à jour l'écran uniquement si l'état change réellement. GetX est toujours plus économique que tout autre gestionnaire d'état réactif, mais il consomme un peu plus de RAM que GetBuilder. En y réfléchissant et en visant à maximiser la consommation de ressources, Obx a été créé. Contrairement à GetX et GetBuilder, vous ne pourrez pas initialiser un contrôleur à l'intérieur d'un Obx, c'est juste un widget avec un StreamSubscription qui reçoit les événements de changement de vos widgets enfants, c'est tout. Il est plus économique que GetX, mais perd face à GetBuilder, ce qui était prévisible, car il est réactif, et GetBuilder a l'approche la plus simpliste qui existe, de stocker le hashcode d'un widget et son StateSetter. Avec Obx, vous n'avez pas besoin d'écrire votre type de contrôleur, et vous pouvez entendre le changement de plusieurs contrôleurs différents, mais il doit être initialisé avant, soit en utilisant l'approche d'exemple au début de ce readme, soit en utilisant la classe Bindings. ================================================ FILE: documentation/id_ID/dependency_management.md ================================================ - [Dependency Management](#dependency-management) - [Menginstansiasi method](#menginstansiasi-method) - [Get.put()](#getput) - [Get.lazyPut](#getlazyput) - [Get.putAsync](#getputasync) - [Get.create](#getcreate) - [Menggunakan method/kelas yang terinstansiasi](#menggunakan-methodkelas-yang-terinstansiasi) - [Perbedaan antar method](#perbedaan-antar-method) - [Bindings](#bindings) - [Bindings class](#bindings-class) - [BindingsBuilder](#bindingsbuilder) - [SmartManagement](#smartmanagement) - [Cara mengubahnya](#cara-mengubahnya) - [SmartManagement.full](#smartmanagementfull) - [SmartManagement.onlyBuilders](#smartmanagementonlybuilders) - [SmartManagement.keepFactory](#smartmanagementkeepfactory) - [Cara kerja bindings dibalik layar](#cara-kerja-bindings-dibalik-layar) - [Catatan](#catatan) # Dependency Management Get memiliki dependency manager sederhana dan powerful yang memungkinkan anda mendapatkan kelas yang setara dengan Bloc atau Controller hanya dengan 1 baris kode, tanpa Provider context, tanpa inheritedWidget: ```dart Controller controller = Get.put(Controller()); ``` Daripada menginstansiasi kelas anda didalam kelas yang anda gunakan, cukup lakukan hal itu di dalam Get instance, ini akan membuatnya tersedia di semua tempat di Aplikasimu. Jadi anda bisa menggunakan controller (atau class Bloc) secara normal. - Catatan: Jika anda menggunakan State Manager milik Get, harap untuk lebih memperhatikan [Bindings](#bindings) api, yang mana akan membuat pengkoneksian View terhadap Controller jadi lebih mudah. - Note²: Dependency Management Get terpisah dari bagian lain dari package, jadi jika sebagai contoh aplikasi anda sudah menggunakan state manager (tidak peduli apapun itu), anda tidak perlu menulis ulang sama sekali, anda bisa menggunakan dependency injection tanpa masalah. ## Menginstansiasi method Berikut adalah metode dan parameternya yang dapat dikonfigurasi: ### Get.put() Cara paling umum untuk memasukkan dependensi, untuk kontroler dari view anda contohnya. ```dart Get.put(SomeClass()); Get.put(LoginController(), permanent: true); Get.put(ListItemController, tag: "some unique string"); ``` Berikut adalah semua opsi yang bisa anda atur ketika menggunakan put: ```dart Get.put( // wajib: kelas yang ingin anda simpan, seperti controller, atau apapun // catatan: "S" menandakan bahwa tipenya bisa jadi sebuah kelas dari tipe apapun. S dependency // opsional: ini digunakan ketika anda ingin memasukkan banyak kelas yang memiliki tipe yang sama. // berhubung normalnya anda memanggil kelas menggunakan Get.find(), // anda perlu menggunakan tag untuk menandai instance mana yang anda butuhkan // tag harus unik, dan bertipe String. String tag, // opsional: secara default, get akan men-dispose instance setelah tidak digunakan lagi (contoh, // sebuah controller dari view yang ditutup), tapi mungkin anda membutuhkannya untuk digunakan // ditempat lain di aplikasi anda, contohnya seperti sebuah instance dari SharedPreference, atau yang lain. // Maka anda perlu ini // nilai defaultnya adalah false bool permanent = false, // opsional: memungkinkan anda setelah menggunakan kelas abstrak didalam test, menggantinya dengan yang lain dan mengikuti testnya. // nilai defaultnya adalah false bool overrideAbstract = false, // opsional: memungkinkan anda untuk memasukkan dependensi menggunakan fungsi daripada dependensi itu sendiri. // ini jarang dipakai. InstanceBuilderCallback builder, ) ``` ### Get.lazyPut Anda bisa melakukan lazyload terhadap sebuah dependensi supaya dependensi tersebut terinstansiasi hanya ketika digunakan saja. Sangat berguna untuk kelas komputasional yang "mahal" atau jika anda ingin menginstansiasi beberapa kelas hanya dalam satu lokasi (seperti pada kelas Bindings) dan anda tahu anda tidak akan menggunakannya secara langsung. ```dart /// ApiMock hanya akan dipanggil ketika seseorang menggunakan Get.find pertama kali. Get.lazyPut(() => ApiMock()); Get.lazyPut( () { // ... beberapa logic jika diperlukan.. return FirebaseAuth(); }, tag: Math.random().toString(), fenix: true ) Get.lazyPut( () => Controller() ) ``` Berikut adalah semua opsi yang bisa anda atur ketika menggunakan lazyPut: ```dart Get.lazyPut( // wajib: sebuah method yang akan di eksekusi ketika kelas anda dipanggil untuk pertama kali InstanceBuilderCallback builder, // opsional: sama seperti Get.put(), ini digunakan ketika anda menginginkan banyak instance berbeda dengan kelas yang sama // harus unik dan harus String. String tag, // opsional: Mirip seperti "permanent", bedanya adalah instance akan dihapus ketika tidak // digunakan, tetapi ketika diperlukan lagi, Get akan membuat ulang instance yang sama, // seperti "SmartManagement.keepFactory" pada bindings api. bool fenix = false ) ``` ### Get.putAsync Jika anda ingin mendaftarkan instance yang asynchronous, anda bisa menggunakan `Get.putAsync()`: ```dart Get.putAsync(() async { final prefs = await SharedPreferences.getInstance(); await prefs.setInt('counter', 12345); return prefs; }); Get.putAsync( () async => await YourAsyncClass() ) ``` Berikut adalah semua opsi yang anda bisa atur ketika menggunakan putAsync: ```dart Get.putAsync( // wajib: sebuah async method yang akan di eksekusi untuk menginstansiasi kelas anda AsyncInstanceBuilderCallback builder, // opsional: sama seperti Get.put(), ini digunakan ketika anda menginginkan banyak instance berbeda dengan kelas yang sama // harus unik dan harus String. String tag, // opsional: sama seperti Get.put(), digunakan ketika anda ingin mempertahankan // instance tersebut (keep-alive) untuk digunakan diseluruh aplikasi anda. // nilai defaultnya adalah false bool permanent = false ) ``` ### Get.create Yang satu ini agak rumit. Penjelasan lebih detail tentang ini dan perbedaannya dengan yang lain bisa ditemukan di sesi [Perbedaan antar method](#perbedaan-antar-method). ```dart Get.create(() => SomeClass()); Get.create(() => LoginController()); ``` Berikut adalah semua opsi yang bisa anda atur ketika menggunakan create: ```dart Get.create( // diperlukan: sebuah fungsi yang mereturn sebuah kelas yang akan "terfabrikasi" setiap // kali `Get.find()` dipanggil // Contoh: Get.create(() => YourClass()) FcBuilderFunc builder, // opsional: sama seperti Get.put(), ini digunakan ketika anda menginginkan // banyak instance berbeda dengan kelas yang sama. // Berguna dalam kasus ketika anda memiliki sebuah list yang setiap isinya membutuhkan // controllernya masing-masing. // Harus unik dan harus String. Cukup ganti `tag` menjadi `name`. String name, // opsional: sama seperti Get.put(), digunakan ketika anda ingin mempertahankan // instance tersebut (keep-alive) untuk digunakan diseluruh aplikasi anda. // Untuk Get.create, `permanent` nilainya `true` secara default. bool permanent = true ``` ## Menggunakan method/kelas yang terinstansiasi Bayangkan anda bernavigasi melewati route yang sangat banyak, dan anda membutuhkan data yang tertinggal didalam controller jauh di belakang route sebelumnya, anda akan butuh state manager dikombinasikan dengan Provider atau Get_it, benar kan? Tidak dengan Get. Anda hanya perlu meminta Get untuk "menemukan" controllernya, anda tidak perlu dependensi tambahan: ```dart final controller = Get.find(); // ATAU Controller controller = Get.find(); // Ya, terlihat seperti Sulap, Get akan menemukan controller anda, dan akan mengantarkannya ke lokasi anda. // Anda bisa memiliki 1 juta controller terinisialisasi, Get akan selalu memberimu controller yang tepat. ``` Dan setelahnya anda bisa memperoleh data yang tertinggal sebelumnya: ```dart Text(controller.textFromApi); ``` Berhubung value yang direturn adalah sebuah kelas normal, anda bisa melakukan apapun yang anda mau: ```dart int count = Get.find().getInt('counter'); print(count); // keluaran: 12345 ``` Untuk menghapus sebuah instance dari Get: ```dart Get.delete(); // biasanya anda tidak perlu melakukan ini karena GetX sudah melakukannya untuk anda ``` ## Perbedaan antar method Sebelum kita mulai, mari kita bahas tentang `fenix` dari Get.lazyPut dan `permanent` dari method lainnya. Perbedaan mendasar diantara `permanent` dan `fenix` adalah bagaimana anda ingin menyimpannya (kelas anda). Menguatkan: secara default, GetX menghapus instance ketika tidak digunakan. Artinya: Jika screen 1 memiliki controller 1, dan screen 2 memiliki controller 2, dan anda menghapus route pertama dari stack, (seperti pada halnya anda menggunakan `Get.off()` atau `Get.offNamed()`), controller 1 akan kehilangan status kegunaannya dan akan dihapus. Tapi jika anda menggunakan `permanent:true`, maka controller tidak akan dihapus pada saat berpindah halaman - yang mana sangat berguna untuk service yang ingin anda pertahankan supaya tetap ada (keep-alive) diseluruh aplikasi anda. `fenix` di sisi lain adalah sebuah service yang anda tidak perlu khawatir kehilangan ketika berpindah antar halaman, tetapi ketika anda membutuhkan service tersebut, anda berekspektasi bahwa service tersebut masih ada. Walaupun sebenarnya, controller/service/class tetap ter-dispose, dan ketika anda membutuhkannya, dia akan membuat ulang (dari abu-nya) sebuah instance baru. Lanjut dengan perbedaan antar method: - Get.put dan Get.putAsync mengikuti urutan pembuatan yang sama, bedanya, yang kedua menggunakan asynchronous method: kedua method tersebut membuat dan menginisialisasi sebuah instance. Dimasukkan secara langsung kedalam memori, menggunakan method internal `insert` dengan parameter `permanent:false` dan `isSingleton:true` (isSingleton parameter ini hanya hanya bertujuan untuk membedakan apakah harus menggunakannya pada `dependency` atau menggunakannya pada `FcBuilderFunc`). Setelah itu, `Get.find()` dipanggil dan segera menginisialisasi instance yang ada didalam memori. - Get.create: seperti namanya, dia akan "membuat" dependensi anda! Mirip seperti `Get.put()`, dia juga memanggil metode internal `insert` untuk menginstansiasi. Namun mengubah `permanent` menjadi true dan `isSingleton` menjadi false (karena kita "membuat" dependensi, tidak ada cara untuk menjadikannya sebagai singleton, inilah kenapa nilainya false). Dan karena dia memiliki `permanent:true`, kita secara default memiliki keuntungan untuk tidak kehilangannya pada saat berpindah halaman! Dan juga, `Get.find()` tidak dipanggil secara langsung, dia menunggu untuk digunakan disuatu halaman untuk dipanggil. Ini dibuat dengan cara tersebut supaya bisa menggunakan parameter `permanent`, sementara itu, perlu diketahui, `Get.create()` dibuat dengan tujuan untuk membuat instance yang tidak dapat di-share, tetapi juga tidak ter-dispose, seperti contohnya sebuah button didalam ListView, dan anda menginginkan sebuah instance unik untuk list tersebut - karena itu, Get.create harus digunakan bersamaan dengan GetWidget. - Get.lazyPut: seperti namanya, ini membuat "lazy process". Instance nya dibuat, namun tidak digunakan secara langsung, dia menunggu untuk dipanggil. Bertentangan dengan metode lain, `insert` tidak dipanggil disini. Sebaliknya, instance dimasukkan ke bagian lain didalam memori, bagian yang bertanggung jawab untuk memberi tahu apakah instance bisa dibuat ulang atau tidak, mari kita sebut itu sebagai "factory". Jika kita ingin membuat sesuatu untuk digunakan nanti, ini tidak akan tercampur dengan yang digunakan sekarang. Dan disini adalah dimana "sihir" `fenix` terjadi: jika anda memilih untuk membiarkan `fenix: false`, dan `smartManagement` bukan `keepFactory`, maka ketika menggunakan `Get.find`, instance akan mengubah posisinya didalam memori supaya tersedia di bagian terpisah ini, bahkan menuju ke area umum, untuk dipanggil lagi di masa yang akan datang. ## Bindings Salah satu dari perbedaan besar dari package ini, mungkin, adalah suatu kemungkinan untuk mengintegrasikan route, state manager, dan dependency manager secara penuh. Ketika route dihapus dari Stack, semua kontroler, variabel, dan instance yang berhubungan dengan itu akan dihapus dari memori. Jika anda menggunakan stream atau timer, mereka akan ditutup secara otomatis, dan anda tidak perlu khawatir tentang hal itu. Di versi 2.10, Get benar-benar mengimplementasi Bindings API. Sekarang anda tidak perlu menggunakan init method. Bahkan anda tidak perlu mengetik controller yang ingin anda tuju jika anda mau. Anda bisa memulai controller dan service di tempat yang tepat untuk itu. Binding class adalah sebuah kelas yang memisahkan dependency injection, sembari "mengaitkan" route ke state manager dan dependency manager. Ini memungkinkan Get untuk mengetahui tampilan mana yang sedang ditampilkan ketika controller yang bersangkutan digunakan dan untuk mengetahui bagaimana cara men-dispose nya. Sebagai tambahan, Binding class memungkinkan anda untuk memiliki kontrol terhadap konfigurasi SmartManager. Anda bisa mengkonfigurasi dependensi anda untuk diurutkan ketika penghapusan route dari stack, atau ketika widget yang digunakan diletakkan, atau tidak keduanya. Anda akan memiliki dependensi management pintar yang bekerja untuk anda, tapi meski begitu, anda bisa mengkonfigurasinya sesuka hati anda. ### Bindings class - Buat sebuah kelas yang meng-implements Bindings ```dart class HomeBinding implements Bindings {} ``` IDE anda akan secara otomatis meminta anda untuk meng-override "dependencies method", dan anda hanya perlu klik ikon lampu, override method nya, dan masukkan semua kelas yang akan anda gunakan dalam route tersebut: ```dart class HomeBinding implements Bindings { @override void dependencies() { Get.lazyPut(() => HomeController()); Get.put(()=> Api()); } } class DetailsBinding implements Bindings { @override void dependencies() { Get.lazyPut(() => DetailsController()); } } ``` Sekarang anda hanya perlu memberi tahu route anda, bahwa anda akan menggunakan binding tersebut untuk membuat koneksi diantara route manager, dependency manager dan state. - Menggunakan named route: ```dart getPages: [ GetPage( name: '/', page: () => HomeView(), binding: HomeBinding(), ), GetPage( name: '/details', page: () => DetailsView(), binding: DetailsBinding(), ), ]; ``` - Menggunakan normal route: ```dart Get.to(Home(), binding: HomeBinding()); Get.to(DetailsView(), binding: DetailsBinding()) ``` Disana, anda tidak perlu lagi khawatir tentang manajemen memori aplikasi anda, Get akan melakukannya untuk anda. Binding akan dipanggil ketika route dipanggil, anda juga bisa membuat sebuah "initialBinding" di GetMaterialApp dan memasukkan semua dependensi yang diperlukan. ```dart GetMaterialApp( initialBinding: SampleBind(), home: Home(), ); ``` ### BindingsBuilder Secara default, untuk membuat sebuah binding adalah dengan membuat kelas baru yang meng-implements Bindings. Secara alternatif, anda bisa menggunakan `BindingsBuilder` untuk menginstansiasi apapun yang anda mau melalui sebuah fungsi callback. Example: ```dart getPages: [ GetPage( name: '/', page: () => HomeView(), binding: BindingsBuilder(() { Get.lazyPut(() => ControllerX()); Get.put(()=> Api()); }), ), GetPage( name: '/details', page: () => DetailsView(), binding: BindingsBuilder(() { Get.lazyPut(() => DetailsController()); }), ), ]; ``` Dengan cara ini, anda bisa menghindari membuat satu kelas Binding untuk setiap route yang anda buat, membuatnya jadi semakin simpel. Kedua cara tersebut bekerja secara sempurna dan kami ingin anda menggunakan yang manapun yang anda rasa cocok untuk anda. ### SmartManagement GetX secara default men-dispose controller yang tidak digunakan dari memori, bahkan jika kegagalan terjadi dan widget yang menggunakannya tidak ter-dispose dengan benar. Ini adalah apa yang disebut dengan `full` mode dari sebuah dependency management. Tetapi jika anda ingin mengubah cara kerja GetX dalam mengontrol "disposal" terhadap kelas, anda memiliki kelas `SmartManagement` yang anda bisa atur perilakunya. #### Cara mengubahnya Jika anda ingin mengubah konfigurasi ini (yang biasanya tidak diperlukan), ini caranya: ```dart void main () { runApp( GetMaterialApp( smartManagement: SmartManagement.onlyBuilders // Disini home: Home(), ) ) } ``` #### SmartManagement.full Ini adalah pengaturan default. Dispose semua kelas yang tidak digunakan dan tidak ditandai sebagai permanent. Di kebanyakan kasus anda ingin konfigurasi ini tidak disentuh. Jika anda baru di GetX, jangan mengubahnya. #### SmartManagement.onlyBuilders Dengan opsi ini, hanya controller yang dimulai didalam `init:` atau yang ter-load kedalam sebuah Binding dengan `Get.lazyPut()`, yang akan di dispose. Jika anda menggunakan `Get.put()` atau `Get.putAsync()` atau cara lain, SmartManagement tidak akan memiliki izin untuk meng-exclude dependensi ini. Dengan perilaku default, bahkan widget yang terinstansiasi dengan "Get.put" akan dihapus, tidak seperti SmartManagement.onlyBuilders. #### SmartManagement.keepFactory Sama seperti SmartManagement.full, ini akan menghapus semua dependensi ketika tidak digunakan lagi. Meski begitu, ini akan menyimpan factory mereka, yang artinya dia akan membuat ulang dependensi jika anda membutuhkannya lagi. ### Cara kerja bindings dibalik layar Bindings membuat factory sementara, yang mana dibuat ketika anda meng-klik untuk pindah ke halaman lain, dan akan dihapus segera setelah animasi perpindahan halaman terjadi. Ini terjadi begitu cepat bahkan analyzer sekalipun tidak bisa meregistrasikannya. Ketika anda kembali ke halaman itu lagi, factory baru akan dipanggil, jadi ini lebih disarankan daripada menggunakan SmartManagement.keepFactory, tapi jika anda tidak ingin membuat Bindings, atau ingin menyimpan semua dependensi didalam Binding yang sama, itu akan sangat membantu. Konsumsi memori terhadap Factory sangatlah kecil, mereka tidak memegang instance, hanya sebuah fungsi dengan "bentukan" dari kelas yang anda inginkan. Ini sangat menghemat penggunaan memori, tetapi karena tujuan dari lib ini adalah untuk mendapatkan performa maksimum dan menggunakan resource seminimum mungkin, Get menghapus bahkan si factory itu sendiri secara default. Gunakan mana yang anda rasa mudah untuk anda. ## Catatan - JANGAN GUNAKAN SmartManagement.keepFactory jika anda menggunakan lebih dari satu Bindings. Ini dedesain untuk digunakan tanpa Bindings, atau dengan satu Binding terhubung dengan initialBinding milik GetMaterialApp. - Menggunakan Binding sifatnya opsional, jika anda mau, anda bisa menggunakan `Get.put()` dan `Get.find()` untuk kelas yang menggunakan controller tanpa masalah. Meski begitu, jika anda bekerja dengan Service atau segala jenis abstraksi lain, Saya merekomendasikan menggunakan Bindings supaya pengorganisiran menjadi lebih baik. ================================================ FILE: documentation/id_ID/route_management.md ================================================ - [Route Management](#route-management) - [Cara pakai](#cara-pakai) - [Navigasi tanpa named route](#navigasi-tanpa-named-route) - [Navigasi menggunakan named route](#navigasi-menggunakan-named-route) - [Mengirim data ke named route](#mengirim-data-ke-named-route) - [Tautan URL dinamis](#tautan-url-dinamis) - [Middleware](#middleware) - [Navigasi tanpa konteks](#navigasi-tanpa-konteks) - [SnackBar](#snackbar) - [Dialog](#dialog) - [BottomSheet](#bottomsheet) - [Navigasi Bersarang](#navigasi-bersarang) # Route Management Ini adalah penjelasan lengkap mengenai route management di GetX. ## Cara pakai Tambahkan ini kedalam pubspec.yaml anda: ```yaml dependencies: get: ``` Jika anda akan menggunakan route/snackbar/dialog/bottomsheet tanpa konteks, atau menggunakan high-level API dari Get, cukup tambahkan "Get" sebelum MaterialApp, mengubahnya menjadi GetMaterialApp, dan selamat menikmati! ```dart GetMaterialApp( // Sebelumnya: MaterialApp( home: MyHome(), ) ``` ## Navigasi tanpa named route Untuk pindah ke halaman baru: ```dart Get.to(NextScreen()); ``` Untuk menutup snackbar, dialog, bottomsheet, atau apapun yang normalnya anda tutup menggunakan Navigator.pop(context); ```dart Get.back(); ``` Untuk pergi ke halaman baru dan mencegah user kembali ke halaman sebelumnya (biasanya digunakan untuk SplashScreen, LoginScreen, dsb). ```dart Get.off(NextScreen()); ``` Untuk pergi ke halaman baru dan batalkan navigasi sebelumnya (berguna untuk shopping cart, polls, dan test). ```dart Get.offAll(NextScreen()); ``` Untuk pergi ke halaman baru dan menerima atau memperbarui data segera setelah anda kembali dari halaman tersebut: ```dart var data = await Get.to(Payment()); ``` pada halaman lain, kirim data ke halaman sebelumnya: ```dart Get.back(result: 'success'); ``` Lalu gunakan: contoh: ```dart if(data == 'success') madeAnything(); ``` Bukankah anda ingin mempelajari sintaks kami? Cukup ubah Navigator (uppercase) ke navigator (lowercase), dan anda akan mendapatkan semua fungsi standar navigasi, tanpa harus menggunakan konteks. Contoh: ```dart // Navigator bawaan Flutter Navigator.of(context).push( context, MaterialPageRoute( builder: (BuildContext context) { return HomePage(); }, ), ); // Get menggunakan sintaks Flutter tanpa membutuhkan konteks. navigator.push( MaterialPageRoute( builder: (_) { return HomePage(); }, ), ); // Sintaks Get (Lebih baik, tapi anda juga berhak untuk tidak setuju) Get.to(HomePage()); ``` ## Navigasi menggunakan named route - Jika anda lebih suka bernavigasi menggunakan namedRoutes, Get juga bisa melakukannya. Untuk pindah ke halaman nextScreen ```dart Get.toNamed("/NextScreen"); ``` Untuk pindah dan hapus halaman sebelumnya dari widget tree. ```dart Get.offNamed("/NextScreen"); ``` Untuk pindah dan hapus semua halaman sebelumnya dari widget tree. ```dart Get.offAllNamed("/NextScreen"); ``` Untuk mendifinisikan route, gunakan GetMaterialApp: ```dart void main() { runApp( GetMaterialApp( initialRoute: '/', getPages: [ GetPage(name: '/', page: () => MyHomePage()), GetPage(name: '/second', page: () => Second()), GetPage( name: '/third', page: () => Third(), transition: Transition.zoom ), ], ) ); } ``` Untuk menangani navigasi ke route yang tidak terdefinisi (404), anda bisa mendefinisikan sebuah halaman unknownRoute didalam GetMaterialApp. ```dart void main() { runApp( GetMaterialApp( unknownRoute: GetPage(name: '/notfound', page: () => UnknownRoutePage()), initialRoute: '/', getPages: [ GetPage(name: '/', page: () => MyHomePage()), GetPage(name: '/second', page: () => Second()), ], ) ); } ``` ### Mengirim data ke named route Cukup kirim apa yang anda mau sebagai arguments. Get menerima apapun disitu, baik dalam bentuk String, Map, List atau bahkan instance dari sebuah Kelas. ```dart Get.toNamed("/NextScreen", arguments: 'Get is the best'); ``` di dalam kelas atau controller anda: ```dart print(Get.arguments); // keluaran: Get is the best ``` ### Tautan URL dinamis Get menawarkan tautan URL dinamis lebih lanjut sama seperti di Web. Para Web developer mungkin sudah menginginkan fitur ini di Flutter, dan mungkin juga sering melihat sebuah package menjanjikan fitur ini dan mengantarkan sintaks yang benar benar berbeda dari sebuah URL yang kita miliki di web, Get juga menyelesaikan masalah ini. ```dart Get.offAllNamed("/NextScreen?device=phone&id=354&name=Enzo"); ``` didalam controller/bloc/stateful/stateless class anda: ```dart print(Get.parameters['id']); // keluaran: 354 print(Get.parameters['name']); // keluaran: Enzo ``` Anda juga bisa menerima NamedParameters dengan Get dengan mudah: ```dart void main() { runApp( GetMaterialApp( initialRoute: '/', getPages: [ GetPage( name: '/', page: () => MyHomePage(), ), GetPage( name: '/profile/', page: () => MyProfile(), ), // Anda bisa mendefinisikan halaman berbeda untuk routes dengan arguments, // dan yang lainnya tanpa arguments, namun untuk itu anda perlu slash '/' // pada route yang tidak menerima arguments seperti diatas. GetPage( name: '/profile/:user', page: () => UserProfile(), ), GetPage( name: '/third', page: () => Third(), transition: Transition.cupertino ), ], ) ); } ``` Kirim data ke named route ```dart Get.toNamed("/profile/34954"); ``` Pada halaman kedua, ambil data menggunakan parameter ```dart print(Get.parameters['user']); // keluaran: 34954 ``` atau kirim beberapa parameter seperti ini ```dart Get.toNamed("/profile/34954?flag=true"); ``` Pada layar kedua, ambil data berdasarkan parameter seperti biasanya ```dart print(Get.parameters['user']); print(Get.parameters['flag']); // keluaran: 34954 true ``` Dan sekarang, yang anda perlu lakukan adalah menggunakan Get.toNamed() untuk bernavigasi ke named route anda, tanpa konteks (anda bisa memanggil route secara langsung dari kelas BLoC atau Controller), dan ketika aplikasi anda di-compile di web, route anda akan muncul di url <3 ### Middleware Jika anda ingin me-listen sebuah Get event untuk melakukan sebuah action, anda bisa menggunakan routingCallback didalamnya. ```dart GetMaterialApp( routingCallback: (routing) { if(routing.current == '/second'){ openAds(); } } ) ``` Jika anda tidak menggunakan GetMaterialApp, anda bisa menggunakan API untuk mengaitkan Middleware observer secara manual. ```dart void main() { runApp( MaterialApp( onGenerateRoute: Router.generateRoute, initialRoute: "/", navigatorKey: Get.key, navigatorObservers: [ GetObserver(MiddleWare.observer), // Disini ], ), ); } ``` Membuat sebuah kelas Middleware ```dart class MiddleWare { static observer(Routing routing) { /// Anda bisa me-listen sebuah route, snackbar, dialog, dan bottomsheet disetiap halaman. /// Jika anda harus memasukkan salah satu dari 3 event tersebut secara langsung disini, /// anda perlu menyebutkan bahwa event tersebut != (tidak sama dengan) apa yang mau anda lakukan. if (routing.current == '/second' && !routing.isSnackbar) { Get.snackbar("Halo", "Anda sedang berada di route kedua"); } else if (routing.current =='/third'){ print('route terakhir dipanggil'); } } } ``` Sekarang, gunakan Get dalam kode anda: ```dart class First extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( leading: IconButton( icon: Icon(Icons.add), onPressed: () { Get.snackbar("halo", "saya adalah snackbar modern"); }, ), title: Text('Halaman Pertama'), ), body: Center( child: ElevatedButton( child: Text('Pindah halaman'), onPressed: () { Get.toNamed("/second"); }, ), ), ); } } class Second extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( leading: IconButton( icon: Icon(Icons.add), onPressed: () { Get.snackbar("halo", "saya adalah snackbar modern"); }, ), title: Text('Halaman kedua'), ), body: Center( child: ElevatedButton( child: Text('Pindah halaman'), onPressed: () { Get.toNamed("/third"); }, ), ), ); } } class Third extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Halaman ketiga"), ), body: Center( child: ElevatedButton( onPressed: () { Get.back(); }, child: Text('Kembali!'), ), ), ); } } ``` ## Navigasi tanpa konteks ### SnackBar Untuk mendapatkan SnackBar sederhana dengan Flutter, anda harus mendapatkan konteks dari sebuah Scaffold, atau anda harus menggunakan GlobalKey yang dikaitkan pada Scaffold anda ```dart final snackBar = SnackBar( content: Text('Halo!'), action: SnackBarAction( label: 'Saya adalah snackbar tua yang jelek :(', onPressed: () {} ), ); // Temukan Scaffold didalam widget tree dan gunakan itu // untuk menampilkan snackbar Scaffold.of(context).showSnackBar(snackBar); ``` Dengan Get: ```dart Get.snackbar('Halo', 'Saya adalah snackbar modern'); ``` Dengan Get, yang anda butuhkan hanya memanggil Get.snackbar darimanapun di kode anda atau menyesuaikannya sesuka hati anda! ```dart Get.snackbar( "Halo, saya snackbar milik Get!", // judul "Sulit dipercaya! Saya menggunakan SnackBar tanpa konteks, tanpa boilerplate, tanpa Scaffold, ini benar benar keren!", // pesan icon: Icon(Icons.alarm), shouldIconPulse: true, onTap: () {}, barBlur: 20, isDismissible: true, duration: Duration(seconds: 3), ); ////////// SEMUA FITUR ////////// // Color colorText, // Duration duration, // SnackPosition snackPosition, // Widget titleText, // Widget messageText, // bool instantInit, // Widget icon, // bool shouldIconPulse, // double maxWidth, // EdgeInsets margin, // EdgeInsets padding, // double borderRadius, // Color borderColor, // double borderWidth, // Color backgroundColor, // Color leftBarIndicatorColor, // List boxShadows, // Gradient backgroundGradient, // TextButton mainButton, // OnTap onTap, // bool isDismissible, // bool showProgressIndicator, // AnimationController progressIndicatorController, // Color progressIndicatorBackgroundColor, // Animation progressIndicatorValueColor, // SnackStyle snackStyle, // Curve forwardAnimationCurve, // Curve reverseAnimationCurve, // Duration animationDuration, // double barBlur, // double overlayBlur, // Color overlayColor, // Form userInputForm /////////////////////////////////// ``` Jika anda lebih menyukai snackbar tradisional, atau ingin menyesuaikannya sendiri dari awal, termasuk menambahkan hanya satu baris (Get.snackbar memanfaatkan title dan message yang diperlukan), anda bisa gunakan `Get.rawSnackbar();` yang akan menyediakan RAW API untuk Get.snackbar yang dibuat. ### Dialog Untuk membuka dialog: ```dart Get.dialog(YourDialogWidget()); ``` Untuk membuka default dialog: ```dart Get.defaultDialog( onConfirm: () => print("Ok"), middleText: "Dialog made in 3 lines of code" ); ``` Anda juga bisa menggunakan Get.generalDialog daripada showGeneralDialog. Untuk semua widget dialog di Flutter, termasuk cupertino, anda bisa menggunakan Get.overlayContext daripada context, dan membukanya darimanapun di kode anda. Untuk widget yang tidak menggunakan Overlay, anda bisa menggunakan Get.context. Kedua konteks akan bekerja dalam 99% kasus untuk me-replace konteks dari UI anda, kecuali untuk kasus dimana inheritedWidget digunakan tanpa konteks navigasi. ### BottomSheet Get.bottomSheet sama seperti showModalBottomSheet, tapi tidak membutuhkan konteks. ```dart Get.bottomSheet( Container( child: Wrap( children: [ ListTile( leading: Icon(Icons.music_note), title: Text('Music'), onTap: () {} ), ListTile( leading: Icon(Icons.videocam), title: Text('Video'), onTap: () {}, ), ], ), ) ); ``` ## Navigasi Bersarang Get membuat navigasi bersarang milik Flutter menjadi lebih mudah. Anda tidak perlu konteks, dan anda akan menemukan stack navigasi melalui Id. - CATATAN: Membuat stack navigasi parallel bisa jadi berbahaya. Sebaiknya hindari penggunaan Navigasi Bersarang, atau gunakan dengan bijak. Jika proyek anda membutuhkannya, silahkan, tapi mohon di ingat bahwa menyimpan lebih dari satu navigation stack didalam memori mungkin bukan ide yang bagus untuk konsumsi RAM. Lihat betapa sederhananya ini: ```dart Navigator( key: Get.nestedKey(1), // buat sebuah key menggunakan index initialRoute: '/', onGenerateRoute: (settings) { if (settings.name == '/') { return GetPageRoute( page: () => Scaffold( appBar: AppBar( title: Text("Main"), ), body: Center( child: TextButton( color: Colors.blue, onPressed: () { Get.toNamed('/second', id:1); // pindah ke halaman bersarang anda menggunakan index }, child: Text("Go to second"), ), ), ), ); } else if (settings.name == '/second') { return GetPageRoute( page: () => Center( child: Scaffold( appBar: AppBar( title: Text("Main"), ), body: Center( child: Text("second") ), ), ), ); } } ), ``` ================================================ FILE: documentation/id_ID/state_management.md ================================================ - [State Management](#state-management) - [Reactive State Manager](#reactive-state-manager) - [Keuntungan](#keuntungan) - [Performa Maksimum](#performa-maksimum) - [Mendeklarasikan reactive variable](#mendeklarasikan-reactive-variable) - [Memiliki sebuah reactive state itu, mudah](#memiliki-sebuah-reactive-state-itu-mudah) - [Menggunakan value didalam view](#menggunakan-value-didalam-view) - [Kondisi untuk rebuild](#kondisi-untuk-rebuild) - [Dimana .obs bisa digunakan](#dimana-obs-bisa-digunakan) - [Catatan mengenai List](#catatan-mengenai-list) - [Mengapa harus menggunakan .value](#mengapa-harus-menggunakan-value) - [Obx()](#obx) - [Worker](#worker) - [Simple State Manager](#simple-state-manager) - [Keuntungan](#keuntungan-1) - [Penggunaan](#penggunaan) - [Bagaimana Get meng-handle controller](#bagaimana-get-meng-handle-controller) - [Anda tidak membutuhkan StatefulWidget lagi](#anda-tidak-membutuhkan-statefulwidget-lagi) - [Mengapa GetX ada](#mengapa-getx-ada) - [Cara lain dalam menggunakannya](#cara-lain-dalam-menggunakannya) - [Unique ID](#unique-id) - [Mencampur 2 state manager](#mencampur-2-state-manager) - [GetBuilder vs GetX vs Obx vs MixinBuilder](#getbuilder-vs-getx-vs-obx-vs-mixinbuilder) # State Management GetX tidak menggunakan Stream atau ChangeNotifier seperti state manager lainnya. Mengapa? Selain membangun aplikasi untuk Android, iOS, Web, Linux, MacOS, dan Linux, dengan GEtX anda bisa membangun aplikasi server dengan sintaks yang sama seperti Flutter/GetX. Untuk meningkatkan waktu response dan mengurangi konsumsi RAM, kami menciptakan GetValue dan GetStream, yang memiliki solusi latensi rendah yang dapat memberikan performa yang banyak dengan biaya operasi yang rendah. Kami menggunakan ini sebagai dasar untuk membangun semua resource kami, termasuk state management. - _Kompleksitas_: Beberapa state manager yang ada bisa dibilang kompleks dan memiliki banyak boilerplate. Dengan GetX anda tidak perlu mendefinisikan sebuah kelas untuk setiap event, kode yang dibuat pun bersih dan jelas, dan anda bisa melakukan banyak hal dengan menulis lebih sedikit kode. Banyak orang menyerah menggunakan Flutter karena hal ini, dan pada akhirnya mereka mendapatkan solusi yang sangat sederhana untuk me-manage state. - _Tidak ada code generator_: Anda menghabiskan separuh waktu development anda menuliskan logic dari aplikasi yang anda buat. Beberapa state manager bergantung pada code generator yang menghasilkan kode dengan keterbacaan yang rendah. Mengubah sebuah variabel dan perlu menjalankan run build_runner bisa jadi tidak produktif, dan terkadang waktu menunggu setelah sebuah flutter clean akan lama, dan anda harus meminum banyak kopi untuk itu. Dengan GetX semuanya reactive, dan tidak bergantung pada code generator, meningkatkan produktifitas di segala aspek development anda. - _Tidak bergantung pada konteks_: Anda mungkin pernah diharuskan untuk mengirim konteks dari view ke sebuah controller, membuat keterkaitan yang tinggi terhadap View yang anda buat dengan Business Logic. Anda mungkin pernah diharuskan untuk menggunakan dependensi untuk suatu tempat yang tidak memiliki konteks, dan harus mengirim koteks tersebut melalui berbagai macam kelas dan fungsi. Hal ini tidak ada di GetX. Anda memiliki akses terhadap controller anda dari controller yang anda buat tanpa konteks apapun. Anda tidak perlu mengirim konteks melalui parameter untuk hal yang secara harfiah tidak diperlukan. - _Kontrol granular_: Kebanyakan state manager didasari oleh ChangeNotifier. ChangeNotifier akan memberi tahu semua widget yang bergantung padanya ketika notifyListener dipanggil. Jika anda memiliki 40 widget didalam satu halaman, yang mana memiliki variabel dari kelas ChangeNotifier anda, ketika anda memperbarui satu dari mereka, semuanya akan me-rebuild. Dengan GetX, bahkan nested widget pun dihargai. Jika anda memiliki Obx membungkus ListView anda, dan yang lain membungkus sebuah checkbox didalam ListView tersebut, ketika nilai dari CheckBox berubah, hanya CheckBox tersebut yang akan diperbarui, ketika nilai dari List yang berubah, hanya ListView yang akan diperbarui. - _Hanya merekonstruksi jika variabelnya BENAR-BENAR berubah_: GetX memiliki kontrol flow, yang artinya jika anda menampilkan Text dengan 'Paola', jika anda mengubah variabel observabel menjadi 'Paola' lagi, widget tidak akan direkonstruksi. Ini karena GetX tahu bahwa 'Paola' sudah ditampilkan di Text, dan tidak akan melakukan rekonstruksi yang tidak diperlukan. Kebanyakan state manager (jika tidak semuanya) saat ini akan merebuild tampilan. ## Reactive State Manager Reactive programming bisa meng-alienasi banya orang karena katanya, sulit dimengerti. GetX mengubah reactive programming menjadi sesuatu yang cukup sederhana: - Anda tidak perlu membuat StreamController. - Anda tidak perlu membuat StreamBuilder untuk setiap variabel. - Anda tidak perlu membuat kelas untuk setiap state. - Anda tidak perlu membuat get untuk sebuah value awal (initial value). - Anda tidak perlu menggunakan generator kode. Reactive programming dengan Get semudah menggunakan setState. Bayangkan anda memiliki variabel nama, dan setiap kali anda mengubahnya, semua widget yang menggunakannya akan berubah secara otomatis. Ini variabel count anda: ```dart var name = 'Jonatas Borges'; ``` Untuk membuatnya "observable", anda hanya perlu menambahkan ".obs" di belakangnya: ```dart var name = 'Jonatas Borges'.obs; ``` Selesai! _Sesederhana_ itu. Mulai saat ini, kami akan mereferensikan variabel reactive-".obs"(ervables) sebagai _Rx_. Apa yang kami lakukan dibalik layar? Kami membuat sebuah `Stream` dari `String`, mengajukan value awal `"Jonatas Borges"`, kami memberi tahu semua widget yang menggunakan `"Jonatas Borges"` bahwa mereka sekarang "milik" variabel tersebut, dan ketika nilai _Rx_ berubah, mereka harus mengubahnya juga. Ini adalah **keajaiban dari GetX**, terima kasih untuk kemampuan Dart. Tapi, seperti yang kita ketahui, sebuah `Widget` hanya bisa dirubah jika lokasinya berada didalam sebuah fungsi, karena kelas static tidak memiliki kemampuan untuk "otomatis berubah" Anda akan harus untuk membuat `StreamBuilder`, berlangganan ke variabel tersebut untuk "mendengar" perubahan, dan membuat sebuah "cascade" dari nested `StreamBuilder` jika anda ingin mengubah beberapa variabel didalam scope, benar kan? Tidak, anda tidak perlu `StreamBuilder`, tapi anda benar tentang kelas static. Yah, didalam view, kita biasanya memiliki banyak boilerplate ketika kita ingin mengubah sebuah Widget secara spesifik, itu adalah cara Flutter. Dengan **GetX** anda juga bisa melupakan tentang boilerplate code ini. `StreamBuilder( … )`? `initialValue: …`? `builder: …`? Tidak, anda hanya perlu meletakkan variabel kedalam Widget `Obx()`. ```dart Obx (() => Text (controller.name)); ``` _Apa yang perlu anda ingat?_ Hanya `Obx(() =>`. Anda hanya mengirim Widget itu melalui sebuah arrow-function kedalam sebuah `Obx()` (sebuah "Pengamat" daripada _Rx_). `Obx` cukup pintar, dan akan selalu berubah jika value dari `controller.name` berubah. Jika `name` nilainya `"John"`, dan anda mengubahnya ke `"John"` (`name.value = "John"`), karena `value` nya sama seperti sebelumnya, tidak akan ada perubahan apapun di layar, dan `Obex`, untuk menghemat resource, akan secara sederhana mengabaikan value baru yang diberikan dan tidak akan merebuild Widget. **Keren kan?** > Lalu, bagaimana jika Saya memiliki 5 variabel _Rx_ (observable) didalam `Obx`? Dia hanya akan memperbarui ketika **semuanya** berubah. > Dan jika Saya memiliki 30 variabel didalam sebuah kelas, ketika Saya memperbarui salah satu dari mereka, apakah akan memperbarui **semua** variabel didalam kelas tersebut? Tidak, hanya **Widget tertentu* yang menggunakan variabel _Rx_ tersebut. Jadi, **GetX** hanya memperbarui tampilan, ketika nilai dari variabel _Rx_ berubah. ```dart final isOpen = false.obs; // TIDAK AKAN terjadi apa apa... nilainya sama. void onButtonTap() => isOpen.value=false; ``` ### Keuntungan **GetX()** membantu anda ketika anda membutuhkan kontrol **granular** atas apa yang sedang diperbarui. Jika anda tidak membutuhkan `unique ID`, karena semua variabel anda akan di modifikasi ketika anda melakukan sebuah aksi, maka gunakanlah `GetBuilder`, karena dia adalah Simple State Updater (didalam block, seperti `setState()`), dibuat hanya dengan beberapa baris kode. Ini dibuat sederhana, untuk mendapatkan pengaruh CPU yang sedikit, dan hanya untuk memenuhi satu kebutuhan (sebuah _State_ rebuild) dan menggunakan resource se-minimal mungkin. Jika anda perlu sebuah State Manager yang **powerful**, anda tidak akan salah dengan **GetX**. Ini tidak bekerja dengan variabel, melainkan __aliran (flows)__, semuanya adalah `Streams` dibalik layar. Anda bisa menggunakan _rxDart_ bersamaan dengannya, karena semuanya adalah `Streams`, anda bisa me-listen sebuah `event` dari setiap "variabel _Rx_", karena semua didalamnya adalah `Streams`. Ini secara harfiah adalah cara yang digunakan _BLoC_, lebih mudah dari _MobX_, dan tanpa code generator atau decoration. Anda bisa mengubah **apapun** menjadi sebuah _"Observable"_ hanya dengan `.obs`. ### Performa Maksimum Selain memiliki sebuah algoritma pintar untuk minimal rebuild, **GetX** menggunakan pembanding untuk memastikan State nya berubah. Jika anda mengalami error di aplikasi anda, dan mengirim sebuah duplikat terhadap perubahan State, **GetX** akan memastikan aplikasi anda tidak crash. Dengan **GetX**, State hanya berubah ketika `value` nya berubah. Itu adalah perbedaan utama diantara **GetX**, dan dengan menggunakan _`computed` dari MobX_. Ketika menggabungkan kedua __observable__, dan salah satunya berubah; listener dari _observable_ itu akan berubah juga. Dengan **GetX**, jika anda menggabungkan dua variabel, `GetX()` (mirip seperti `Observer()`) hanya akan merebuild jika State nya benar-benar berubah. ### Mendeklarasikan reactive variable Anda memiliki 3 cara untuk mengubah variabel menjadi sebuah "observable". 1 - Yang pertama adalah dengan menggunakan **`Rx{Type}`**. ```dart // value awal direkomendasikan, tetapi tidak wajib final name = RxString(''); final isLogged = RxBool(false); final count = RxInt(0); final balance = RxDouble(0.0); final items = RxList([]); final myMap = RxMap({}); ``` 2 - Yang kedua adalah dengan menggunakan **`Rx`** dan Darts Generics, `Rx` ```dart final name = Rx(''); final isLogged = Rx(false); final count = Rx(0); final balance = Rx(0.0); final number = Rx(0) final items = Rx>([]); final myMap = Rx>({}); // Kelas Kustom - ini bisa jadi kelas apapun, secara harfiah final user = Rx(); ``` 3 - Yang ketiga, cara yang lebih praktis, mudah dan lebih disukai, cukup tambahkan **`.obs`** sebagai properti dari `value` anda: ```dart final name = ''.obs; final isLogged = false.obs; final count = 0.obs; final balance = 0.0.obs; final number = 0.obs; final items = [].obs; final myMap = {}.obs; // Kelas Kustom - ini bisa jadi kelas apapun, secara harfiah final user = User().obs; ``` #### Memiliki sebuah reactive state itu, mudah Seperti yang kita ketahui, _Dart_ saat ini sedang mempersiapkan ke _null safety_. Sebagai persiapan, mulai dari sekarang, anda harus selalu memulai variabel _Rx_ anda dengan sebuah **initial value** (nilai awal). > Mengubah sebuah variabel menjadi sebuah _observable_ + _initial value_ dengan **GetX** adalah cara yang paling sederhana, dan sangat praktis. Anda hanya cukup menambahkan sebuah "`.obs`" di akhir variabel anda, dan **selesai**, anda telah membuatnya observable, dan untuk `.value` nya, yah, _initial value_ yang anda berikan. ### Menggunakan value didalam view ```dart // file controller final count1 = 0.obs; final count2 = 0.obs; int get sum => count1.value + count2.value; ``` ```dart // file view GetX( builder: (controller) { print("count 1 rebuild"); return Text('${controller.count1.value}'); }, ), GetX( builder: (controller) { print("count 2 rebuild"); return Text('${controller.count2.value}'); }, ), GetX( builder: (controller) { print("count 3 rebuild"); return Text('${controller.sum}'); }, ), ``` Jika kita meng-increment `count1.value++`, ini akan mencetak: - `count 1 rebuild` - `count 3 rebuild` karena `count1` memiliki nilai `1`, dan `1 + 0 = 1`, mengubah nilai dari getter `sum`. Jika kita mengubah `count2.value++`, ini akan mencetak: - `count 2 rebuild` - `count 3 rebuild` karena `count2.value` berubah, dan hasil dari `sum` sekarang adalah `2`. - CATATAN: Secara default, event yang paling pertama akan merebuild widget, meskipun `value` nya sama. perilaku ini hadir karena variabel Boolean. Bayangkan anda melakukan ini: ```dart var isLogged = false.obs; ``` Dan kemudian, anda melakukan pengecekan apakah user "sudah login" untuk men-trigger sebuah event didalam `ever`. ```dart @override onInit(){ ever(isLogged, fireRoute); isLogged.value = await Preferences.hasToken(); } fireRoute(logged) { if (logged) { Get.off(Home()); } else { Get.off(Login()); } } ``` jika `hasToken` nilainya `false`, tidak akan terjadi apa apa kepada `isLogged`, jadi `ever()` tidak akan dipanggil. Untuk menghindari jenis perilaku ini, perubahan awal terhadap sebuah _observable_ harus selalu men-trigger sebuah event, bahkan ketika mereka memiliki `.value` yang sama. Anda bisa menghapus perilaku ini jika anda mau, menggunakan: `isLogged.firstRebuild = false;` ### Kondisi untuk rebuild Selain itu, Get menyediakan state kontrol yang telah dipoles. Anda bisa mengkondisikan sebuah event (seperti menambahkan sebuah object kedalam list), dalam suatu kondisi. ```dart // Parameter pertama: kondisi, harus me-return true atau false // Parameter kedua: nilai baru yang akan dimasukkan jika kondisinya true list.addIf(item < limit, item); ``` Tanpa decoration, tanpa code generator, tanpa komplikasi :smile: Anda tahu counter app milik Flutter? Controller anda mungkin terlihat seperti ini: ```dart class CountController extends GetxController { final count = 0.obs; } ``` Hanya dengan: ```dart controller.count.value++ ``` Anda bisa memperbarui variabel counter di UI anda, tidak perduli dimanapun itu diletakkan. ### Dimana .obs bisa digunakan Anda bisa mengubah apapun dengan obs. Berikut adalah dua cara untuk melakukannya: * Anda bisa mengkonversi value kelas anda menjadi obs ```dart class RxUser { final name = "Camila".obs; final age = 18.obs; } ``` * atau anda bisa mengkonversi seluruh kelasnya menjadi observable ```dart class User { User({String name, int age}); var name; var age; } // saat menginstansiasi: final user = User(name: "Camila", age: 18).obs; ``` ### Catatan mengenai List List, dia observable secara menyeluruh, termasuk objek didalamnya. Dengan itu, jika anda menambahkan value kedalam list, dia akan secara otomatis merebuild widget yang menggunakannya. Anda juga tidak perlu menggunakan ".value" ketika mengakses list, API dart yang luar biasa mengizinkan kita untuk menghapusnya. Sayangnya, tipe data primitif seperti String dan int tidak bisa di extend, membuat penggunaan .value menjadi suatu hal yang wajib, tapi itu bukan masalah jika anda bekerja menggunakan getter dan setter untuk mereka. ```dart // Didalam Controller final String title = 'User Info:'.obs final list = List().obs; // Didalam View Text(controller.title.value), // String harus memiliki .value didepannya ListView.builder ( itemCount: controller.list.length // list tidak perlu itu ) ``` Ketika anda membuat kelas anda sendiri observable, ada berbagai cara untuk memperbaruinya: ```dart // didalam file model // kita akan membuat seluruh kelas menjadi observable daripada // mengimplementasikannya pada masing-masing atribut. class User() { User({this.name = '', this.age = 0}); String name; int age; } // didalam file controller final user = User().obs; // ketika anda perlu mengupdate variabel user: user.update( (user) { // parameter ini adalah kelas itu sendiri yang akan anda update user.name = 'Jonny'; user.age = 18; }); // cara alternatif untuk melakukannya: user(User(name: 'João', age: 35)); // didalam view: Obx(()=> Text("Name ${user.value.name}: Age: ${user.value.age}")) // anda juga bisa mengakses nilai model tanpa .value: user().name; // perhatikan bahwa yang digunakan adalah variabel user, bukan kelasnya (variabel memiliki "u" kecil) ``` Anda tidak perlu bekerja dengan sets jika anda tidak mau. Anda bisa menggunakan API "assign" dan "assignAll". API "assign" akan membersihkan list anda, dan memasukkan satu objek sebagai permulaan. API "assignAll" akan membersihkan list yang sudah ada dan menambahkan objek iterable yang anda inject kedalamnya. ### Mengapa harus menggunakan .value Kami bisa saja menghapus kebijakan untuk menggunakan 'value' pada `String` dan `int` dengan decoration sederhana dan code generator, tapi tujuan dari library ini adalah untuk menghindari dependensi eksternal. Kami ingin menawarkan sebuah environment yang siap untuk programming, melibatkan hal-hal penting (route, dependency, dan state management), dengan cara yang sederhana, ringan, dan cepat, tanpa membutuhkan package dari luar. Anda bisa secara harfial menambahkan 3 huruf ke pubspec anda dan sebuah titik dua dan mulai membuat sebuah program. Semua solusi sudah termasuk secara default, mulai dari route management, hingga state management, menargetkan kemudahan, produktifitas dan performa. Berat total dari library ini kurang dari satu state manager, meskipun ini adalah solusi komplit, dan inilah yang harus anda pahami. Jika anda terganggu dengan `.value`, dan menyukai code generator, MobX adalah alternatif yang baik, dan anda bisa menggunakannya bersamaan dengan Get. Untuk kalian yang ingin menambahkan satu dependensi di pubspec dan mulai membuat sebuah program tanpa mengkhawatirkan versi dari sebuah package tidak kompatibel dengan yang lain, atau sebuah error dari perbaruan state datang dari state manager atau dependensi, atau semacamnya, dan tidak mau khawatir tentang ketersediaan controller, ataupun secara harfiah "hanya ingin membuat program", get sudah sangat sempurna. Jika anda merasa tidak masalah dengan code generator MobX, atau tidak masalah dengan boilerplate dari BLoC, anda bisa menggunakan Get untuk route, dan lupakan bahwa dia memiliki state manager. Get SEM dan RSM lahir karena kebutuhan, perusahaan saya memiliki proyek dengan lebih dari 90 controller, dan code generator sederhananya memakan waktu 30 menit untuk menyelesaikan tugasnya setelah Flutter Clean di komputer yang secara masuk akal bagus, jika proyek anda memiliki 5, 10, hingga 15 controller, state manager apapun akan mensuplai anda dengan baik. Jika anda memiliki proyek yang secara absurd berskala besar, dan code generator adalah masalah untuk anda, maka anggap ini sebagai hadiah. Jelas, jika seseorang ingin berkontribusi terhadap proyek dan membuat sebuah code generator, atau sejenisnya, Saya akan tautkan kedalam readme ini sebagai alternatif, kebutuhan saya bukan kebutuhan untuk semua developer, namun untuk saat ini Saya mengatakan, sudah ada solusi yang baik diluar sana yang sudah melakukan hal itu, seperti MobX. ### Obx() Memberi tipe di Get saat menggunakan Binding tidak diperlukan. Anda bisa menggunakan widget Obx daripada GetX yang mana hanya menerima fungsi anonim yang membuat sebuah widget. Jelas, jika anda tidak menggunakan sebuah tipe, anda harus memiliki sebuah instance dari controller anda untuk menggunakan variabel didalamnya, atau gunakan `Get.find()` .value atau Controller.to.value untuk mengambil value. ### Worker Worker akan membantu anda, men-trigger callback spesifik ketika sebuah event terjadi. ```dart /// Terpangil setiap kali `count1` berubah. ever(count1, (_) => print("$_ telah dirubah")); /// Terpanggil hanya pada saat pertama kali variabel $_ dirubah. once(count1, (_) => print("$_ telah dirubah sekali")); /// Anti DDos - Terpangil setiap kali the user berhenti mengetik setelah 1 detik, contoh: debounce(count1, (_) => print("debouce$_"), time: Duration(seconds: 1)); /// Abaikan semua perubahan selama 1 detik. interval(count1, (_) => print("interval $_"), time: Duration(seconds: 1)); ``` Semua worker (kecuali `debounce`) memiliki sebuah `condition` named parameter, yang bisa jadi sebuah `bool` atau sebuah callback yang mereturn `bool` `condition` ini terdefinisikan ketika fungsi `callback` di eksekusi. Semua worker mereturn sebuah instance `Worker`, yang bisa anda gunakan untuk membatalkan worker tersebut (melalui `dispose()`). - **`ever`**\ terpanggil setiap kali variabel _Rx_ meng-emit value baru. - **`everAll`**\ Mirip seperti `ever`, tapi menerima `List` sebagai nilai _Rx_, terpanggil setiap kali variabel berubah. - **`once`**\ Terpanggil hanya sekali pada saat pertama kali variabel berubah. - **`debounce`**\ 'debounce' sangat berguna pada fungsi pencarian, dimana anda hanya ingin API dipanggil ketika user selesai mengetik. Jika user mengetik "Jonny", anda akan pendapatkan 5 pencarian di API, dengan huruf J, o, n, n, dan y. Dengan Get, ini tidak terjadi lagi, karena anda memiliki sebuah "debounce" worker, yang akan men-trigger pada akhir pengetikan. - **`interval`**\ berbeda dengan debounce. untuk debounce, jika user melakukan 1000 perubahan terhadap sebuah variabel dalam 1 detik, dia akan mengirim hanya yang terakhir setelah waktu yang ditetapkan (defaultnya adalah 800 milisekon). \ \ Interval bekerja sebaliknya, dia akan mengabaikan semua interaksi user dalam rentang waktu yang ditentukan. Jika anda mengirim event dalam 1 menit, 1000 per detik, debounce akan mengirimkan anda yang terakhir, ketika user berhenti melakukan spam terhadap event. Interval akan mengantar event setiap detik, dan jika diatur menjadi 3 detik, dia akan mengirimkan 20 event setiap menit. \ \ Ini direkomendasikan untuk menghindari penyalahgunaan, dalam fungsi dimana user bisa secara cepat melakukan klik pada sesuatu untuk mendapatkan sebuah keuntungan (bayangkan jika user bisa mendapat koin dengan meng-klik pada sesuatu, jika dia mengklik 300 kali dalam 1 menit, dia akan mendapatkan 300 koin, menggunakan interval, anda bisa mengatur jangka waktu selama 3 detik, dan meskipun user meng-klik sebanyak 300 kali atau ribuan kali, maksimum koin yang bisa dia dapatkan dalam 1 menit akan selalu 20 koin, bahkan ketika dia meng-klik 1 juta kali sekalipun) \ \ Debounce cocok sebagai anti-DDos, untuk fungsi seperti search dimana setiap perubahan terhadap onChange akan mengirim sebuah query ke API anda. Debounce akan menunggu user berhenti mengetik nama, untuk membuat sebuah request. Jika ini digunakan pada skenario koin diatas, user hanya akan mendapatkan 1 koin, karena hanya akan di eksekusi ketika user memberi jeda terhadap waktu yang ditentukan. - CATATAN: Worker harus selalu digunakan ketika memulai sebuah Controller atau Class, jadi dia harus selalu diletakkan didalam onInit (direkomendasikan), Class constructor, atau initState dari StatefulWidget (praktik ini tidak direkomendasikan dalam kebanyakan kasus, dan tidak akan ada side effect). ## Simple State Manager Get memiliki state manager yang sangat ringan dan mudah, dan tidak menggunakan ChangeNotifier, yang akan memenuhi kebutuhan khususnya untuk mereka yang baru di Flutter, dan tidak akan membuat masalah untuk aplikasi besar. GetBuilder membidik dengan tepat pada state control multipel. Bayangkan anda menambahkan 30 produk kedalam sebuah keranjang belanja, anda meng-klik "delete" pada salah satu produk tersebut, di waktu yang sama, list, harga, dan badge diperbarui ke angka yang lebih kecil. Pendekatan ini membuat GetBuilder mematikan, karena mengelompokkan state dan mengubah semuanya secara bersamaan dalam satu waktu tanpa "computational logic" untuk itu. Getbuilder dibuat dengan mempertimbangkan situasi seperti ini, untuk perubahan state "ephemeral", anda bisa menggunakan setState dan anda tidak membutuhkan sebuah state manager untuk itu. Dengan begitu, jika anda menginginkan controller yang bekerja secara individu, anda bisa mengajukan ID untuknya, atau gunakan GetX. Semuanya terserah anda, mengingat bahwa semakin banyak "individual" widget yang anda miliki, performa dari GetX akan semakin terlihat, sementara performa dari GetBuilder harusnya lebih superior, ketika terjadi perubahan state secara multipel. ### Keuntungan 1. Hanya memperbarui widget yang diperlukan 2. Tidak menggunakan changeNotifier, ini adalah state manager yang menggunakan memori lebih kecil (mendekati 0mb). 3. Lupakan StatefulWidget! Dengan Get, anda tidak akan pernah membutuhkannya. Dengan state manager lain, anda mungkin harus menggunakan StatefulWidget untuk mendapatkan sebuah instance milik anda dari Provider, BLoC, MobX Controller, dsb. Tapi pernahkah anda berhenti untuk berfikir bahwa appBar, scaffold, dan kebanyakan widget anda itu stateless? Lalu mengapa menyimpan state dari seluruh kelas, jika anda hanya bisa menyimpan state dari Widget yang stateful? Get menyelesaikan masalah itu, juga. Buat kelas Stateless, buat semuanya stateless. Jika anda butuh update pada satu komponen, bungkus komponen itu dengan GetBuilder, dan state-nya akan di-maintain. 4. Organisir proyek anda secara nyata! Controller tidak seharusnya ada di UI, letakkan TextEditingController anda, atau controller apapun didalam kelas Controller anda. 5. Apakah anda perlu men-trigger sebuah event untuk widget segera setelah dirender? GetBuilder memiliki properti "initState", sama seperti StatefulWidget, dan anda akan bisa memanggil event dari controller anda, langsung dari sana, tidak ada lagi event diletakkan didalam initState anda. 6. Apakah anda perlu men-trigger sebuah action seperti menutup stream, timer, dan semacamnya? GetBuilder juga memiliki properti dispose, dimana anda bisa memanggil event segera setelah widget dihancurkan. 7. Gunakan stream hanya jika dibutuhkan. Anda bisa menggunakan StreamController didalam controller anda secara normal, dan menggunakan StreamBuilder juga secara normal, namun perlu di ingat, sebuah stream cukup memakan memori, reactive programming itu indah, namun anda tidak boleh menyalahgunakannya. 30 stream terbuka secara simultan bisa lebih buruk daripada changeNotifier (dan changeNotifier itu sangat buruk). 8. Perbarui widget tanpa menghabiskan RAM untuk itu. Get menyimpan hanya creator ID milik GetBuilder, dan memperbarui GetBuilder tersebut ketika diperlukan. Konsumsi memori dari get ID storage sangat rendah bahkan untuk ribuan GetBuilder sekalipun. Ketika anda membuat GetBuilder baru, anda sebenarnya berbagi state dari GetBuilder yang memiliki creator ID. State baru tidak dibuat untuk setiap GetBuilder, yang mana menghemat SANGAT BANYAK RAM untuk aplikasi berskala besar. Pada dasarnya, aplikasi anda akan Stateless secara menyeluruh, dan semakin sedikit Widget yang akan Stateful (didalam GetBuilder) akan memiliki satu state, dan oleh karena itu, mengupdate salah satu dari mereka akan mengupdate semuanya. State nya hanya satu. 9. Get maha tahu, dan dalam kebanyakan kasus, dia mengetahui dengan tepat kapan harus menghapus sebuah controller dari memori. Anda tidak perlu khawatir tentang kapan harus men-dispose sebuah controller, Get tahu waktu terbaik untuk melakukannya. ### Penggunaan ```dart // Buat kelas controller dan extends GetxController class Controller extends GetxController { int counter = 0; void increment() { counter++; update(); // gunakan update() untuk mengupdate variabel counter di UI ketika increment dipanggil } } // Pada Stateless/Stateful widget anda, gunakan GetBuilder untuk mengupdate Text ketika increment dipanggil GetBuilder( init: Controller(), // INISIALISASIKAN CONTROLLER HANYA UNTUK PERTAMA KALI builder: (_) => Text( '${_.counter}', ), ) // Inisialisasi controller anda hanya untuk pertama kali. Kedua kalinya anda menggunakan ReBuilder untuk controller yang sama, dan jangan gunakan itu lagi. Controller anda akan secara otomatis dihapus dari memori segera setelah widget yang ditandai sebagai 'init' di-deploy. Anda tidak perlu khawatir tentang itu, Get akan melakukannya secara otomatis, cukup pastikan anda tidak memulai controller yang sama dua kali. ``` **Selesai!** - Anda sudah mempelajari bagaimana caranya memanage state menggunakan Get. - Catatan: Anda mungkin menginginkan organisasi yang lebih besar, dan tidak menggunakan properti init. Untuk itu, anda bisa membuat kelas dan meng-extends kelas Bindings, dan didalamnya, sebutkan controller yang akan dibuat untuk route tersebut. Controller tidak akan dibuat pada waktu itu, sebaliknya, ini hanyalah sebuah statement, jadi pada saat pertama kali anda menggunakan sebuah Controller, Get akan tahu dimana harus mencarinya. Get akan melakukan lazyload, dan akan melanjutkan untuk men-dispose controller yang tidak lagi digunakan. Lihat contoh di pub.dev untuk melihat bagaimana cara kerjanya. Jika anda bernavigasi ke banyak route dan membutuhkan data dari controller yang sebelumnya anda gunakan, anda hanya perlu menggunakan GetBuilder lagi (tanpa init): ```dart class OtherClass extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( body: Center( child: GetBuilder( builder: (s) => Text('${s.counter}'), ), ), ); } ``` Jika anda perlu menggunakan controller anda di banyak tempat, dan diluar dari GetBuilder, cukup buat getter didalam controller anda dan anda akan mendapatkannya dengan mudah (atau gunakan `Get.find()`) ```dart class Controller extends GetxController { /// Anda tidak membutuhkan itu. Saya menyarankan menggunakannya hanya untuk kemudahan sintaks. /// dengan static method: Controller.to.increment(); /// tanpa static method: Get.find().increment(); /// Tidak ada perbedaan dari segi performa, atau efek samping apapun dalam menggunakan kedua sintaks diatas. Yang berbeda hanyalah yang satu tidak memerlukan type, dan yang satu lagi akan di autocomplete oleh IDE. static Controller get to => Get.find(); // Tambahkan baris ini int counter = 0; void increment() { counter++; update(); } } ``` Dan anda bisa mengakses controller secara langsung, dengan cara itu: ```dart FloatingActionButton( onPressed: () { Controller.to.increment(), } // Ini luar biasa simpel! child: Text("${Controller.to.counter}"), ), ``` Ketika anda menekan FloatingActionButton, semua widget yang me-listen variabel 'counter' akan diupdate secara otomatis. ### Bagaimana Get meng-handle controller Anggaplah kita memiliki ini: `Class A => Class B (memiliki controller X) => Class C (memiliki controller X)` Di kelas A, controller belum ada di memori, karena anda belum menggunakannya (Get itu lazyload). Di kelas B anda menggunakan controller tersebut, dan controller itu masuk kedalam memori. Di kelas C, anda menggunakan controller yang sama seperti di kelas B, Get akan berbagi state dari controller B dengan controller C, dan controller yang sama akan tetap ada di memori. Jika anda menutup screen C dan screen B, Get akan secara otomatis menghapus controller X dari memori dan membebaskan resource, karena kelas A tidak menggunakan controller. Jika anda bernavigasi ke kelas B lagi, controller X akan masuk ke memori lagi, jika daripada menuju ke kelas C, anda kembali ke kelas A lagi, Get akan menghapus controller dari memori dengan cara yang sama. Jika kelas C tidak menggunakan controller, dan anda mengeluarkan kelas B dari memori, tidak ada kelas yang menggunakan controller X dan demikian pula kelas itu akan dibuang. Satu-satunya exception yang bisa mengacau dengan Get, adalah jika anda menghapus B dari route secara tidak sengaja, dan mencoba menggunakan controller di C. Dalam kasus ini, creator ID dari controller yang ada di B terhapus, dan Get diprogram untuk menghapusnya dari memori untuk setiap controller yang tidak memiliki creator ID. Jika anda berniat melakukan ini, tambahkan flag "autoRemove: false" ke GetBuilder di kelas B dan gunakan "adoptID = true;" di GetBuilder pada kelas C. ### Anda tidak membutuhkan StatefulWidget lagi Menggunakan StatefulWidget berarti menyimpan sebuah state dari seluruh layar secara tidak perlu, meski karena anda perlu untuk merebuild sebuah widget secara minimal, anda akan menyematkannya kedalam Consumer/Observer/BlocProvider/GetBuilder/GetX/Obx, yang mana akan menjadi StatfulWidget yang lain. StatefulWidget adalah sebuah kelas yang lebih besar daripada StatelessWidget, yang mana akan mengalokasi lebih banyak RAM, dan ini mungkin tidak akan membuat perbedaan secara signifikan antara satu atau dua kelas, tapi itu pasti akan terjadi ketika anda memiliki 100 dari mereka! Kecuali anda perlu menggunakan mixin, seperti TickerProviderStateMixin, akan sangat tidak dibutuhkan menggunakan StatefulWidget dengan Get. Anda bisa memanggil semua method dari StatefulWidget secara langsung melalui GetBuilder. Jika anda perlu memanggil initState() atau dispose() method contohnya, anda bisa memanggilnya secara langsung; ```dart GetBuilder( initState: (_) => Controller.to.fetchApi(), dispose: (_) => Controller.to.closeStreams(), builder: (s) => Text('${s.username}'), ), ``` Cara yang lebih baik daripada ini adalah dengan menggunakan onInit() dan onClose() method secara langsung dari controller anda. ```dart @override void onInit() { fetchApi(); super.onInit(); } ``` - CATATAN: Jika anda ingin memulai sebuah method pada saat controller dipanggil pertama kali, anda TIDAK PERLU menggunakan constructor, faktanya, menggunakan package yang berorientasi pada performa seperti Get, ini berbatas pada bad practice, karena menyimpang dari logic dimana setiap controller dibuat atau dialokasikan (jika anda membuat sebuah instance dari controller, constructornya akan dipanggil dengan segera, anda akan menumpuk controller bahkan sebelum digunakan, anda mengalokasikan memori tanpa digunakan, hal ini benar-benar menyakiti prinsip dasar library ini). method onInit(); dan onClose(); dibuat untuk hal ini, mereka akan dipanggil ketika Controller dibuat, atau digunakan untuk pertama kali, bergantung pada kondisi apakah anda menggunakan Get.lazyPut atau tidak. Jika anda mau, sebagai contoh, membuat sebuah panggilan ke API anda untuk mempopulasikan data, anda bisa lupakan tentang metode lawas initState/dispose, cukup mulai memanggilnya didalam onInit, dan jika anda perlu meng-eksekusi perintah seperti menutup stream, gunakan onClose() untuk hal itu. ### Mengapa GetX ada Tujuan dari package ini adalah secara tepat memberikan anda sebuah solusi komplit untuk navigasi route, dependency dan state management, menggunakan sedikit mungkin dependensi, dengan tingkat decoupling yang tinggi. Get melibatkan semua Flutter API baik dari level tertinggi dan terendah dalam dirinya sendiri, untuk memastikan bahwa anda bekerja dengan sedikit mungkin keterkaitan (coupling). Kami memusatkan semuanya kedalam satu package, untuk memastikan bahwa anda tidak memiliki keterkaitan (coupling) di proyek anda. Dengan begitu, anda bisa menaruh hanya widget di view anda, dan membiarkan bagian tim anda yang bekerja dengan business logic secara bebas, bekerja dengan business logic tanpa bergantung pada elemen apapun yang ada didalam View. Ini menyediakan lingkungan kerja yang lebih bersih, jadi bagian yang lain dari tim anda bisa bekerja hanya dengan widget, tanpa khawatir tentang mengirim data dari controller anda. Jadi untuk menyederhanakannya: Anda tidak perlu memanggil method di initState dan mengirim mereka menggunakan parameter ke controller anda, atau menggunakan constructor didalam controller anda untuk itu, anda memiliki method onInit() yang akan dipanggil di waktu yang tepat untuk memulai service anda. Anda tidak perlu memanggil perangkatnya, anda memiliki method onClose() yang akan dipanggil di momen yang tepat ketika controller tidak lagi dibutuhkan dan akan dihapus dari memori. Dengan begitu, biarkan views hanya untuk widget, menghindari berbagai macam business logic darinya. Jangan memanggil dispose method didalam GetxController, itu tidak akan melakukan apa-apa, ingat bahwa controller bukanlah sebuah Widget, anda tidak seharusnya men-"dispose" sebuah controller, dan dia akan secara otomatis dan secara pandai dihapus dari memori oleh Get. Jika anda menggunakan stream didalamnya dan ingin menutupnya, cukup masukkan itu kedalam close method. Contoh: ```dart class Controller extends GetxController { StreamController user = StreamController(); StreamController name = StreamController(); /// menutup stream = onClose method, bukan dispose. @override void onClose() { user.close(); name.close(); super.onClose(); } } ``` Controller life cycle (atau: siklus kehidupan controller): - onInit() dimana ketika Controller dibuat. - onClose() dimana ketika Controller ditutup untuk membuat segala jenis perubahan dalam rangka persiapan untuk metode penghapusan - deleted: anda tidak lagi memiliki akses ke API ini karena secara harfiah menghapusnya dari memori, dan secara harfiah pula itu dihapus, tanpa meninggalkan jejak. ### Cara lain dalam menggunakannya Anda bisa menggunakan Controller secara langsung didalam GetBuilder value: ```dart GetBuilder( init: Controller(), builder: (value) => Text( '${value.counter}', // Disini ), ), ``` Anda juga mungkin membutuhkan sebuah instance dari controller anda diluar GetBuilder, dan anda bisa menggunakan pendekatan ini: ```dart class Controller extends GetxController { static Controller get to => Get.find(); [...] } // didalam View: GetBuilder( init: Controller(), // Gunakan ini hanya untuk pertama kali pada setiap controller builder: (_) => Text( '${Controller.to.counter}', // Disini ) ), ``` atau ```dart class Controller extends GetxController { // static Controller get to => Get.find(); // tanpa static getter [...] } // didalam stateful/stateless class GetBuilder( init: Controller(), // Gunakan ini hanya untuk pertama kali pada setiap controller builder: (_) => Text( '${Get.find().counter}', // Disini ), ), ``` - Anda bisa menggunakan cara "non-canonical" untuk hal ini. Jika anda menggunakan dependensi manager lain, seperti get_it, modular, etc., dan hanya ingin mengirimkan instance controller, anda bisa melakukan ini: ```dart Controller controller = Controller(); [...] GetBuilder( init: controller, // Disini builder: (_) => Text( '${controller.counter}', // Disini ), ), ``` ### Unique ID Jika anda ingin me-refine update control sebuah widget dengan GetBuilder, anda bisa mengajukan unique ID untuk mereka: ```dart GetBuilder( id: 'text' // Disini init: Controller(), builder: (_) => Text( '${Get.find().counter}', ), ), ``` Dan mengupdatenya dalam bentuk ini: ```dart update(['text']); ``` Anda juga bisa menerapkan kondisi untuk updatenya: ```dart update(['text'], counter < 10); ``` GetX melakukan ini secara otomatis dan hanya merekonstruksi widget yang menggunakan variabel persis yang dirubah, jika anda mengubah sebuah variabel dengan hal yang sama seperti sebelumnya, dan tidak mengimplifikasikan sebuah perubahan sate , GetX tidak akan merebuild widget untuk menghemat memori dan siklus CPU. ## Mencampur 2 state manager Beberapa orang membuka sebuah permintaan fitur, karena mereka hanya ingin menggunakan satu jenis variabel reaktif, dan mekanisme lain, dan diharuskan untuk memasukkan Obx kedalam GetBuilder untuk hal ini. Memikirkan hal ini, MixinBuilder dibuat. Ini mengizinkan kedua perubahan reaktif dengan mengubah variabel ".obs", dan mekanikal update via update(). Meski begitu, dari 4 widget dia adalah satu-satunya yang mengonsumsi resource paling banyak, karena selain memiliki langganan untuk menerima event perubahan dari children nya, dia juga berlangganan kepada metode update di controllernya. Meng-extend GetxController sangatlah penting, karena mereka memiliki siklus dan bisa "memulai" dan "mengakhiri" event di method onInit() dan onClose() mereka. Anda bisa menggunakan kelas apapun untuk ini, tapi Saya sangat merekomendasikan anda untuk menggunakan kelas GetxController untuk menempatkan variabel anda, baik apakah mereka observable atau tidak. ## GetBuilder vs GetX vs Obx vs MixinBuilder Dalam satu dekade bekerja dengan programming Saya mendapat beberapa pelajaran berharga. Kontak pertama saya dengan reactive programming adalah seperti "waw, ini luar biasa" dan faktanya, reactive programming itu luar biasa. Meski begitu, ini tidak cocok untuk segala situasi. Terkadang semua yang anda butuhkan adalah mengubah state dari 2 atau 3 widget dalam waktu yang sama, atau sebuah perubahan state sementara, yang mana reactive programming tidaklah buruk, tapi tidak cocok. Reactive programming memiliki konsumsi lebih tinggi dalam penggunaan RAM yang bisa dikompensasi oleh alur kerja individu, dimana akan memastikan bahwa hanya satu widget direbuild dan jika dibutuhkan, namun membuat sebuah list dari 80 objek, masing masing dengan beberapa stream bukanlah ide yang bagus. Buka dart inspect dan cek berapa banyak yang dikonsumsi oleh StreamBuilder, dan anda akan memahami apa yang saya coba katakan kepada anda. Dengan memikirkan hal itu, Saya membuat sebuah state manager yang simpel. Ini simpel, dan itulah yang harus anda tuntut darinya: mengupdate state dalam sebuah block dengan cara yang sederhana, dan dengan cara yang paling ekonomis. GetBuilder sangat ekonomis di RAM, dan hampir tidak ada pendekatan yang lebih ekonomis darinya (setidaknya saya tidak bisa membayangkannya, jika itu ada, mohon beri tahu kami). Meski begitu, GetBuilder tetaplah sebuah state manager mekanik, anda perlu memanggil update() seperti anda perlu memanggil notifyListeners() milik Provider. Ada beberapa situasi lain dimana reactive programming benar benar menarik, dan tidak bekerja dengan itu sama saja seperti "reinventing the wheel". Dengan memikirkan hal itu, GetX dibuat untuk menyediakan semuanya yang paling modern dan advanced dalam sebuah state manager. Dia mengupdate hanya apa yang diperlukan dan ketika diperlukan, jika anda memiliki error dan mengirim 300 perubahan state secara beruntun, GetX akan memfilter dan mengupdate layar hanya jika state nya benar-benar berubah. GetX masih lebih ekonomis dari reactive state manager yang lain, namun dia juga menkonsumsi sedikit lebih banyak RAM daripada GetBuilder. Memikirkan tentang hal itu dan menargetkan untuk memaksimalkan konsumsi resource yang dibuat oleh Obx. Tidak seperti GetX dan GetBuilder, anda tidak akan bisa menginisialisasi sebuah controller didalam Obx, itu hanyalah sebuah widget dengan StreamSubscription yang menerima event perubahan dari children anda, itu saja. Ini lebih ekonomis dari GetX, namun kalah dari GetBuilder, yang memang diharapkan, karena dia reactive dan GetBuilder memiliki pendekatan yang paling sederhana yang ada, untuk menyimpan hashCode milik sebuah widget dan StateSetter nya. Dengan Obx anda tidak perlu menulis tipe controller, dan anda bisa mendengar perubahan dari banyak controller yang berbeda, namun itu harus di inisialisasi sebelumnya, baik menggunakan contoh pendekatan di awal readme ini, atau menggunakan Binding class. ================================================ FILE: documentation/ja_JP/dependency_management.md ================================================ # 依存オブジェクト管理 - [依存オブジェクト管理](#依存オブジェクト管理) - [インスタンス化に使用するメソッド](#インスタンス化に使用するメソッド) - [Get.put()](#getput) - [Get.lazyPut()](#getlazyput) - [Get.putAsync()](#getputasync) - [Get.create()](#getcreate) - [インスタンス化したクラスを使う](#インスタンス化したクラスを使う) - [依存オブジェクトの置換](#依存オブジェクトの置換) - [各メソッドの違い](#各メソッドの違い) - [Bindings(Routeと依存オブジェクトの結束)](#Bindings(Routeと依存オブジェクトの結束)) - [Bindingsクラス](#bindingsクラス) - [BindingsBuilder](#bindingsbuilder) - [SmartManagement](#smartmanagement) - [設定の変更方法](#設定の変更方法) - [SmartManagement.full](#smartmanagementfull) - [SmartManagement.onlyBuilder](#smartmanagementonlybuilder) - [SmartManagement.keepFactory](#smartmanagementkeepfactory) - [Bindingsの仕組み](#Bindingsの仕組み) - [補足](#補足) Getにはシンプルで強力な依存オブジェクト管理機能があります。たった1行のコードで、Provider contextやinheritedWidgetを使うことなく、BlocもしくはControllerのインスタンスを取得することができます。 ```dart Controller controller = Get.put(Controller()); // Controller controller = Controller() の代わりに ``` UIクラスの中でControllerクラスをインスタンス化する代わりに、Getインスタンスの中でインスタンス化することで、アプリ全体でControllerを利用できるようになります。 - 注: Getの状態管理機能を使用する場合は、[Bindings](#bindings)の使用も検討してください。Bindingsを使うことでビューにControllerを結合させることができます。 - 注²: Getの依存オブジェクト管理機能は、パッケージの他の部分から独立しています。そのため、たとえばあなたのアプリが既に他の状態管理ライブラリを使用している場合(どんなものでも)、何の問題もなく2つを組み合わせることができます。 ## インスタンス化に使用するメソッド Controllerを初期化するのに使用するメソッドとその設定パラメーターについてご説明します。 ### Get.put() 依存オブジェクトを注入するための最も基本のメソッド。たとえば、ビューで使用するControllerに使います。 ```dart Get.put(SomeClass()); Get.put(LoginController(), permanent: true); Get.put(ListItemController, tag: "some unique string"); ``` 以下が put() を使用する際に設定できるパラメーターです。 ```dart Get.put( // 必須。インスタンスを保存しておきたいControllerなどを設定 // 注: "S" 型はジェネリクスで、どんな型でもOK S dependency // オプション。これは同じ型のControllerインスタンスが複数存在する場合に、 // タグIDにより識別したい場合に使用します。 // Get.find(tag: ) でこのputで設定したインスタンスを探します。 // tag はユニークなStringである必要があります。 String tag, // オプション。デフォルトでは使用されなくなったインスタンスは破棄されますが、 // (たとえばビューが閉じられた場合など) SharedPreferencesのインスタンスなど、 // アプリ全体を通して生かしておきたい場合があるかと思います。 // その場合はこれをtrueにしてください。デフォルトはfalseです。 bool permanent = false, // オプション。テストで抽象クラスを使用した後、別クラスに置換してテストを追うことができます。 // デフォルトはfalseです。 bool overrideAbstract = false, // オプション: 依存オブジェクトを関数を使って作ることができます。 // 使用頻度は低いと思います。 InstanceBuilderCallback builder, ) ``` ### Get.lazyPut() 依存オブジェクトをすぐにロードする代わりに、lazy(遅延、消極的)ロードすることができます。実際に使用されるときに初めてインスタンス化されます。計算量の多いクラスや、Bindingsを使って複数のControllerをまとめてインスタンス化したいが、その時点ではすぐにそれらを使用しないことがわかっている場合などに非常に便利です。 ```dart /// この場合のApiMockは Get.find を使用した時点でインスタンス化されます。 Get.lazyPut(() => ApiMock()); Get.lazyPut( () { // 必要ならここに何かのロジック return FirebaseAuth(); }, tag: Math.random().toString(), fenix: true ) Get.lazyPut( () => Controller() ) ``` これが .lazyPut で設定できるパラメーターです。 ```dart Get.lazyPut( // 必須。クラスSが初めてfindの対象になったときに実行されるメソッド InstanceBuilderCallback builder, // オプション。Get.put()のtagと同様に同じクラスの異なるインスタンスが必要なときに使用 // ユニークな値を指定 String tag, // オプション。"permanent" に似ていますが、使用されていないときはインスタンスが // 破棄され、再び使用するときにGetがインスタンスを再び作成する点が異なります。 // Bindings APIの "SmartManagement.keepFactory " と同じです。 // デフォルトはfalse bool fenix = false ) ``` ### Get.putAsync() SharedPreferencesなど、非同期のインスタンスを登録したいときに使います。 ```dart Get.putAsync(() async { final prefs = await SharedPreferences.getInstance(); await prefs.setInt('counter', 12345); return prefs; }); Get.putAsync( () async => await YourAsyncClass() ) ``` これが .putAsync で設定できるパラメーターです。 ```dart Get.putAsync( // 必須。クラスをインスタンス化するための非同期メソッドを指定 AsyncInstanceBuilderCallback builder, // オプション。Get.put() と同じです。同じクラスの異なるインスタンスを作りたいときに使用 // ユニークな値を指定 String tag, // オプション。Get.put() と同じです。アプリ全体を通して生かしておきたい場合に使用 // デフォルトはfalse bool permanent = false ) ``` ### Get.create() これは使いどころに迷うかもしれません。他のものとの違いなど詳細な説明は「[各メソッドの違い](#各メソッドの違い)」のセクションをご一読ください。 ```dart Get.Create(() => SomeClass()); Get.Create(() => LoginController()); ``` これが .create で設定できるパラメーターです。 ```dart Get.create( // 必須。Get.find() が呼ばれるたびにインスタンスがこの関数で新たに組み立てられる。 // 例: Get.create(() => YourClass()) FcBuilderFunc builder, // オプション。Get.put() のtagと同様で、同じクラスによる // 複数インスタンスを扱うときに使用します。 // リストのアイテムにそれぞれコントローラが必要な場合に便利です。 // 文字列はユニークである必要があります。 String name, // オプション。Get.put() と同様アプリ全体でインスタンスを維持するときに使用。 // 唯一の違いは Get.create() のpermanentはデフォルトでtrueということだけです。 bool permanent = true ``` ## インスタンス化したクラスを使う いくつかのRouteを渡り歩いた後、以前のControllerに残してきたデータが必要になったとしたら、Provider や Get_it と組み合わせる必要がありますよね?Getの場合は違います。GetにControllerの「検索」を依頼するだけで追加の依存オブジェクトの注入は必要ありません。 ```dart final controller = Get.find(); // もしくは Controller controller = Get.find(); // マジックみたいですよね。でも実際にGetはControllerのインスタンスを探して届けてくれます。 // 100万ものControllerをインスタンス化していても、Getは常に正しいControllerを探してくれます。 ``` そしてそのControllerが以前取得したデータをあなたは復元することができます。 ```dart Text(controller.textFromApi); ``` 戻り値は通常のクラスなので、そのクラスで可能なことは何でもできます。 ```dart int count = Get.find().getInt('counter'); print(count); // 出力: 12345 ``` インスタンスを明示的に削除したい場合はこのようにしてください。 ```dart Get.delete(); // 通常であれば、GetXは未使用Controllerを自動削除するので、この作業は必要ありません。 ``` ## 依存オブジェクトの置換 注入されている依存オブジェクトは `replace` または `lazyReplace` メソッドを使って、子クラスなど関連クラスのインスタンスに置き換えることができます。これは元の抽象クラスを型指定することで取得することができます。 ```dart abstract class BaseClass {} class ParentClass extends BaseClass {} class ChildClass extends ParentClass { bool isChild = true; } Get.put(ParentClass()); Get.replace(ChildClass()); final instance = Get.find(); print(instance is ChildClass); //true class OtherClass extends BaseClass {} Get.lazyReplace(() => OtherClass()); final instance = Get.find(); print(instance is ChildClass); // false print(instance is OtherClass); //true ``` ## Differences between methods まずは Get.lazyPut の `fenix` プロパティと、他メソッドの `permanent` プロパティの違いについてご説明します。 `permanent` と `fenix` の根本的な違いは、インスタンスをどのように保持したいかという点に尽きます。 しつこいようですが、GetXでは使われていないインスタンスは削除されるのがデフォルトの動作です。 これはもし画面AがController A、画面BがController Bを持っている場合において、画面Bに遷移するときに画面Aをスタックから削除した場合(`Get.off()` や `Get.offNamed()` を使うなどして)、Controller Aは消えてなくなるということです。 しかし Get.put() する際に `permanent:true` としていれば、Controller Aはこの画面削除により失われることはありません。これはアプリケーション全体を通してControllerを残しておきたい場合に大変便利です。 一方の `fenix` は、画面削除に伴っていったんはControllerが消去されますが、再び使いたいと思ったときに復活させることができます。つまり基本的には未使用の Controller / サービス / その他クラス は消去されますが、必要なときは新しいインスタンスを「燃えカス」から作り直すことができるのです。 各メソッドを使用する際のプロセスの違いをご説明します。 - Get.put と Get.putAsync はインスタンスを作成して初期化するプロセスは同じですが、後者は非同期メソッドを使用するという違いがあります。この2つのメソッドは内部に保有するメソッド `insert` に `permanent: false` と `isSingleton: true` という引数を渡して、メモリに直接インスタンスを挿入します (この isSingleton が行っていることは、依存オブジェクトを `dependency` と `builder` プロパティのどちらから拝借するかを判断することだけです)。その後に `Get.find()` が呼ばれると、メモリ上にあるインスタンスを即座に初期化するというプロセスをたどります。 - Get.create はその名の通り、依存オブジェクトを「クリエイト」します。Get.put() と同様に内部メソッドである `insert` を呼び出してインスタンス化します。違いは `permanent: true` で `isSingleton: false` である点です (依存オブジェクトを「クリエイト」しているため、シングルトンにはなりません。それが false になっている理由です)。また `permanent: true` となっているので、デフォルトでは画面の破棄などでインスタンスを失わないというメリットがあります。また `Get.find()` はすぐに呼ばれず、画面内で実際に使用されてから呼ばれます。これは `permanent` の特性を活かすための設計ですが、それゆえ `Get.create()` は共有しないけど破棄もされないインスタンスを作成する目的で作られたと言えます。たとえば、ListViewの中のボタンアイテムに使うControllerインスタンスのように、そのリスト内でしか使わないけどリストアイテムごとに固有のインスタンスが必要なケースなどが考えられます。そのため、Get.create は GetWidget との併用がマストです。 - Get.lazyPut は初期化をlazy(遅延、消極的)に行います。実行されるとインスタンスは作成されますが、すぐに使用できるように初期化はされず、待機状態になります。また他のメソッドと異なり `insert` メソッドは呼び出されません。その代わり、インスタンスはメモリの別の部分に挿入されます。この部分を「ファクトリー」と呼ぶことにしましょう。「ファクトリー」は、そのインスタンスが再生成できるかどうかを決める役割を持っています。これは後で使う予定のものを、現在進行形で使われているものと混ざらないようにするための工夫です。ここで `fenix` によるマジックが登場します。デフォルトの `fenix: false` のままにしており、かつ `SmartManagement` が `keepFactory` ではない場合において `Get.find` を使用すると、インスタンスは「ファクトリー」から共有メモリ領域に移動します。その直後にインスタンスは「ファクトリー」から削除されます。しかし `fenix: true` としていた場合、インスタンスは「ファクトリー」に残るため、共有メモリ領域から削除されても再び呼び出すことができるのです。 ## Bindings(Routeと依存オブジェクトの結束) このパッケージの一番の差別化要素は、Route管理 / 状態管理 / 依存オブジェクト管理 を統合したことにあると思っています。 スタックからRouteが削除されれば、関係するController、変数、オブジェクトのインスタンスがすべてメモリから削除されます。たとえばStreamやTimerを使用している場合も同様ですので、開発者は何も心配する必要はありません。 Getはバージョン2.10からBindings APIをフル実装しました。 Bindingsを使用すれば init でControllerを起動する必要はありません。またControllerの型を指定する必要もありません。Controllerやサービスは各々適切な場所で起動することができるようになりました。 Bindingsは依存オブジェクトの注入をビューから切り離すことができるクラスです。それに加え、状態と依存オブジェクトの管理機能をRouteに「結束(bind)」してくれます。 これによりGetは、あるControllerが使用されたときにどの画面UIが表示されているかを知ることができます。つまり、そのControllerをどのタイミングでどう処分するかを判断することができるということです。 さらにBindingsでは SmartManager の制御により、依存オブジェクトをどのタイミング(スタックからRouteを削除したときか、それに依存するWidgetを表示したときか、いずれでもないか)で整理するかを設定することができます。インテリジェントな依存オブジェクトの自動管理機能を持ちつつ、自分の好きなように設定できるのです。 ### Bindingsクラス - Bindings機能を実装したクラスを作成することができます。 ```dart class HomeBinding implements Bindings {} ``` "dependencies" メソッドをオーバーライドするようIDEに自動で指摘されます。表示をクリックしてメソッドを override し、そのRoute内で使用するすべてのクラスを挿入してください。 ```dart class HomeBinding implements Bindings { @override void dependencies() { Get.lazyPut(() => HomeController()); Get.put(()=> Api()); } } class DetailsBinding implements Bindings { @override void dependencies() { Get.lazyPut(() => DetailsController()); } } ``` Bindingsを設定したら、このクラスが Route管理 / 依存オブジェクト管理 / 状態管理 を互いに接続する目的で使用されるものだということをRouteに知らせてあげます。 - 名前付きRouteを使う場合 ```dart getPages: [ GetPage( name: '/', page: () => HomeView(), binding: HomeBinding(), ), GetPage( name: '/details', page: () => DetailsView(), binding: DetailsBinding(), ), ]; ``` - 通常のRouteを使う場合 ```dart Get.to(Home(), binding: HomeBinding()); Get.to(DetailsView(), binding: DetailsBinding()) ``` これでアプリケーションのメモリ管理を気にする必要がなくなります。Getがすべてやってくれます。 BindingsクラスはRouteの呼び出しと同時に呼び出されます。また、すべてに共通の依存オブジェクトを挿入するためには GetMaterialApp の initialBinding プロパティを使用してください。 ```dart GetMaterialApp( initialBinding: SampleBind(), home: Home(), ); ``` ### BindingsBuilder Bindingsを作成する一般的な方法は Bindings を実装したクラスを作成することですが、`BindingsBuilder` コールバックを使う方法もあります。 Example: ```dart getPages: [ GetPage( name: '/', page: () => HomeView(), binding: BindingsBuilder(() { Get.lazyPut(() => ControllerX()); Get.put(()=> Api()); }), ), GetPage( name: '/details', page: () => DetailsView(), binding: BindingsBuilder(() { Get.lazyPut(() => DetailsController()); }), ), ]; ``` この方法ならRouteごとにBindingsクラスを作る必要はありません。 どちらの方法でも効果は変わりませんのでお好みの方法を使ってください。 ### SmartManagement エラーが発生してControllerを使用するWidgetが正しく破棄されなかった場合でも、Controllerが未使用になればGetXはデフォルトの動作通りそれをメモリから削除します。 これがいわゆる依存オブジェクト管理機能の `full` モードと呼ばれるものです。 しかしもしGetXによるオブジェクト破棄の方法をコントロールしたい場合は、`SmartManagement`クラスを使って設定してください。 #### 設定の変更方法 この設定は通常変更する必要はありませんが、変更されたい場合はこのようにしてください。 ```dart void main () { runApp( GetMaterialApp( smartManagement: SmartManagement.onlyBuilder // ここで設定 home: Home(), ) ) } ``` #### SmartManagement.full これがデフォルトのモードです。使用されていない、かつ `permanent: true` が設定されていないオブジェクトを自動で破棄してくれます。特殊な事情がない限り、この設定は触らない方がいいでしょう。GetXを使って間がない場合は特に。 #### SmartManagement.onlyBuilder `init:` もしくはBindings内で `Get.lazyPut()` により設定したビルダー製のオブジェクトだけを破棄するモードです。 もしそれが `Get.put()` や `Get.putAsync()` などのアプローチで生成したオブジェクトだとしたら、SmartManagement は勝手にメモリから除外することはできません。 それに対してデフォルトのモードでは `Get.put()` で生成したオブジェクトも破棄します。 #### SmartManagement.keepFactory SmartManagement.full と同じように、オブジェクトが使用されていない状態になれば破棄します。ただし、前述の「ファクトリー」に存在するものだけは残します。つまりそのインスタンスが再び必要になった際は依存オブジェクトを再度生成するということです。 ### Bindingsの仕組み Bindingsは「一過性のファクトリー」のようなものを作成します。これはそのRouteに画面遷移した瞬間に作成され、そこから画面移動するアニメーションが発生した瞬間に破棄されます。 この動作は非常に高速で行われるので、アナライザーでは捕捉できないほどです。 再び元の画面に戻ると新しい「一過性のファクトリー」が呼び出されます。そのためこれは SmartManagement.keepFactory を使用するよりも多くの場合好ましいですが、Bindingsを作成したくない場合やすべての依存オブジェクトを同じBindingsに持っておきたい場合は SmartManagement.keepFactory を使うといいでしょう。 ファクトリーのメモリ使用量は少なく、インスタンスを保持することはありません。その代わりにそのクラスのインスタンスを形作る関数を保持します。 メモリコストは非常に低いのですが、最小リソースで最大パフォーマンスを得ることが目的のGetではデフォルトでファクトリーを削除します。 どちらか都合に合う方ををお使いいただければと思います。 ## 補足 - 複数のBindingsを使う場合は SmartManagement.keepFactory は**使わない**でください。これは Bindings を使わないケースや、GetMaterialAppのinitialBindingに設定された単独のBindingと一緒に使うケースを想定されて作られました。 - Bindingsを使うことは必須ではありません。`Get.put()` と `Get.find()` だけでも全く問題ありません。 ただし、サービスやその他抽象度の高いクラスをアプリに取り入れる場合はコード整理のために使うことをおすすめします。 ================================================ FILE: documentation/ja_JP/route_management.md ================================================ - [Route管理](#Route管理) - [使い方](#使い方) - [通常Routeによるナビゲーション](#通常Routeによるナビゲーション) - [名前付きRouteによるナビゲーション](#名前付きRouteによるナビゲーション) - [名前付きRouteにデータを送る](#名前付きRouteにデータを送る) - [動的URLの生成](#動的URLの生成) - [ミドルウェアの使用](#ミドルウェアの使用) - [contextを使わないナビゲーション](#contextを使わないナビゲーション) - [SnackBar](#snackbar) - [Dialog](#dialog) - [BottomSheet](#bottomsheet) - [ネスト構造のナビゲーション](#ネスト構造のナビゲーション) # Route管理 このドキュメントではGetXにおけるRoute管理のすべてをご説明します。 ## How to use 次の3文字を pubspec.yaml ファイルに追加してください。 ```yaml dependencies: get: ``` Route / SnackBar / Dialog / BottomSheet をcontextなしで、あるいは高レベルのGet APIを使用するには MaterialApp の前に「Get」を追加してください。それだけで GetMaterialApp の機能が使用できます。 ```dart GetMaterialApp( // 変更前: MaterialApp( home: MyHome(), ) ``` ## 名前付きRouteによる画面遷移 次の画面に遷移するには Get.to を使ってください。 ```dart Get.to(NextScreen()); ``` SnackBar / Dialog / BottomSheet など Navigator.pop(context) で閉じるものと同じものを閉じるには Get.back を使います。 ```dart Get.back(); ``` 次の画面に遷移しつつ、前の画面に戻れないようにするには Get.off を使います(スプラッシュスクリーンやログイン画面などで使用)。 ```dart Get.off(NextScreen()); ``` 次の画面に遷移して、それ以前のRouteはすべて破棄するには Get.offAll を使います(ショッピングカート、投票、テストなどで使用) ```dart Get.offAll(NextScreen()); ``` 次の画面に遷移して、戻ったらデータを受け取る方法はこちら。 ```dart var data = await Get.to(Payment()); ``` 次の画面では、このようにデータを前の画面に送る必要があります。 ```dart Get.back(result: 'success'); ``` そして使いましょう。 ex: ```dart if(data == 'success') madeAnything(); ``` どのようなシンタックスがあるかもっと知りたいですか? いつもの Navigator ではなく navigator と入れてみてください。通常のNavigatorで使えるプロパティがcontextなしで使えるようになっているかと思います。 ```dart // 通常のFlutterによるNavigator Navigator.of(context).push( context, MaterialPageRoute( builder: (BuildContext context) { return HomePage(); }, ), ); // GetではFlutterのシンタックスをcontextなしで使えます navigator.push( MaterialPageRoute( builder: (_) { return HomePage(); }, ), ); // Getのシンタックス(上記よりかなり短いですね) Get.to(HomePage()); ``` ## 名前付きRouteによる画面遷移 - Getは名前付きRouteによる遷移もサポートしています。 次の画面への遷移はこう。 ```dart Get.toNamed("/NextScreen"); ``` Get.off の名前付きRoute版。 ```dart Get.offNamed("/NextScreen"); ``` Get.offAll の名前付きRoute版。 ```dart Get.offAllNamed("/NextScreen"); ``` Routeを定義するにはGetMaterialAppを使ってください。 ```dart void main() { runApp( GetMaterialApp( initialRoute: '/', getPages: [ GetPage(name: '/', page: () => MyHomePage()), GetPage(name: '/second', page: () => Second()), GetPage( name: '/third', page: () => Third(), transition: Transition.zoom ), ], ) ); } ``` 未定義Route(404エラー)に遷移させるには、GetMaterialAppで unknownRoute を設定してください。 ```dart void main() { runApp( GetMaterialApp( unknownRoute: GetPage(name: '/notfound', page: () => UnknownRoutePage()), initialRoute: '/', getPages: [ GetPage(name: '/', page: () => MyHomePage()), GetPage(name: '/second', page: () => Second()), ], ) ); } ``` ### 名前付きRouteにデータを送る 次の画面に渡すデータは arguments で引数を設定します。Getでは引数にどんなものでも指定できます。StringでもMapでもListでも、クラスのインスタンスでも大丈夫です。 ```dart Get.toNamed("/NextScreen", arguments: 'Get is the best'); ``` ビュー側のクラスやControllerで値を使うにはこうしてください。 ```dart print(Get.arguments); // Get is the best が表示される ``` ### 動的URLの生成 Getはウェブのような高度な動的URLを提供します。ウェブ開発者はFlutterにこの機能が提供されることを待ち望んでいたことでしょう。この機能の提供を謳うパッケージは存在しますが、ウェブ上のURLとは全く異なるシンタックスが表示されているのを見たことがあるかもしれません。Getはこの点も解決します。 ```dart Get.offAllNamed("/NextScreen?device=phone&id=354&name=Enzo"); ``` ビュー側のクラスやControllerで値を使う方法。 ```dart print(Get.parameters['id']); // 出力: 354 print(Get.parameters['name']); // 出力: Enzo ``` この名前付きパラメーターはこのように簡単に受け取ることもできます。 ```dart void main() { runApp( GetMaterialApp( initialRoute: '/', getPages: [ GetPage( name: '/', page: () => MyHomePage(), ), GetPage( name: '/profile/', page: () => MyProfile(), ), // 引数userを使う場合と使わない場合で別ページを定義することが可能です。 // ただ、そのためにはスラッシュ '/' をベースのRoute名の後に入れる必要があります。 GetPage( name: '/profile/:user', page: () => UserProfile(), ), GetPage( name: '/third', page: () => Third(), transition: Transition.cupertino ), ], ) ); } ``` Route名を使ってデータを送る方法。 ```dart Get.toNamed("/profile/34954"); ``` 次の画面でデータを受け取る方法。 ```dart print(Get.parameters['user']); // out: 34954 ``` 複数のパラメーターを送信するにはこちら。 ```dart Get.toNamed("/profile/34954?flag=true&country=italy"); ``` もしくは ```dart var parameters = {"flag": "true","country": "italy",}; Get.toNamed("/profile/34954", parameters: parameters); ``` 次の画面でデータを受け取る方法。 ```dart print(Get.parameters['user']); print(Get.parameters['flag']); print(Get.parameters['country']); // 出力: 34954 true italy ``` あとは Get.toNamed() を使い、名前付きRouteを指定するだけです(contextを使わないので BLoC や Controller から直接Routeを呼び出すことができます)。ウェブアプリとしてコンパイルされると、Routeが正しくURLに表示されます。 ### ミドルウェアの使用 何かのアクションのトリガーとなるイベントを取得したい場合は、routingCallbackを使用してください。 ```dart GetMaterialApp( routingCallback: (routing) { if(routing.current == '/second'){ openAds(); } } ) ``` GetMaterialAppを使用しない場合は、手動のAPIを使ってミドルウェアオブザーバーを設定してください。 ```dart void main() { runApp( MaterialApp( onGenerateRoute: Router.generateRoute, initialRoute: "/", navigatorKey: Get.key, navigatorObservers: [ GetObserver(MiddleWare.observer), // ここ ], ), ); } ``` ミドルウェアクラスを作成する ```dart class MiddleWare { static observer(Routing routing) { /// Routeの他に SnackBar / Dialog / BottomSheet のイベントも監視することができます。 /// また、ここで直接この3つのいずれかを表示したい場合は、 /// イベント自身が「それではない」ことを事前にチェックする必要があります。 if (routing.current == '/second' && !routing.isSnackbar) { Get.snackbar("Hi", "You are on second route"); } else if (routing.current =='/third'){ print('last route called'); } } } ``` それではGetをコードで使ってみましょう。 ```dart class First extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( leading: IconButton( icon: Icon(Icons.add), onPressed: () { Get.snackbar("hi", "i am a modern snackbar"); }, ), title: Text('First Route'), ), body: Center( child: ElevatedButton( child: Text('Open route'), onPressed: () { Get.toNamed("/second"); }, ), ), ); } } class Second extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( leading: IconButton( icon: Icon(Icons.add), onPressed: () { Get.snackbar("hi", "i am a modern snackbar"); }, ), title: Text('second Route'), ), body: Center( child: ElevatedButton( child: Text('Open route'), onPressed: () { Get.toNamed("/third"); }, ), ), ); } } class Third extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Third Route"), ), body: Center( child: ElevatedButton( onPressed: () { Get.back(); }, child: Text('Go back!'), ), ), ); } } ``` ## contextを使わないナビゲーション ### SnackBar FlutterでシンプルなSnackBarを表示したいとき、Scaffoldのcontextか、GlobalKeyを取得する必要があります。 ```dart final snackBar = SnackBar( content: Text('Hi!'), action: SnackBarAction( label: 'I am a old and ugly snackbar :(', onPressed: (){} ), ); // WidgetツリーでScaffoldを探し、それをSnackBar表示に使用します。 Scaffold.of(context).showSnackBar(snackBar); ``` Getならこうなります。 ```dart Get.snackbar('Hi', 'i am a modern snackbar'); ``` コードのどこにいようと、Get.snackbar を呼ぶだけでいいのです。カスタマイズも自由自在です。 ```dart Get.snackbar( "Hey i'm a Get SnackBar!", // タイトル "It's unbelievable! I'm using SnackBar without context, without boilerplate, without Scaffold, it is something truly amazing!", // 本文 icon: Icon(Icons.alarm), shouldIconPulse: true, onTap:(){}, barBlur: 20, isDismissible: true, duration: Duration(seconds: 3), ); ////////// すべてのプロパティ ////////// // Color colorText, // Duration duration, // SnackPosition snackPosition, // Widget titleText, // Widget messageText, // bool instantInit, // Widget icon, // bool shouldIconPulse, // double maxWidth, // EdgeInsets margin, // EdgeInsets padding, // double borderRadius, // Color borderColor, // double borderWidth, // Color backgroundColor, // Color leftBarIndicatorColor, // List boxShadows, // Gradient backgroundGradient, // TextButton mainButton, // OnTap onTap, // bool isDismissible, // bool showProgressIndicator, // AnimationController progressIndicatorController, // Color progressIndicatorBackgroundColor, // Animation progressIndicatorValueColor, // SnackStyle snackStyle, // Curve forwardAnimationCurve, // Curve reverseAnimationCurve, // Duration animationDuration, // double barBlur, // double overlayBlur, // Color overlayColor, // Form userInputForm /////////////////////////////////// ``` 従来の SnackBar がお好みの場合や、ゼロからカスタマイズしたい場合 (たとえば Get.snackbar ではタイトルと本文が必須項目となっています)は `Get.rawSnackbar();` を使ってください。SnackBarの元々のAPIを取得できます。 ### Dialog ダイアログを表示する方法。 ```dart Get.dialog(YourDialogWidget()); ``` デフォルトのダイアログを表示する方法。 ```dart Get.defaultDialog( onConfirm: () => print("Ok"), middleText: "Dialog made in 3 lines of code" ); ``` また showGeneralDialog の代わりに Get.generalDialog が使えます。 Overlayを使用するCupertino含むその他のFlutterのダイアログについては、contextの代わりに Get.overlayContext を使うことでコードのどこでもダイアログを表示することができます。 Overlayを使わないWidgetについては、Get.context が使えます。 これら2つのcontextはほとんどのケースでUIのcontextを代替することができるでしょう。ただし、ナビゲーションのcontextを使用せずInheritedWidgetが使われているケースは例外です。 ### BottomSheet Get.bottomSheet は showModalBottomSheet に似ていますが、contextが不要です。 ```dart Get.bottomSheet( Container( child: Wrap( children: [ ListTile( leading: Icon(Icons.music_note), title: Text('Music'), onTap: () {} ), ListTile( leading: Icon(Icons.videocam), title: Text('Video'), onTap: () {}, ), ], ), ) ); ``` ## ネスト構造のナビゲーション GetはFlutterのネスト構造のナビゲーションの扱いも簡単にしてくれます。 contextを必要とせず、IDによりナビゲーションのスタックを見つけることができます。 - 注: 並列のナビゲーションスタックを作成することは危険です。ネスト構造のNavigatorを使用しないか、使用を控えめにするのが理想です。必要なら使っていただいても問題ありませんが、複数のナビゲーションのスタックを保持することはRAM消費の面で好ましくないということは覚えておいてください。 こんなに簡単にできます。 ```dart Navigator( key: Get.nestedKey(1), // インデックス指定でkey作成 initialRoute: '/', onGenerateRoute: (settings) { if (settings.name == '/') { return GetPageRoute( page: () => Scaffold( appBar: AppBar( title: Text("Main"), ), body: Center( child: TextButton( color: Colors.blue, onPressed: () { Get.toNamed('/second', id:1); // インデックス指定でネスト型Routeに遷移 }, child: Text("Go to second"), ), ), ), ); } else if (settings.name == '/second') { return GetPageRoute( page: () => Center( child: Scaffold( appBar: AppBar( title: Text("Main"), ), body: Center( child: Text("second") ), ), ), ); } } ), ``` ================================================ FILE: documentation/ja_JP/state_management.md ================================================ * [状態管理](#状態管理) + [リアクティブな状態管理](#リアクティブな状態管理) - [利点](#利点) - [パフォーマンスの最大化](#パフォーマンスの最大化) - [リアクティブな変数の宣言](#リアクティブな変数の宣言) - [初期値の設定](#初期値の設定) - [Observableの値をビュー内で使う](#Observableの値をビュー内で使う) - [更新条件を設定](#更新条件を設定) - [.obsの使いどころ](#.obsの使いどころ) - [List(Rx)に関する補足](#List(Rx)に関する補足) - [なぜ「.value」を使う必要があるのか](#なぜ「.value」を使う必要があるのか) - [Obx()](#obx) - [Worker](#worker) + [非リアクティブな状態管理](#非リアクティブな状態管理) - [利点](#利点) - [使用例](#使用例) - [Controllerインスタンスの扱い](#Controllerインスタンスの扱い) - [StatefulWidgetsはもういらない](#StatefulWidgetsはもういらない) - [Getの目的](#Getの目的) - [Controllerの様々な使用方法](#Controllerの様々な使用方法) - [ユニークIDの設定](#ユニークIDの設定) + [状態管理ソリューションを混在させる](#状態管理ソリューションを混在させる) + [StateMixin](#StateMixin) + [GetBuilder VS GetX VS Obx VS MixinBuilder](#GetBuilder-VS-GetX-VS-Obx-VS-MixinBuilder) # 状態管理 GetXは他の状態管理ライブラリのように Stream や ChangeNotifier を使用する必要がありません。なぜか?私たちは応答時間とRAM消費量を改善するために GetValueとGetStream という低遅延のソリューションを開発しましたが、状態管理機能を含むGetXのリソースはすべてこれをベースに作られているためです。このソリューションはより低い運用コストと高いパフォーマンスを実現します。GetXを使えばAndroid、iOS、Web、Linux、macOS用のアプリケーションを作成するだけでなく、Flutter/GetXと同じシンタックスでサーバーアプリケーションを作ることができます。 * _バカみたいにシンプル_: 他の状態管理アプローチの中には、複雑で多くのボイラープレートコードを書かなければいけないものもあります。この問題により少なくない人たちがFlutterを見限りましたが、今ようやく、バカみたいにシンプルなソリューションを手に入れることができました。GetXを使えば、非常にすっきりとした、記述量の少ないコードでより多くのことができるようになります。イベントごとにクラスを定義する必要もありません。 * _コード生成にサヨナラ_: 開発時間の半分はアプリケーションロジックの作成に費やします。それにも関わらず、状態管理ライブラリの中には、ミニマルなコードを作るためにコード生成ツールに依存しているものがあります。変数を変更して build_runner を実行するのは非生産的ですし、flutter clean 後の待ち時間もコーヒーをたくさん飲まなければならないほど長くなることもあります。 GetXはすべてがリアクティブであり、コード生成ツールに依存しないため、開発のあらゆる面において生産性が向上します。 * _context依存にサヨナラ_: ビューとビジネスロジックを連携させるため、ビューのcontextをControllerに送る必要に迫られた。contextがないところで依存オブジェクトの注入をする必要があり、contextを方々のクラスや関数からなんとか渡した。これらの経験、誰もが通ってきた道かと思います。しかし、GetXではこのような経験をすることはありません。contextなしで、Controllerの中から別のControllerにアクセスすることができます。パラメーターを通じて無駄にcontextを送る必要はもうありません。 * _細かいコントロール_: 多くの状態管理ソリューションは、ChangeNotifierをベースにしています。ChangeNotifierは、notifyListenerが呼ばれたときに、依存するすべてのWidgetに通知します。画面に40個のWidgetがあるとしましょう。それらがすべてChangeNotifierの変数に依存している場合、変数を1つでも更新すれば、すべてのWidgetが更新されます。 GetXを使えばネストされたWidgetさえも的確にビルドを処理することができます。ListViewを担当するObxと、ListViewの中のチェックボックスを担当するObxがあれば、チェックボックスの値を変更した場合はチェックボックスWidgetだけが更新され、Listの値を変更した場合はListViewだけが更新されます。 * _変数が本当に変わったときだけ更新する_: GetXはデータの流れをコントロールします。つまり、Textに紐づいたObservable(監視可能)変数の値 'Paola' を、同じ 'Paola' に変更してもWidgetは更新されません。これは、GetXがTextに'Paola'がすでに表示されていることをわかっているためです。 多くの状態管理ソリューションは、この場合更新を行います。 ## リアクティブな状態管理 リアクティブプログラミングは複雑であると言われがちなためか、多くの人に敬遠されています。しかし、GetXはリアクティブプログラミングを非常にシンプルなものにしてくれます。 * StreamControllerを作る必要はありません。 * 変数ごとにStreamBuilderをセットする必要はありません。 * 状態ごとにクラスを作る必要はありません。 * 初期値のためにgetを準備する必要はありません。 Getによるリアクティブプログラミングは、setState並に簡単です。 たとえば name という変数があり、それを変更するたびに変数に依存するすべてのWidgetを自動更新したいとします。 これがその name 変数です。 ``` dart var name = 'Jonatas Borges'; ``` これをObservable(監視可能)にするには、値の末尾に ".obs" を付け足すだけです。 ``` dart var name = 'Jonatas Borges'.obs; ``` これで終わりです。*こんなに* 簡単なんですよ。 (以後、このリアクティブな ".obs" 変数、Observable(監視可能)を _Rx_ と呼ぶことがあります。) 内部ではこのような処理を行っています: `String`の`Stream`を作成し、初期値`"Jonatas Borges"`を割り当て、`"Jonatas Borges"`に依存するすべてのWidgetに、あなたは今この変数の影響下にあるから、_Rx_の値が変更されたときには、あなたも同様に変更する必要がある旨を通知。 これがDartの機能のおかげで実現できた **GetX マジック** です。 しかし皆さんご存知の通り、`Widget` は関数の中にいなければ自らを更新できません。静的クラスには「自動更新」の機能がないからです。 それなら、同じスコープ内で複数の変数に依存してWidgetをビルドする場合は、複数の `StreamBuilder` をネストして変数の変化を監視する必要がありますね。 いいえ、**GetX** なら `StreamBuilder` すら不要です。 またWidgetを更新する際のボイラープレートコードについても、**GetX**では忘れてください。 `StreamBuilder( ... )` ? `initialValue: ...` ? `builder: ...` ? これらはすべて不要で、対象のWidgetを `Obx()` の中に入れるだけです。 ``` dart Obx (() => Text (controller.name)); ``` _覚えること?_ それは `Obx(() =>` だけです。 そのWidgetをアロー関数を通じて `Obx()`(_Rx_のObserver(監視者))に渡すだけです。 `Obx` は非常に賢く、`controller.name` の値が本当に変わったときにのみ、Widgetの更新をかけます。 `name` が `"John"` だとして、それを `"John"` ( `name.value = "John"` ) に変更しても、以前と同じ `value` のため画面上では何も変化しません。`Obx` はリソースを節約するために値を無視し、Widgetを更新しません。**すごいでしょ?** > では、もしも `Obx` の中に_Rx_(Observable)変数が5つあったらどうでしょう? 5つの**いずれかに**値の変化があればWidgetは更新されます。 > また、1つのControllerクラスに30もの変数がある場合、1つの変数を更新したら変数に関わるWidgetが**すべて**更新されてしまうのでしょうか? いいえ、_Rx_ 変数を使う特定のWidgetだけが更新されます。 言い換えるなら、**GetX**は _Rx_ 変数の値が変化したときだけ画面更新をしてくれるということです。 ```dart final isOpen = false.obs; // 同じ値なので何も起きません。 void onButtonTap() => isOpen.value=false; ``` ### 利点 **GetX()**は何を更新して何をしないのか、の**細かい**コントロールが可能です。 すべての更新するのでそのようなコントロールが不要な場合は、`GetBuilder` を検討してください。 これはわずか数行のコードで作られた、状態更新のためのシンプルなビルダーです。(`setState()`のようにブロックで) CPUへの影響を最小限にするために作られており、単一の目的(_状態_ の再構築)を果たすため、可能な限りリソース消費を抑えました。 **強力な** 状態管理のソリューションを求めているなら、**GetX**で間違いはありません。 変数をそのまま扱うことはできませんが、内部では `Stream` としてデータが扱われています。 すべてが `Stream` なので、_RxDart_ を組み合わせることも可能ですし、 "_Rx_ 変数" のイベントや状態を監視することも可能です。 GetXは _MobX_ より簡単で、コード自動生成や記述量を減らした_BLoC_ 型アプローチと言えるかもしれません。 値の末尾に `.obs` を付けるだけで**なんでも** _"Observable(監視可能)"_ にできるのです。 ### パフォーマンスの最大化 ビルドを最小限に抑えるための賢いアルゴリズムに加えて、 **GetX**はコンパレーターを使用して状態が変更されたことを確認します。 アプリでなにかしらのエラーが発生し、状態が変更された情報を 二重に送信してしまったとしても**GetX**はクラッシュを防いでくれます。 **GetX**では値が変化したときにはじめて「状態」が変化するためです。 これが **GetX** と _MobX の `computed`_ を使う際の主な違いです。 2つの __Observable__ を組み合わせて一つが変化したとき、それを監視しているオブジェクトも変化します。 これは `GetX()` (`Observer()`のようなもの) において2つの変数を組み合わせた場合においても、 それが本当に状態の変化を意味するときだけWidgetの更新が行われるということでもあります。 ### リアクティブな変数の宣言 変数を "Observable" にする方法は3つあります。 1 - **`Rx{Type}`** を使用する ``` dart // 初期値を入れることを推奨しますが、必須ではありません final name = RxString(''); final isLogged = RxBool(false); final count = RxInt(0); final balance = RxDouble(0.0); final items = RxList([]); final myMap = RxMap({}); ``` 2 - **`Rx`** とジェネリクスによる型指定の組み合わせ ``` dart final name = Rx(''); final isLogged = Rx(false); final count = Rx(0); final balance = Rx(0.0); final number = Rx(0); final items = Rx>([]); final myMap = Rx>({}); // 任意の型を指定可能 - どんなクラスでもOK final user = Rx(); ``` 3 - 最も実用的で簡単な方法として、**`.obs`** を値に付ける ``` dart final name = ''.obs; final isLogged = false.obs; final count = 0.obs; final balance = 0.0.obs; final number = 0.obs; final items = [].obs; final myMap = {}.obs; // カスタムクラスのインスタンスにも付けられます final user = User().obs; ``` ##### 初期値の設定 ご存知の通り、_Dart_ は現在 _null safety_ へ移行しているところです。 それに備えるために今後は _Rx_ 変数は常に**初期値**を設定してください。 > **GetX** で変数を _Observable_ にしつつ _初期値_ を設定するのはとても簡単です。 変数の末尾に `.obs` を付ける。**それだけ。** めでたく Observable とそのプロパティ `.value` (つまり _初期値_)ができました。 ### Observableの値をビュー内で使う ``` dart // Controllerクラス final count1 = 0.obs; final count2 = 0.obs; int get sum => count1.value + count2.value; ``` ``` dart // ビュークラス GetX( builder: (controller) { print("count 1 rebuild"); return Text('${controller.count1.value}'); }, ), GetX( builder: (controller) { print("count 2 rebuild"); return Text('${controller.count2.value}'); }, ), GetX( builder: (controller) { print("count 3 rebuild"); return Text('${controller.sum}'); }, ), ``` `count1.value++` を実行すると、以下の通りprintされます。 * `count 1 rebuild` * `count 3 rebuild` なぜなら `count1` の値が `1` に変わり、それに伴ってgetter `sum` の値にも `1 + 0 = 1` と変化が起こるからです。 今度は `count2.value++` を実行してみましょう。 * `count 2 rebuild` * `count 3 rebuild` もうおわかりですね。これは `count2.value` が変わり、その結果 `sum` が `2` になったからです。 * 注: デフォルト仕様では、`value` に変化がなかったとしても、それが最初のイベントであればWidgetを更新します。 この仕様はbool変数の性質から来るものです。 たとえばこの場合を想像してみてください。 ``` dart var isLogged = false.obs; ``` そして、isLogged(ユーザーがログインしたかどうか)の変化をトリガーにever関数内のコールバックfireRouteを呼び出したいとします。 ``` dart @override onInit() async { // everは引数1が変化するたびに引数2を実行するリスナー ever(isLogged, fireRoute); isLogged.value = await Preferences.hasToken(); } fireRoute(logged) { if (logged) { Get.off(Home()); } else { Get.off(Login()); } } ``` もし `hasToken` が `false` なら `isLogged` に変化はありません。すると `ever()` のコールバックは永遠に呼び出されないことになります。 このような挙動を防ぐために _Observable_ への最初の更新は、それがたとえ同じ `.value` だったとしても 常にイベントを引き起こすようにしています。 ご参考までに、この仕様は以下の設定で解除することができます。 `isLogged.firstRebuild = false;` ### 更新条件を設定 Getにはさらに洗練された「状態」のコントロール方法があります。イベント(Listへのオブジェクト追加など)に対して条件を付けることが可能です。 ``` dart // 引数1: Listにオブジェクトを加える条件。trueかfalseを返すこと // 引数2: 条件がtrueの場合に加える新しいオブジェクト list.addIf(item < limit, item); ``` 最低限のコードで、コード生成ツールも使わず、とても簡単ですね :smile: カウンターアプリもこのようにシンプルに実現できます。 ``` dart class CountController extends GetxController { final count = 0.obs; } ``` Controllerを設定して、下記を実行するだけ。 ``` dart controller.count.value++ ``` UIの数字が置き換わりましたね。このようにアプリのどこであっても更新をかけることができます。 ### .obsの使いどころ .obs を使うことでどんなものもObservableにすることができます。方法は2つ。 * クラスのインスタンス変数をobsに変換する ``` dart class RxUser { final name = "Camila".obs; final age = 18.obs; } ``` * クラスのインスタンスを丸々obsに変換する ``` dart class User { User({String name, int age}); var name; var age; } // インスタンス化の際 final user = User(name: "Camila", age: 18).obs; ``` ### List(Rx)に関する補足 List(Rx)はその中のオブジェクトと同様、監視可能(Observable)です。そのためオブジェクトを追加すると、List(Rx)に依存するWidgetは自動更新されます。 またList(Rx)をListとするために ".value" を使う必要はありません。DartのAPIがこれを可能にしてくれました。ただ、残念ながら他のStringやintのようなプリミティブ型は拡張ができないため、.value を使う必要があります。getterやsetterを活用するのであればあまり問題になりませんが。 ``` dart // Controllerクラス final String title = 'User Info:'.obs final list = List().obs; // ビュークラス Text(controller.title.value), // Stringの場合は .value が必要 ListView.builder ( itemCount: controller.list.length // Listの場合は不要 ) ``` カスタムのクラスをObservableにした場合は、様々な方法で値を更新することができます。 ``` dart // モデルクラス // 属性をobsにするやり方ではなく、クラス全体をobsにする方法を採ります class User() { User({this.name = '', this.age = 0}); String name; int age; } // Controllerクラス final user = User().obs; // user変数を更新するときはこのようなメソッドを作ります user.update( (user) { // このパラメーターは更新するオブジェクトそのもの user.name = 'Jonny'; user.age = 18; }); // あるいは、この方法でも。変数名は呼び出し可能です。 user(User(name: 'João', age: 35)); // ビュークラス Obx(()=> Text("Name ${user.value.name}: Age: ${user.value.age}")) // .value を使わずにモデルのプロパティにアクセスすることも可能です user().name; // userがUserではないことに注目。user()でUserを受け取れます。 ``` ListのsetAllやsetRangeメソッドの代わりに、"assign" "assignAll" APIを使っていただくことも可能です。 "assign" APIはListの内容をクリアした後に、指定した単独のオブジェクトを追加してくれます。 "assignAll" APIはそのIterable版です。 ### なぜ「.value」を使う必要があるのか ちょっとしたアノテーションとコード生成ツールを使って`String`や`int`で `.value` を使わなくて済むようにもすることはできますが、このライブラリの目的は「外部依存パッケージを減らす」ことです。私たちは、外部パッケージを必要としない、必須ツール(Route、依存オブジェクト、状態の管理)が揃った開発環境を軽量かつシンプルな方法で提供したいと考えています。 まさに pubspecに3文字(get)とコロンを加えて、プログラミングを始めることができるのです。Route管理から状態管理まで、必要なソリューションが標準装備されています。GetXはシンプルさ、生産性、高いパフォーマンスを目指します。 これほど多機能であるにも関わらず、このライブラリの総容量は他の多くの状態管理ライブラリよりも少ないです。その点をご理解いただけるとうれしいです。 `.value` が嫌でコード生成ツールを使いたいという方には、MobXは素晴らしいライブラリだと思いますし、Getと併用することもできます。逆に多くの外部パッケージに依存したくない方、パッケージ間の互換性を気にしたくない方、状態管理ツールや依存オブジェクトから状態更新エラーが出ているかどうかを気にせずプログラミングをしたい方、依存するControllerクラスのインスタンスがあるかどうかを都度都度心配したくない方にとってはGetはまさに最適です。 MobXのコード生成や、BLoCのボイラープレートコードが気にならないのであれば、Route管理にだけでもGetをお使いいただけるとうれしいです。GetのSEMとRSMは必要に迫られて生まれたものです。私の会社で以前、90以上のControllerを持つプロジェクトがあり、それなりの性能のマシンでflutter cleanを行った後でさえ、コード生成ツールがタスクを完了するのに30分以上かかりました。もしあなたが大きなプロジェクトに関わっており、コード生成ツールが問題になっているのであれば、Getを検討してみてください。 もちろん、コード生成ツールをGetXに導入したい方が実際にツールを作成してプロジェクトに貢献した場合は、このReadMeに代替ソリューションとして掲載させていただきます。私はすべての開発者のニーズをかなえたいわけではありませんが、今はこの質問に対しては、「すでにMobXのように同様のことを実現してくれる良いソリューションがある」とだけ言わせてください。 ### Obx() GetX()の代わりにObx()を使用することもできます。ObxはWidgetを生成する匿名関数をパラメーターに持ちます。複数のControllerに対応することができますが、自身はControllerのインスタンスを持たず、型指定もできません。そのため別途Controllerのインスタンスを作るか、`Get.find()` でインスタンスを探しておく必要があります。 ### Worker Worker はイベント発生に伴って指定したコールバックを呼び出すことができます。 ``` dart /// `count1` が更新されるたびに第2引数のコールバックが実行される ever(count1, (_) => print("$_ has been changed")); /// `count1` の最初の更新時のみ実行される once(count1, (_) => print("$_ was changed once")); /// DDoS攻撃対策に最適。たとえば、ユーザーが打鍵やクリックを止めて1秒後に実行など debounce(count1, (_) => print("debouce$_"), time: Duration(seconds: 1)); /// 1秒以内の連続更新はすべて無視して実行しない interval(count1, (_) => print("interval $_"), time: Duration(seconds: 1)); ``` すべてのWorker(`debounce` 以外) は `condition` パラメーターを持ちます。`condition` は `bool` でも `bool` を返すコールバックでも構いません。 この `condition` が Worker のコールバックを実行するかどうかを決めています。 また Worker は `Worker` インスタンスを返します。これは `dispose()` などを通じて Worker を破棄するときに使用します。 * **`ever`** は _Rx_ 変数が新しい値になるたびに呼ばれます。 * **`everAll`** `ever` とほぼ同じですが、_Rx_ 変数の `List` を受け取ります。いずれかの値が更新されれば、その更新後の値を受け取ってコールバックが実行されます。 * **`once`** 変数が最初に更新されたときのみに呼ばれます。 * **`debounce`** 'debounce' は検索機能などで導入するととても有益です。たとえば、ユーザーがタイピングを止めたときにのみAPIを呼び出したいときに使います。ユーザーが "Jonny" と入れたときに 5回も APIに問い合わせを行うのは避けたいですよね。Getなら "debounce" があるので大丈夫です。 * **`interval`** `interval` は debounce とは異なります。ユーザーが1秒間に1000回変数に変更を加えた場合、debounceが一定期間経過後(デフォルトは800ミリ秒)に最後の変更イベントだけ送信するのに対して、intervalは代わりに一定期間の間のユーザーによるアクションをすべて無視します。intervalは1秒ごとにイベントを送信しており、3秒に設定した場合は1分間に20個のイベントを送信します。これはユーザーがキーやマウスを連打することで何かしらの報酬を得られる場合に、その悪用を避けるために使用できます(ユーザーが何かをクリックしてコインを獲得できるとします。たとえ何秒かかったとしても300回クリックすれば300枚のコインを得ることができてしまいます。intervalを使用してインターバルを3秒に設定した場合は、何回クリックしようが1分間で得られるコインの上限は20枚になります)。一方のdebounceはアンチDDosや、検索のように変更を加えるたびにonChangeからAPI問い合わせが発生するような機能に適しています。ユーザーが入力し終わるのを待ってリクエストを送信するのです。debounceを前述のコイン獲得のケースで使用した場合、ユーザーはコインを1枚しか獲得できません。これは指定した期間、ユーザーが動作を「一時停止」したときにのみ実行されるからです。 * 注: Workerを使用する場合は、Controllerなどを起動するときに次のいずれかの方法で登録する必要があります。onInit(推奨)内、クラスのコンストラクタ、またはStatefulWidgetのinitState内(この方法は推奨しませんが、副作用はないはずです)。 ## 非リアクティブな状態管理 GetはChangeNotifierを使わない軽量かつシンプルな状態管理機能を有しています。特にFlutterに慣れていない方のニーズを満たし、大規模なアプリケーションでも問題を起こすことがないと信じています。 GetBuilderは複数の状態を扱う場面で使われることを想定して作られました。たとえばショッピングカートに30個の商品があるとします。そしてあなたが商品を一つ削除すると同時に、カートのリストが更新され、合計金額が更新され、アイテム数を示すバッジが更新されます。GetBuilderはこのようなユースケースに最適です。というのも、GetBuilderは状態をControllerで束ねてそのControllerに依存するすべてのWidgetを一度に更新させることができるからです。 それらとは独立したControllerが必要な場合は、GetBuilderのidプロパティに専用IDを割り当てるか、GetXを使ってください。ケースバイケースですが、そのような「独立した」Widetが多いほど GetX() のパフォーマンスが際立ち、複数の状態変化がありそれに伴うWidgetの更新が多いほど GetBuilder() のパフォーマンスが勝ることを覚えておいてください。 ### 利点 1. 必要なWidgetのみ更新される。 2. ChangeNotifierを使わず、メモリ使用量が少ない。 3. StatefulWidgetのことはもう忘れましょう。Getでは必要ありません。他の状態管理ライブラリではStatefulWidgetを使用することがあるでしょう。しかしAppBarやScaffoldなどクラス内のほとんどのWidgetがStatelessであるにも関わらず、StatefulなWidgetの状態だけを保持する代わりに、クラス全体の状態を保持しているのはなぜでしょうか?Getならクラス全体をStatelessにすることができます。更新が必要なコンポーネントは GetBuilder などで囲むことで「状態」が保持されます。 4. プロジェクトを整理しましょう!ControllerはUIの中にあってはいけません。TextEditControllerなどの類はすべてControllerクラスに配置してしまいましょう。 5. Widgetのレンダリングが開始されると同時にイベントを実行してWidgetを更新させる必要がありますか?GetBuilderにはStatefulWidgetと同様の「initState」プロパティがあり、そこからControllerのイベントを直接呼び出すことができます。initStateを使用する必要はもうありません。 6. StreamやTimerのインスタンスを破棄したい場合はGetBuilderのdisposeプロパティを利用してください。Widgetが破棄されると同時にイベントを呼び出すことができます。 7. GetXとStreamController / StreamBuilderを組み合わせるなどしてStreamを普通に使っていただいても問題ありませんが、必要なときに限って使うことをおすすめします。Streamのメモリ消費は適度であり、リアクティブプログラミングは美しいですが、たとえば30ものStreamを同時に立ち上げることを考えてみてください。これはChangeNotifierを使うよりもよくないことのように思います。 8. 必要以上にRAMを使わずWidgetを更新します。GetはGetBuilderのクリエーターIDのみを保存し、必要に応じてGetBuilderを更新します。何千ものGetBuilderを作成したとしても、ID保存のためのメモリ消費量は非常に少ないです。GetBuilderを新規に作成するということは、実際にはクリエーターIDを持つ GetBuilder の状態を共有しているに過ぎないためです。GetBuilderごとに状態が新たに作成されるわけではないため、特に大規模なアプリケーションでは多くのRAMを節約できます。基本的にGetXで作成するアプリケーションは全体的にStatelessであり、いくつかのStatefulなWidget(GetBuilder内のWidget)は単一の状態を持っているため、一つを更新すればすべてが更新されます。 9. Getはアプリ全体の流れをよく把握しており、Controllerをメモリから破棄するタイミングを正確に知っています。実際の破棄はGetがやってくれるため、開発者が心配する必要はありません。 ### 使用例 ``` dart // Controllerクラスを作成してGetxControllerを継承しましょう class Controller extends GetxController { int counter = 0; void increment() { counter++; update(); // increment 実行時にcounter変数に依存するUIを更新。 // GetBuilderを使うWidgetの場合はupdate()が必要。 } } // ビュー側のクラスでGetBuilderを使ってcounter変数を組み込む GetBuilder( init: Controller(), // 最初に使用するときのみ初期化 builder: (_) => Text( '${_.counter}', ), ) // Controllerの初期化は最初の1回だけ行ってください。同じControllerを再度 GetBuilder / GetX で使用する場合は初期化する必要はありません。コントローラは、それを「init」とマークしたウィジェットがデプロイされると同時に、自動的にメモリから削除されます。Getがすべて自動で行ってくれるので、何も心配することはありません。同じControllerを2つ立ち上げることがないよう、それだけご注意ください。 ``` **最後に** * 以上、Getを使った状態管理の手法をご説明させていただきました。 * 注: もっと柔軟に管理する手法として、initプロパティを使わない方法もあります。Bindingsを継承したクラスを作成し、dependenciesメソッドをoverrideしてその中でGet.put()でControllerを注入してください(複数可)。このクラスとUI側のクラスを紐づけることでControllerをそのRoute内で使用できます。そしてそのControllerを初めて使用するとき、Getはdependencies内を見て初期化を実行してくれます。このlazy(遅延、消極的)ロードを維持しつつ、不要になったControllerは破棄し続けます。具体的な仕組みについてはpub.devの例をご参照ください。 Routeを移動して以前使用したControllerのデータが必要になった場合は、再度GetBuilderを使用してください。initする必要はありません。 ``` dart class OtherClass extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( body: Center( child: GetBuilder( builder: (s) => Text('${s.counter}'), ), ), ); } ``` GetBuilderの外でControllerを使用する場合は、Controller内にgetterを作成しておくと便利です。Controller.to で呼び出しましょう。(もしくは `Get.find()` を使うのもありです) ``` dart class Controller extends GetxController { /// 記述量を省くためにstaticメソッドにすることをおすすめします。 /// staticメソッド使う場合 → Controller.to.increment(); /// 使わない場合 → Get.find().increment(); /// どちらを使ってもパフォーマンスに影響があったり副作用が出たりはしません。前者は型の指定が不要という違いがあるだけです static Controller get to => Get.find(); // これを追加 int counter = 0; void increment() { counter++; update(); } } ``` これで以下のようにControllerに直接アクセスできます。 ``` dart FloatingActionButton( onPressed: () { Controller.to.increment(), } // とっても簡単ですね! child: Text("${Controller.to.counter}"), ), ``` FloatingActionButton を押すと counter変数 に依存するWidgetがすべて自動的に更新されます。 ### Controllerインスタンスの扱い 次のような画面の流れがあるとします。 `画面A => 画面B (Controller X を使用) => 画面C (Controller X を使用)` 画面Aの段階ではまだ未使用なので、Controllerはメモリにありません(Getは基本lazyロードなので)。画面Bに遷移すると、Controllerがメモリ内に保存されます。画面Cでは画面Bと同じControllerを使用しているため、GetはBとCでControllerの状態を共有し、同じControllerがメモリ内に引き続きいることになります。画面Cを閉じてさらに画面Bを閉じ、画面Aに戻ったとしましょう。そこではControllerが使われていないため、GetはControllerをメモリから出してリソースを解放します。そこで再度画面Bに遷移すると、Controllerは再度メモリに保存されます。そして今度は画面Cに行かずに画面Aに戻ります。Getは同様にControllerをメモリから破棄してくれます。また、仮に画面CがControllerを使っておらず画面Cにいたとして、画面BをRouteスタックから削除したとしましょう。するとControllerを使用している画面(クラス)はなくなりますので、同様にメモリから破棄されます。Getが正常動作しないと考えられる唯一の例外は、画面Cにいるときに画面Bを誤ってRouteスタックから削除してしまい、Controllerの使用を試みたときです。この場合は、画面Bで作成されたControllerのクリエーターIDが削除されてしまったことが原因です(GetはクリエーターIDのないControllerはメモリから破棄するようプログラムされています)。もし意図があってこの事例に対応したい場合は、画面BのGetBuilderに "autoRemove: false" フラグを追加した上で、CクラスのGetBuilderに "adoptID: true" を追加してください。 ### StatefulWidgetsはもういらない StatefulWidgetsを使用すると、画面全体の状態を不必要に保存することになります。ウィジェットを最小限に再構築する必要がある場合は、Consumer/Observer/BlocProvider/GetBuilder/GetX/Obxの中に埋め込むことになりますが、それは別のStatefulWidgetになります。 StatefulWidgetはStatelessWidgetよりも多くのRAMが割り当てられます。これは1つや2つのStatefulWidgetでは大きな違いは産まないかもしれませんが、それが100もあった場合は確実に違いが出ます。 TickerProviderStateMixinのようなMixinを使用する必要がない限り、GetでStatefulWidgetを使用する必要はありません。 たとえばinitState()やdispose()メソッドなど、StatefulWidgetのメソッドをGetBuilderから直接呼び出すことも可能です。 ``` dart GetBuilder( initState: (_) => Controller.to.fetchApi(), dispose: (_) => Controller.to.closeStreams(), builder: (s) => Text('${s.username}'), ), ``` しかし、これよりもベターなアプローチはControllerの中で直接 onInit() や onClose() メソッドを呼び出すことです。 ``` dart @override void onInit() { fetchApi(); super.onInit(); } ``` * 注: コンストラクタを通じてControllerを立ち上げる必要はありません。このようなプラクティスは、パフォーマンス重視であるGetのControllerの作成や割り当ての原理、考え方から外れてしまいます(コンストラクタ経由でインスタンスを作成すれば、実際に使用される前の段階でControllerを生成し、メモリを割り当てることになります)。onInit() と onClose() メソッドはこのために作られたもので、Controllerのインスタンスが作成されたとき、または初めて使用されたときに呼び出されます(Get.lazyPutを使用しているか否か次第)。たとえば、データを取得するためにAPIを呼び出したい場合は initState/dispose の代わりに onInit() を使用し、Streamを閉じるなどのコマンドを実行する必要がある場合は onClose() を使用してください。 ### Getの目的 このパッケージの目的は、Routeのナビゲーション、依存オブジェクトと状態の管理のための完全なソリューションを、開発者が外部パッケージに極力依存せずに済むような形で提供し、高度なコード分離性(デカップリング)を実現することです。それを確実なものとするため、Getはあらゆる高レベルおよび低レベルのFlutter APIを取り込んでいます。これによりビューとロジックを切り分けることが容易になり、UIチームにはWidgetの構築に集中してもらい、ビジネスロジック担当チームにはロジックに集中してもらうことができます。Getを使うことでよりクリーンな作業環境を構築することができるのです。 要するに、initState内でメソッドを呼び出してパラメーターを通じてControllerにデータを送信する必要も、そのためにControllerのコンストラクタを使用する必要もありません。Getには必要なタイミングでサービスを呼び出してくれう onInit() メソッドがあります。 Controllerが不要になれば、onClose() メソッドがジャストなタイミングでメモリから破棄してくれます。これにより、ビューとビジネスロジックを分離することができるのです。 GetxController 内に dispose() メソッドがあっても何も起こらないので記述しないでください。ControllerはWidgetではないので「dispose」できません。たとえばController内でStreamを使用していてそれを閉じたい場合は、以下のように onClose() メソッドにコードを記述してください。 ``` dart class Controller extends GetxController { StreamController user = StreamController(); StreamController name = StreamController(); /// Streamを閉じる場合は dispose() ではなく onClose() @override void onClose() { user.close(); name.close(); super.onClose(); } } ``` Controllerのライフサイクルについて。 * onInit() はControllerが作成されたタイミングで実行されます。 * onClose() は onDelete() メソッドが実行される直前のタイミングで実行されます。 * Controllerが削除されるとそのAPIにアクセスすることはできません。文字通りメモリからの削除だからです。削除のトレースログも残りません。 ### Controllerの様々な使用方法 ControllerインスタンスはGetBuilderのvalueを通じて使用することができます。 ``` dart GetBuilder( init: Controller(), builder: (value) => Text( '${value.counter}', // ここ ), ), ``` GetBuilderの外でControllerインスタンスを使う場合は、このアプローチをおすすめします。 ``` dart class Controller extends GetxController { static Controller get to => Get.find(); [...] } // ビュー側で GetBuilder( init: Controller(), // 最初に使うときだけ必要 builder: (_) => Text( '${Controller.to.counter}', // ここ ) ), ``` もしくは ``` dart class Controller extends GetxController { // static get を省き、 [...] } // ビュー側で GetBuilder( init: Controller(), // 最初に使うときだけ必要 builder: (_) => Text( '${Get.find().counter}', // ここ ), ), ``` * get_it や modular など他の依存オブジェクト管理ライブラリを使用しているため、単にControllerのインスタンスを渡したいだけの場合は、このような「非正規」な方法もあります。 ``` dart Controller controller = Controller(); [...] GetBuilder( init: controller, // ここ builder: (_) => Text( '${controller.counter}', // ここ ), ), ``` ### ユニークIDの設定 GetBuilderを使ってWidgetの更新をコントロールしたい場合は、このようにユニークIDを振ってください。 ``` dart GetBuilder( id: 'text' init: Controller(), // 最初に使うときだけ必要 builder: (_) => Text( '${Get.find().counter}', // ここ ), ), ``` そして以下のようにWidgetを更新します。 ``` dart update(['text']); ``` さらに更新に条件を設けることができます。 ``` dart update(['text'], counter < 10); ``` GetXはこの更新を自動で行ってくれます。指定したIDを持ち、その変数に依存するWidgetのみを更新します。また変数を前の値と同じ値に変更しても、それが状態の変化を意味しない場合はメモリとCPUサイクルを節約するためにWidgetを更新しません (画面に 3 が表示されているときに、変数を再び 3 に変更したとします。このような場合にWidgetを更新する状態管理ソリューションも存在しますが、GetXでは実際に状態が変更された場合にのみ更新されます)。 ## 状態管理ソリューションを混在させる MixinBuilderはObxとGetBuilderを併用したいというリクエストから発想して作られました。これは ".obs" 変数の変更によるリアクティブな更新と、update() メソッドによるメカニカルな更新の両方を混在可能にします。ただし、GetBuiler / GetX / Obx / MixinBuilder の4つの中で最もリソースを消費するWidgetです。というのも、Widgetからのイベントを検知するためのSubscriptionに加えて、Controller自身のupdateメソッドも購読する必要があるからです。 MixinBuilderに使用するControllerクラスには、変数を `.obs`(Observable)とするかどうかに関わらず、GetxControllerを継承したものを使用してください。GetxControllerにはライフサイクルがあり、onInit() および onClose() メソッドでイベントを「開始」したり「終了」したりすることができます。 ## StateMixin `StateMixin` を使うことでさらに `UI` の「状態」を便利に扱うことができます。 `with` を使って `StateMixin` をControllerにミックスインしてください。Tにはモデルのクラス名が入ります。 ``` dart class Controller extends GetController with StateMixin{} ``` 状態を変更するには `change()` メソッドを使ってください。 パラメーターにはビューに渡すデータと「状態」をセットします。 ```dart change(data, status: RxStatus.success()); ``` RxStatus の「状態」は以下の4つです。 ``` dart RxStatus.loading(); // ロード中 RxStatus.success(); // ロード成功 RxStatus.empty(); // データが空 RxStatus.error('message'); // エラー ``` それぞれの「状態」をUIで表すには以下のようにします。 ```dart class OtherClass extends GetView { @override Widget build(BuildContext context) { return Scaffold( body: controller.obx( (state)=>Text(state.name), // ここはカスタムのロードインジケーターでも可能ですが、 // デフォルトは Center(child:CircularProgressIndicator()) onLoading: CustomLoadingIndicator(), onEmpty: Text('No data found'), // ここもカスタムのエラーWidgetでも構いませんが、 // デフォルトは Center(child:Text(error)) onError: (error)=>Text(error), ), ); } ``` ## GetBuilder VS GetX VS Obx VS MixinBuilder 私は10年間プログラミングに携わってきて、いくつか貴重な教訓を得ることができました。 リアクティブプログラミングに初めて触れたとき、「おお、これはすごい」と感嘆せずにはいられませんでしたし、実際にすごいものでした。 しかし、リアクティブプログラミングはすべての状況に適しているわけではありません。多くの場合、必要なのは2,3のWidgetの状態を同時に更新すること、またはローカルでの一時的な状態の変更であり、これらの場合はリアクティブである必要はありません。 リアクティブプログラミングのRAM消費量の多さは、必要なときだけ、かつ1つのWidgetだけ同時に更新するようすることである程度補うことができます。ただ、たとえば80ものオブジェクトを持つリストがあったとして、それぞれが複数のStreamを持つのは得策ではありません。Dartのインスペクターを開いて、StreamBuilderがどれだけRAMを消費しているか見てみてください。私が伝えたいことを理解してもらえると思います。 そのことを念頭に私はシンプルな状態管理ソリューションを作りました。このシンプルさに期待することは、リソース面で経済的である点、Widget単位ではなくブロック単位で状態を更新できる点であるべきです。 GetBuilderはRAM消費の面で最も経済的なソリューションだと信じています(もしあれば、ぜひ教えてください)。 しかし、GetBuilderは依然として update() により更新がかかるスタイルのメカニカルな状態管理ソリューションであり、notifyListeners() と呼び出し回数は変わりありません。 一方で、ここでリアクティブプログラミングを使わないのは車輪の再発明なんじゃないかと思えるような状況もあります。この点を考慮して、GetX() は先進的な状態管理の手法を提供するために作られました。必要なものを必要なときだけ更新し、エラーが発生してユーザーが300ものイベントを同時に送信したとしても、GetX() は状態の変化をフィルタリングして画面を更新してくれます。 GetX() は他のリアクティブな状態管理ソリューションに比べて経済的であることに変わりはありませんが、GetBuilder() よりは少しだけ多くのRAMを消費します。その点を考慮し、リソース消費を最大限活かすことを目指して Obx() は開発されました。GetX() や GetBuilder() と異なり、Obx() の中でControllerを初期化することはできません。Obx() は、子Widgetからの更新イベントを受け取る Stream購読Widgetでしかありません。GetX() よりは経済的ですが、GetBuilder() には負けます。GetBuilder() はWidgetのハッシュ値と状態のsetterを保持しているだけなので、これはある意味当然です。Obx() はControllerの型を指定する必要がなく、複数の異なるコントローラからの変更を聞くことができます。ただし、Obx() の外かBindingsで事前にControllerを初期化しておく必要があります。 ================================================ FILE: documentation/kr_KO/dependency_management.md ================================================ # 종속성 관리 - [종속성 관리](#종속성-관리) - [인스턴스 메서드](#인스턴스-메서드) - [Get.put()](#getput) - [Get.lazyPut](#getlazyput) - [Get.putAsync](#getputasync) - [Get.create](#getcreate) - [인스턴스화 된 메서드/클래스 사용](#인스턴스화-된-메서드/클래스-사용) - [메서드간의 차이점](#메서드간의-차이점) - [바인딩](#바인딩) - [사용 방법](#사용-방법) - [BindingsBuilder](#bindingsbuilder) - [SmartManagement](#smartmanagement) - [변경하는 방법](#변경하는-방법) - [SmartManagement.full](#smartmanagementfull) - [SmartManagement.onlyBuilders](#smartmanagementonlybuilders) - [SmartManagement.keepFactory](#smartmanagementkeepfactory) - [바인딩이 작동하는 자세한 설명](#바인딩이-작동하는-자세한-설명) - [주석](#주석) Get은 Provider context, inheritedWidget 없이 단 1줄의 코드로 Bloc 나 Controller 같은 클래스를 찾을수 있는 간단하고 강력한 종속성 관리자가 있습니다. ```dart Controller controller = Get.put(Controller()); // Controller controller = Controller(); 대체 ``` 사용중인 클래스 내에서 클래스를 인스턴스화하는 대신 Get 인스턴스 내에서 인스턴스화하여 앱 전체에서 사용할 수 있습니다. 그러고나면 컨트롤러 (또는 Bloc 클래스)를 정상적으로 사용할 수 있습니다. - 주석: Get의 상태 관리자를 사용하는 경우 [바인딩](#바인딩) api에 더 많은 주의를 기울여야합니다. 그러면 뷰를 컨트롤러에 더 쉽게 연결할 수 있습니다. - 주석²: Get의 종속성 관리는 패키지의 다른 부분에서 분리되므로 예를 들어 앱이 이미 상태 관리자를 사용하고있는 경우라도(하나라도 상관 없음) 변경할 필요가 없습니다. 아무 문제 없이 종속성 주입 관리자를 사용할 수 있습니다. ## 인스턴스 메서드 메서드와 구성 파라미터는 다음과 같습니다: ### Get.put() 종속성 인스턴스화의 가장 흔한 방법 입니다. 예를 들어 뷰의 controller들에 좋습니다. ```dart Get.put(SomeClass()); Get.put(LoginController(), permanent: true); Get.put(ListItemController, tag: "some unique string"); ``` put 사용시 설정 가능한 모든 사항: ```dart Get.put( // 필수: cotroller나 어떤것이든 get에 저장하려는 클래스 // 주석: "S"는 모든 유형의 클래스가 가능합니다. S dependency // 선택: 동일한 유형의 여러 클래스를 사용하기를 원하면 // 일반적으로 Get.find() 로 클래스를 가져오므로 // 어떤 인스턴스인지 구분을 위해 tag를 사용해야합니다. // 고유한 string 이여야 합니다. String tag, // 선택: 기본적으로 get은 더이상 사용하지 않는 인스턴스는 dispose 합니다. by default, get will dispose instances after they are not used anymore (example, // (예를 들어 뷰의 controller가 닫힌 경우) 하지만 sharedPreferences와 같은 인스턴스는 앱 전체에서 유지되어야 할 필요가 있습니다. // 이런 경우 사용합니다. // 기본값은 false bool permanent = false, // 선택: 테스트에서 추상 클래스를 사용한 후에 다른 클래스로 교체하고 테스트를 수행합니다. // 기본값은 false bool overrideAbstract = false, // 선택: 자체 종속성 대신에 함수로 종속성을 생성합니다. // 이것은 일반적으로 사용되지 않습니다. InstanceBuilderCallback builder, ) ``` ### Get.lazyPut 인스턴스를 사용하는 경우에만 의존성을 lazyLoad 할 수 있습니다. 계산 비용이 많이 드는 클래스나 한곳에서 다양한 클래스를 당장 사용하지 않으면서 인스턴스화 하기를 원한다면(Bindings 클래스처럼) 매우 유용합니다. ```dart /// ApiMock은 처음으로 Get.find을 사용하는 경우에만 호출됩니다. Get.lazyPut(() => ApiMock()); Get.lazyPut( () { // 어떤 로직이 필요하다면 ... return FirebaseAuth(); }, tag: Math.random().toString(), fenix: true ) Get.lazyPut( () => Controller() ) ``` lazyPut을 사용시 설정 가능한 모든 사항: ```dart Get.lazyPut( // 필수: 이 메서드는 처음으로 클래스가 호출할 때 실행될 것입니다. InstanceBuilderCallback builder, // 선택: Get.put()과 같이 같은 클래스를 다중으로 인스턴스할 경우 사용합니다. // 고유값이어야 합니다. String tag, // 선택: "permanent"와 유사합니다. 차이점은 인스턴스가 사용되지 않으면 폐기되지만 // 다시 사용할 때 Get이 바인딩 api의 "SmartManagement.keepFactory"와 동일하게 인스턴스를 재생성한다는 것입니다. // 기본값은 false bool fenix = false ) ``` ### Get.putAsync 만약 비동기로 인스턴스를 등록하길 원하면 `Get.putAsync`를 사용할 수 있습니다.: ```dart Get.putAsync(() async { final prefs = await SharedPreferences.getInstance(); await prefs.setInt('counter', 12345); return prefs; }); Get.putAsync( () async => await YourAsyncClass() ) ``` putAsync 사용시 설정 가능한 모든 사항: ```dart Get.putAsync( // 필수: 클래스를 인스턴스화 하기 위해 실행되는 비동기 메서드입니다. AsyncInstanceBuilderCallback builder, // 선택: Get.put()과 같이 같은 클래스를 다중으로 인스턴스할 경우 사용합니다. // 고유값이어야 합니다. String tag, // 선택: Get.put()과 같이 앱 유지중에 인스턴스가 활성되어야 하는 경우 사용합니다. // 기본값은 false bool permanent = false ) ``` ### Get.create 이것은 까다롭습니다. 이것이 무엇인지 상세한 설명과 다른것과의 차이점에 대해서는 [메서드간의 차이점:](#메서드간의-차이점) 섹션에서 확인할 수 있습니다. ```dart Get.Create(() => SomeClass()); Get.Create(() => LoginController()); ``` create 사용 시 설정 가능한 모든 사항: ```dart Get.create( // 필수: `Get.find()`가 호출 될 때마다 만들어진 클래스를 반환하는 메서드입니다. // 예시: Get.create(() => YourClass()) FcBuilderFunc builder, // 선택: Get.put()과 거의 동일하지만 동일 클래스의 여러 인스턴스가 필요할 때 사용합니다. // 개개의 리스트별로 controller가 필요한 경우 유용합니다. // 고유값이어야 합니다. 단지 tag에서 name으로 변경되었습니다. String name, // 선택: Get.put()과 같이 앱 유지중에 인스턴스가 활성되어야 하는 경우 사용합니다. // Get.create에서 다른점은 // permanent의 기본값이 true 입니다. bool permanent = true ``` ## 인스턴스화 된 메서드/클래스 사용 여러 경로를 탐색했고 controller에 남겨진 데이터가 필요하다고 상상해보세요. Provider 또는 Get_it과 결합된 상태 관리자가 필요합니다. 맞습니까? Get은 아닙니다. 어떤 종속적인 추가가 필요 없고 단지 Get에게 controller를 "find"하라고 하면 됩니다: ```dart final controller = Get.find(); // OR Controller controller = Get.find(); // 그렇습니다. 마법 같아요. Get은 controller를 찾아 가져다 줍니다. // Get은 백만개의 contrller를 인스턴스화해서 가질수 있고 항상 올바르게 전달해 줍니다. ``` 그리고 나서 얻어낸 controller에서 데이터를 가져올 수 있습니다: ```dart Text(controller.textFromApi); ``` 반환된 값은 일반 클래스라서 무엇이든 할 수 있습니다: ```dart int count = Get.find().getInt('counter'); print(count); // out: 12345 ``` Get의 인스턴스에서 삭제합니다: ```dart Get.delete(); // 보통 GetX는 미사용 controller를 삭제하기 때문에 수행할 필요가 없습니다 ``` ## 대체 인스턴스 지정 현재 추가된 인스턴스는 `replace` 또는 `lazyReplace` 메소드를 사용하여 유사하거나 확장된 클래스 인스턴스로 교체할 수 있습니다. 이후 원본 클래스를 사용하여 찾을 수 있습니다. ```dart abstract class BaseClass {} class ParentClass extends BaseClass {} class ChildClass extends ParentClass { bool isChild = true; } Get.put(ParentClass()); Get.replace(ChildClass()); final instance = Get.find(); print(instance is ChildClass); //true class OtherClass extends BaseClass {} Get.lazyReplace(() => OtherClass()); final instance = Get.find(); print(instance is ChildClass); // false print(instance is OtherClass); //true ``` ## 메서드간의 차이점 첫째, Get.lazyPut의 `fenix`와 다른 메서드들의 `permanent`을 살펴보겠습니다. `permanent`와 `fenix` 사이의 근본적인 차이점은 인스턴스를 저장하는 방법입니다. 추가 내용: 기본적으로 GetX는 사용하지 않을때 인스턴스를 삭제합니다. 이것은 다음을 의미합니다: 만약 화면 1이 컨트롤러 1을 가지고 있고 화면 2가 컨트롤러 2를 가졌을때 스택에서 첫번째 경로가 제거되면(`Get.off()`나 `Get.offNamed()`를 사용했을 때처럼) 컨트롤러 1은 더이상 사용되지 않기 때문에 지워질 것입니다. 하지만 `permanent:true`를 설정하면 컨트롤러가 화면이 바뀌는동안 사라지지 않을 것입니다. 즉, 전체 어플리케이션이 실행되는 동안에 계속 유지하려고 하는 서비스에 매우 유용합니다. 반면 `fenix`는 화면이 바뀌는동안 컨트롤러가 사라지는 것은 상관없지만, 컨트롤러가 필요한 시점에 살아있어야 하는 서비스를 위해 존재합니다. 그래서 기본적으로는 사용하지 않는 컨트롤러/서비스/클래스를 폐기하지만, 필요한 경우 새 인스턴스를 "남아있는 흔적으로부터 다시 생성"합니다. 메서드간 차이점에 대해: - Get.put과 Get.putAsync는 동일한 생성 명령을 따르지만 두번째가 비동기 메서드를 사용하는 것이 차이점입니다: 두 메서드는 인스턴스를 생성하고 초기화 합니다. 이것은 `permanent: false`와 `isSingleton: true` 파라미터들과 내부 `insert` 메서드를 사용하여 메모리에 직접 추가됩니다.(여기의 isSingleton 파라미터의 목적은 `dependency`에 의한 종속성을 사용할 것인지 `FcBuilderFunc`에 의한 종속성을 사용할 것인지 알려주는 것입니다.) 이후에 `Get.find()`는 즉시 초기화한 메모리안의 인스턴스를 호출합니다. - Get.create: 이름 그대로 종속성을 "생성"합니다! `Get.put()`과 마찬가지로 내부 메서드 `insert`를 호출하여 인스턴스화 합니다. 그러나 `permanent`가 true가 되고 `isSingleton`이 false가 됩니다(종속성을 "생성중"인 상태라 싱글톤 인스턴스가 될 방법이 없어서 false 입니다.) 그리고 `permanent: true`이기 때문에 기본적으로 화면 전환간에 손실되지 않는 장점이 있습니다! 또한 `Get.find()`는 즉시 호출되지 않으며 호출될 화면에서 사용되기를 기다립니다. `permanent` 파라미터를 사용하기 위한 방법으로 만들어졌습니다. 다음의 가치를 가지고 있습니다. 생성을 위한 목적으로 `Get.create()`는 공유되는 인스턴스가 아니지만 폐기되지 않습니다. 예를들어 리스트뷰 안의 버튼은 리스트를 위한 고유한 인스턴스입니다. - 이때문에 Get.create는 GetWidget과 함께 사용되어야만 합니다. - Get.lazyPut: 이름 그대로 lazy 처리됩니다. 인스턴스가 만들어지나 즉시 사용되도록 호출되지 않고 호출되기를 기다립니다. 다른 메서드와 다르게 `insert`가 여기에서 호출되지 않습니다. 대신 인스턴스는 메모리의 다른 부분에 추가됩니다. 인스턴스가 재생성 가능한지 아닌지를 책임지는 부분으로 "factory"라고 부릅니다. 나중에 사용할 어떤 것을 생성하기 원한다면 지금 사용했던 것과 섞이지 않아야 할 것입니다. 그리고 여기에서 `fenix` 마법이 시작됩니다: `fenix: false`를 그대로두고 `smartManagement`는 `keepFactory`가 아니면 `Get.find`를 사용할 때 인스턴스는 "factory"에서 공통 인스턴스 메모리 영역으로 위치가 변경됩니다. 바로 그뒤에 기본적으로 "factory"에서 제거됩니다. 이제 `fenix: true`로 설정하면 인스턴스는 전용부분에서 계속 존재하며 공통 영역으로 이동하여 미래에 다시 호출됩니다. ## 바인딩 이 패키지의 가장 큰 특이한점 중 하나는 아마도 라우트, 상태 관리자, 종속성 관리자의 완전한 통합의 가능성 일 것입니다. 스택에서 라우트가 삭제되면 모든 컨트롤러, 변수 및 관련된 인스턴스 오브젝트가 메모리에서 제거됩니다. 스트림이나 타이머를 사용중이면 자동적으로 종료되고 이것에 대한 어떤 걱정도 할 필요가 없습니다. Get의 2.10 버전에는 Bindings API를 완전히 구현했습니다. 이제 init 메서드는 더 이상 사용할 필요가 없습니다. 원하지 않으며 컨트롤러 타입도 필요 없습니다. 컨트롤러와 서비스를 위한 적절한 위치에서 시작할 수 있습니다. 바인딩 클래스는 상태 관리자와 종속성 관리자에 라우트를 "결합"하는 동시에 종속성 주입을 분리하는 클래스입니다. 이를 통해 Get은 특정 컨트롤러가 사용될때 표시되는 스크린을 알고 어디서 어떻게 이것이 제거 되는지 알수 있습니다. 추가로 Binding 클래스로 SmartManager 구성을 제어 할 수 있습니다. 스택에서 경로를 제거하거나 경로를 사용한 위젯이 배치되거나 둘 다 배치되지 않을 때 정렬되도록 종속성을 설정 할 수 있습니다. 지능적으로 종속성 관리가 동작하지만 원하는대로 구성 할 수 있습니다. ### 사용 방법 - class를 생성하고 Binding을 implement 합니다. ```dart class HomeBinding implements Bindings {} ``` IDE가 자동적으로 "종속적인" 메서드를 오버라이딩할지 요청하며 램프를 클릭하기만 하면 됩니다. 그리고 메서드를 오버라이딩하고 해당 경로에 사용할 모든 클래스들을 추가하면 됩니다: ```dart class HomeBinding implements Bindings { @override void dependencies() { Get.lazyPut(() => HomeController()); Get.put(()=> Api()); } } class DetailsBinding implements Bindings { @override void dependencies() { Get.lazyPut(() => DetailsController()); } } ``` 이제 라우트에 라우트, 종속성, 상태 관리자 사이를 연결하는데 해당 바인딩을 사용할 것이라고 알리기만 하면 됩니다. - 명명된 라우트 사용법: ```dart getPages: [ GetPage( name: '/', page: () => HomeView(), binding: HomeBinding(), ), GetPage( name: '/details', page: () => DetailsView(), binding: DetailsBinding(), ), ]; ``` - 일반 라우트 사용법: ```dart Get.to(Home(), binding: HomeBinding()); Get.to(DetailsView(), binding: DetailsBinding()) ``` 이제 어플리케이션의 어디서도 메모리 관리에 대해서 걱정하지 않아도 됩니다. Get이 그것을 처리 할 것입니다. Binding 클래스는 라우트가 호출될 때 불려집니다. GetMaterialApp의 "initialBinding"에서 모든 종속성을 추가하여 생성할 수 있습니다. ```dart GetMaterialApp( initialBinding: SampleBind(), home: Home(), ); ``` ### BindingsBuilder 바인딩을 생성하는 기본 방법은 바인딩을 구현한 클래스를 만드는 것 입니다. 하지만 대안으로 `BindingsBuilder` 콜백을 사용하여 간단하게 원하는 것을 인스턴스화하는 함수를 사용할 수 있습니다. 예시: ```dart getPages: [ GetPage( name: '/', page: () => HomeView(), binding: BindingsBuilder(() { Get.lazyPut(() => ControllerX()); Get.put(()=> Api()); }), ), GetPage( name: '/details', page: () => DetailsView(), binding: BindingsBuilder(() { Get.lazyPut(() => DetailsController()); }), ), ]; ``` 이 방법은 각 라우트에 대해 한개의 바인딩 클래스 만드는 것을 피할수 있어서 더 간단하게 할 수 있습니다. 두 방법 모두 완벽하게 작동하며 취향에 맞춰서 사용하시면 됩니다. ### SmartManagement GetX는 기본적으로 메모리에서 사용되지 않는 컨트롤러들을 제거합니다. 문제가 생기거나 적절히 사용하지 않는 위젯도 마찬가지 입니다. 이것이 종속성 관리의 `full`모드 입니다. 하지만 GetX 클래스의 제거를 제어하는 방식을 변경하기 원한다면 `SmartManagement` 클래스로 다른 행동을 설정할 수 있습니다. #### 변경하는 방법 구성을 변경(보통은 필요 없습니다)하려면 이렇게 하세요: ```dart void main () { runApp( GetMaterialApp( smartManagement: SmartManagement.onlyBuilders // 이곳 home: Home(), ) ) } ``` #### SmartManagement.full 이것이 기본입니다. permanent가 설정되지 않으면 사용되지 않는 클래스를 제거합니다. 대부분의 경우 이 구성을 변경하지 않고 유지하십시오. GetX가 처음이라면 바꾸지 마십시오. #### SmartManagement.onlyBuilders 이 옵션은 오직 컨트롤러가 `init:`에서 시작되었거나 `Get.lazyPut()`으로 바인딩되어 로드되면 제거됩니다. `Get.put()`이나 `Get.putAsync()` 또는 다른 접근을 를 사용하면 SmartManagement는 종속성 제거를 위한 권한을 가지지 못합니다. 기본 동작은 SmartManagement.onlyBuilders와 다르게 "Get.put"으로 인스턴스화된 위젯들도 삭제됩니다. #### SmartManagement.keepFactory SmartManagement.full과 같이 더이상 사용되지 않으면 종속성이 제거됩니다. 하지만 factory가 유지되어 다시 인스턴스가 필요하면 종속성이 재생성됩니다. ### 바인딩이 작동하는 자세한 설명 바인딩은 다른 화면으로 이동하려고 클릭하는 순간에 임시 factory를 생성합니다. 그리고 화면 전환 애니메이션이 발생하는 즉시 제거됩니다. 이 행위는 매우 빨라 분석기에 등록도 되지 않습니다. 다시 화면으로 이동하면 새로운 임시 factory를 호출하므로 SmartManagement.keepFactory 사용을 선택할 수도 있습니다. 그러나 바인딩을 생성하지 않거나 동일한 바인딩에 대해 모든 종속성을 유지하고 싶다면 도움이 됩니다. Factory는 적은 메모리만 사용하고 인스턴스를 가지지 않지만 원하는 클래스의 "형태"를 가진 함수를 가집니다. 메모리에서 매우 적은 비용을 가지지만 이 라이브러리의 목적이 최소 리소스를 사용하여 가능한 최대 성능을 가지는 것이라 GetX는 기본적으로 factory를 제거합니다. 가장 편한방법을 취하십시오. ## 주석 - 다중 바인딩을 사용한다면 SmartManagement.keepFactory를 사용하지 마세요. 바인딩을 사용하지 않거나 GetMaterialApp의 initialBinding에 연결한 1개의 바인딩만 설계하세요. - 바인딩의 사용은 완전히 선택사항입니다. 클래스에서 `Get.put()`과 `Get.find()`를 사용하면 아무 문제없이 컨트롤러가 주어집니다. 그러나 서비스나 다른 추상적인 동작을 원하면 더 나은 구성의 바인딩을 추천합니다. ================================================ FILE: documentation/kr_KO/route_management.md ================================================ - [라우트 관리](#라우트-관리) - [사용하는 방법](#사용하는-방법) - [이름없는 라우트 탐색](#이름없는-라우트-탐색) - [이름있는 라우트 탐색](#이름있는-라우트-탐색) - [이름있는 라우트에 데이터 보내기](#이름있는-라우트에-데이터-보내기) - [동적 url 링크](#동적-url-링크) - [미들웨어](#미들웨어) - [context 없이 탐색](#context-없이-탐색) - [SnackBars](#snackbars) - [Dialogs](#dialogs) - [BottomSheets](#bottomsheets) - [중첩된 탐색](#중첩된-탐색) # 라우트 관리 라우트 관리가 문제 있는 경우 GetX가 모든 것을 완벽히 설명해줍니다. ## 사용하는 방법 pubspec.yaml 파일에 추가: ```yaml dependencies: get: ``` context 없이 routes/snackbars/dialogs/bottomsheets을 사용하거나 고급 GetX API를 사용하려면 MaterialApp 앞에 "Get"만 추가하여 GetMaterialApp으로 바꿔서 이용하세요! ```dart GetMaterialApp( // 이전: MaterialApp( home: MyHome(), ) ``` ## 이름없는 라우트 탐색 새 화면으로 이동: ```dart Get.to(NextScreen()); ``` snackbars, dialogs, bottomsheets 또는 Navigator.pop(context);로 보통 닫았던 것들을 닫기 ```dart Get.back(); ``` 다음 화면으로 이동하고 이전 화면에서 돌아오지 않는 경우 (스플래시나 로그인 화면 등을 사용하는 경우) ```dart Get.off(NextScreen()); ``` 다음 화면으로 이동하고 이전 화면이 모두 닫히는 경우 (장바구니, 투표, 테스트에 유용함) ```dart Get.offAll(NextScreen()); ``` 다음 화면으로 이동하고 돌아올때 바로 데이터를 받거나 업데이트할 경우: ```dart var data = await Get.to(Payment()); ``` 다른 화면에서 이전화면으로 데이터를 전달할때: ```dart Get.back(result: 'success'); ``` 그리고 사용방법: 예시: ```dart if(data == 'success') madeAnything(); ``` 우리의 문법을 배우고 싶지 않습니까? Navigator를 navigator로 바꾸시면 됩니다. 그리고 context를 사용하지 않아도 표준 navigator의 모든 기능이 가능합니다. 예시: ```dart // 기본 Flutter navigator Navigator.of(context).push( context, MaterialPageRoute( builder: (BuildContext context) { return HomePage(); }, ), ); // GetX는 context 필요 없이 Flutter 문법을 사용 navigator.push( MaterialPageRoute( builder: (_) { return HomePage(); }, ), ); // GetX 문법 (이것은 동의하지 않겠지만 더 좋습니다) Get.to(HomePage()); ``` ## 이름있는 라우트 탐색 - namedRoutes로 탐색하기를 선호하면 GetX도 지원합니다. nextScreen으로 이동 ```dart Get.toNamed("/NextScreen"); ``` 다음으로 이동하고 트리에서 이전 화면을 지웁니다. ```dart Get.offNamed("/NextScreen"); ``` 다음으로 이동하고 트리에서 이전 화면 전체를 지웁니다. ```dart Get.offAllNamed("/NextScreen"); ``` GetMaterialApp를 사용하여 라우트들을 정의: ```dart void main() { runApp( GetMaterialApp( initialRoute: '/', getPages: [ GetPage(name: '/', page: () => MyHomePage()), GetPage(name: '/second', page: () => Second()), GetPage( name: '/third', page: () => Third(), transition: Transition.zoom ), ], ) ); } ``` 정의 안된 라우트로 이동시 제어 (404 에러), GetMaterialApp에 unknownRoute를 정의할 수 있습니다. ```dart void main() { runApp( GetMaterialApp( unknownRoute: GetPage(name: '/notfound', page: () => UnknownRoutePage()), initialRoute: '/', getPages: [ GetPage(name: '/', page: () => MyHomePage()), GetPage(name: '/second', page: () => Second()), ], ) ); } ``` ### 이름있는 라우트에 데이터 보내기 무엇이든 인수를 통해 전달합니다. GetX는 String, Map, List, 클래스 인스턴스등 모든 것을 허용합니다. ```dart Get.toNamed("/NextScreen", arguments: 'Get is the best'); ``` 클래스 또는 컨트롤러에서: ```dart print(Get.arguments); // 출력: Get is the best ``` ### 동적 url 링크 GetX는 웹과 같이 향상된 동적 url을 제공합니다. 웹 개발자들은 아마 Flutter에서 이미 이 기능을 원하고 있을 것 입니다. 대부분의 경우 패키지가 이 기능을 약속하고 URL이 웹에서 제공하는 것과 완전히 다른 구문을 제공하는 것을 보았을 것입니다. 하지만 GetX는 이 기능을 해결합니다. ```dart Get.offAllNamed("/NextScreen?device=phone&id=354&name=Enzo"); ``` controller/bloc/stateful/stateless 클래스에서: ```dart print(Get.parameters['id']); // 출력: 354 print(Get.parameters['name']); // 출력: Enzo ``` GetX는 쉽게 NamedParameters 전달을 할 수 있습니다: ```dart void main() { runApp( GetMaterialApp( initialRoute: '/', getPages: [ GetPage( name: '/', page: () => MyHomePage(), ), GetPage( name: '/profile/', page: () => MyProfile(), ), //You can define a different page for routes with arguments, and another without arguments, but for that you must use the slash '/' on the route that will not receive arguments as above. GetPage( name: '/profile/:user', page: () => UserProfile(), ), GetPage( name: '/third', page: () => Third(), transition: Transition.cupertino ), ], ) ); } ``` 경로 명으로 데이터 보냄 ```dart Get.toNamed("/profile/34954"); ``` 다음 화면에서 파라미터로 데이터를 가져옴 ```dart print(Get.parameters['user']); // 출력: 34954 ``` 또는 이와 같은 여러 매개 변수를 보냅니다. ```dart Get.toNamed("/profile/34954?flag=true"); ``` 두 번째 화면에서 일반적으로 매개 변수별로 데이터를 가져옵니다. ```dart print(Get.parameters['user']); print(Get.parameters['flag']); // 출력: 34954 true ``` 이제 Get.toNamed()를 사용하여 어떤 context도 없이 명명된 라우트를 탐색하고 (BLoC 또는 Controller 클래스로 부터 직접 라우트를 호출할 수 있음) 앱이 웹으로 컴파일되면 경로는 url에 표시됩니다. <3 ### 미들웨어 만약 GetX 이벤트를 받아서 행동을 트리거 하려면 routingCallback을 사용하면 가능합니다. ```dart GetMaterialApp( routingCallback: (routing) { if(routing.current == '/second'){ openAds(); } } ) ``` GetMaterialApp을 사용하지 않는다면 수동 API를 사용해서 Middleware observer를 추가할 수 있습니다. ```dart void main() { runApp( MaterialApp( onGenerateRoute: Router.generateRoute, initialRoute: "/", navigatorKey: Get.key, navigatorObservers: [ GetObserver(MiddleWare.observer), // 여기 !!! ], ), ); } ``` MiddleWare class 생성 ```dart class MiddleWare { static observer(Routing routing) { /// 각 화면의 routes, snackbars, dialogs와 bottomsheets에서 추가하여 받을 수 있습니다. /// If you need to enter any of these 3 events directly here, /// you must specify that the event is != Than you are trying to do. if (routing.current == '/second' && !routing.isSnackbar) { Get.snackbar("Hi", "You are on second route"); } else if (routing.current == '/third'){ print('last route called'); } } } ``` 이제, 코드에서 Get을 사용하세요: ```dart class First extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( leading: IconButton( icon: Icon(Icons.add), onPressed: () { Get.snackbar("hi", "i am a modern snackbar"); }, ), title: Text('First Route'), ), body: Center( child: ElevatedButton( child: Text('Open route'), onPressed: () { Get.toNamed("/second"); }, ), ), ); } } class Second extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( leading: IconButton( icon: Icon(Icons.add), onPressed: () { Get.snackbar("hi", "i am a modern snackbar"); }, ), title: Text('second Route'), ), body: Center( child: ElevatedButton( child: Text('Open route'), onPressed: () { Get.toNamed("/third"); }, ), ), ); } } class Third extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Third Route"), ), body: Center( child: ElevatedButton( onPressed: () { Get.back(); }, child: Text('Go back!'), ), ), ); } } ``` ## context 없이 탐색 ### SnackBars Flutter로 간단한 SnackBar를 사용하려면 Scaffold의 context가 반드시 주어지거나 Scaffold에 GlobalKey를 추가해서 사용해야만 합니다. ```dart final snackBar = SnackBar( content: Text('Hi!'), action: SnackBarAction( label: 'I am a old and ugly snackbar :(', onPressed: (){} ), ); // 위젯 트리에서 Scaffold를 찾아서 사용하면 // SnackBar가 보여집니다. Scaffold.of(context).showSnackBar(snackBar); ``` Get을 사용할때: ```dart Get.snackbar('Hi', 'i am a modern snackbar'); ``` Get을 사용하면 코드의 어디에서든지 Get.snackbar를 호출하거나 원하는데로 수정하기만 하면 됩니다! ```dart Get.snackbar( "Hey i'm a Get SnackBar!", // title "It's unbelievable! I'm using SnackBar without context, without boilerplate, without Scaffold, it is something truly amazing!", // message icon: Icon(Icons.alarm), shouldIconPulse: true, onTap:(){}, barBlur: 20, isDismissible: true, duration: Duration(seconds: 3), ); ////////// ALL FEATURES ////////// // Color colorText, // Duration duration, // SnackPosition snackPosition, // Widget titleText, // Widget messageText, // bool instantInit, // Widget icon, // bool shouldIconPulse, // double maxWidth, // EdgeInsets margin, // EdgeInsets padding, // double borderRadius, // Color borderColor, // double borderWidth, // Color backgroundColor, // Color leftBarIndicatorColor, // List boxShadows, // Gradient backgroundGradient, // TextButton mainButton, // OnTap onTap, // bool isDismissible, // bool showProgressIndicator, // AnimationController progressIndicatorController, // Color progressIndicatorBackgroundColor, // Animation progressIndicatorValueColor, // SnackStyle snackStyle, // Curve forwardAnimationCurve, // Curve reverseAnimationCurve, // Duration animationDuration, // double barBlur, // double overlayBlur, // Color overlayColor, // Form userInputForm /////////////////////////////////// ``` 기존 스낵바를 선호하거나 한 줄만 추가하는 것을 포함하여 처음부터 커스텀하려는 경우(Get.snackbar는 필수로 제목과 메시지를 사용함) 다음을 사용할 수 있습니다. Get.snackbar가 빌드된 RAW API를 제공하는`Get.rawSnackbar ();`. ### Dialogs dialog 열기: ```dart Get.dialog(YourDialogWidget()); ``` default dialog 열기: ```dart Get.defaultDialog( onConfirm: () => print("Ok"), middleText: "Dialog made in 3 lines of code" ); ``` showGeneralDialog 대신에 Get.generalDialog를 사용할 수 있습니다. cupertinos를 포함한 다른 모든 Flutter 대화 상자 위젯의 경우 context 대신 Get.overlayContext를 사용하고 코드의 어느 곳에서나 열 수 있습니다. 오버레이를 사용하지 않는 위젯의 경우 Get.context를 사용할 수 있습니다. 이 두 context는 탐색 context 없이 inheritedWidget이 사용되는 경우를 제외하고 99%의 경우에 UI의 context를 대체하여 동작합니다. ### BottomSheets Get.bottomSheet는 showModalBottomSheet와 같지만 context가 필요 없습니다. ```dart Get.bottomSheet( Container( child: Wrap( children: [ ListTile( leading: Icon(Icons.music_note), title: Text('Music'), onTap: () {} ), ListTile( leading: Icon(Icons.videocam), title: Text('Video'), onTap: () {}, ), ], ), ) ); ``` ## 중첩된 탐색 GetX는 Fultter의 중첩된 탐색을 더 쉽게 만듭니다. context가 필요 없고 Id로 탐색 스택을 찾을 수 있습니다. - 주석: 병렬 탐색 스택을 만드는 것은 위험 할 수 있습니다. 이상적인 것은 NestedNavigators를 사용하지 않거나 아껴서 사용하는 것입니다. 프로젝트에 필요한 경우 여러 탐색 스택을 메모리에 유지하는 것이 RAM 소비에 좋지 않을 수 있음을 명심하십시오. 간단합니다: ```dart Navigator( key: Get.nestedKey(1), // index로 key를 생성 initialRoute: '/', onGenerateRoute: (settings) { if (settings.name == '/') { return GetPageRoute( page: () => Scaffold( appBar: AppBar( title: Text("Main"), ), body: Center( child: TextButton( color: Colors.blue, onPressed: () { Get.toNamed('/second', id:1); // index로 중첩된 경로를 탐색 }, child: Text("Go to second"), ), ), ), ); } else if (settings.name == '/second') { return GetPageRoute( page: () => Center( child: Scaffold( appBar: AppBar( title: Text("Main"), ), body: Center( child: Text("second") ), ), ), ); } } ), ``` ================================================ FILE: documentation/kr_KO/state_management.md ================================================ - [상태 관리자](#상태-관리자) - [반응형 상태 관리자](#반응형-상태-관리자) - [장점](#장점) - [최대의 성능](#최대의-성능) - [반응형 변수 선언하기](#반응형-변수-선언하기) - [반응형 상태를 갖는 간단한 방법](#반응형-상태를-갖는-간단한-방법) - [변수를 화면에 적용하기](#변수를-화면에-적용하기) - [재빌드에 조건 걸기](#재빌드에-조건-걸기) - [.obs를 사용하는 방법](#obs를-사용하는-방법) - [List를 사용할 때](#list를-사용할-때) - [어째서 .value를 사용하는가](#어째서-value를-사용하는가) - [Obx()](#obx) - [Workers](#workers) - [간단한 상태 관리자](#간단한-상태-관리자) - [장점](#장점-1) - [사용법](#사용법) - [controller의 동작 방식](#controller의-동작-방식) - [StatefulWidget을 더 이상 사용할 필요없습니다](#statefulwidget을-더-이상-사용할-필요없습니다) - [이 패키지의 목표](#이-패키지의-목표) - [다른 사용법](#다른-사용법) - [고유 ID](#고유-id) - [2개의 상태 관리자 섞어쓰기](#2개의-상태-관리자-섞어쓰기) - [GetBuilder vs GetX vs Obx vs MixinBuilder](#getbuilder-vs-getx-vs-obx-vs-mixinbuilder) # 상태 관리자 GetX는 다른 상태 관리자처럼 Streams나 ChangeNotifier를 사용하지 않습니다. 어째서일까요? GetX를 사용하면 Android, iOS, 웹, Linux, macOS, Linux 용 어플리케이션뿐만 아니라, 서버 어플리케이션도 Flutter/GetX의 문법으로 만들 수 있습니다. 반응 시간을 줄이고, RAM을 효율적으로 사용하기 위해, 우리는 GetValue와 GetStream을 만들었습니다. GetValue와 GetStream은 적은 연산 자원으로 낮은 레이턴시와 높은 퍼포먼스를 보여줍니다. 우리는 이를 토대로 상태관리를 포함해 모든 리소스를 빌드합니다. - _복잡도_: 어떤 상태관리자들은 복잡하고 매우 비슷한 형태를 띕니다. GetX를 이용하면 모든 이벤트를 위한 클래스를 정의할 필요가 없습니다. 이는 당신의 코드를 매우 깔끔하게 만들어주며, 적을 코드들을 줄여줍니다. 많은 Flutter 개발자들이 이런 이유로 개발을 포기해왔지만, 드디어 상태관리를 해결해줄 엄청나게 간단한 방법이 나왔습니다. - _code generators에 의존하지 않음_: 당신은 어플리케이션 개발을 위한 로직을 작성하는데 개발시간의 절반을 할애했을 것입니다. 어떤 상태관리자들은 code generator에 의존하여 읽기 쉬운 코드를 작성했을 것입니다. 변수를 바꾸고 build_runner를 실행해야 하는 것은 비생산적일 수 있으며, 심지어 Flutter가 이를 반영되기를 기다리면서 커피 한 잔을 즐겨야 할 정도로 오래 기다릴 수 있습니다. GetX는 모든것을 즉각적으로 반응합니다. code generator에 의존하지 않고, 모든 면에서 당신의 생산성을 높여줍니다. - _필요없는 context_: 아마 당신은 비즈니스 로직을 UI에 반영하기 위해, 여러 위젯 컨트롤러에 context를 넘겨주었을 것입니다. context를 이용한 위젯을 사용하기 위해, context를 다양한 클래스와 함수들을 이용하여 전달하였을 것입니다. GetX를 이용하면 그럴 필요가 없습니다. context없이 controller만으로 접근하여 사용할 수 있습니다. 말 그대로 아무 의미 없이 context를 파라미터로 넘겨줄 필요가 없습니다. - _세분화된 컨트롤_: 대부분의 상태관리자들은 ChangeNotifier을 기반으로 동작합니다. ChangeNotifier는 notifyListeners가 호출되면 모든 위젯들에게 알릴 것입니다. 만약 당신 스크린에 수많은 ChangeNotifier 클래스를 갖는 40개의 위젯이 있다면, 한 번 업데이트 할 때마다 모든 위젯들이 다시 빌드될 것입니다. GetX를 이용하면 위젯이 중첩되더라도, 변경된 위젯만 다시 빌드됩니다. 한 Obx가 ListView를 보고있고, 다른 Obx가 ListView 안의 checkbox를 보고있을 때, checkBox 값이 변경되면, checkBox만 업데이트 되고, ListView 값이 변경되면 ListView만 업데이트 됩니다. - _**정말** 바뀌었을 때만 재구성_: GetX는 흐름제어를 합니다. '진탁'이라는 Text를 화면에 보여준다고 해봅시다. 만약 당신이 observable 변수인 '진탁'을 다시 한 번 '진탁'으로 변경한다면, 그 위젯은 재구성되지 않습니다. 왜냐하면 GetX는 '진탁'이 이미 Text로 보여주고 있다는 것을 알고 있기 때문에, 불필요한 재구성을 하지 않습니다. 대부분(모두일 수도 있는) 지금까지의 대부분의 상태관리자들은 스크린에 다시 빌드하여 보여줍니다. ## 반응형 상태 관리자 반응형 프로그래밍은 복잡하다고 말해지기 때문에 많은 사람들이 접하기 힘들게 합니다. GetX는 반응형 프로그래밍을 꽤 간단한 것으로 만들어줍니다: - StreamControllers를 생성할 필요가 없습니다. - StreamBuilder를 각 변수마다 생성할 필요가 없습니다. - 각 상태마다 클래스를 만들어줄 필요가 없습니다. - 초기값을 위한 get을 만들어줄 필요가 없습니다. GetX와 함께하는 반응형 프로그래밍은 setState를 사용하는 것만큼 쉽습니다. 이름 변수가 하나 있고, 이 변수가 변경될 때마다 해당 변수를 사용하는 모든 위젯들이 자동적으로 변경된다고 해봅시다. 여기 이름 변수가 있습니다. ```dart var name = 'Jonatas Borges'; ``` 이 변수를 observable로 만들고 싶다면, 맨 뒤에 ".obs"만 붙이면 됩니다. ```dart var name = 'Jonatas Borges'.obs; ``` 이게 끝입니다. 정말 쉽죠? 지금부터, 우리는 이런 반응형-".obs"(ervables) 변수들을 _Rx_ 라고 부르겠습니다. 우리가 내부에서 무엇을 했나요? 우리는 `String`의 `Stream`에 초기값인 `"Jonatas Borges"`를 할당했습니다. 우리는 `"Jonatas Borges"`을 사용하는 위젯들에게, 이제 이 변수에 "속한다"고 알리며 이 Rx 변수가 바뀔 때마다, 그 위젯들도 바뀌어야 한다고 알립니다. 이것이 Dart 언어의 기능에 기반한, **GetX의 마법**입니다. 하지만, 알다시피 static 클래스들은 "자동 변경"할 힘이 없기 때문에, `위젯`은 함수 안에 있는 경우에만 변경이 가능합니다. 몇몇 변수를 동일한 범위 내에서 변화시키고 싶을 때, 당신은 `StreamBuilder`를 생성하여 이 변수를 지켜보면서 변화를 감지하고, "연쇄적으로" 중첩된 `StreamBuilder`를 만들 것입니다, 맞죠? 아뇨, 더 이상 `StreamBuilder`를 만들 필요 없습니다. 하지만 static 클래스를 사용한다는 점은 맞아요. 특정한 위젯을 변경하고 싶을 때, 우리는 모양이 비슷한 코드들을 봐야 했습니다. 그건 Flutter 방식이죠. **GetX**를 이용하면 이런 비슷한 모양의 코드는 잊어버릴 수 있습니다. `StreamBuilder( … )`? `initialValue: …`? `builder: …`? 아뇨, 당신은 그저 `Obx()` 위젯 안에 변수를 넣기만 하면 됩니다. ```dart Obx (() => Text (controller.name)); ``` _당신이 기억해야할 것은?_ `Obx(() =>`만 기억하세요. 당신은 화살표함수를 통해 그 위젯을 `Obx()`으로 전달하는 것입니다. (그 _Rx_ 의 "Observer") `Obx`는 꽤 스마트하며 `controller.name`이 바뀔 경우에만 바뀔 것입니다. 만약 `name`이 `"John"`이고, 당신이 이를 `"John"` (`name.value = "John"`)으로 바꾼다면, 기존 `value`와 동일하므로 화면상으로 바뀌는 것이 없습니다. 그리고 `Obx`는 리소스를 아끼기 위해 새 값을 무시하고 재빌드하지 않습니다. **놀랍지 않나요?** > 그래서, 제가 만약 5개의 _Rx_ (observable) 변수를 `Obx`안에 가지고 있다면 어떻게 되나요? 그 변수들 중 **아무거나** 변경이 되었을 때 업데이트 됩니다. > 그리고 제가 만약 클래스 안에 30개의 변수를 갖고 있고 하나만 업데이트 했다면, 클래스 안의 **모든** 변수가 업데이트 되나요? 아뇨, 단지 그 _Rx_ 변수를 사용하는 **특정한 위젯만** 업데이트 됩니다. 그래서 **GetX**는 _Rx_ 변수의 변경이 있을 때만 화면에 업데이트 합니다. ``` final isOpen = false.obs; // 아무일도 일어나지 않습니다. 동일한 값이기 때문입니다. void onButtonTap() => isOpen.value=false; ``` ### 장점 **GetX()** 는 업데이트된 것들을 **세부적으로** 제어해야할 때 유용합니다. 어떤 동작을 수행할 때 모든 변수가 수정되어 `고유 ID`가 필요없을 때 `GetBuilder`를 사용하세요. `GetBuilder`는 단 몇줄의 코드로 상태를 변경시켜줍니다(`setState()`처럼). 이것은 단순하면서 CPU에 최소한의 부담을 주며, State 재빌드라는 하나의 목적을 수행하기 위해 가능한 한 최소의 리소스를 사용합니다. **강력한** 상택 관리자가 필요하다면, **GetX**와 함께하세요. GetX는 변수를 이용하지 않고, 내부에서 모든 것이 `Streams`로 구성된 __flow__ 를 이용합니다. 모든 것이 `Streams`이기 때문에, 접속사로써 _rxDart_ 를 이용합니다. 모든 것이 `Streams`이기 때문에, 각 "_Rx_ 변수"의 `event`를 주시할 수 있습니다. 말 그대로 _BLoC_ 의 접근 법이며, generator와 decoration 없이 _MobX_ 보다 쉽습니다. **모든 것들을** `.obs`를 붙임으로써 _"Observable"_ 하게 만들 수 있습니다. ### 최대의 성능 최소의 재빌드를 위해 똑똑한 알고리즘을 적용하기 위해, **GetX**는 상태가 변했는지 확인하는 comparator를 사용합니다. 당신의 앱에서 에러가 발생하고 상태 변경을 중복하여 보내면, **GetX**는 충돌하지 않도록 보장해줍니다. **GetX**를 사용하면 `value`가 변경된 경우만 상태가 변경됩니다. 이 점이 **GetX**와 _`computed`를 사용하는 MobX_ 와의 주요 차이점입니다. 2개의 __observable__ 변수를 결합하고 하나만 변경되는 경우, 그 _observable_ 를 참조하는 것 또한 변경됩니다. **GetX**를 사용하면, 2개의 변수를 결합한 경우 (`Oberver()`와 비슷한)`GetX()`는 정말 상태가 변경된 경우만 재빌드됩니다. ### 반응형 변수 선언하기 변수를 "observable"하게 만드는 방법은 3가지가 있습니다. 1 - 첫 번째 방법: **`Rx{Type}`**. ```dart // 초기값을 설정하는 것을 추천하지만, 필수는 아닙니다. final name = RxString(''); final isLogged = RxBool(false); final count = RxInt(0); final balance = RxDouble(0.0); final items = RxList([]); final myMap = RxMap({}); ``` 2 - 두 번째 방법: **`Rx`**와 Dart의 제너릭을 이용 `Rx` ```dart final name = Rx(''); final isLogged = Rx(false); final count = Rx(0); final balance = Rx(0.0); final number = Rx(0); final items = Rx>([]); final myMap = Rx>({}); // 커스텀 클래스 - 그 어떤 클래스도 가능합니다 final user = Rx(); ``` 3 - The third, more practical, easier and preferred approach, just add **`.obs`** as a property of your `value`: 3 - 세 번째 방법: 실용적이며 쉽고 선호되는 방법으로, 단순히 **`.obs`**를 `value`의 속성으로 덧붙이는 방법 ```dart final name = ''.obs; final isLogged = false.obs; final count = 0.obs; final balance = 0.0.obs; final number = 0.obs; final items = [].obs; final myMap = {}.obs; // 커스텀 클래스 - 그 어떤 클래스도 가능합니다 final user = User().obs; ``` ##### 반응형 상태를 갖는 간단한 방법 알다시피 _Dart_ 는 _null safety_ 가 곧 도입될 것입니다. 이를 대비하기 위해 지금부터, _Rx_ 변수를 항상 **초기값**으로 초기화해주세요. > 변수를 **GetX** 를 이용하여 _observable_ + _초기값_ 으로 바꾸는 것은 매우 쉽고 실용적입니다. 변수의 맨 뒤에 "`.obs`" 글자를 붙이기만 하면 되고, **이게 다입니다**. 당신은 변수를 observable 하게 만들었으며 `.value`를 이용하여 _초기값_ 에 접근할 수 있습니다. ### 변수를 화면에 적용하기 ```dart // controller file final count1 = 0.obs; final count2 = 0.obs; int get sum => count1.value + count2.value; ``` ```dart // view file GetX( builder: (controller) { print("count 1 rebuild"); return Text('${controller.count1.value}'); }, ), GetX( builder: (controller) { print("count 2 rebuild"); return Text('${controller.count2.value}'); }, ), GetX( builder: (controller) { print("sum rebuild"); return Text('${controller.sum}'); }, ), ``` `count1.value++`를 하면, 다음이 출력됩니다: - `count 1 rebuild` - `sum rebuild` because `count1` has a value of `1`, and `1 + 0 = 1`, changing the `sum` getter value. 왜냐하면 `count1`은 `1`을 갖고 있고, `1 + 0 = 1`이며 `sum` getter의 값을 변경하기 때문입니다. `count2.value++`를 하면, 다음이 출력됩니다: - `count 2 rebuild` - `sum rebuild` 왜냐하면 `count2.value`가 바뀌었고 `sum` 결과는 이제 `2`이기 때문입니다. - 참고: 기본적으로, 동일한 `value`로 변경되더라도, 첫 번째 이벤트는 위젯을 재빌드합니다. 이 동작은 Boolean 변수로 인해 일어납니다. 다음과 같이 했다고 생각해봅시다: ```dart var isLogged = false.obs; ``` 그리고 `ever`에서 이벤트를 발생시키기 위해, 사용자가 "로그인" 되어있는지 확인한다고 해봅시다. ```dart @override onInit(){ ever(isLogged, fireRoute); isLogged.value = await Preferences.hasToken(); } fireRoute(logged) { if (logged) { Get.off(Home()); } else { Get.off(Login()); } } ``` 만약 `hasToken`이 `false`라면, `isLogged`에 변화는 없을 것입니다. 그러면 `ever()`는 호출되지 않을 것입니다. 이런 동작을 피하기 위해서, `.value`가 동일한 값으로 변경되더라도 _observable_ 의 첫 변경은 이벤트를 발생시킬 것입니다. 이런 동작을 원하지 않는다면, 다음으로 막을 수 있습니다: `isLogged.firstRebuild = false;` ### 재빌드에 조건 걸기 또한, Get은 정교한 상태 관리 기능을 제공합니다. 특정 조건에서 이벤트를 조건화할 수 있습니다.(리스트에 요소를 추가하는 등) ```dart // 첫 번째 parameter: 조건, 반드시 true 혹은 false를 return // 두 번째 parameter: 조건이 true 일 경우 적용할 새 value list.addIf(item < limit, item); ``` decoration 없이, code generator 없이, 복잡함 없이 :smile: Flutter의 counter 앱을 아시나요? 당신의 Controller 클래스는 아마 다음과 같을 것입니다. ```dart class CountController extends GetxController { final count = 0.obs; } ``` 간단합니다: ```dart controller.count.value++ ``` 어디에서 변경되든지 간에 관계없이, counter 변수를 당신의 UI 내에서 업데이트 할 수 있습니다. ### .obs를 사용하는 방법 그 어떠한 것도 obs로 바꿀 수 있습니다. 2가지 방법이 있습니다. * 클래스 값들을 obs로 바꿀 수 있습니다. ```dart class RxUser { final name = "Camila".obs; final age = 18.obs; } ``` * 또는 클래스 전체를 observable로 만들 수 있습니다. ```dart class User { User({String name, int age}); var name; var age; } // 인스턴스화 할때 final user = User(name: "Camila", age: 18).obs; ``` ### List를 사용할 때 리스트내 요소들과 마찬가지로 리스트 또한 완벽하게 observable로 만들 수 있습니다. 이렇게 하면, 리스트에 요소를 추가하였을 때 자동적으로 그 리스트를 사용하는 위젯들을 리빌드할 수 있습니다. 리스트는 ".value"를 이용할 필요가 없습니다. 놀랍게도 dart api가 ".value" 없이도 사용할 수 있게 만들어줍니다. 불행히도 String, int와 같은 primitive type들은 확장할 수 없기때문에 ".value"가 반드시 필요합니다만, getter와 setter를 이용하면 이러한 문제는 해결됩니다. ```dart // On the controller final String title = 'User Info:'.obs final list = List().obs; // on the view Text(controller.title.value), // String은 .value가 필요합니다 ListView.builder ( itemCount: controller.list.length // 리스트는 .value가 필요없습니다. ) ``` 당신이 만든 observable 클래스를 만들었을 때, 업데이트를 하는 다른 방법이 있습니다. ```dart // model 파일에서 // 각 field들을 observable로 만드는 대신, 클래스 전체를 observable로 만들 것입니다. class User() { User({this.name = '', this.age = 0}); String name; int age; } // controller 파일에서 final user = User().obs; // when you need to update the user variable: // user의 변수를 업데이트해야할 때 user.update( (user) { // 이 parameter는 업데이트 하길 원하는 인스턴스 자체입니다. user.name = 'Jonny'; user.age = 18; }); // user 인스턴스를 업데이트하는 또다른 방법 user(User(name: 'João', age: 35)); // on view: Obx(()=> Text("Name ${user.value.name}: Age: ${user.value.age}")) // .value 없이 model의 value에 접근할 수 있습니다. user().name; // User 클래스가 아니라, user 변수임을 주의하세요 (변수는 소문자 u를 갖고 있습니다.) ``` 원하지 않으면 set을 이용하지 않아도 됩니다. "assign"과 "assignAll" api를 이용할 수 있습니다. "assign" api는 당신의 리스트를 비우고, 채우고 싶은 하나의 요소를 넣을 수 있습니다. "assignAll" api는 존재하는 리스트를 비우고, 삽입하길 원하는 iterable 객체들을 추가할 수 있습니다. ### 어째서 .value를 사용하는가 code generator와 decoration을 이용하면 `String`과 `int`와 같은 타입에도 '.value'를 이용하지 않아도 되었겠지만, 이 라이브러리의 목표는 외부 종속성을 피하는 것입니다. 우리는 외부 패키지를 이용하지 않고, 간단하고 가벼우며 성능좋은 필수요소들(라우트 관리, 종속성 관리, 상태 관리)을 제공하고, 쾌적한 프로그래밍 환경을 제공하고 싶었습니다. 단 3글자(get)와 콜론(;)만 pubspec에 적고 프로그래밍을 시작하세요. 모든 솔루션들이 기본적으로 제공되며, 쉽고 생산성과 성능 좋게 라우트와 상태 관리를 할 수 있습니다. 이 라이브러리는 완벽한 솔루션임에도 불구하고 단일 상태 관리 패키지보다 가볍습니다. 이 점을 꼭 아셔야 합니다. 만약 `.value`가 code generator처럼 당신을 괴롭힌다면, MobX가 훌륭한 대안으로 Get과 함께 활용할 수 있습니다. pubspec에서 단일 종속성을 원하고, 호환되지 않는 버전의 패키지들을 걱정하지 않고 프로그래밍을 시작하길 원하거나, 상태 업데이트 오류가 상태 관리자나 패키지에서 비롯되는 경우, controller에 사용가능성에 대한 걱정하기 원하지 않고 말그대로 "프로그래밍만"을 하고 싶은 경우, get은 완벽한 방안입니다. 당신이 MobX의 code generator를 이용하는데 문제가 없었거나 BloC의 boilerplate를 이용하는데 문제가 없었다면, Get을 라우트하는데 쉽게 사용할 수 있을 것이며 상태 관리자를 갖고 있다는 사실을 잊을 것입니다. Get의 SEM과 RSM은 필수적으로 탄생했습니다. 저희 회사에는 90개의 controller가 넘는 프로젝트가 있었는데, 충분히 좋은 디바이스에서도 flutter clean 이후, code generator는 작업을 끝내는데 30분 이상이 걸렸습니다. 당신의 프로젝트에 5, 10, 15개 정도의 controller들만 있다면 어떤 상태 관리자들도 충분히 좋겠지만, 엄청 큰 프로젝트를 진행하고 있다면 code generator는 문제를 일으킬 것입니다. get은 매우 훌륭한 해결책입니다. 누군가 이 프로젝트에 기여하고자 code generator나 비슷한 것을 만들고 있다면, 저는 이 readme로 링크시키겠습니다. 저한테 필요한 것들이 모든 개발자에게 필요한 것들은 아니겠지만, 지금으로써는 MobX와 같은 좋은 솔루션들이 있다고 말할 수 있습니다. ### Obx() 바인딩을 이용해 Get을 입력하는 것은 불필요합니다. 익명 함수만 받는 GetX 대신 Obx 위젯을 이용할 수 있습니다. 타입을 이용하지 않는다면, 변수를 사용하기 위한 controller 객체를 이용하거나, `Get.find().value` 혹은 `Controller.to.value`를 이용하여 value에 접근하면 됩니다. ### Workers Worker는 이벤트가 일어났을 때, 특정 콜백함수들을 호출하는 것을 도와줍니다. ```dart /// 'count1'이 변경될 때마다 호출 ever(count1, (_) => print("$_ has been changed")); /// 'count1'이 처음으로 변경될 때 호출 once(count1, (_) => print("$_ was changed once")); /// Anti DDos - 'count1'이 변경되고 1초간 변화가 없을 때 호출 debounce(count1, (_) => print("debouce$_"), time: Duration(seconds: 1)); /// 'count1'이 변경되고 있는 동안 1초 간격으로 호출 interval(count1, (_) => print("interval $_"), time: Duration(seconds: 1)); ``` (`debounce`를 제외한)모든 worker들은 `condition` parameter를 가집니다. 이 parameter는 `bool` 이거나 `bool`을 return 하는 콜백함수입니다. 이 `condition`은 `callback` 함수가 언제 실행될지 정의합니다. 모든 worker들은 `Worker` 객체를 return하며, `dispose()`를 이용하여 worker 동작을 취소시킬 수 있습니다. - **`ever`** _Rx_ 변수가 바뀔 때마다 항상 호출됩니다. - **`everAll`** `ever`처럼, `List` _Rx_ 변수가 주어지고 변경될 때마다 호출됩니다. - **`once`** 변수가 최초로 변경될 때(한 번만) 호출됩니다. - **`debounce`** 'debounce'는 검색 함수를 구현하는 데에 매우 유용합니다. API 호출을 타이핑이 모두 끝났을 때만 호출 시킬 수 있습니다. 만약 사용자가 "진탁"을 타이핑한다면, 당신은 'ㅈ,ㅣ,ㄴ,ㅌ,ㅏ,ㄱ'에 해당하는 6번의 검색을 해야할 것입니다. Get을 이용하면 이런 일은 일어나지 않을 것입니다. 왜냐하면 "debounce" Worker는 타이핑이 끝났을 때에만 검색하도록 만들어주기 때문입니다. - **`interval`** 'interval'은 debounce와 다릅니다. 사용자가 1초에 1000번의 변화를 주는 행동을 한다고 해봅시다. debounce는 마지막 변화가 있은 후, 정해진 시간(기본적으로는 800ms)이 지나면 한 번만 호출됩니다. interval은 정해진 시간동안 사용자의 행동들을 무시합니다. 사용자가 1분동안 1초에 1000번의 변화를 주는 행동을 지속한다면, debounce는 사용자가 행동을 멈춘 후 한 번만 호출됩니다. 1초로 time이 설정된 interval은 매 초마다 1번씩 총 60번 호출되며, 3초로 time이 설정된 interval은 3초마다 1번씩 총 20번 호출 될 것입니다. interval은 엄청 빠른 터치(클릭)를 이용한 어뷰징(abusing)을 막는데 사용하는 데에 사용하기를 추천합니다.(예를 들어 특정 버튼을 눌러 코인을 얻는다고 해봅시다. 1분에 300번의 터치를 한다면, 사용자는 1분에 300코인을 얻을 것입니다. 하지만 interval를 이용하여 3초를 time으로 설정하면 사용자가 300번을 터치하든, 수 천번을 터치하든지 간에 20코인밖에 얻지 못할 것입니다.) 검색과 같이 변화가 api를 통해 쿼리를 호출해야 하는 경우, debounce는 DDos 공격을 막는데 효과적입니다. debounce는 사용자가 타이핑을 멈추길 기다리고, 멈추면 호출되기 때문입니다. 위쪽의 코인 시나리오에 대입해서 생각해보면, 터치를 "멈춘" 때, 코인 1개만 얻을 수 있을 것입니다. - 참고: Worker는 Controller 혹은 클래스를 시작할 때만 사용할 수 있습니다. 그래서 항상 onInit 내에 있거나(권장사항), 클래스 생성자, StatefulWidget의 initState 안에(권장하지는 않지만 부작용은 없습니다.) 있어야 합니다. ## 간단한 상태 관리자 Get은 ChangeNotifier를 사용하지 않고, 엄청나게 가볍고 사용하기 쉬운 상태 관리자를 제공합니다. 이 상태 관리자는 Flutter가 처음인 사람들의 요구를 충족하며 대규모 어플리케이션에서도 문제를 발생시키지 않습니다. GetBuilder는 여러 상태 컨트롤을 정확하게 해내는 것을 목표로 합니다. 장바구니에 30개의 상품이 있고, 사용자가 하나를 삭제하기 위해 터치(클릭)하면 상품 목록이 업데이트 되어 가격과 품목 수가 줄어든다고 해봅시다. 이런 상황에서 GetBuilder는 매우 유용합니다. 왜냐하면 상태들을 그룹화하여 "연산 로직"없이 한 번에 변경하기 때문입니다. GetBuilder는 이런 상황을 고려하여 만들어졌습니다. 일시적인 상태 변화를 위해 setState를 사용하면 되므로 상태 관리자가 필요없기 때문입니다. 이렇게 하면 개별적 controller를 원하는 경우, 각 ID를 할당해주거나 GetX를 사용할 수 있습니다. 어떤 것을 선택할지 당신에게 달려있지만 이 점을 기억하세요. "개별" 위젯이 많은 경우에는 GetX의 성능이 뛰어나고, 상태 변화가 여러 번 일어나는 경우에는 GetBuilder의 성능이 뛰어납니다. ### 장점 1. 필요한 위젯만 업데이트 해줍니다. 2. ChangeNotifier를 사용하지 않고, 적은 메모리(거의 0mb)를 이용하여 상태 관리를 해줍니다. 3. StatfulWidget을 이용 안 해도 됩니다! Get을 이용하면 더 이상 StatefulWidget이 필요 없습니다. 다른 상태 관리자들을 사용하면, Provider, BLoC, MobX Controller 등의 객체를 갖기 위해 StatefulWidget을 사용해야 합니다. appBar, Scaffold, 그리고 대부분의 위젯들이 StatelessWidget로 구성된 것을 생각해본 적이 있나요? Get은 이 부분도 해결해줍니다. 모든 것들을 Stateless로 만드세요. 하나의 위젯만 업데이트할 필요가 있다면, GetBuilder로 감싸면 해당 상태를 가질 수 있습니다. 4. 당신의 프로젝트를 잘 정돈할 수 있습니다! Controller들은 UI내에 두지 않고, TextEditController나 다른 controller 들을 당신의 Controller 클래스 내에 두세요! 5. 렌더링 된 직후, 위젯을 업데이트하기 위해 이벤트를 발생시킬 필요가 있나요? GetBuilder는 StatefulWidget처럼 "initState"를 갖고 있습니다. 그리고 controller로부터 직접적으로 이벤트를 호출하고, 더 이상 이벤트를 initState내에 배치할 필요가 없습니다. 6. timers 등과 같은 streams를 닫는 행동을 트리거해야 하나요? GetBuilder는 dispose 또한 갖고 있어서, 위젯이 없어지자마자 dispose를 호출할 수 있습니다. 7. streams는 정말 필요할 때만 사용하세요. StreamController와 StreamBuilder는 controller 안에 정상적으로 사용할 수 있습니다만! 기억하세요. stream은 메모리를 적당히 사용하지만, 남용해서는 안됩니다. 30개의 stream이 동시에 열려있다면 ChangeNotifier보다 안좋습니다(ChangeNotifier는 매우 나쁩니다). 8. RAM의 소모 없이 위젯을 업데이트 하세요. Get은 GetBuilder의 creator ID만 저장하고 필요한 경우에만 해당 GetBuilder만 업데이트 합니다. 수천개의 GetBuilder가 있어도 get은 ID를 저장하는데 매우 적은 메모리를 사용합니다. 새 GetBuilder를 생성한다면, creator ID를 갖고 있는 GetBuilder의 상태를 공유합니다. 각 GetBuilder의 새 상태는 생성 되지 않기 때문에 큰 규모의 어플리케이션에서도 **많은** RAM 자원을 절약합니다. 기본적으로 당신의 어플리케이션은 전반적으로 Stateless 이고, 극히 일부의 위젯만 단일 상태를 가진 (GetBuilder를 포함한)Stateful일 것이기 때문에, 하나의 업데이트만으로 그것들을 전부 업데이트 합니다. 상태는 단 하나뿐입니다. 9. Get은 전지적으로 대부분의 경우 어느 타이밍에 메모리에서 제거해야할 지 알 고 있습니다. 당신은 언제 컨트롤러를 dispose해야하는지만 걱정하세요. ### 사용법 ```dart // GetxController를 상속(extends)하는 controller 클래스를 만드세요 class Controller extends GetxController { int counter = 0; void increment() { counter++; update(); // increment()가 호출되었을 때, counter 변수가 변경되어 UI에 반영되어야 한다는 것을 update()로 알려주세요 } } // 당신의 Stateless/Stateful 클래스에서, increment()가 호출되었을 때 GetBuilder를 이용해 Text를 업데이트 하세요 GetBuilder( init: Controller(), // 맨 처음만! 초기화(init)해주세요 builder: (_) => Text( '${_.counter}', ), ) // controller는 처음만 초기화하면 됩니다. 같은 controller로 GetBuilder를 또 사용하려는 경우에는 init을 하지 마세요. // 중복으로 'init'이 있는 위젯이 배치되자마자, controller는 자동적으로 메모리에서 제거될 것입니다. // 걱정하실 필요 없이, Get은 자동적으로 controller를 찾아서 해줄겁니다. 그냥 2번 init하지 않는 것만 하시면 됩니다. ``` **끝입니다!** - 당신은 이미 Get을 이용하여 상태관리를 어떻게 하는지 다 배우셨어요! - 참고: 큰 규모의 프로젝트를 진행하면서 init 속성을 사용하지 않을 수도 있습니다. 그럴 때에는, Binding 클래스를 상속(extends)한 클래스를 만들고, 그 클래스 내에서 해당 라우트에 생성되어야 하는 controller를 선언하세요. controller가 즉각적으로 만들어지지는 않지만, controller가 처음 사용될 때 Get이 알아서 잘 만들어줄 것입니다. Get은 lazyLoad를 지원하며, controller가 더 이상 필요하지 않을 때 dispose를 해줍니다. 사용예제는 pub.dev에서 확인하세요. 수많은 라우트를 진행하면서 예전에 사용하였던 controller의 데이터가 필요하면, GetBuilder를 다시 사용하시면 됩니다 (init 없이): ```dart class OtherClass extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( body: Center( child: GetBuilder( builder: (s) => Text('${s.counter}'), ), ), ); } ``` GetBuilder 밖의 여러 곳에서 controller를 사용해야 하는 경우, 간단하게 Controller 클래스 안의 getter로 접근할 수 있습니다. (아니면 `Get.find()`를 사용하세요) ```dart class Controller extends GetxController { /// You do not need that. I recommend using it just for ease of syntax. /// with static method: Controller.to.increment(); /// with no static method: Get.find().increment(); /// There is no difference in performance, nor any side effect of using either syntax. Only one does not need the type, and the other the IDE will autocomplete it. static Controller get to => Get.find(); // add this line int counter = 0; void increment() { counter++; update(); } } ``` 그리고 아래와 같은 방법으로, controller에 바로 접근하세요: ```dart FloatingActionButton( onPressed: () { Controller.to.increment(), } // This is incredibly simple! child: Text("${Controller.to.counter}"), ), ``` FloatingActionButton을 눌렀을 때, counter 변수를 주시(listen)하고 있는 위젯은 자동적으로 업데이트됩니다. ### controller의 동작 방식 아래와 같은 상황을 가정해보겠습니다: `Class A => Class B (has controller X) => Class C (has controller X)` A 클래스에서 controller를 아직 사용하지 않았기 때문에, controller는 메모리에 없습니다(Get은 lazyLoad를 지원합니다). B 클래스에서는 controller를 사용하기 때문에 메모리에 로드됩니다. C 클래스에서는 B 클래스에서 사용한 controller와 같은 controller를 사용하기 때문에, Get은 B의 controller와 C의 controller의 상태를 공유하며, 그 동일한 controller는 여전히 메모리에 있습니다. 그리고 C 화면과 B 화면을 닫으면, Get은 자동적으로 controller X (B와 C에서 쓰인 controller)를 메모리에서 해제해줄 것입니다. 왜냐하면 A 클래스에서는 controller X를 사용하지 않기 때문이죠. 만약 B 화면으로 라우팅한다면 controller X는 다시 메모리에 로드됩니다. 그리고 C 화면으로 라우팅되는 대신, A 화면으로 되돌아간다면 같은 방식으로 controller X는 메모리에서 해제됩니다. 만약 클래스 C에서 controller를 사용하지 않고, 클래스 B가 메모리에서 해제된다면, controller를 사용하는 클래스가 없기 때문에 같은 방식으로 controller는 메모리에서 해제됩니다. Get이 에러날 수 있는 유일한 예외 상황은 B가 예기치않게 라우트 상에서 제거되고, C에서 controller를 사용하려고 하는 경우입니다. 이 경우, B에 있던 controller의 creator ID가 제거되는데, Get은 creator ID를 갖고 있지 않은 controller는 메모리에서 제거하도록 프로그래밍되어 있습니다. 이런 일을 원하지 않으신다면, "authoRemove: false" 플래그를 B 클래스의 GetBuilder에 추가하고, "assignId: true"를 C 클래스의 GetBuilder에 추가해주세요. ### StatefulWidget을 더 이상 사용할 필요없습니다 SatefullWidget을 사용한다는 것은 위젯을 최소한으로 재빌드해야하는 경우에도, 위젯을 Consumer / Oberver / BlocProvider / GetBuilder / GetX / Obx 안에 넣어줄 것이기 때문에, 또다른 StatefulWidget을 사용하는 것과 마찬가지이므로 화면 전체의 상태를 불필요하게 저장합니다. StatefulWidget 클래스는 StatelessWidget 클래스보다 더 많은 RAM 할당이 필요한 큰 규모의 클래스입니다. 1개나 2개 정도의 클래스라면 별 차이가 없겠지만, 100개 이상부터는 차이가 있을 것입니다! TickerProviderStateMixin과 같은 mixin이 필요없는 경우, Get을 사용하면 StatefulWidget은 필요 없습니다. StatefulWidget에서 메소드를 직접적으로 호출하는 것처럼, Getbuilder를 통해 메소드를 호출할 수 있습니다. initState()나 dispose()를 호출할 필요가 있을 때에도, 직접적으로 호출할 수 있습니다. ```dart GetBuilder( initState: (_) => Controller.to.fetchApi(), dispose: (_) => Controller.to.closeStreams(), builder: (s) => Text('${s.username}'), ), ``` 위 방법보다 더 좋은 방법은 Controller에서 onInit()과 onClose()를 이용하는 것입니다. ```dart @override void onInit() { fetchApi(); super.onInit(); } ``` - 참고: controller가 처음 불려졌을 때 어떤 메소드가 호출되길 원하는 경우, (좋은 성능을 목표로하는 Get 패키지를 이용하면)이를 위해서 생성자를 **사용할 필요가 없습니다**. 생성자를 사용한다는 것은 controller가 생성되거나 할당되었을 때의 로직에서 벗어나는 일이기 때문에 좋지 않습니다. (controller 객체를 생성하려하면 생성자는 즉시 호출되며, controller를 사용하기 이전부터 메모리에 로드됩니다. 이러한 동작은 이 라이브러리의 성능을 저해합니다.) onInit()과 onClose()는 이를 위해 만들어졌습니다. Get.lazyPut하는지 여부에 따라, controller가 생성되거나 처음 사용될 때 onInit()과 onClose()가 호출됩니다. API를 호출하기 위한 데이터를 초기화 등을 위해 구식 방식의 initState/dispose를 사용하는 대신 onInit()을 사용하고, stream을 닫는 등의 동작이 필요하면 onClose()를 사용하세요. ### 이 패키지의 목표 이 패키지의 목표는 당신에게 최소의 종속성(pubspec의 dependencies)으로 라우트/상태/종속성 관리를 위한 완전한 솔루션을 제공하는 것입니다. Get은 어떤 종속성에서도, 어떤 버전의 Flutter API를 사용하더라도 동작하도록 보장해줍니다. 어떤 종속성에서도 당신의 프로젝트가 동작하도록 단일 패키지로 모든 것을 집약하였습니다. 이런 방법으로 당신은 화면상 위젯들만 신경쓰고, 팀원 중 일부는 비즈니스 로직에만 신경쓸 수 있도록 하였습니다. 이 점은 당신에게 더 나은 작업환경을 제공합니다. 팀원 중 일부는 controller에 보내지는 데이터에 신경 쓰지 않고 위젯에만 집중하고, 다른 팀원들은 위젯 배치에는 신경쓰지 않고 비즈니스 로직에만 집중하여 작업할 수 있습니다. 간단히 말하면: 여러 메소드들을 initState에서 호출할 필요도 없고, 메소드들을 parameter로 controller에 넘겨줄 필요 없고, controller 생성자를 호출할 필요도 없습니다. 여러분의 서비스가 시작하고 호출되는 onInit()를 이용하면 됩니다. 그리고 controller가 더 이상 필요 없을 때 메모리에서 제거 될 때 호출되는 onClose()를 이용하세요. 이 방법으로 화면 구성은 위젯 배치만 신경써도 되게 해줍니다. GetxController 안에서 dispose를 호출하지 마세요. 아무동작도 하지 않을 뿐더러 controller는 위젯이 아니기 때문에 "dispose"할 수 없다는 점을 기억하세요. Get에 의해 자동적으로 똑똑하게 메모리에서 해제 될 것입니다. 만약 stream들을 닫고 싶다면 onClose() 메소드 안에서 닫아주세요. 예를 들어: ```dart class Controller extends GetxController { StreamController user = StreamController(); StreamController name = StreamController(); /// dispose가 아니라 onClose()에서 stream을 닫으세요 @override void onClose() { user.close(); name.close(); super.onClose(); } } ``` Controller 생명 주기: - onInit()은 생성되었을 때 호출 - onClose()는 delete 메소드를 준비하기 위해 닫히는 경우 - deleted: controller가 메모리에서 해제되어 더 이상 API에 접근할 수 없을 때. 말 그대로 삭제되어 추적할 수 없습니다. ### 다른 사용법 Controller 객체를 GetBuilder 안에서 value로 직접 접근할 수 있습니다: ```dart GetBuilder( init: Controller(), builder: (value) => Text( '${value.counter}', // 여기! ), ), ``` GetBuilder 바깥에서도 controller 객체가 필요하면, 다음과 같이 접근할 수 있습니다: ```dart class Controller extends GetxController { static Controller get to => Get.find(); [...] } // 화면상 GetBuilder( init: Controller(), // 각 controller를 처음 사용할 때 init 하세요 builder: (_) => Text( '${Controller.to.counter}', // 여기! ) ), ``` 아니면 ```dart class Controller extends GetxController { // static Controller get to => Get.find(); // static get 없이 [...] } // stateless/stateful 클래스에서 GetBuilder( init: Controller(), // 각 controller를 처음 사용할 때 init 하세요 builder: (_) => Text( '${Get.find().counter}', // 여기! ), ), ``` - "비표준"적인 방식의 접근도 다음과 같이 가능합니다. get_it, modular 등과 같은 다른 종속성 관리자를 사용한다면, 아래와 같이 controller 객체를 전달해줄 수 있습니다: ```dart Controller controller = Controller(); [...] GetBuilder( init: controller, // 여기! builder: (_) => Text( '${controller.counter}', // 여기! ), ), ``` ### 고유 ID GetBuilder로 위젯의 업데이트를 좀 더 세분화하여 다루고 싶다면, 고유 ID를 부여하세요: ```dart GetBuilder( id: 'text' init: Controller(), // 각 controller를 처음 사용할 때 init 하세요 builder: (_) => Text( '${Get.find().counter}', // 여기! ), ), ``` 그리고 다음과 같이 update 하세요: ```dart update(['text']); ``` 또, update하는데 조건도 줄 수 있습니다: ```dart update(['text'], counter < 10); ``` GetX는 위젯이 정말 값이 변경되었을 때에만 재빌드합니다. 만약 변수의 값이 전과 같은 값으로 변경되었다면, GetX는 메모리와 CPU 자원을 아끼기 위해 재빌드하지 않습니다.(화면에 3이라는 숫자가 보여지고 있고, 그 숫자가 다시 3으로 변경된 경우를 가정하겠습니다. 대부분의 상태 관리자들은 이러한 경우에 재빌드를 합니다만, GetX를 사용하면 정말 값이 변경되었을 때에만 재빌드를 합니다.) ## 2개의 상태 관리자 섞어쓰기 단 하나의 반응변수(.obs)와 다른 메커니즘(update())가 모두 필요해서 Getbuilder 안에 Obx를 넣어야 하는 경우가 필요했습니다. 이 경우를 위해 MixinBuilder가 만들어졌습니다. ".obs"변수의 값이 바뀔 때 즉각적으로 바뀌는 것과 update()를 통해 바뀌는 것 모두를 지원합니다. 하지만, 이 위젯이 GetBuilder, GetX, Obx 보다 더 많은 리소스를 필요로합니다. 왜냐하면 controller의 update() 메소드와 자식으로부터의 .obs 변수의 변화를 주시하고 있어야 하기 때문입니다. GetxController를 상속(extends)하는 것은 중요합니다. onInit()과 onClose()에서 "시작"과 "종료" 이벤트를 수행할 수 있는 생명 주기를 갖고 있기 때문입니다. 이를 위해서 어떤 클래스를 사용해도 괜찮지만, obervable한 변수든 아니든 간에 GetXController를 사용해 변수를 다루길 적극 권장합니다. ## GetBuilder vs GetX vs Obx vs MixinBuilder 10년간 프로그래밍을 하며 일해오면서, 귀중한 교훈을 얻을 수 있었습니다. 반응형 프로그래밍을 처음 접했을 때 "와, 진짜 굉장한데!"라고 느꼈고, 실제로도 반응형 프로그래밍은 정말 놀랍습니다. 하지만, 모든 상황에서 올바르지는 않습니다. 많은 경우 2개내지 3개의 위젯의 상태를 동시에 변경하거나 일시적으로 변경해야하는데, 반응형 프로그래밍은 나쁘지 않지만 적적하지 않습니다. 반응형 프로그래밍은 각각의 워크플로우를 위한 RAM 자원을 많이 필요로 합니다. 하나의 위젯만이 재빌드 되어야 하는 경우는 괜찮지만, 리스트에 80개 가량의 요소가 있고, 각각 stream이 있을 경우에는 좋지 않습니다. dart inspect 창을 열고 StreamBuilder가 얼마나 많은 리소스를 사용하는지 보신다면, 제 말이 이해가 가실겁니다. 이런 생각에, 저는 간단한 상태 관리자를 만들었습니다. 간단합니다. 그리고 이것이 여러분이 정말 요구하는 것이에요: 블록 단위로 매우 경제적이고 간단하게 상태를 업데이트합니다. `GetBuilder`는 RAM의 측면에서 가장 경제적입니다. 이보다 더 경제적인 방법은 없을 겁니다(만약 그런 방법을 고안하신다면, 꼭 저희에게 알려주세요!) 하지만 `GetBuilder`는 수동적인 상태 관리자입니다. Provider의 notifyListeners()를 호출하는 것처럼 update()를 호출해야만 합니다. 반응형 프로그래밍이 정말 도움이 되는 상황들이 많습니다. 이를 활용하지 않는건 바퀴를 다시 만드는 것과 마찬가지입니다. 이런 생각에, `GetX`는 상태 관리자로 가장 현대적이고 높은 수준의 기능들을 제공하기 위해 만들어졌습니다. 이것은 필요한 것들을 필요한 때에 업데이트합니다. 만약 에러가 있고 300개의 상태가 동시에 변경되면, `GetX`는 화면상에 반영되어야할 것들만 필터링하여 업데이트 합니다. `GetX`는 다른 반응형 상태 관리자들보다 경제적이지만, `GetBuiler`보다 조금 더 RAM을 소모합니다. 능동적이면서 RAM의 최대한 효율적으로 사용하기 위해 `Obx`가 만들어졌습니다. `GetX`와 `GetBuilder`와 다르게 `Obx`안에서 controller를 초기화할 수 없습니다. 단지 자식으로부터 변화를 감지하는 StreamSubscription을 갖고 있는 위젯일 뿐입니다. `Obx`는 `GetX`보다는 경제적이지만 `GetBuilder`보다는 덜 경제적입니다. 왜냐하면 `Obx`는 반응적이고, `GetBuilder`는 위젯의 해시코드와 StateSetter만 저장하는 최적의 접근 방식을 갖고 있기 때문입니다. `Obx`를 이용하면 controller의 타입을 적어줄 필요가 없고, 여러 개의 다른 controller 변화를 감지합니다. 하지만 사용 전에 초기화거나, readme에 있는 예제처럼 사용하거나, Binding 클래스를 사용해야 합니다. ================================================ FILE: documentation/pt_BR/dependency_management.md ================================================ # Gerenciamento de dependência - [Gerenciamento de dependência](#gerenciamento-de-dependência) - [Gerenciamento de dependências simples](#gerenciamento-de-dependências-simples) - [Métodos de criar instâncias](#métodos-de-criar-instâncias) - [Get.put()](#getput) - [Get.lazyPut](#getlazyput) - [Get.putAsync](#getputasync) - [Get.create](#getcreate) - [Usando as classes/dependências instanciadas](#usando-as-classesdependências-instanciadas) - [Diferenças entre os métodos](#diferenças-entre-os-métodos) - [Bindings](#bindings) - [Classe Bindings](#classe-bindings) - [BindingsBuilder](#bindingsbuilder) - [SmartManagement](#smartmanagement) - [Como alterar](#como-alterar) - [SmartManagement.full](#smartmanagementfull) - [SmartManagement.onlyBuilders](#smartmanagementonlybuilders) - [SmartManagement.keepFactory](#smartmanagementkeepfactory) - [Como os Bindings funcionam](#como-os-bindings-funcionam) - [Notas](#notas) Get tem um gerenciador de dependência simples e poderoso que permite você pegar a mesma classe que seu Bloc ou Controller com apenas uma linha de código, sem Provider context, sem inheritedWidget: ```dart Controller controller = Get.put(Controller()); // Em vez de Controller controller = Controller(); ``` Em vez de instanciar sua classe dentro da classe que você está usando, você está instanciando ele dentro da instância do Get, que vai fazer ele ficar disponível por todo o App Para que então você possa usar seu controller (ou uma classe Bloc) normalmente - Nota: Se você está usando o gerenciado de estado do Get, você não precisa se preocupar com isso, só leia a documentação, mas dê uma atenção a api [Bindings](#bindings), que vai fazer tudo isso automaticamente para você. - Nota²: O gerenciamento de dependência do get é desacoplado de outras partes do package, então se por exemplo seu aplicativo já está usando um outro gerenciador de estado (qualquer um, não importa), você não precisa de reescrever tudo, pode simplesmente usar só a injeção de dependência sem problemas ## Métodos de criar instâncias Todos os métodos e seus parâmetros configuráveis são: ### Get.put() A forma mais comum de instanciar uma dependência. Bom para os controllers das views por exemplo. ```dart Get.put(Classe()); Get.put(LoginController(), permanent: true); Get.put( ListItemController, tag: "uma string única", ); ``` E essas são todas as opções que você pode definir: ```dart Get.put( // obrigatório: a classe que você quer salvar, como um controller ou qualquer outra coisa // obs: Esse "S" significa que pode ser qualquer coisa S dependency // opcional: isso é pra quando você quer múltiplas classess que são do mesmo tipo // já que você normalmente pega usando "Get.find()", // você precisa usar uma tag para dizer qual das instâncias vc precisa // precisa ser uma string única String tag, // opcional: por padrão, get vai descartar as instâncias quando elas não são mais usadas (exemplo, // o controller de uma view que foi fechada) // Mas talvez você precisa quea instância seja mantida por todo o app, como a instância do SharedPreferences por exemplo // então vc usa isso // padrão: false bool permanent = false, // opcional: permite criar a dependência usando uma função em vez da dependênia em si InstanceBuilderCallback builder, ) ``` ### Get.lazyPut É possível que você vá inserir essa instância, mas sabe que não vai usá-la imediatamente no app. Nesses casos pode ser usado o lazyPut que só cria a instância no momento que ela for necessária pela primeira vez. É útil também caso seja uma classe que é muito pesada e você não quer carregar ela junto com tudo quando o app abre. ```dart /// ApiMock só será instanciado quando Get.find for usado pela primeira vez Get.lazyPut(() => ApiMock()); Get.lazyPut( () { // ... alguma lógica se necessário return FirebaseAuth(); }, tag: Math.random().toString(), fenix: true ) Get.lazyPut( () => Controller() ) ``` E essas são todas as opções que você pode definir: ```dart Get.lazyPut( // obrigatório: um método que vai ser executado quando sua classe é chamada pela primeira vez InstanceBuilderCallback builder, // opcional: igual ao Get.put(), é usado quando você precisa de múltiplas instâncias de uma mesma classe // precisa ser uma string única String tag, // opcional: é similar a "permanent", mas a instância é descartada quando // não é mais usada e é refeita quando precisa ser usada novamente // Assim como a opção SmartManagement.keepFactory na api Bindings // padrão: false bool fenix = false ) ``` ### Get.putAsync Se você quiser criar uma instância assíncrona, você pode usar `Get.putAsync`: ```dart Get.putAsync(() async { final prefs = await SharedPreferences.getInstance(); await prefs.setInt('counter', 12345); return prefs; }); Get.putAsync( () async => await SuaClasseAssincrona() ) ``` E essas são todas as opções que você pode definir: ```dart Get.putAsync( // Obrigatório: um método assíncrono que vai ser executado para instanciar sua classe AsyncInstanceBuilderCallback builder, // opcional: igual ao Get.put(), é usado quando você precisa de múltiplas instâncias de uma mesma classe // precisa ser uma string única String tag, // opcional: igual ao Get.put(), usado quando você precisa manter a instância ativa no app inteiro. // padrão: false bool permanent = false ``` ### Get.create Esse é mais específico. Uma explicação detalhada do que esse método é e as diferenças dele para os outros podem ser encontradas em [Diferenças entre os métodos](#diferenças-entre-os-métodos) ```dart Get.Create(() => SomeClass()); Get.Create(() => LoginController()); ``` E essas são todas as opções que você pode definir: ```dart Get.create( // Obrigatório: Uma função que retorna uma classe que será "fabricada" toda vez que Get.find() for chamado InstanceBuilderCallback builder, // opcional: igual ao Get.put(), mas é usado quando você precisa de múltiplas instâncias de uma mesma classe. // Útil caso você tenha uma lista em que cada item precise de um controller próprio // precisa ser uma string única. Apenas mudou o nome de tag para name. String name, // opcional: igual ao Get.put(), usado quando você precisa manter a instância ativa no app inteiro. A diferença // é que com Get.create o permanent está habilitado por padrão bool permanent = true ``` ## Usando as classes/dependências instanciadas Agora, imagine que você navegou por inúmeras rotas e precisa de dados que foram deixados para trás em seu controlador. Você precisaria de um gerenciador de estado combinado com o Provider ou Get_it, correto? Não com Get. Você só precisa pedir ao Get para "procurar" pelo seu controlador, você não precisa de nenhuma dependência adicional para isso: ```dart final controller = Get.find(); // OU Controller controller = Get.find(); // Sim, parece Magia, o Get irá descobrir qual é seu controller, e irá te entregar. // Você pode ter 1 milhão de controllers instanciados, o Get sempre te entregará o controller correto. // Apenas se lembre de Tipar seu controller, final controller = Get.find(); por exemplo, não irá funcionar. ``` E então você será capaz de recuperar os dados do seu controller que foram obtidos anteriormente: ```dart Text(controller.textFromApi); ``` Já que o valor retornado é uma classe normal, você pode fazer o que quiser com ela: ```dart int valor = Get.find().getInt('contador'); print(valor); // Imprime: 123456 ``` Para remover a instância do Get: ```dart Get.delete(); ``` ## Diferenças entre os métodos Primeiro, vamos falar do `fenix` do Get.lazyPut e o `permanent` dos outros métodos. A diferença fundamental entre `permanent` e `fenix` está em como você quer armazenar as suas instâncias. Reforçando: por padrão, o Get apaga as instâncias quando elas não estão em uso. Isso significa que: Se a tela 1 tem o controller 1 e a tela 2 tem o controller 2 e você remove a primeira rota da stack (usando `Get.off()` ou `Get.offNamed`), o controller 1 perdeu seu uso portanto será apagado. Mas se você optar por usar `permanent: true`, então ela não se perde nessa transição - o que é muito útil para serviços que você quer manter rodando na aplicação inteira. Já o `fenix`, é para serviços que você não se preocupa em perder por uma tela ou outra, mas quando você precisar chamar o serviço, você espera que ele "retorne das cinzas" (`fenix: true`), criando uma nova instância. Prosseguindo com as diferenças entre os métodos: - Get.put e Get.putAsync seguem a mesma ordem de criação, com a diferença que o Async opta por aplicar um método assíncrono: Esses dois métodos criam e já inicializam a instância. Esta é inserida diretamente na memória, através do método interno `insert` com os parâmetros `permanent: false` e `isSingleton: true` (esse parâmetro `isSingleton` serve apenas para dizer se é para utilizar a dependência colocada em `dependency`, ou se é para usar a dependência colocada no `InstanceBuilderCallback`). Depois disso, é chamado o `Get.find` que imediatamente inicializa as instâncias que estão na memória. - Get.create: Como o nome indica, você vai "criar" a sua dependência! Similar ao `Get.put`, ela também chama o método interno `insert` para instanciamento. Contudo, `permanent` e `isSingleton` passam a ser `true` e `false` (Como estamos "criando" a nossa dependência, não tem como ela ser um Singleton de algo, logo, `false`). E por ser `permanent: true`, temos por padrão o benefício de não se perder entre telas! Além disso, não é chamado o `Get.find`, logo ela fica esperando ser chamada para ser usada. Ele é criado dessa forma para aproveitar o uso do parâmetro `permanent`, já que, vale ressaltar, o Get.create foi criado com o objetivo de criar instâncias não compartilhadas, mas que não se perdem, como por exemplo um botão em um listView, que você quer uma instância única para aquela lista - por conta disso, o Get.create deve ser usado em conjunto com o GetWidget. - Get.lazyPut: Como o nome dá a entender, é um processo preguiçoso (lazy). A instância é criada, mas ela não é chamada para uso logo em seguida, ela fica aguardando ser chamada. Diferente dos outros métodos, o `insert` não é chamado. Ao invés disso, a instância é inserida em outra parte na memória, uma parte responsável por dizer se a instância pode ser recriada ou não, vamos chamá-la de "fábrica". Se queremos criar algo para ser chamado só depois, não vamos misturá-lo com as coisas que estão sendo usadas agora. E é aqui que entra a mágica do `fenix`. Se você optou por deixar `fenix: false`, e seu `smartManagement` não for `keepFactory`, então ao usar o `Get.find` a instância passa da "fábrica" para a área comum das instância. Em seguinda, por padrão é removida da "fábrica". Agora, se você optou por `fenix: true`, a instância continua a existir nessa parte dedicada, mesmo indo para a área comum, para ser chamada futuramente caso precise. ## Bindings Um dos grandes diferenciais desse package, talvez, seja a possibilidade de integração total com rotas, gerenciador de estado e gerenciador de dependências. Quando uma rota é removida da stack, todos os controllers, variáveis e instâncias de objetos relacionados com ela são removidos da memória. Se você está usando streams ou timer, eles serão fechados automaticamente, e você não precisa se preocupar com nada disso. Na versão 2.10 Get implementou completamente a API Bindings. Agora você não precisa mais usar o método `init`. Você não precisa nem tipar seus controllers se não quiser. Você pode começar seus controllers e services num lugar apropriado para isso. A classe Binding é uma classe que vai desacoplar a injeção de dependência, enquanto liga as rotas ao gerenciador de estados e o gerenciador de dependências. Isso permite Get saber qual tela está sendo mostrada quando um controller particular é usado e saber onde e como descartar o mesmo. Somando a isso, a classe Binding vai permitir que você tenha um controle de configuração SmartManager. Você pode configurar as dependências que serão organizadas quando for remover a rota da stack, ou quando o widget que usa ele é definido, ou nada disso. Você vai ter gerenciador de dependências inteligente trabalhando para você, e você pode configurá-lo como quiser. ### Classe Bindings Crie uma classe qualquer que implemente a Bindings. ```dart class HomeBinding implements Bindings {} ``` Sua IDE vai automaticamente te perguntar para dar override no método `dependencies()`, aí você clica na lâmpada, clica em "override the method", e insira todas as classes que você vai usar nessa rota: ```dart class HomeBinding implements Bindings{ @override void dependencies() { Get.lazyPut(() => HomeController()); Get.lazyPut(()=> Api()); } } class DetalhesBinding implements Bindings { @override void dependencies() { Get.lazyPut(() => DetalhesController()); } } ``` Agora você só precisa informar sua rota que você vai usar esse binding para fazer a conexão entre os gerenciadores de rotas, dependências e estados. Usando rotas nomeadas ```dart getPages: [ GetPage( name: '/', page: () => HomeView(), binding: HomeBinding(), ), GetPage( name: '/detalhes', page: () => DetalhesView(), binding: DetalhesBinding(), ), ]; ``` Usando rotas normais: ```dart Get.to(Home(), binding: HomeBinding()); Get.to(DetalhesView(), binding: DetalhesBinding()) ``` Então, você não vai precisar se preocupar com gerenciamento da memória da sua aplicação mais, Get vai fazer para você. A classe Bindings é chamada quando uma rota é chamada. Você pode criar uma Binding inicial no seu GetMaterialApp para inserir todas as dependências que serão criadas. ```dart GetMaterialApp( initialBinding: SampleBind(), home: Home(), ) ``` ### BindingsBuilder A forma padrão de criar um binding é criando uma classe que implementa o Bindings. Mas alternativamente, você também pode usar a função `BindingsBuilder` par que você possa simplesmente usar uma função pra criar essas instâncias Exemplo: ```dart getPages: [ GetPage( name: '/', page: () => HomeView(), binding: BindingsBuilder(() { Get.lazyPut(() => ControllerX()); Get.put(()=> Api()); }), ), GetPage( name: '/detalhes', page: () => DetalhesView(), binding: BindingsBuilder(() { Get.lazyPut(() => DetalhesController()); }), ), ]; ``` Dessa forma você pode evitar criar uma classe Binding para cada rota, deixando tudo mais simples. As duas formas funcionam perfeitamente e você é livre para usar o que mais se encaixa no seu estilo de uso ### SmartManagement GetX por padrão descarta controllers não utilizados da memória, mesmo que uma falha ocorra e um widget que usa ele não for propriamente descartado. Essa é o chamado modo `full` do gerenciamento de dependências. Mas se você quiser mudar a forma que o GetX controla o descarte das classes, você tem a sua disposição a classe `SmartManagement` que pode definir diferentes comportamentos. #### Como alterar Se você quiser alterar essa configuração (que normalmente você não precisa mudar) essa é a forma: ```dart void main () { runApp( GetMaterialApp( smartManagement: SmartManagement.onlyBuilders //Aqui home: Home(), ) ) } ``` #### SmartManagement.full É o padrão. Descarta as classes que não estão mais sendo utilizadas e que não foram definidas para serem permanents. Na grande maioria dos casos você não vai querer nem precisar alterar essa configuração. Se você for novo com GetX, não altere isto. #### SmartManagement.onlyBuilders Com essa opção, somente controllers iniciados pelo `init:` or iniciados dentro de um Binding com `Get.lazyPut()` serão descartados. Se você usar `Get.put()` ou `Get.putAsync()` or qualquer outra forma, SmartManagement não vai ter permissão para excluir essa dependência. Com o comportamento padrão, até widgets instanciados com `Get.put()` serão removidos, ao contrário do `SmartManagement.onlyBuilders`. #### SmartManagement.keepFactory Assim como o modo `full`, ele vai descartar as dependências quando não estiverem sendo mais utilizadas. Porém, ele irá manter a "factory" de cada dependência. Isso significa que caso você precise de da dependência novamente, ele vai recriar aquele instância novamente. #### Como os Bindings funcionam Bindings cria fábricas transitórias, que são criadas no momento que você clica para ir para outra tela, e será destruído assim que a animação de mudança de tela acontecer. É tão pouco tempo, tão rápido, que o analyzer sequer conseguirá registrá-lo. Quando você navegar para essa tela novamente, uma nova fábrica temporária será chamada, então isso é preferível à usar `SmartManagement.keepFactory`, mas se você não quer ter o trabalho de criar Bindings, ou deseja manter todas suas dependências no mesmo Binding, isso certamente irá te ajudar. Fábricas ocupam pouca memória, elas não guardam instâncias, mas uma função com a "forma" daquela classe que você quer. Isso é muito pouco, mas como o objetivo dessa lib é obter o máximo de desempenho possível usando o mínimo de recursos, Get remove até as fábricas por padrão. Use o que achar mais conveniente para você. ## Notas * Nota: NÃO USE SmartManagement.keepfactory se você está usando vários Bindings. Ele foi criado para ser usado sem Bindings, ou com um único Binding ligado ao GetMaterialApp lá no `initialBinding` * Nota²: Usar Bindings é completamente opcional, você pode usar Get.put() e Get.find() em classes que usam o controller sem problemas. Porém, se você trabalhar com Services ou qualquer outra abstração, eu recomendo usar Bindings. Especialmente em grandes empresas. ================================================ FILE: documentation/pt_BR/route_management.md ================================================ - [Navegação sem rotas nomeadas](#navegação-sem-rotas-nomeadas) - [SnackBars](#snackbars) - [Dialogs](#dialogs) - [BottomSheets](#bottomsheets) - [Navegar com rotas nomeadas](#navegar-com-rotas-nomeadas) - [Enviar dados para rotas nomeadas](#enviar-dados-para-rotas-nomeadas) - [Links de Url dinâmicos](#links-de-url-dinâmicos) - [Middleware](#middleware) - [Change Theme](#change-theme) - [Configurações Globais Opcionais](#configurações-globais-opcionais) - [Nested Navigators](#nested-navigators) ## Navegação sem rotas nomeadas Para navegar para uma próxima tela: ```dart Get.to(ProximaTela()); ``` Para fechar snackbars, dialogs, bottomsheets, ou qualquer coisa que você normalmente fecharia com o `Navigator.pop(context)` (como por exemplo fechar a View atual e voltar para a anterior): ```dart Get.back(); ``` Para ir para a próxima tela e NÃO deixar opção para voltar para a tela anterior (bom para SplashScreens, telas de login e etc.): ```dart Get.off(ProximaTela()); ``` Para ir para a próxima tela e cancelar todas as rotas anteriores (útil em telas de carrinho, votações ou testes): ```dart Get.offAll(ProximaTela()); ``` Para navegar para a próxima rota, e receber ou atualizar dados assim que retornar da rota: ```dart var dados = await Get.to(Pagamento()); ``` Na outra tela, envie os dados para a rota anterior: ```dart Get.back(result: 'sucesso'); ``` E use-os: ```dart if (dados == 'sucesso') fazerQualquerCoisa(); ``` Não quer aprender nossa sintaxe? Apenas mude o `Navigator` (letra maiúscula) para `navigator` (letra minúscula), e você terá todas as funcionalidades de navegação padrão, sem precisar usar `context` Exemplo: ```dart // Navigator padrão do Flutter Navigator.of(context).push( context, MaterialPageRoute( builder: (BuildContext context) { return HomePage(); }, ), ); // Get usando a sintaxe Flutter sem precisar do context navigator.push( MaterialPageRoute( builder: (_) { return HomePage(); }, ), ); // Sintaxe do Get (é bem melhor, mas você tem o direito de discordar) Get.to(HomePage()); ``` ### SnackBars Para ter um `SnackBar` simples no Flutter, você precisa do `context` do Scaffold, ou uma `GlobalKey` atrelada ao seu Scaffold. ```dart final snackBar = SnackBar( content: Text('Olá!'), action: SnackBarAction( label: 'Eu sou uma SnackBar velha e feia :(', onPressed: (){} ), ); // Encontra o Scaffold na árvore de Widgets e // o usa para mostrar o SnackBar Scaffold.of(context).showSnackBar(snackBar); ``` Com o Get: ```dart Get.snackbar('Olá', 'eu sou uma SnackBar moderna e linda!'); ``` Com Get, tudo que você precisa fazer é chamar `Get.snackbar()` de qualquer lugar no seu código, e/ou customizá-lo da forma que quiser! ```dart Get.snackbar( "Ei, eu sou uma SnackBar Get!", // título "É inacreditável! Eu estou usando uma SnackBar sem context, sem boilerplate, sem Scaffold!", // mensagem icon: Icon(Icons.alarm), shouldIconPulse: true, onTap:(){}, barBlur: 20, isDismissible: true, duration: Duration(seconds: 3), ); ////////// TODOS OS RECURSOS ////////// // Color colorText, // Duration duration, // SnackPosition snackPosition, // Widget titleText, // Widget messageText, // bool instantInit, // Widget icon, // bool shouldIconPulse, // double maxWidth, // EdgeInsets margin, // EdgeInsets padding, // double borderRadius, // Color borderColor, // double borderWidth, // Color backgroundColor, // Color leftBarIndicatorColor, // List boxShadows, // Gradient backgroundGradient, // TextButton mainButton, // OnTap onTap, // bool isDismissible, // bool showProgressIndicator, // AnimationController progressIndicatorController, // Color progressIndicatorBackgroundColor, // Animation progressIndicatorValueColor, // SnackStyle snackStyle, // Curve forwardAnimationCurve, // Curve reverseAnimationCurve, // Duration animationDuration, // double barBlur, // double overlayBlur, // Color overlayColor, // Form userInputForm /////////////////////////////////// ``` Se você prefere a SnackBar tradicional, ou quer customizar por completo, como por exemplo fazer ele ter uma só linha (`Get.snackbar` tem os parâmetros `title` e `message` obrigatórios), você pode usar `Get.rawSnackbar();` que fornece a API bruta na qual `Get.snackbar` foi contruído. ### Dialogs Para abrir um dialog: ```dart Get.dialog(SeuWidgetDialog()); ``` Para abrir um dialog padrão: ```dart Get.defaultDialog( onConfirm: () => print("Ok"), middleText: "Dialog made in 3 lines of code", ); ``` Você também pode usar `Get.generalDialog` em vez de `showGeneralDialog`. Para todos os outros Widgets do tipo dialog do Flutter, incluindo os do Cupertino, você pode usar `Get.overlayContext` em vez do `context`, e abrir em qualquer lugar do seu código. Para widgets que não usam `overlayContext`, você pode usar `Get.context`. Esses dois contextos vão funcionar em 99% dos casos para substituir o context da sua UI, exceto em casos onde o `inheritedWidget` é usado sem a navigation context. ### BottomSheets `Get.bottomSheet()` é tipo o `showModalBottomSheet()`, mas não precisa do context. ```dart Get.bottomSheet( Container( child: Wrap( children: [ ListTile( leading: Icon(Icons.music_note), title: Text('Música'), onTap: () {} ), ListTile( leading: Icon(Icons.videocam), title: Text('Vídeo'), onTap: () {}, ), ], ), ), ); ``` ## Navegar com rotas nomeadas - Se você prefere navegar por rotas nomeadas, Get também dá suporte a isso: Para navegar para uma nova tela ```dart Get.toNamed("/ProximaTela"); ``` Para navegar para uma tela sem a opção de voltar para a rota atual. ```dart Get.offNamed("/ProximaTela"); ``` Para navegar para uma nova tela e remover todas rotas anteriores da stack ```dart Get.offAllNamed("/ProximaTela"); ``` Para definir rotas, use o `GetMaterialApp`: ```dart void main() { runApp( GetMaterialApp( initialRoute: '/', getPages: [ GetPage(name: '/', page: () => Home()), GetPage(name: '/login', page: () => Login()), GetPage(name: '/cadastro', page: () => Cadastro(), transition: Transition.cupertino), ] ) ); } ``` Para lidar com a navegação para rotas não definidas (erro 404), você pode definir uma página unknownRoute em GetMaterialApp. ```dart void main() { runApp( GetMaterialApp( unknownRoute: GetPage(name: '/notfound', page: () => UnknownRoutePage()), initialRoute: '/', getPages: [ GetPage(name: '/', page: () => MyHomePage()), GetPage(name: '/second', page: () => Second()), ], ) ); } ``` ### Enviar dados para rotas nomeadas Apenas envie o que você quiser no parâmetro `arguments`. Get aceita qualquer coisa aqui, seja String, Map, List, ou até a instância de uma classe. ```dart Get.toNamed("/ProximaTela", arguments: 'Get é o melhor'); ``` Na sua classe ou controller: ```dart print(Get.arguments); //valor: Get é o melhor ``` #### Links de Url dinâmicos Get oferece links de url dinâmicos assim como na Web. Desenvolvedores Web provavelmente já queriam essa feature no Flutter, e muito provavelmente viram um package que promete essa feature mas entrega uma sintaxe totalmente diferente do que uma url teria na web, mas o Get também resolve isso. ```dart Get.offAllNamed("/ProximaTela?device=phone&id=354&name=Enzo"); ``` na sua classe controller/bloc/stateful/stateless: ```dart print(Get.parameters['id']); // valor: 354 print(Get.parameters['name']); // valor: Enzo ``` Você também pode receber parâmetros nomeados com o Get facilmente: ```dart void main() => runApp( GetMaterialApp( initialRoute: '/', getPages: [ GetPage(name: '/', page: () => Home()), /// Importante! ':user' não é uma nova rota, é somente uma /// especificação do parâmentro. Não use '/segunda/:user/' e '/segunda' /// se você precisa de uma nova rota para o user, então /// use '/segunda/user/:user' se '/segunda' for uma rota GetPage(name: '/segunda/:user', page: () => Segunda()), // recebe a ID GetPage(name: '/terceira', page: () => Terceira(), transition: Transition.cupertino), ] ), ); ``` Envie dados na rota nomeada ```dart Get.toNamed("/segunda/34954"); ``` Na segunda tela receba os dados usando `Get.parameters[]` ```dart print(Get.parameters['user']); // valor: 34954 ``` ou envie vários parâmetros como este ```dart Get.toNamed("/profile/34954?flag=true"); ``` Na segunda tela, pegue os dados por parâmetros normalmente ```dart print(Get.parameters['user']); print(Get.parameters['flag']); // valor: 34954 true ``` E agora, tudo que você precisa fazer é usar `Get.toNamed)` para navegar por suas rotas nomeadas, sem nenhum `context` (você pode chamar suas rotas diretamente do seu BLoc ou do Controller), e quando seu aplicativo é compilado para a web, suas rotas vão aparecer na url ❤ #### Middleware Se você quer escutar eventos do Get para ativar ações, você pode usar `routingCallback` para isso ```dart GetMaterialApp( routingCallback: (route){ if(routing.current == '/segunda'){ openAds(); } } ) ``` Se você não estiver usando o `GetMaterialApp`, você pode usar a API manual para anexar um observer Middleware. ```dart void main() { runApp( MaterialApp( onGenerateRoute: Router.generateRoute, initialRoute: "/", navigatorKey: Get.key, navigatorObservers: [ GetObserver(MiddleWare.observer), // AQUI !!! ], ) ); } ``` Criar uma classe MiddleWare ```dart class MiddleWare { static observer(Routing routing) { /// Você pode escutar junto com as rotas, snackbars, dialogs /// e bottomsheets em cada tela. /// Se você precisar entrar em algum um desses 3 eventos aqui diretamente, /// você precisa especificar que o evento é != do que você está tentando fazer if (routing.current == '/segunda' && !routing.isSnackbar) { Get.snackbar("Olá", "Você está na segunda rota"); } else if (routing.current =='/terceira'){ print('última rota chamada'); } } } ``` Agora, use Get no seu código: ```dart class Primeira extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( leading: IconButton( icon: Icon(Icons.add), onPressed: () { Get.snackbar("Oi", "eu sou uma snackbar moderna"); }, ), title: Text('Primeira rota'), ), body: Center( child: ElevatedButton( child: Text('Abrir rota'), onPressed: () { Get.toNamed("/segunda"); }, ), ), ); } } class Segunda extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( leading: IconButton( icon: Icon(Icons.add), onPressed: () { Get.snackbar("Oi", "eu sou uma snackbar moderna"); }, ), title: Text('Segunda rota'), ), body: Center( child: ElevatedButton( child: Text('Abrir rota'), onPressed: () { Get.toNamed("/terceira"); }, ), ), ); } } class Terceira extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Terceira Rota"), ), body: Center( child: ElevatedButton( onPressed: () { Get.back(); }, child: Text('Voltar!'), ), ), ); } } ``` ### Change Theme Por favor não use nenhum widget acima do `GetMaterialApp` para atualizá-lo. Isso pode ativar keys duplicadas. Muitas pessoas estão acostumadas com a forma pré-história de criar um widget `ThemeProvider` só pra mudar o tema do seu app, e isso definitamente NÃO é necessário com o Get. Você pode criar seu tema customizado e simplesmente adicionar ele dentro de `Get.changeTheme()` sem nenhum boilerplate para isso: ```dart Get.changeTheme(ThemeData.light()); ``` Se você quer criar algo como um botão que muda o tema com um toque, você pode combinar duas APIs do Get para isso, a API que checa se o tema dark está sendo usado, e a API de mudança de tema. E dentro de um `onPressed` você coloca isso: ```dart Get.changeTheme(Get.isDarkMode? ThemeData.light(): ThemeData.dark()); ``` Quando o modo escuro está ativado, ele vai alterar para o modo claro, e vice versa. Se você quer saber a fundo como mudar o tema, você pode seguir esse tutorial no Medium que até te ensina a persistir o tema usando Get e shared_preferences: - [Dynamic Themes in 3 lines using Get](https://medium.com/swlh/flutter-dynamic-themes-in-3-lines-c3b375f292e3) - Tutorial by [Rod Brown](https://github.com/RodBr). ### Configurações Globais Opcionais Você pode mudar configurações globais para o Get. Apenas adicione `Get.config` no seu código antes de ir para qualquer rota ou faça diretamente no seu GetMaterialApp ```dart // essa forma GetMaterialApp( enableLog: true, defaultTransition: Transition.fade, opaqueRoute: Get.isOpaqueRouteDefault, popGesture: Get.isPopGestureEnable, transitionDuration: Get.defaultDurationTransition, defaultGlobalState: Get.defaultGlobalState, ); // ou essa Get.config( enableLog = true, defaultPopGesture = true, defaultTransition = Transitions.cupertino ) ``` ### Nested Navigators Get fez a navegação aninhada no Flutter mais fácil ainda. Você não precisa do `context`, e você encontrará sua `navigation stack` pela ID. * Nota: Criar navegação paralela em stacks pode ser perigoso. O idela é não usar `NestedNavigators`, ou usar com moderação. Se o seu projeto requer isso, vá em frente, mas fique ciente que manter múltiplas stacks de navegação na memória pode não ser uma boa ideia no quesito consumo de RAM. Veja como é simples: ```dart Navigator( key: nestedKey(1), // crie uma key com um index initialRoute: '/', onGenerateRoute: (settings) { if (settings.name == '/') { return GetPageRoute( page: () => Scaffold( appBar: AppBar( title: Text("Principal"), ), body: Center( child: TextButton( color: Colors.blue, child: Text("Ir para a segunda"), onPressed: () { Get.toNamed('/segunda', id:1); // navega pela sua navegação aninhada usando o index }, ) ), ), ); } else if (settings.name == '/segunda') { return GetPageRoute( page: () => Center( child: Scaffold( appBar: AppBar( title: Text("Principal"), ), body: Center( child: Text("Segunda") ), ), ), ); } } ), ``` ================================================ FILE: documentation/pt_BR/state_management.md ================================================ - [Simple State Manager](#simple-state-manager) - [Uso do gerenciador de estado simples](#uso-do-gerenciador-de-estado-simples) - [Sem StatefulWidget](#sem-statefulwidget) - [Formas de uso](#formas-de-uso) - [Reactive State Manager](#reactive-state-manager) - [GetX vs GetBuilder vs Obx vs MixinBuilder](#getx-vs-getbuilder-vs-obx-vs-mixinbuilder) - [Workers](#workers) ## Simple State Manager Há atualmente vários gerenciadores de estados para o Flutter. Porém, a maioria deles envolve usar `ChangeNotifier` para atualizar os widgets e isso é uma abordagem muito ruim no quesito performance em aplicações de médio ou grande porte. Você pode checar na documentação oficial do Flutter que o [`ChangeNotifier` deveria ser usado com um ou no máximo dois listeners](https://api.flutter.dev/flutter/foundation/ChangeNotifier-class.html), fazendo-o praticamente inutilizável em qualquer aplicação média ou grande. Outros gerenciadores de estado são bons, mas tem suas nuances. * BLoC é bem seguro e eficiente, mas é muito complexo (especialmente para iniciantes), o que impediu pessoas de desenvolverem com Flutter. * MobX é mais fácil que o BLoc e é reativo, quase perfeito eu diria, mas você precisa usar um code generator que, para aplicações de grande porte, reduz a produtividade (você terá que beber vários cafés até que seu código esteja pronto denovo depois de um `flutter clean`, o que não é culpa do MobX, na verdade o code generator que é muito lento!). * Provider usa o `InheritedWidget` para entregar o mesmo listener, como uma forma de solucionar o problema reportado acima com o ChangeNotifier, o que indica que qualquer acesso ao ChangeNotifier dele tem que ser dentro da árvore de widgets por causa do `context` necessário para acessar o Inherited. Get não é melhor ou pior que nenhum gerenciador de estado, mas você deveria analisar esses pontos tanto quanto os argumentos abaixo para escolher entre usar Get na sua forma pura, ou usando-o em conjunto com outro gerenciador de estado. Definitivamente, Get não é o inimigo de nenhum gerenciador, porque Get é um microframework, não apenas um gerenciador, e pode ser usado tanto sozinho quanto em conjunto com eles. Get tem um gerenciador de estado que é extremamente leve e fácil que não usa ChangeNotifier, vai atender a necessidade especialmente daqueles novos no Flutter, e não vai causar problemas em aplicações de grande porte. **Que melhoras na performance o Get traz?** 1. Atualiza somente o widget necessário. 2. Não usa o `ChangeNotifier`, é o gerenciador de estado que utiliza menos memória (próximo de 0mb até agora). 3. Esqueça StatefulWidget's! Com Get você nunca mais vai precisar deles. Com outros gerenciadores de estado, você provavelmente precisa usar um StatefulWidget para pegar a instância do seu Provider, BLoc, MobX controller, etc. Mas já parou para pensar que seu AppBar, seu Scaffold e a maioria dos widgets que estão na sua classe são stateless? Então porque salvar o estado de uma classe inteira, se você pode salvar somente o estado de um widget stateful? Get resolve isso também. Crie uma classe Stateless, faça tudo stateless. Se você precisar atualizar um único componente, envolva ele com o `GetBuilder`, e seu estado será mantido. 4. Organize seu projeto de verdade! Controllers não devem ficar na sua UI, coloque seus `TextEditController`, ou qualquer controller que você usa dentro da classe Controller. 5. Você precisa acionar um evento para atualizar um widget assim que ele é renderizado? GetBuilder tem a propriedade `initState()` assim como um StatefulWidget, e você pode acionar eventos a partir do seu controller, diretamente de lá. Sem mais de eventos serem colocados no initState. 6. Você precisa acionar uma ação como fechar Streams, timers, etc? GetBuilder também tem a propriedade `dispose()`, onde você pode acionar eventos assim que o widget é destruído. 7. Use `Stream`s somente se necessário. Você pode usar seus StreamControllers dentro do seu controller normalmente, e usar `StreamBuilder` normalmente também, mas lembre-se, um Stream consume uma memória razoável, programação reativa é linda, mas você não abuse. 30 Streams abertos simultaneamente podem ser ainda piores que o `ChangeNotifier` (e olha que o ChangeNotifier é bem ruim) 8. Atualizar widgets sem gastar memória com isso. Get guarda somente a "ID do criador" do GetBuilder, e atualiza esse GetBuilder quando necessário. O consumo de memória do ID do GetBuilder é muito baixo mesmo para milhares de GetBuilders. Quando você cria um novo GetBuilder, na verdade você está compartilhando o estado do GetBuilder quem tem um ID do creator. Um novo estado não é criado para cada GetBuilder, o que reduz MUITO o consumo de memória RAM em aplicações grandes. Basicamente sua aplicação vai ser toda stateless, e os poucos widgets que serão Stateful (dentro do GetBuilder) vão ter um estado único, e assim atualizar um deles vai atualizar todos eles. O estado é um só. 9. Get é onisciente e na maioria dos casos sabe o momento exato de tirar um controller da memória. Você não precisa se preocupar com quando descartar o controller, Get sabe o melhor momento para fazer isso. Vamos analisar o seguite exemplo: `Class A => Class B (ControllerX) => Class C (ControllerX)` * Na classe A o controller não está ainda na memória, porque você ainda não o usou (Get carrega só quando precisa). * Na classe B você usou o controller, e ele entrou na memória. * Na classe C você usou o mesmo controller da classe B, então o Get vai compartilhar o estado do controller B com o controller C, e o mesmo controller ainda estará na memória. * Se você fechar a classe C e classe B, Get vai tirar o controller X da memória automaticamente e liberar recursos, porque a classe A não está usando o controller. * Se você navegar para a Classe B denovo, o controller X vai entrar na memória denovo. * Se em vez de ir para a classe C você voltar para a classe A, Get vai tirar o controller da memória do mesmo jeito. * Se a classe C não usar o controller, e você tirar a classe B da memória, nenhuma classe estaria usando o controller X, e novamente o controller seria descartado. **Nota**: A única exceção que pode atrapalhar o Get, é se Você remover classe B da rota de forma inesperada, e tentasse usar o controller na classe C. Nesse caso, o ID do creator do controller que estava em B seria deletado, e o Get foi programado para remover da memória todo controller que não tem nenhum ID de creator. Se é sua intenção fazer isso, adicione a config `autoRemove: false` no GetBuilder da classe B, e use `adoptID = true;` no GetBuilder da classe C. ### Uso do gerenciador de estado simples ```dart // Crie a classe Controller e entenda ela do GetController class Controller extends GetController { int counter = 0; void increment() { counter++; update(); // use update() para atualizar a variável counter na UI quando increment for chamado } } // Na sua classe Stateless/Stateful, use o GetBuilder para atualizar o texto quando a função increment for chamada GetBuilder( init: Controller(), // INICIE O CONTROLLER SOMENTE NA PRIMEIRA VEZ builder: (controller) => Text( '${controller.counter}', ), ), // Inicialize seu controller somente uma vez. Na segunda vez que você for usar GetBuilder para o mesmo controller, não Inicialize denovo. Seu controller será automaticamente removido da memória. Você não precisa se preocupar com isso, Get vai fazer isso automaticamente, apenas tenha certeza que você não vai inicializar o mesmo controller duas vezes. ``` **Feito!** Você já aprendeu como gerenciar estados com o Get. Nota: Você talvez queira uma maior organização, e não querer usar a propriedade `init`. Para isso, você pode criar uma classe e extendê-la da classe `Bindings`, e nela mencionar os controllers que serão criados dentro daquela rota. Controllers não serão criados naquele momento exato, muito pelo contrário, isso é apenas uma declaração, para que na primeira vez que vc use um Controller, Get vai saber onde procurar. Get vai continuar no formato "lazyLoad" (carrega somente quando necessário) e vai continuar descartando os Controllers quando eles não forem mais necessários. Veja pub.dev example para ver como funciona. Se você navegar por várias rotas e precisa de algum dado que estava em um outro controller previamente utilizado, você só precisa utilizar o GetBuilder novamente (sem o init): ```dart class OutraClasse extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( body: Center( child: GetBuilder( builder: (s) => Text('${s.counter}'), ), ), ); } } ``` Se você precisa utilizar seu controller em vários outros lugares, e fora do GetBuilder, apenas crie um getter no seu controller que você consegue ele facilmente (ou use `Get.find()` ) ```dart class Controller extends GetController { /// Você não precisa disso. Eu recomendo usar isso apenas /// porque a sintaxe é mais fácil. /// com o método estático: Controller.to.increment(); /// sem o método estático: Get.find().increment(); /// Não há diferença em performance, nem efeito colateral por usar esse sintaxe. Só uma não precisa da tipage, e a outra forma a IDE vai autocompletar. static Controller get to => Get.find(); // adicione esta linha int counter = 0; void increment() { counter++; update(); } } ``` E então você pode acessar seu controller diretamente, desse jeito: ```dart FloatingActionButton( onPressed:(){ Controller.to.increment(), } // Isso é incrivelmente simples! child: Text("${Controller.to.counter}"), ), ``` Quando você pressionar o FloatingActionButton, todos os widgets que estão escutando a variável `counter` serão atualizados automaticamente. #### Sem StatefulWidget Usar StatefulWidget's significa guardar o estado de telas inteiras desnecessariamente, mesmo porque se você precisa recarregar minimamente algum widget, você vai incorporá-lo num Consumer/Observer/BlocProvider/GetBuilder/GetX/Obx, que vai ser outro StatefulWidget. A classe StatefulWidget é maior que a classe StatelessWidget, o que vai alocar mais memória, e isso pode não fazer uma diferença significativa com uma ou duas classes, mas com certeza fará quando você tiver 100 delas! A não ser que você precise usar um mixin, como o `TickerProviderStateMixin`, será totalmente desnecessário usar um StatefulWidget com o Get. Você pode chamar todos os métodos de um StatefulWidget diretamente de um GetBuilder. Se você precisa chamar o método `initState()` ou `dispose()` por exemplo, é possível chamá-los diretamente: ```dart GetBuilder( initState: (_) => Controller.to.fetchApi(), dispose: (_) => Controller.to.closeStreams(), builder: (s) => Text('${s.username}'), ), ``` Uma abordagem muito melhor que isso é usar os métodos `onInit()` e `onClose()` diretamente do seu controller. ```dart @override void onInit() { fetchApi(); super.onInit(); } ``` * Nota: Se você quiser executar um método no momento que o controller é chamado pela primeira vez, você NÃO precisa usar construtores para isso, na verdade, usando um package que é focado em performance como o Get, isso chega no limite de má prática, porque se desvia da lógica de que os controllers são criados ou alocados (Se você criar uma instância desse controller, o construtor vai ser chamado imediatamente, e ele será populado antes de ser usado, ou seja, você está alocando memória e não está utilizando, o que fere os princípios desse package). Os métodos `onInit()` e `onClose()` foram criados para isso, eles serão chamados quando o controller é criado, ou usados pela primeira vez, dependendo de como você está utilizando o Get (lazyPut ou não). Se quiser, por exemplo, fazer uma chamada para sua API para popular dados, você pode esquecer do estilo antigo de usar `initState()/dispose()`, apenas comece sua chamada para a api no `onInit`, e apenas se você precisar executar algum comando como fechar stream, use o `onClose()`. O propósito desse package é precisamente te dar uma solução completa para navegação de rotas, gerenciamente de dependências e estados, usando o mínimo possível de dependências, com um alto grau de desacoplamento. Get envolve em todas as APIs de baixo e alto nível dentro de si mesmo, para ter certeza que você irá trabalhar com o mínimo possível de acoplamento. Nós centralizamos tudo em um único package. Dessa forma, você pode colocar somente widgets na sua view, e o controller pode ter só lógica de negócio, sem depender de nenhum elemento da View. Isso fornece um ambiente de trabalho muito mais limpo, para que parte do seu time possa trabalhar apenas com os widgets, sem se preocupar sobre enviar dados para o controller, e outra parte se preocupe apenas com a lógica de negócio, sem depender de nenhum elemento da view. Então, para simplificar isso: Você não precisa chamar métodos no `initState()` e enviá-los para seu controller via parâmetros, nem precisa do construtor do controller pra isso, você possui o método `onInit()` que é chamado no momento certo para você inicializar seus services. Você não precisa chamar o método `dispose()`, você tem o método `onClose()` que vai ser chamado no momento exato quando seu controller não for mais necessário e será removido da memória. Dessa forma, você pode deixar a view somente para os widgets, e o controller só para as regras de negócio. Não chame o método `dispose()` dentro do GetController, não vai fazer nada. Lembre-se que o controller não é um widget, você não deveria usar o dispose lá, e esse método será automaticamente e inteligentemente removido da memória pelo Get. Se você usou algum stream no controller e quer fechá-lo, apenas insira o método para fechar os stream dentro do método `onClose()`. Exemplo: ```dart class Controller extends GetController { var user = StreamController(); var name = StreamController(); /// fechar stream = método onClose(), não dispose(). @override void onClose() { user.close(); name.close(); super.onClose(); } } ``` Ciclo de vida do controller: * `onInit()`: Onde ele é criado. * `onClose()`: Onde ele é fechado para fazer mudanças em preparação para o método delete() * deleted: Você não tem acesso a essa API porque ela está literalmente removendo o controller da memória. Está literalmente deletado, sem deixar rastros. ##### Formas de uso Você pode usar uma instância do Controller diretamente no `value` do GetBuilder ```dart GetBuilder( init: Controller(), builder: (value) => Text( '${value.counter}', //aqui ) ), ``` Você talvez também precise de uma instância do seu controller fora do GetBuilder, e você pode usar essas abordagens para conseguir isso: essa: ```dart class Controller extends GetController { static Controller get to => Get.find(); // criando um getter estático [...] } // Na sua view/tela GetBuilder( init: Controller(), // use somente uma vez por controller, não se esqueça builder: (_) => Text( '${Controller.to.counter}', //aqui ) ), ``` ou essa: ```dart class Controller extends GetController { // sem nenhum método estático [...] } // Numa classe stateful/stateless GetBuilder( init: Controller(), // use somente uma vez por controller, não se esqueça builder: (_) => Text( '${Get.find().counter}', //aqui ) ), ``` * Você pode usar outras abordagens "menos regulares". Se você está utilizando outro gerenciador de dependências, como o get_it, modular, etc., e só quer entregar a instância do controller, pode fazer isso: ```dart Controller controller = Controller(); [...] GetBuilder( init: controller, //aqui builder: (_) => Text( '${controller.counter}', // aqui ) ), ``` Essa abordagem não é recomendada, uma vez que você vai precisar descartar os controllers manualmente, fechar seus stream manualmente, e literalmente abandonar um dos grandes benefícios desse package, que é controle de memória inteligente. Mas se você confia no seu potencial, vai em frente! Se você quiser refinar o controle de atualização de widgets do GetBuilder, você pode assinalar a ele IDs únicas ```dart GetBuilder( id: 'text' init: Controller(), // use somente uma vez por controller, não se esqueça builder: (_) => Text( '${Get.find().counter}', //aqui ) ), ``` E atualizá-los dessa forma: ```dart update(['text']); ``` Você também pode impor condições para o update acontecer: ```dart update(['text'], counter < 10); ``` GetX faz isso automaticamente e somente reconstrói o widget que usa a exata variável que foi alterada. Se você alterar o valor da variável para o mesmo valor que ela era anteriormente e isso não sugira uma mudança de estado, GetX não vai reconstruir esse widget, economizando memória e ciclos de CPU (Ex: 3 está sendo mostrado na tela, e você muda a variável para ter o valor 3 denovo. Na maioria dos gerenciadores de estado, isso vai causar uma reconstrução do widget, mas com o GetX o widget só vai reconstruir se de fato o estado mudou). GetBuilder é focado precisamente em múltiplos controles de estados. Imagine que você adicionou 30 produtos ao carrinho, você clica pra deletar um deles, e ao mesmo tempos a lista é atualizada, o preço é atualizado e o pequeno círculo mostrando a quantidade de produtos é atualizado. Esse tipo de abordagem faz o GetBuilder excelente, porque ele agupa estados e muda todos eles de uma vez sem nenhuma "lógica computacional" pra isso. GetBuilder foi criado com esse tipo de situação em mente, já que pra mudanças de estados simples, você pode simplesmente usar o `setState()`, e você não vai precisar de um gerenciador de estado para isso. Porém, há situações onde você quer somente que o widget onde uma certa variável mudou seja reconstruído, e isso é o que o GetX faz com uma maestria nunca vista antes. Dessa forma, se você quiser controlar individualmente, você pode assinalar ID's para isso, ou usar GetX. Isso é com você, apenas lembre-se que quando mais "widgets individuais" você tiver, mais a performance do GetX vai se sobressair. Mas o GetBuilder vai ser superior quando há multiplas mudanças de estado. Você pode usar os dois em qualquer situação, mas se quiser refinar a aplicação para a melhor performance possível, eu diria isso: se as suas variáveis são alteradas em momentos diferentes, use GetX, porque não tem competição para isso quando o widget é para reconstruir somente o que é necessário. Se você não precisa de IDs únicas, porque todas as suas variáveis serão alteradas quando você fazer uma ação, use GetBuilder, porque é um atualizador de estado em blocos simples, feito com apenas algumas linhas de código, para fazer justamente o que ele promete fazer: atualizar estado em blocos. Não há forma de comparar RAM, CPU, etc de um gerenciador de estado gigante com um simples StatefulWidget (como GetBuilder) que é atualizado quando você chama `update()`. Foi feito de uma forma simples, para ter o mínimo de lógica computacional, somente para cumprir um único papel e gastar o mínimo de recursos possível. Se você quer um gerenciador de estados poderoso, você pode ir sem medo para o GetX. Ele não funciona com variáveis, mas sim fluxos. Tudo está em seus streams por baixo dos panos. Você pode usar `rxDart` em conjunto com ele, porque tudo é um stream, você pode ouvir o evento de cada "variável", porque tudo é um stream, é literalmente BLoc, só que mais fácil que MobX e sem code generators ou decorations. ## Reactive State Manager Se você quer poder, Get té dá o mais avançado gerenciador de estado que você pode ter. GetX foi construído 100% baseado em Stream, e te dá todo o poder de fogo que o BLoc te dá, com uma sintaxe mais fácil que a do MobX. Sem decorations, você poder tornar qualquer coisa em um `Observable` com somete um `.obs` Performance máxima: Somando ao fato de ter um algoritmo inteligente para reconstrução mínima, Get usa comparadores para ter certeza que o estado mudou. Se você encontrar erros na sua aplicação, e enviar uma mudança de estado duplicada, Get vai ter certeza que sua aplicação não entre em colapso. O estado só muda se o valor mudar. Essa é a principal diferença entre Get, e usar o `Computed` do MobX. Quando juntar dois observables, se um deles é alterado, a escuta daquele observable vai mudar. Com Get, se você juntar duas variáveis (que na verdade é desnecessário), GetX(similar ao Observer) vai somente mudar se implicar numa mudança real de estado. Exemplo: ```dart final count1 = 0.obs; final count2 = 0.obs; int get sum => count1.value + count2.value; ``` ```dart GetX( builder: (_) { print("count 1 foi reconstruído"); return Text('${_.count1.value}'); }, ), GetX( builder: (_) { print("count 2 foi reconstruído"); return Text('${_.count2.value}'); }, ), GetX( builder: (_) { print("sum foi reconstruído"); return Text('${_.sum}'); }, ), ``` Se nós incrementarmos o número do `count1`, somente `count1` e `sum` serão reconstruídos, porque `count1` agora tem um valor de 1, e 1 + 0 = 1, alterando o valor do `sum`. Se nós mudarmos `count2`, somente `count2` e `sum` serão reconstruídos, porque o valor do 2 mudou, e o resultado da variável `sum` é agora 2. Se definirmos o valor de `count1` para 1, nenhum widget será reconstruído, porque o valor já era 1. Se definirmos o valor de `count1` para 1 denovo, e definirmos o valor de `count2` para 2, então somente o `count2`e o `sum` vão ser reconstruídos, simplesmente porque o GetX não somente altera o que for necessário, ele também evita eventos duplicados. Somando a isso, Get provê um controle de estado refinado. Você pode adicionar uma condição a um evento (como adicionar um objeto a uma lista). ```dart list.addIf(item < limit, item); ``` Sem decorations, sem code generator, sem complicações, GetX vai mudar a forma que você controla seus estados no Flutter, e isso não é uma promessa, isso é uma certeza! Sabe o app de contador do Flutter? Sua classe Controller pode ficar assim: ```dart class CountController extends RxController { final count = 0.obs; } ``` E com um simples: ```dart controller.count.value++ ``` Você pode atualizar a variável counter na sua UI, independente de onde esteja sendo armazenada. Você pode transformar qualquer coisa em obs: ```dart class RxUsuario { final nome = "Camila".obs; final idade = 18.obs; } class Usuario { Usuario({String nome, int idade}); final rx = RxUsuario(); String get nome => rx.nome.value; set nome(String value) => rx.nome.value = value; int get idade => rx.idade.value; set idade(int value) => rx.idade.value = value; } ``` ```dart void main() { final usuario = Usuario(); print(usuario.nome); usuario.idade = 23; usuario.rx.idade.listen((int idade) => print(idade)); usuario.idade = 24; usuario.idade = 25; } ___________ saída: Camila 23 24 25 ``` Trabalhar com `Lists` usando Get é algo muito agradável. Elas são completamente observáveis assim como os objetos dentro dela. Dessa forma, se você adicionar uma valor a lista, ela vai automaticamente reconstruir os widgets que a usam. Você também não precisa usar `.value` com listas, a api dart nos permitiu remover isso. Infelizmente tipos primitivos como String e int não podem ser extendidos, fazend o uso de `.value` obrigatório, mas isso não será um problema se você usar getters e setters para esses. ```dart final list = List().obs; ListView.builder ( itemCount: list.length ) ``` Você não precisa trabalhar com sets se não quiser. Você pode usar as APIs `assign` e `assignAll` A api `assign` vai limpar sua lista e adicionar um único objeto que você quer. A api `assignAll` vai limpar sua lista e vai adicionar objetos `Iterable` que você precisa. Nós poderíamos remover a obrigação de usar o value em String e int com uma simples decoration e code generator, mas o propósito desse package é precisamente não precisar de nenhuma dependência externa. É para oferecer um ambiente pronto para programar, envolvendo os essenciais (gerenciamento de rotas, dependências e estados), numa forma simples, leve e performática sem precisar de packages externos. Você pode literalmente adicionar 3 letras e um ':' no seu pubspec.yaml e começar a programar. Todas as soluções incluídas por padrão, miram em facilidade, produtividade e performance. O peso total desse package é menor que o de um único gerenciador de estado, mesmo sendo uma solução completa, e isso é o que você precisa entender. Se você está incomodado com o `.value`, e gosta de code generator, MobX é uma excelente alternativa, e você pode usá-lo em conjunto com o Get. Para aqueles que querem adicionar uma única dependência no pubspec.yaml e começar a programar sem se preocupar sobre a versão de um package sendo incompatível com outra, ou se o erro de uma atualização do estado está vindo do gerenciador de estado ou da dependência, ou ainda, não quer se preocupar com a disponibilidade de controllers, prefere literalmente "só programar", Get é perfeito. Agora se você não tem problemas com o code generator do MobX, ou não vê problema no boilerplate do BLoc, você pode simplesmente usar o Get para rotas, e esquecer que nele existe um gerenciador de estado. Get nasceu da necessidade, minha empresa tinha um projeto com mais de 90 controllers e o code generator simplesmente levava mais de 30 minutos para completar suas tarefas depois de um `flutter clean` numa máquina razoavelmente boa, se o seu projeto tem 5, 10, 15 controllers, qualquer gerenciador de estado vai ter satisfazer. Se você tem um projeto absurdamente grande, e code generator é um problema para você, então você foi presenteado com essa solução que é o Get. Obviamente, se alguém quiser contribuir para o projeto e criar um code generator, or algo similar, eu vou linkar no README como uma alternativa, minha necessidade não é a necessidade de todos os devs, mas por agora eu digo: há boas soluções que já fazem isso, como MobX. Tipagem no Get usando Bindings é desnecessário. Você pode usar o widget `Obx()` em vez do GetX, e ele só recebe a função anônima que cria o widget. ### GetX vs GetBuilder vs Obx vs MixinBuilder Em uma década de trabalho com programação eu fui capaz de aprender umas lições valiosas. Meu primeiro contato com programação reactiva foi tão "Nossa, isso é incrível" e de fato é incrível. Porém, não é ideal para todas as situações. De vez em quando tudo que você precisa é mudar o estado de duas ou três variáveis ao mesmo tempo, ou uma mudança de estado diferente, que nesses casos a programação reativa não é ruim, mas não é apropriado. Programação reativa tem um consumo de RAM maior que pode ser compensado pelo fluxo individual, que vai se encarregar que apenas o Widget necessário é reconstruído, mas criar uma lista com 80 objetos, cada um com vários streams não é uma boa ideia. Abra o `dart inspect` e cheque quando um StreamBuilder consome, e você vai entender o que eu estou tentando dizer a você. Com isso em mente, eu criar o gerenciador de estados simples. É simples, e é exatamente o que você deve exigir dele: alterar vários estados em blocos de uma forma simples, e mais econômico possível. GetBuilder economiza muito quando se trata de RAM, e dificilmente vai existir uma forma mais econômica de lidar com isso do que ele (pelo menos eu não consigo imaginar nenhum. Se existir, me avise). Entretando, GetBuilder ainda é um gerenciador de estados "mecânico", você precisa chamar o `update()` assim como você faria com o `notifyListeners()` do `Provider`. Há outras situações onde a programação reativa é muito interessante, e não trabalhar com ela é a mesma coisa que reinventar a roda. Com isso em mente, GetX foi criado para prover tudo que há de mais moder e avançado num gerenciado de estado. Ele atualiza somente o que é necessário quando for necessário. Se por exemplo você tiver um erro que mande 300 alterações de estado simultaneamente, GetX vai filtrar e alterar a tela somente se o estado mudar de verdade. GetX ainda é mais econômico que qualquer outro gerenciador de estados reativo, mas consome um pouco mais de RAM do que o GetBuilder. Pensando nisso e mirando em minimizar o consumo de recursos que o Obx foi criado. Ao contrário de GetX e GetBuilder, você não será capaz de inicializar o controller dentro do Obx, é só um Widget com um `StreamSubscription` que recebe eventos de mundança das childrens ou child, só isso. É mais econômico que o GetX, mas perde para o GetBuilder, que é nada mais que o esperado, já que é reativo. GetBuilder tem uma das formas mais simplística que existe, de guardar o hashcode de um Widget e seu StateSetter. Com Obs você não precisa escrever seu tipo de controller, e você pode ouvir mudanças de múltiplos controllers diferentes, mas eles precisam ser inicializados antes, tanto usando o forma demonstrada no exemplo no início desse README, ou usando a classe `Bindings` Concluindo, uma pessoa abriu um pedido para uma feature nova, como ele queria usar somente um tipo de variável reativa, e precisava inserir um Obx junto do GetBuilder para isso. Pensando nisso, `MixinBuilder` foi criado. Ele permite as duas formas de alterar estados: usando variáveis com `.obs`, ou a forma mecânica via `update()` Porém, desses 4 widgets, esse é o que consome mais recursos, já que usa os dois gerenciadores de estado combinados. * Nota: Para usar GetBuilder e MixinBuilder você precisa usar GetController. Para usar GetX ou Obx você precisa usar RxController. ## Workers Workers vai te dar suporte, ativando callbacks específicos quando um evento ocorrer. ```dart /// Chamada toda vez que a variável for alterada ever(count1, (value) => print("novo valor: $value")); /// Chamada apenas na primeira vez que a variável for alterada once(count1, (value) => print("novo valor: $value (não vai mudar mais)")); /// Anti DDos - Chamada toda vez que o usuário parar de digitar por 1 segundo, por exemplo. debounce(count1, (value) => print("debouce $value"), time: Duration(seconds: 1)); /// Ignore todas as mudanças num período de 1 segundo. interval(count1, (value) => print("interval $value"), time: Duration(seconds: 1)); ``` * **ever** é chamado toda vez que a variável mudar de valor. É só isso. * **once** é chamado somente na primeira vez que a variável mudar de valor. * **debounce** É muito útil em funções de pesquisa, onde você somente quer que a API seja chamada depois que o usuário terminar de digitar. Normalmente, se o usuário digitar "Jonny", será feita 5 requests na sua API, pelas letras 'J', 'o', 'n', 'n' e 'y'. Com o `debounce` isso não acontece, porque você tem a sua disposição uma função que só fazer a pesquisa na api quando o usuário parar de digitar. O `debounce` é bom para táticas anti-DDos, para funções de pesquisa em que cada letra digitada ativaria um request na API. Debounce vai esperar o usário parar de digitar o nome, para então fazer o request para API. * **interval** Quando se usa `debounce` , se o usuário fizer 1000 mudanças numa variável em 1 segundo, o `debounce` só computa a última mudança feita após a inatividade por um tempo estipulado (o padrão é 800 milisegundos). `interval` por outro lado vai ignorar todas as ações do usuário pelo período estipulado. Se o `interval` for de 1 segundo, então ele só conseguirá enviar 60 eventos por segundo. Se for 3 segundos de prazo, então o `interval` vai enviar 20 eventos por minuto (diferente do `debounce` que só enviaria o evento depois que o prazo estipulado acabar). Isso é recomendado para evitar abuso em funções que o usuário pode clicar rapidamente em algo para ter uma vantagem. Para exemplificar, imagine que o usuário pode ganhar moedas clicando num botão. Se ele clicar 300 vezes no botão em um minuto, ele vai ganhar 300 moedas. Usando o `interval`, você pode definir um prazo de 1 segundo por exemplo, e mesmo se ele clicasse 300 vezes em um minuto, ele ganharia apenas 60 moedas. Se fosse usado o `debounce`, o usuário ganharia apenas 1 moeda, porque só é executado quando o usuário para de clicar por um prazo estabelecido. - NOTA: Workers devem ser utilizados sempre na inicialização de um Controller ou Classe, dessa forma, ele deve estar sempre no onInit (recomendado), no Construtor de classe, ou no initState de um StatefulWidget (essa prática não é recomendada na maioria dos casos, mas não deve trazer efeitos colaterais). ================================================ FILE: documentation/ru_RU/dependency_management.md ================================================ # Управление зависимостями - [Управление зависимостями](#управление-зависимостями) - [Методы создания экземпляров](#методы-создания-экземпляров) - [Get.put()](#getput) - [Get.lazyPut](#getlazyput) - [Get.putAsync](#getputasync) - [Get.create](#getcreate) - [Применение методов/классов создания экземпляров](#применение-методовклассов-создания-экземпляров) - [Различия между методами](#различия-между-методами) - [Подвязки](#подвязки) - [Класс Bindings](#класс-bindings) - [BindingsBuilder](#bindingsbuilder) - [SmartManagement](#smartmanagement) - [Как поменять](#как-поменять) - [SmartManagement.full](#smartmanagementfull) - [SmartManagement.onlyBuilders](#smartmanagementonlybuilders) - [SmartManagement.keepFactory](#smartmanagementkeepfactory) - [Как подвязки работают под капотом](#как-подвязки-работают-под-капотом) - [Примечания](#примечания) Get имеет простой и мощный менеджер зависимостей, позволяющий вам получить тот же класс, что и ваш Bloc или Controller, с помощью 1 строки кода, без Provider и inheritedWidget: ```dart Controller controller = Get.put(Controller()); // Rather Controller controller = Controller(); ``` Вместо того, чтобы создавать экземпляр вашего класса в классе, который вы используете, вы создаёте его в экземпляре Get, что сделает его доступным во всем приложении. Таким образом Вы можете использовать свой контроллер (или Bloc) - Примечание: Если вы применяете менеджер состояний Get, обратите внимание на [Bindings](#bindings) api, упрощающего подключение вашего предсталения к контроллеру. - Примечаие²: Управление зависимостями Get отделено от других частей пакета, поэтому, если, например, ваше приложение уже использует другой менеджер состояний, вам не нужно его менять, вы можете использовать этот менеджер внедрения зависимостей без каких-либо проблем. ## Методы создания экземпляров Методы и настраиваемые параметры: ### Get.put() Самый распространенный способ внедрения зависимости. Например, хорош для контроллеров ваших представлений. ```dart Get.put(SomeClass()); Get.put(LoginController(), permanent: true); Get.put(ListItemController, tag: "some unique string"); ``` Это все параметры, которые вы можете установить при использовании put: ```dart Get.put( // mandatory: the class that you want to get to save, like a controller or anything // note: "S" means that it can be a class of any type S dependency // optional: this is for when you want multiple classess that are of the same type // since you normally get a class by using Get.find(), // you need to use tag to tell which instance you need // must be unique string String tag, // optional: by default, get will dispose instances after they are not used anymore (example, // the controller of a view that is closed), but you might need that the instance // to be kept there throughout the entire app, like an instance of sharedPreferences or something // so you use this // defaults to false bool permanent = false, // optional: allows you after using an abstract class in a test, replace it with another one and follow the test. // defaults to false bool overrideAbstract = false, // optional: allows you to create the dependency using function instead of the dependency itself. // this one is not commonly used InstanceBuilderCallback builder, ) ``` ### Get.lazyPut Можно отложить загрузку зависимости, чтобы она создавалась только тогда, когда она используется. Очень полезно для вычислительных ресурсоёмких классов или если вы хотите создать экземпляры нескольких классов в одном месте (например, в классе Bindings), и вы знаете, что не собираетесь использовать этот класс в то время. ```dart /// ApiMock will only be called when someone uses Get.find for the first time Get.lazyPut(() => ApiMock()); Get.lazyPut( () { // ... some logic if needed return FirebaseAuth(); }, tag: Math.random().toString(), fenix: true ) Get.lazyPut( () => Controller() ) ``` Это все параметры, которые вы можете установить при использовании lazyPut: ```dart Get.lazyPut( // mandatory: a method that will be executed when your class is called for the first time InstanceBuilderCallback builder, // optional: same as Get.put(), it is used for when you want multiple different instance of a same class // must be unique String tag, // optional: It is similar to "permanent", the difference is that the instance is discarded when // is not being used, but when it's use is needed again, Get will recreate the instance // just the same as "SmartManagement.keepFactory" in the bindings api // defaults to false bool fenix = false ) ``` ### Get.putAsync Если вы хотите зарегистрировать асинхронный экземпляр, вы можете использовать `Get.putAsync`: ```dart Get.putAsync(() async { final prefs = await SharedPreferences.getInstance(); await prefs.setInt('counter', 12345); return prefs; }); Get.putAsync( () async => await YourAsyncClass() ) ``` Это все параметры, которые вы можете установить при использовании putAsync: ```dart Get.putAsync( // mandatory: an async method that will be executed to instantiate your class AsyncInstanceBuilderCallback builder, // optional: same as Get.put(), it is used for when you want multiple different instance of a same class // must be unique String tag, // optional: same as in Get.put(), used when you need to maintain that instance alive in the entire app // defaults to false bool permanent = false ) ``` ### Get.create Это хитрый метод. Подробное объяснение того, что это такое, и различий между ними можно найти в разделе [Различия между методами](#различия-между-методами): ```dart Get.Create(() => SomeClass()); Get.Create(() => LoginController()); ``` Это все параметры, которые вы можете установить при использовании create: ```dart Get.create( // required: a function that returns a class that will be "fabricated" every // time `Get.find()` is called // Example: Get.create(() => YourClass()) FcBuilderFunc builder, // optional: just like Get.put(), but it is used when you need multiple instances // of a of a same class // Useful in case you have a list that each item need it's own controller // needs to be a unique string. Just change from tag to name String name, // optional: just like int`Get.put()`, it is for when you need to keep the // instance alive thoughout the entire app. The difference is in Get.create // permanent is true by default bool permanent = true ``` ## Применение методов/классов создания экземпляров Представьте, что вы прошли через множество маршрутов и вам нужны данные, которые остались в вашем контроллере. Вам понадобится менеджер состояний в сочетании с Provider или Get_it, верно? Только не с Get. Вам просто нужно попросить Get «найти» ваш контроллер, никаких дополнительных зависимостей вам не потребуется: ```dart final controller = Get.find(); // OR Controller controller = Get.find(); // Yes, it looks like Magic, Get will find your controller, and will deliver it to you. // You can have 1 million controllers instantiated, Get will always give you the right controller. ``` И тогда вы сможете восстановить данные вашего контроллера, которые были там получены: ```dart Text(controller.textFromApi); ``` Поскольку возвращаемое значение является обычным классом, вы можете делать все, что захотите: ```dart int count = Get.find().getInt('counter'); print(count); // out: 12345 ``` Чтобы удалить экземпляр Get: ```dart Get.delete(); //usually you don't need to do this because GetX already delete unused controllers ``` ## Различия между методами Сперва давайте рассмотрим параметр `fenix` метода Get.lazyPut и `permanent` других методов. Фундаментальное различие между `permanent` и `fenix` заключается в том, как вы хотите хранить свои экземпляры. Закрепляя это: по умолчанию GetX удаляет экземпляры, когда они не используются. Это означает, что: если на экране 1 есть контроллер 1, а на экране 2 есть контроллер 2, и вы удаляете первый маршрут из стека (например, если вы используете `Get.off()` или `Get.offNamed()`), контроллер 1 теперь не используется, поэтому он будет стёрт. Но если вы используете `permanent: true`, тогда контроллер не будет потерян при этом переходе - что очень полезно для служб, которые вы хотите поддерживать на протяжении всего приложения. `fenix` предназначен для сервисов, о потере которых вы не беспокоитесь между сменами экрана, но когда вам нужен этот сервис, вы ожидаете, что он будет активен. По сути, он избавится от неиспользуемого контроллера/службы/класса, но когда он вам понадобится, он «воссоздает из пепла» новый экземпляр. Исходя из различий между методами: - Get.put и Get.putAsync следует одному и тому же порядку создания, с той разницей, что второй использует асинхронный метод: эти два метода создают и инициализируют экземпляр. Они вставляются непосредственно в память с использованием внутреннего метода `insert` с параметрами `permanent: false` и `isSingleton: true` (параметр isSingleton предназначен только для того, чтобы указать, следует ли использовать зависимость от `dependency` или она должна использовать зависимость от `FcBuilderFunc`). После этого вызывается `Get.find()`, который немедленно инициализирует экземпляры, находящиеся в памяти. - Get.create: Как следует из названия, он «создаст» вашу зависимость! Подобно `Get.put()`, он также вызывает внутренний метод `insert` для создания экземпляра. Но `permanent` становится true и `isSingleton` становится false (поскольку мы «создаем» нашу зависимость, она не может быть синглтоном, поэтому false). И поскольку `permanent: true`, мы по умолчанию не теряем его между экранами! Также, `Get.find()` не вызывается немедленно, он ожидает использования на экране для вызова. Он создан таким образом, чтобы использовать параметр `permanent`, `Get.create()` был создан с целью создания не общих экземпляров, но не удаляемых, как, например, кнопка в listView, где вам нужен уникальный экземпляр для этого списка - по этой причине Get.create необходимо использовать вместе с GetWidget. - Get.lazyPut: Как следует из названия, это ленивый процесс. Экземпляр создается, но он не вызывается для немедленного использования, он остается в ожидании вызова. В отличие от других методов, `insert` не вызывается здесь. Вместо этого экземпляр вставляется в другую часть памяти, часть, отвечающую за определение возможности воссоздания экземпляра, назовем это «фабрикой». Если мы хотим создать что-то, что будет использоваться позже, это не будет смешиваться с вещами, которые использовались сейчас. И здесь вступает в силу магия `fenix`: если вы решаете оставить `fenix: false`, и ваш `smartManagement` не является `keepFactory`, то, при использовании `Get.find`, экземпляр изменит место в памяти с «фабрики» на область памяти общего экземпляра. Сразу после этого по умолчанию удаляется с «фабрики». Теперь, если вы выберете `fenix: true`, экземпляр продолжит существовать в этой выделенной части, даже перейдя в общую область, для повторного вызова в будущем. ## Подвязки Возможно, одной из главных особенностей этого пакета является возможность полной интеграции маршрутов, менеджера состояний и менеджера зависимостей. Когда маршрут удаляется из стека, все контроллеры, переменные и экземпляры связанных с ним объектов удаляются из памяти. Если вы используете потоки или таймеры, они закроются автоматически, и вам не о чем беспокоиться. В версии 2.10 полностью реализован API привязок. Теперь вам больше не нужно использовать метод инициализации. Вам даже не нужно вводить контроллеры, если вы этого не хотите. Вы можете запустить свои контроллеры и серисы в соответствующем для этого месте. Класс Binding - это класс, который будет разделять внедрение зависимостей, при этом «привязывая» маршруты к диспетчеру состояний и диспетчеру зависимостей. Этот класс позволяет Get узнать, какой экран отображается при использовании конкретного контроллера, а также узнать, где и как его удалить. Кроме того, класс Binding позволит вам контролировать конфигурацию SmartManager. Вы можете настроить зависимости, которые будут упорядочены при удалении маршрута из стека, или когда виджет, который его использовал, выкладывается, или ни то, ни другое. На вас будет работать интеллектуальное управление зависимостями, но даже в этом случае вы можете настроить его по своему усмотрению. ### Класс Bindings - Создайте класс и реализуйте Binding ```dart class HomeBinding implements Bindings {} ``` Ваша IDE автоматически попросит вас переопределить метод «зависимостей», и вам просто нужно последовать этой просьбе, переопределить метод и вставить все классы, которые вы собираетесь использовать на этом маршруте: ```dart class HomeBinding implements Bindings { @override void dependencies() { Get.lazyPut(() => HomeController()); Get.put(()=> Api()); } } class DetailsBinding implements Bindings { @override void dependencies() { Get.lazyPut(() => DetailsController()); } } ``` Теперь вам просто нужно сообщить своему маршруту, что вы будете использовать эту привязку для установления связи между диспетчером маршрутов, зависимостями и состояниями. - Используя именованные маршруты: ```dart getPages: [ GetPage( name: '/', page: () => HomeView(), binding: HomeBinding(), ), GetPage( name: '/details', page: () => DetailsView(), binding: DetailsBinding(), ), ]; ``` - Используя обычные маршруты: ```dart Get.to(Home(), binding: HomeBinding()); Get.to(DetailsView(), binding: DetailsBinding()) ``` Вам больше не нужно беспокоиться об управлении памятью вашего приложения, Get сделает это за вас. Класс Binding вызывается при вызове маршрута, вы можете создать "initialBinding" в GetMaterialApp, чтобы вставить все зависимости, которые будут созданы. ```dart GetMaterialApp( initialBinding: SampleBind(), home: Home(), ); ``` ### BindingsBuilder По умолчанию привязка создается путем создания класса, реализующего привязки. Но в качестве альтернативы вы можете использовать обратный вызов `BindingsBuilder`, чтобы просто использовать функцию для создания всего, что вы хотите. Example: ```dart getPages: [ GetPage( name: '/', page: () => HomeView(), binding: BindingsBuilder(() { Get.lazyPut(() => ControllerX()); Get.put(()=> Api()); }), ), GetPage( name: '/details', page: () => DetailsView(), binding: BindingsBuilder(() { Get.lazyPut(() => DetailsController()); }), ), ]; ``` Таким образом, вы можете избежать создания одного класса привязки для каждого маршрута, что сделает это еще проще. Оба способа работают идеально, и мы хотим, чтобы вы использовали то, что больше всего соответствует вашим вкусам. ### SmartManagement GetX по умолчанию удаляет неиспользуемые контроллеры из памяти, даже если происходит сбой и виджет, который их использует, не удаляется должным образом. Это то, что называется `полным` режимом управления зависимостями. Но если вы хотите поменять способ, которым GetX управляет удалением классов, у вас есть класс `SmartManagement`, в котором вы можете задавать другое поведение. #### Как поменять Если вы хотите поменять эту конфигурацию (что обычно не требуется), вот способ: ```dart void main () { runApp( GetMaterialApp( smartManagement: SmartManagement.onlyBuilders //here home: Home(), ) ) } ``` #### SmartManagement.full Это значение по умолчанию. Удаляет классы, которые не используются и не были постоянными. В большинстве случаев вы захотите оставить эту конфигурацию нетронутой. Если вы новичок в GetX, не меняйте это. #### SmartManagement.onlyBuilders С этой опцией будут удалены только контроллеры, запущенные в `init:` или загруженные в Binding с помощью `Get.lazyPut()`. Если вы используете `Get.put()` или `Get.putAsync()` или любой другой подход, SmartManagement не будет иметь разрешений на исключение этой зависимости. При поведении по умолчанию даже виджеты, созданные с помощью Get.put, будут удалены, в отличие от SmartManagement.onlyBuilders. #### SmartManagement.keepFactory Как и SmartManagement.full, он удаляет зависимости, когда он больше не используется. Однако он сохранит свою фабрику, что означает, что он воссоздает зависимость, если вам снова понадобится этот экземпляр. ### Как подвязки работают под капотом Привязки создают временные фабрики, которые создаются в тот момент, когда вы кликаете для перехода на другой экран, и будут уничтожены, как только произойдет анимация смены экрана. Это происходит так быстро, что анализатор даже не сможет это зарегистрировать. Когда вы снова перейдете на этот экран, будет вызвана новая временная фабрика, поэтому это предпочтительнее, чем использование SmartManagement.keepFactory, но если вы не хотите создавать привязки или хотите сохранить все свои зависимости в одной привязке, это вам поможет. Фабрики занимают мало памяти, они содержат не экземпляры, а функцию с «формой» того класса, который вам нужен. Это имеет очень низкую стоимость памяти, но поскольку цель этой библиотеки - получить максимально возможную производительность с использованием минимальных ресурсов, Get по умолчанию удаляет даже фабрики. Используйте то, что вам удобнее. ## Примечания - НЕ ИСПОЛЬЗУЙТЕ SmartManagement.keepFactory, если вы используете несколько привязок. Он был разработан для использования без привязок или с одной привязкой, связанной в initialBinding GetMaterialApp. - Использование привязок совершенно необязательно, если вы хотите, вы можете без проблем использовать `Get.put()` и `Get.find()` для классов, которые используют данный контроллер. Однако, если вы работаете со службами или любой другой абстракцией, я рекомендую использовать привязки для лучшей организации. ================================================ FILE: documentation/ru_RU/route_management.md ================================================ - [Управление маршрутами](#управление-маршрутами) - [Как использовать](#как-использовать) - [Навигация без именованных маршрутов](#навигация-без-именованных-маршрутов) - [Навигация по именованным маршрутам](#навигация-по-именованным-маршрутам) - [Отправить данные по именованному маршруту](#отправить-данные-по-именованному-маршруту) - [Динамические url-ссылки](#динамические-url-ссылки) - [Middleware](#middleware) - [Навигация без контекста](#навигация-без-контекста) - [SnackBars](#snackbars) - [Dialogs](#dialogs) - [BottomSheets](#bottomsheets) - [Вложенная навигация](#вложенная-навигация) # Управление маршрутами Это полное объяснение всего, что нужно Getx, когда речь идет об управлении маршрутами. ## Как использовать Добавьте к вашему pubspec.yaml следующее: ```yaml dependencies: get: ``` Если вы собираетесь использовать маршруты/snackbars/dialogs/bottomSheets без контекста или использовать высокоуровневые API Get, вам нужно просто добавить «Get» перед вашим MaterialApp, превратив его в GetMaterialApp! ```dart GetMaterialApp( // Before: MaterialApp( home: MyHome(), ) ``` ## Навигация без именованных маршрутов Для навигации на новый экран: ```dart Get.to(NextScreen()); ``` Чтобы закрыть snackbars, диалог, bottomsheets, или все, что вы обычно закрывали с помощью Navigator.pop(context); ```dart Get.back(); ``` Для перехода к следующему экрану и отсутствия возможности вернуться к предыдущему экрану (для использования в SplashScreens, экранах входа и т.д.) ```dart Get.off(NextScreen()); ``` Для перехода к следующему экрану и отмены всех предыдущих маршрутов (полезно в корзинах для покупок, опросах и тестах) ```dart Get.offAll(NextScreen()); ``` Чтобы перейти к следующему маршруту и ​​получить или обновить данные, как только вы вернетесь с него: ```dart var data = await Get.to(Payment()); ``` отправить данные на предыдущий экран: ```dart Get.back(result: 'success'); ``` И использовать их: пример: ```dart if(data == 'success') madeAnything(); ``` Нет желания учить наш синтаксис? Просто измените Navigator(верхний регистр) на navigator (нижний регистр), и вы получите все функции стандартной навигации без использования контекста. Пример: ```dart // Default Flutter navigator Navigator.of(context).push( context, MaterialPageRoute( builder: (BuildContext context) { return HomePage(); }, ), ); // Get using Flutter syntax without needing context navigator.push( MaterialPageRoute( builder: (_) { return HomePage(); }, ), ); // Get syntax (It is much better, but you have the right to disagree) Get.to(HomePage()); ``` ## Навигация по именованным маршрутам - Если вы предпочитаете перемещаться по именованным маршрутам, Get также поддерживает это. Для навигации на следующий экран ```dart Get.toNamed("/NextScreen"); ``` Для навигации и удаления предыдущего экрана из дерева. ```dart Get.offNamed("/NextScreen"); ``` Для навигации и удаления всех предыдущих экранов из дерева. ```dart Get.offAllNamed("/NextScreen"); ``` Для определения маршрутов используйте GetMaterialApp: ```dart void main() { runApp( GetMaterialApp( initialRoute: '/', getPages: [ GetPage(name: '/', page: () => MyHomePage()), GetPage(name: '/second', page: () => Second()), GetPage( name: '/third', page: () => Third(), transition: Transition.zoom ), ], ) ); } ``` Для обработки навигации по неопределенным маршрутам (ошибка 404) вы можете определить страницу unknownRoute в GetMaterialApp. ```dart void main() { runApp( GetMaterialApp( unknownRoute: GetPage(name: '/notfound', page: () => UnknownRoutePage()), initialRoute: '/', getPages: [ GetPage(name: '/', page: () => MyHomePage()), GetPage(name: '/second', page: () => Second()), ], ) ); } ``` ### Отправить данные по именованному маршруту Просто отправьте то, что хотите в качестве аргументов. Get принимает здесь всё, что угодно, будь то String, Map, List или даже экземпляр класса. ```dart Get.toNamed("/NextScreen", arguments: 'Get is the best'); ``` в вашем классе или контроллере: ```dart print(Get.arguments); //print out: Get is the best ``` ### Динамические url-ссылки Получите расширенные динамические url-адреса, как в Интернете. Веб-разработчики, вероятно, уже хотели эту функцию на Flutter, и, скорее всего, видели, что пакет обещает эту функцию и предоставляет совершенно другой синтаксис, чем url-адрес в Интернете, но Get также решает эту проблему. ```dart Get.offAllNamed("/NextScreen?device=phone&id=354&name=Enzo"); ``` в вашем controller/bloc/stateful/stateless классе: ```dart print(Get.parameters['id']); // out: 354 print(Get.parameters['name']); // out: Enzo ``` Вы также можете легко получить именованные параметры с помощью Get: ```dart void main() { runApp( GetMaterialApp( initialRoute: '/', getPages: [ GetPage( name: '/', page: () => MyHomePage(), ), GetPage( name: '/profile/', page: () => MyProfile(), ), //You can define a different page for routes with arguments, and another without arguments, but for that you must use the slash '/' on the route that will not receive arguments as above. GetPage( name: '/profile/:user', page: () => UserProfile(), ), GetPage( name: '/third', page: () => Third(), transition: Transition.cupertino ), ], ) ); } ``` Отправьте данные по именованному маршруту ```dart Get.toNamed("/profile/34954"); ``` На втором экране возьмите данные по параметрам ```dart print(Get.parameters['user']); // out: 34954 ``` или отправьте несколько таких параметров ```dart Get.toNamed("/profile/34954?flag=true"); ``` На втором экране взять данные по параметрам как обычно. ```dart print(Get.parameters['user']); print(Get.parameters['flag']); // out: 34954 true ``` И теперь все, что вам нужно сделать, это использовать Get.toNamed() для навигации по именованным маршрутам без какого-либо контекста (вы можете вызывать свои маршруты непосредственно из класса BLoC или контроллера), а когда ваше приложение будет скомпилировано в Интернете, ваше маршруты появятся в url <3 ### Middleware Если вы хотите прослушивать события Get для запуска действий, вы можете использовать для этого routingCallback ```dart GetMaterialApp( routingCallback: (routing) { if(routing.current == '/second'){ openAds(); } } ) ``` Если вы не используете GetMaterialApp, вы можете использовать ручной API для подключения наблюдателя. ```dart void main() { runApp( MaterialApp( onGenerateRoute: Router.generateRoute, initialRoute: "/", navigatorKey: Get.key, navigatorObservers: [ GetObserver(MiddleWare.observer), // HERE !!! ], ), ); } ``` Создайте класс MiddleWare ```dart class MiddleWare { static observer(Routing routing) { /// You can listen in addition to the routes, the snackbars, dialogs and bottomsheets on each screen. ///If you need to enter any of these 3 events directly here, ///you must specify that the event is != Than you are trying to do. if (routing.current == '/second' && !routing.isSnackbar) { Get.snackbar("Hi", "You are on second route"); } else if (routing.current =='/third'){ print('last route called'); } } } ``` Теперь используйте Get в своем коде: ```dart class First extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( leading: IconButton( icon: Icon(Icons.add), onPressed: () { Get.snackbar("hi", "i am a modern snackbar"); }, ), title: Text('First Route'), ), body: Center( child: ElevatedButton( child: Text('Open route'), onPressed: () { Get.toNamed("/second"); }, ), ), ); } } class Second extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( leading: IconButton( icon: Icon(Icons.add), onPressed: () { Get.snackbar("hi", "i am a modern snackbar"); }, ), title: Text('second Route'), ), body: Center( child: ElevatedButton( child: Text('Open route'), onPressed: () { Get.toNamed("/third"); }, ), ), ); } } class Third extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Third Route"), ), body: Center( child: ElevatedButton( onPressed: () { Get.back(); }, child: Text('Go back!'), ), ), ); } } ``` ## Навигация без контекста ### SnackBars Чтобы получить простой SnackBar с Flutter, вы должны получить контекст Scaffold, или вы должны использовать GlobalKey, прикрепленный к вашему Scaffold ```dart final snackBar = SnackBar( content: Text('Hi!'), action: SnackBarAction( label: 'I am a old and ugly snackbar :(', onPressed: (){} ), ); // Find the Scaffold in the widget tree and use // it to show a SnackBar. Scaffold.of(context).showSnackBar(snackBar); ``` Реализация в Get: ```dart Get.snackbar('Hi', 'i am a modern snackbar'); ``` С Get всё, что вам нужно сделать, это вызвать Get.snackbar из любого места кода или настроить его так, как вы хотите! ```dart Get.snackbar( "Hey i'm a Get SnackBar!", // title "It's unbelievable! I'm using SnackBar without context, without boilerplate, without Scaffold, it is something truly amazing!", // message icon: Icon(Icons.alarm), shouldIconPulse: true, onTap:(){}, barBlur: 20, isDismissible: true, duration: Duration(seconds: 3), ); ////////// ALL FEATURES ////////// // Color colorText, // Duration duration, // SnackPosition snackPosition, // Widget titleText, // Widget messageText, // bool instantInit, // Widget icon, // bool shouldIconPulse, // double maxWidth, // EdgeInsets margin, // EdgeInsets padding, // double borderRadius, // Color borderColor, // double borderWidth, // Color backgroundColor, // Color leftBarIndicatorColor, // List boxShadows, // Gradient backgroundGradient, // TextButton mainButton, // OnTap onTap, // bool isDismissible, // bool showProgressIndicator, // AnimationController progressIndicatorController, // Color progressIndicatorBackgroundColor, // Animation progressIndicatorValueColor, // SnackStyle snackStyle, // Curve forwardAnimationCurve, // Curve reverseAnimationCurve, // Duration animationDuration, // double barBlur, // double overlayBlur, // Color overlayColor, // Form userInputForm /////////////////////////////////// ``` Если вы предпочитаете традиционный snackbar, или хотите настроить его с нуля, вы можете использовать `Get.rawSnackbar();` который предоставляет RAW API, на котором был построен Get.snackbar. ### Dialogs Чтобы открыть: ```dart Get.dialog(YourDialogWidget()); ``` Чтобы открыть диалог по умолчанию: ```dart Get.defaultDialog( onConfirm: () => print("Ok"), middleText: "Dialog made in 3 lines of code" ); ``` Вы также можете использовать Get.generalDialog вместо showGeneralDialog. Для всех других виджетов диалога Flutter, включая cupertino, вы можете использовать Get.overlayContext вместо контекста и открывать его в любом месте вашего кода. Для виджетов, которые не используют Overlay, вы можете использовать Get.context. Эти два контекста будут работать в 99% случаев для замены контекста вашего пользовательского интерфейса, за исключением случаев, когда наследуемый виджет используется без контекста навигации. ### BottomSheets Get.bottomSheet похож на showModalBottomSheet, но не требует контекста. ```dart Get.bottomSheet( Container( child: Wrap( children: [ ListTile( leading: Icon(Icons.music_note), title: Text('Music'), onTap: () {} ), ListTile( leading: Icon(Icons.videocam), title: Text('Video'), onTap: () {}, ), ], ), ) ); ``` ## Вложенная навигация Get сделал вложенную навигацию Flutter еще проще. Вам не нужен контекст, и вы найдёте свой стек навигации по Id. - ПРИМЕЧАНИЕ: Создание параллельных стеков навигации может быть опасным. В идеале не используйте NestedNavigators или используйте их редко. Если этого требует ваш проект, продолжайте, но имейте в виду, что хранение нескольких стеков навигации в памяти может быть не лучшим решением для потребления оперативной памяти. Смотрите как это просто: ```dart Navigator( key: Get.nestedKey(1), // create a key by index initialRoute: '/', onGenerateRoute: (settings) { if (settings.name == '/') { return GetPageRoute( page: () => Scaffold( appBar: AppBar( title: Text("Main"), ), body: Center( child: TextButton( color: Colors.blue, onPressed: () { Get.toNamed('/second', id:1); // navigate by your nested route by index }, child: Text("Go to second"), ), ), ), ); } else if (settings.name == '/second') { return GetPageRoute( page: () => Center( child: Scaffold( appBar: AppBar( title: Text("Main"), ), body: Center( child: Text("second") ), ), ), ); } } ), ``` ================================================ FILE: documentation/ru_RU/state_management.md ================================================ - [Управление состоянием](#управление-состоянием) - [Реактивное управление состоянием](#реактивное-управление-состоянием) - [Преимущества](#преимущества) - [Объявление реактивной переменной](#объявление-реактивной-переменной) - [Использование значений в представлении](#использование-значений-в-представлении) - [Условия для перестраивания](#условия-для-перестраивания) - [Где .obs может быть использован](#где-obs-может-быть-использован) - [Примечание о списках](#примечание-о-списках) - [Почему мне нужно использовать .value](#почему-мне-нужно-использовать-value) - [Obx()](#obx) - [Workers](#workers) - [Обычное управление состоянием](#обычное-управление-состоянием) - [Преимущества](#преимущества-1) - [Использование](#использование) - [Как обрабатываются контроллеры](#как-обрабатываются-контроллеры) - [Вам больше не понадобятся StatefulWidgets](#вам-больше-не-понадобятся-statefulwidgets) - [Почему это существует](#почему-это-существует) - [Другие способы использования](#другие-способы-использования) - [Уникальные идентификаторы](#уникальные-идентификаторы) - [Смешивание двух менеджеров состояний](#смешивание-двух-менеджеров-состояний) - [GetBuilder vs GetX vs Obx vs MixinBuilder](#getbuilder-vs-getx-vs-obx-vs-mixinbuilder) # Управление состоянием В настоящее время для Flutter есть несколько менеджеров состояний. Однако большинство из них связано с использованием ChangeNotifier для обновления виджетов, и это плохой и очень плохой подход к производительности средних или больших приложений. Вы можете проверить в официальной документации Flutter, что [ChangeNotifier следует использовать с 1 или максимум 2 слушателями](https://api.flutter.dev/flutter/foundation/ChangeNotifier-class.html), что делает его практически непригодным для любого приложения среднего или большого размера. Остальные менеджеры состояний хороши, но есть свои нюансы: - BLoC безопасен и эффективен, но сложен для новичков, что удерживает людей от разработки с Flutter. - MobX проще, чем BLoC, реактивен, и я бы сказал, почти идеален, но вам нужно использовать генератор кода, который для больших приложений снижает производительность, таким образом вам нужно будет пить много кофе, прежде чем ваш код снова не будет готов после flutter clean (И это не вина MobX, а вина кодогенерации, которая очень медленная!). - Provider использует InheritedWidget для доставки слушателя в качестве способа решения проблемы, описанной выше, с помощью ChangeNotifier, что подразумевает, что любой доступ к классу ChangeNotifier должен находиться в дереве виджетов из-за контекста для доступа к Inherited. Get не лучше и не хуже, чем любой другой менеджер состояний, но вам следует проанализировать эти моменты, а также приведенные ниже пункты, чтобы выбрать между использованием Get в чистом виде (Vanilla) или его вместе с другим менеджером состояний. Определенно, Get - не враг любого другого менеджера состояний, потому что Get - это микрофреймворк, а не просто менеджер состояний, и его можно использовать отдельно или вместе с ними. ## Реактивное управление состоянием Реактивное программирование может оттолкнуть многих людей, потому что считается сложным. GetX превращает реактивное программирование в нечто довольно простое: - Вам не нужно создавать StreamControllers. - Вам не нужно создавать StreamBuilder для каждой переменной. - Вам не нужно создавать класс для каждого состояния. - Вам не нужно создавать получение начального значения. Реактивное программирование с помощью Get так же просто, как использование setState. Представим, что у вас есть переменная `name` и вы хотите, чтобы каждый раз, когда вы её изменяете, все виджеты, которые её используют, менялись автоматически. Это ваша переменная: ```dart var name = 'Jonatas Borges'; ``` Чтобы сделать его наблюдаемым, вам просто нужно добавить в конец «.obs»: ```dart var name = 'Jonatas Borges'.obs; ``` Вот и всё. Это *так* просто. С этого момента мы могли бы называть эти - ".obs" (ervables) переменные как _Rx_. Что мы делали под капотом? Мы создали `Stream` из `String`ов, которому было присвоено начальное значение `"Jonatas Borges"`, мы уведомили все виджеты, которые используют `"Jonatas Borges"`, что они теперь «принадлежат» этой переменной, и когда значение _Rx_ изменится, они также должны будут измениться. Это **волшебство GetX** возможно, благодаря возможностям Dart. Но, как мы знаем, виджет можно изменить только в том случае, если он находится внутри функции, потому что статические классы не имеют права «автоматически изменяться». Вам нужно будет создать `StreamBuilder`, подписаться на эту переменную, чтобы отслеживать изменения, и создать «каскад» вложенных `StreamBuilder`, если вы хотите изменить несколько переменных в одной области, верно? Нет, вам не нужен `StreamBuilder`, но насчёт статических классов вы правы. Что ж, в представлении во Flutter, когда мы хотим изменить конкретный виджет, приходится писать много шаблоного кода. C **GetX** вы можете забыть о шаблонном коде. `StreamBuilder( … )`? `initialValue: …`? `builder: …`? Нет, вам просто нужно поместить эту переменную в виджет `Obx()`. ```dart Obx (() => Text (controller.name)); ``` _Что нужно запомнить?_ Только `Obx(() =>`. Вы просто передаёте этот виджет через стрелочную функцию в `Obx()` ("Observer" в _Rx_). `Obx` довольно умён и изменится только при изменении значения `controller.name`. Если `name` == `"John"`, и вы измените его на `"John"` (`name.value = "John"`), на экране ничего не изменится, так как это то же значение, что и раньше. `Obx` для экономии ресурсов просто проигнорирует новое значение, а не будет перестраивать виджет. **Разве это не потрясающе?** > Итак, что, если у меня есть 5 переменных _Rx_ (observable) в `Obx`? Он просто обновится, когда **любой** из них изменится. > И если у меня есть 30 переменных в классе, когда я обновлю одну, обновятся ли **все** переменные этого класса? Нет, только **конкретный виджет**, который использует эту переменную _Rx_. Итак, **GetX** обновляет экран только тогда, когда переменная _Rx_ меняет свое значение. ``` final isOpen = false.obs; // NOTHING will happen... same value. void onButtonTap() => isOpen.value=false; ``` ### Преимущества **GetX()** поможет вам, когда вам нужен **детальный** контроль над тем, что обновляется. Если вам не нужны уникальные идентификаторы, из-за того что все ваши переменные будут изменены при выполнении, используйте GetBuilder, потому что это простой модуль обновления состояния (как `setState()`), написанный всего в несколько строк кода. Он был сделан простым, чтобы иметь наименьшее влияние на CPU и просто выполнять единственную цель (восстановление состояния), тратя минимально возможные ресурсы. Если вам нужен **мощный** менеджер состояний, то вашим выбором будет **GetX**. Он не работает с переменными, а работает с потоками, все в нем - это `Streams` под капотом. Вы можете использовать _rxDart_ вместе с ним, потому что все это `Streams`, вы можете прослушивать событие каждой «переменной _Rx_», потому что всё в нём - это `Streams`. Это буквально подход _BLoC_, который проще, чем _MobX_, и без генераторов кода и тд. Вы можете превратить что угодно в _"Observable"_ с помощью `.obs`. ### Максимальная производительность: В дополнение к интеллектуальному алгоритму минимальных перестроек, **GetX** использует компараторы, чтобы убедиться, что состояние изменилось. Если вы столкнетесь с ошибками в своем приложении и отправите дублирующее изменение состояния, **GetX** гарантирует, что оно не выйдет из строя. С **GetX** состояние изменяется только при изменении значения. В этом основное отличие между **GetX** и применением _`computed` из MobX_. При объединении двух __observables__, когда один из них изменяется; слушатель этого _observable_ также изменится. В **GetX**, если вы объедините две переменные, `GetX()` (аналогично `Observer()`) будет перестраиваться только в том случае, если это подразумевает реальное изменение состояния. ### Объявление реактивной переменной У вас есть 3 способа превратить переменную в "observable". 1 - Первый использует **`Rx{Type}`**. ```dart // initial value is recommended, but not mandatory final name = RxString(''); final isLogged = RxBool(false); final count = RxInt(0); final balance = RxDouble(0.0); final items = RxList([]); final myMap = RxMap({}); ``` 2 - Второй - использовать **`Rx`** и дженерики `Rx` ```dart final name = Rx(''); final isLogged = Rx(false); final count = Rx(0); final balance = Rx(0.0); final number = Rx(0) final items = Rx>([]); final myMap = Rx>({}); // Custom classes - it can be any class, literally final user = Rx(); ``` 3 - Третий, более практичный, простой и предпочтительный подход, просто добавьте **`.obs`** в качестве свойства вашего значения: ```dart final name = ''.obs; final isLogged = false.obs; final count = 0.obs; final balance = 0.0.obs; final number = 0.obs; final items = [].obs; final myMap = {}.obs; // Custom classes - it can be any class, literally final user = User().obs; ``` ##### Реактивные состояния - это просто. Как мы знаем, _Dart_ сейчас движется в сторону _null safety_. Чтобы быть готовым, с этого момента вы всегда должны начинать свои переменные _Rx_ с **начальным значением**. > Преобразование переменной в _observable_ + _начальное значение_ c **GetX** - самый простой и практичный подход. Вы буквально добавите "`.obs`" в конец своей переменной и **всё**, вы сделали её observable, и её `.value`, будет начальным значением. ### Использование значений в представлении ```dart // controller file final count1 = 0.obs; final count2 = 0.obs; int get sum => count1.value + count2.value; ``` ```dart // view file GetX( builder: (controller) { print("count 1 rebuild"); return Text('${controller.count1.value}'); }, ), GetX( builder: (controller) { print("count 2 rebuild"); return Text('${controller.count2.value}'); }, ), GetX( builder: (controller) { print("count 3 rebuild"); return Text('${controller.sum}'); }, ), ``` Если мы увеличим `count1.value++`, он выведет: - `count 1 rebuild` - `count 3 rebuild` поскольку `count1` имеет значение `1`, а `1 + 0 = 1`, изменяет геттер `sum`. Если мы изменим `count2.value++`, он выведет: - `count 2 rebuild` - `count 3 rebuild` так как `count2.value` изменился, и теперь `sum` равен `2`. - Примечание: По умолчанию самое первое событие перестраивает виджет, даже если это то же значение. Такое поведение существует из-за Boolean переменных. Представьте, что вы сделали это: ```dart var isLogged = false.obs; ``` А затем вы проверили, вошел ли пользователь в систему, чтобы вызвать событие в `ever`. ```dart @override onInit(){ ever(isLogged, fireRoute); isLogged.value = await Preferences.hasToken(); } fireRoute(logged) { if (logged) { Get.off(Home()); } else { Get.off(Login()); } } ``` Если `hasToken` был `false`, `isLogged` не изменится, поэтому `ever()` никогде не будет вызван. Чтобы избежать такого поведения, первое изменение _observable_ всегда будет запускать событие, даже если оно содержит то же самое `.value`. Вы можете убран данное поведение, если хотите, используя: `isLogged.firstRebuild = false;` ### Условия для перестраивания Кроме того, Get обеспечивает усовершенствованный контроль состояния. Вы можете обусловить событие (например, добавление объекта в список) определенным условием. ```dart // First parameter: condition, must return true of false // Second parameter: the new value to aplly if the condition is true list.addIf(item < limit, item); ``` Без украшений, без генератора кода, без сложностей :smile: Вы ведь знаете счётчик Flutter? Ваш класс контроллера может выглядеть так: ```dart class CountController extends GetxController { final count = 0.obs; } ``` С простым: ```dart controller.count.value++ ``` Вы можете обновить переменную счетчика в своем пользовательском интерфейсе, независимо от того, где она хранится. ### Где .obs может быть использован Вы можете преобразовать что угодно в obs. Вот два способа сделать это: * Вы можете преобразовать значения вашего класса в obs ```dart class RxUser { final name = "Camila".obs; final age = 18.obs; } ``` * или вы можете преобразовать весь класс в observable ```dart class User { User({String name, int age}); var name; var age; } // when instantianting: final user = User(name: "Camila", age: 18).obs; ``` ### Примечание о списках Списки полностью наблюдаемы, как и объекты внутри них. Таким образом, если вы добавите значение в список, он автоматически перестроит виджеты, которые его используют. Вам также не нужно использовать ".value" со списками, замечательный API-интерфейс Dart позволяет нам избежать этого. К сожалению, примитивные типы, такие как String и int, не могут быть расширены, что делает использование .value обязательным, но это не будет проблемой, если вы работаете с геттерами и сеттерами для них. ```dart // On the controller final String title = 'User Info:'.obs final list = List().obs; // on the view Text(controller.title.value), // String need to have .value in front of it ListView.builder ( itemCount: controller.list.length // lists don't need it ) ``` Когда вы делаете свои собственные классы наблюдаемыми, есть другой способ их обновить: ```dart // on the model file // we are going to make the entire class observable instead of each attribute class User() { User({this.name = '', this.age = 0}); String name; int age; } // on the controller file final user = User().obs; // when you need to update the user variable: user.update( (user) { // this parameter is the class itself that you want to update user.name = 'Jonny'; user.age = 18; }); // an alternative way of update the user variable: user(User(name: 'João', age: 35)); // on view: Obx(()=> Text("Name ${user.value.name}: Age: ${user.value.age}")) // you can also access the model values without the .value: user().name; // notice that is the user variable, not the class (variable has lowercase u) ``` Вам не нужно работать с наборами, если вы этого не хотите, вы можете использовать API "assign" и "assignAll". API "assign" очистит ваш список и добавит один объект, который вы хотите начать там. API "assignAll" очистит существующий список и добавит любые повторяемые объекты, которые вы в него вставляете. ### Почему мне нужно использовать .value Мы могли бы убрать обязательство использовать 'value' для `String` и `int` с помощью простого оформления и генератора кода, но цель этой библиотеки как раз и состоит в том, чтобы избежать внешних зависимостей. Мы хотим предложить среду, готовую для программирования, включающую в себя самое необходимое (управление маршрутами, зависимостями и состояниями) простым, легким и производительным способом без необходимости во внешнем пакете. Вы можете буквально добавить 3 буквы в свой pubspec (get), двоеточие и начать программировать. Все решения, включенные по умолчанию, от управления маршрутами до управления состоянием, нацелены на простоту, продуктивность и производительность. Общий вес этой библиотеки меньше, чем у одного менеджера состояний, хотя это полное решение, и это то, что вы должны понимать. Если вас беспокоит `.value`, и вам нравится генератор кода, MobX - отличная альтернатива, и вы можете использовать его вместе с Get. Для тех, кто хочет добавить одну зависимость в pubspec и начать программировать, не беспокоясь ни о совместимости версий пакетов, ни об ошибках обновления состояния исходящих от менеджеров состояний или зависимостей и не хочет беспокоиться о доступности контроллеров, а «просто программирование», Get идеален. Если у вас нет проблем с MobX или BLoC, вы можете просто использовать Get для маршрутов и забыть о том, что у него есть менеджер состояний. Простой и реактивный менеджеры состояний Get появились из-за то, что в моей компании был проект с более чем 90 контроллерами, а генератору кода после flutter clean требовалось более 30 минут для выполнения своих задач на достаточно хорошей машине. Если у вас 5, 10, 15 контроллеров, то вам подойдёт любой менеджер состояний. Если у вас абсурдно большой проект и генератор кода является для вас проблемой, то решение прямо перед вами. Очевидно, что если кто-то хочет внести свой вклад в проект и создать генератор кода или что-то подобное, я укажу об этом в readme в качестве альтернативы. Моя потребность не в востребованности для всех разработчиков, я лишь говорю, что есть хорошие решения, которые уже делают это, например, MobX. ### Obx() Bindings в Get необязательны. Вы можете использовать виджет Obx вместо GetX, который получает только анонимную функцию, создающую виджет. Очевидно, что если вы не используете тип, вам потребуется экземпляр вашего контроллера для использования переменных или использовать `Get.find()`.value или Controller.to.value для получения значения. ### Workers Workers помогут вам, инициируя определенные обратные вызовы при возникновении события. ```dart /// Called every time `count1` changes. ever(count1, (_) => print("$_ has been changed")); /// Called only first time the variable $_ is changed once(count1, (_) => print("$_ was changed once")); /// Anti DDos - Called every time the user stops typing for 1 second, for example. debounce(count1, (_) => print("debouce$_"), time: Duration(seconds: 1)); /// Ignore all changes within 1 second. interval(count1, (_) => print("interval $_"), time: Duration(seconds: 1)); ``` У всех workers (кроме `debounce`) есть именованный параметр `condition`, которое может быть `bool` или обратным вызовом возвращающим `bool`. Этот `condition` определяет, когда выполняется функция обратного вызова. Все workers возвращают экземпляр `Worker`, который можно использовать для отмены (с помощью метода `dispose()`) worker. - **`ever`** вызывается каждый раз, когда переменная _Rx_ выдает новое значение. - **`everAll`** как `ever`, но он принимает `List` значений _Rx_ вызываемый каждый раз, когда его переменная изменяется. Вот и всё. - **`once`** 'once' вызывается только при первом изменении переменной. - **`debounce`** 'debounce' очень полезен в функциях поиска, где вы хотите, чтобы API вызывался только тогда, когда пользователь заканчивает ввод. Если пользователь вводит "Jonny", у вас будет 5 поисковых запросов в API по буквам J, o, n, n и y. С Get этого не происходит, потому что у вас будет "debounce" Worker, который будет запускаться только в конце набора. - **`interval`** 'interval' отличается от `debouce`. В `debouce`, если пользователь внесёт 1000 изменений в переменную в течение 1 секунды, он отправит только последнее после установленного таймера (по умолчанию 800 миллисекунд). Вместо этого Interval будет игнорировать все действия пользователя в течение указанного периода. Если вы отправляете события в течение 1 минуты, 1000 в секунду, debounce отправит вам только последнее, когда пользователь прекратит стрелять событиями. Interval будет доставлять события каждую секунду, а если установлен на 3 секунды, то он будет доставлять 20 событий в эту минуту. Это рекомендуется во избежание злоупотреблений в функциях, где пользователь может быстро щелкнуть что-либо и получить некоторое преимущество (представьте, что пользователь может зарабатывать монеты, щелкая что-либо, если он щелкнет 300 раз за ту же минуту, у него будет 300 монет, используя интервал, вы можете установить временной интервал на 3 секунды, и даже если щелкнуть 300 или тысячу раз, максимум, который он получит за 1 минуту, составит 20 монет, щелкнув 300 или 1 миллион раз). Debounce подходит для защиты от DDos, для таких функций, как поиск, где каждое изменение onChange будет вызывать запрос к вашему API. Debounce будет ждать, пока пользователь перестанет вводить имя, чтобы сделать запрос. Если бы он использовался в вышеупомянутом сценарии с монетами, пользователь накликал бы только 1 монету, потому что он выполняется только тогда, когда пользователь "делает паузу" на установленное время. - ПРИМЕЧАНИЕ: должны всегда использоваться при запуске контроллера или класса, поэтому он всегда должен быть в onInit (рекомендуется), в конструкторе класса или в initState StatefulWidget (в большинстве случаев эта практика не рекомендуется, но не должна иметь побочных эффектов). ## Обычное управление состоянием Get имеет чрезвычайно легкий и простой менеджер состояний, который не использует ChangeNotifier, удовлетворит потребности, особенно для тех, кто плохо знаком с Flutter, и не вызовет проблем для больших приложений. GetBuilder нацелен именно на контроль нескольких состояний. Представьте, что вы добавили 30 продуктов в корзину, вы нажимаете удалить один, одновременно с этим обновляется список, обновляется цена, а значок в корзине покупок обновляется до меньшего числа. Такой подход делает GetBuilder убийственным, потому что он группирует состояния и изменяет их все сразу без какой-либо "вычислительной логики" для этого. GetBuilder был создан с учётом такого рода ситуаций, поскольку для временного изменения состояния вы можете использовать setState, и для этого вам не понадобится менеджер состояний. Таким образом, если вам нужен отдельный контроллер, вы можете назначить для него идентификаторы или использовать GetX. Это зависит от вас, помните, что чем больше у вас «индивидуальных» виджетов, тем больше ресурсов будет забирать GetX, в то время как производительность GetBuilder должна быть выше при многократном изменении состояния. ### Преимущества 1. Обновляйте только необходимые виджеты. 2. Не используйте changeNotifier, это менеджер состояний, который использует меньше памяти (около 0 МБ). 3. Забудьте о StatefulWidget! С Get он больше не понадобится. С другими менеджерами состояний вам, вероятно, придется использовать StatefulWidget, чтобы получить экземпляр вашего Provider, BLoC, MobX Controller и т.д. Но задумывались ли вы когда-нибудь о том, что ваш AppBar, Scaffold и большинство виджетов в вашем классе не имеют состояния и по сути являются Stateless? Так зачем хранить состояние всего класса, если можно хранить только состояние виджета, которые истинно Stateful? Get решает и эту проблему. Создавайте классы Stateless, всё делайте stateless. Если вам нужно обновить один компонент, просто оберните его GetBuilder. 4. Организуйте свой проект по-настоящему! Контроллеры не должны быть в вашем пользовательском интерфейсе, поместите ваш TextEditController или любой контроллер, который вы используете, в свой класс Controller. 5. Вам нужно инициировать событие для обновления виджета, как только он будет отрисован? GetBuilder имеет свойство initState, как и StatefulWidget, и вы можете вызывать события вашего контроллера прямо из него. 6. Вам необходимо инициировать такие действия как закрытия потоков, таймеров и т.д.? GetBuilder также имеет свойство dispose, с помощью которого вы можете вызывать события, как только этот виджет будет уничтожен. 7. Используйте потоки только при необходимости. Вы можете использовать свои StreamControllers внутри своего контроллера в обычном режиме, а также использовать StreamBuilder как обычно, но помните, что поток разумно потребляет память и реактивное программирование - это прекрасно, но вы не должны злоупотреблять этим. 30 потоков, открытых одновременно, может быть хуже, чем changeNotifier (а changeNotifier - очень плохо). 8. Обновляйте виджеты, не тратя на это оперативную память. Get сохраняет только идентификатор создателя GetBuilder и обновляет этот GetBuilder при необходимости. Потребление памяти для хранения идентификатора get в памяти очень низкое даже для тысяч GetBuilders. Когда вы создаете новый GetBuilder, вы фактически передаёте состояние GetBuilder, у которого есть идентификатор создателя. Новое состояние не создается для каждого GetBuilder, что экономит МНОГО ОЗУ для больших приложений. В основном ваше приложение будет полностью Stateless, и несколько виджетов, которые Stateful (при помощи GetBuilder), будут иметь общее состояние, и поэтому обновление обного обновит их всех. Состояние всего одно. 9. Get - всеведущий и в большинстве случаев точно знает, в какое время нужно извлечь контроллер из памяти. Вам не следует беспокоиться о том, когда утилизировать контроллер, Get знает, когда это сделать. ### Использование ```dart // Create controller class and extends GetxController class Controller extends GetxController { int counter = 0; void increment() { counter++; update(); // use update() to update counter variable on UI when increment be called } } // On your Stateless/Stateful class, use GetBuilder to update Text when increment be called GetBuilder( init: Controller(), // INIT IT ONLY THE FIRST TIME builder: (_) => Text( '${_.counter}', ), ) //Initialize your controller only the first time. The second time you are using ReBuilder for the same controller, do not use it again. Your controller will be automatically removed from memory as soon as the widget that marked it as 'init' is deployed. You don't have to worry about that, Get will do it automatically, just make sure you don't start the same controller twice. ``` **Готово!** - Вы уже узнали, как управлять состояниями с помощью Get. - Примечание: Возможно, вам нужна более крупная организация и не использовать свойство init. Для этого вы можете создать класс и расширить класс Bindings, указав в нем контроллеры, которые будут созданы в рамках этого маршрута. Контроллеры не будут создаваться в это время, наоборот, это просто инструкция, так что при первом использовании контроллера Get будет знать, где его искать. Get останется lazyLoad и продолжит удалять контроллеры, когда они больше не нужны. Смотрите пример в pub.dev, чтобы увидеть, как это работает. Если вы перемещаетесь по многим маршрутам и вам нужны данные, которые были в вашем ранее используемом контроллере, вам просто нужно использовать снова GetBuilder (без инициализации): ```dart class OtherClass extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( body: Center( child: GetBuilder( builder: (s) => Text('${s.counter}'), ), ), ); } ``` Если вам нужно использовать свой контроллер во многих других местах и ​​за пределами GetBuilder, просто создайте get в своем контроллере и легко его получите. (или используйте `Get.find()`) ```dart class Controller extends GetxController { /// You do not need that. I recommend using it just for ease of syntax. /// with static method: Controller.to.increment(); /// with no static method: Get.find().increment(); /// There is no difference in performance, nor any side effect of using either syntax. Only one does not need the type, and the other the IDE will autocomplete it. static Controller get to => Get.find(); // add this line int counter = 0; void increment() { counter++; update(); } } ``` И тогда вы можете напрямую получить доступ к своему контроллеру: ```dart FloatingActionButton( onPressed: () { Controller.to.increment(), } // This is incredibly simple! child: Text("${Controller.to.counter}"), ), ``` Когда вы нажимаете FloatingActionButton, все виджеты, которые прослушивают переменную 'counter', будут обновлены автоматически. ### Как обрабатываются контроллеры Допустим, у нас есть это: `Class a => Class B (has controller X) => Class C (has controller X)` В классе A контроллер ещё не находится в памяти, потому что вы его ещё не использовали (Get is lazyLoad). В классе B вы использовали контроллер, и он вошёл в память. В классе C вы использовали тот же контроллер, что и в классе B, Get будет разделять состояние контроллера B с контроллером C, и тот же контроллер всё ещё находится в памяти. Если вы закроете экран C и экран B, Get автоматически извлечёт контроллер X из памяти и освободит ресурсы, поскольку класс A не использует контроллер. Если вы снова перейдете к B, контроллер X снова войдет в память, если вместо перехода к классу C вы снова вернётесь к классу A, Get таким же образом выведет контроллер из памяти. Если класс C не использовал контроллер, а вы вынули класс B из памяти, ни один класс не будет использовать контроллер X, и, соответственно, он будет удален. Единственное исключение, которое может случиться с Get, - это если вы неожиданно удалите B из маршрута и попытаетесь использовать контроллер в C. В этом случае идентификатор создателя контроллера, который был в B, был удален, и Get был запрограммирован на удаление его из памяти каждого контроллера, у которого нет идентификатора создателя. Если вы намереваетесь сделать это, добавьте флаг "autoRemove: false" в GetBuilder класса B GetBuilder и используйте adoptID = true в GetBuilder класса C. ### Вам больше не понадобятся StatefulWidgets Использование StatefulWidgets означает ненужное сохранение состояния всех экранов, даже если вам нужно минимально перестроить виджет, вы встроите его в Consumer/Observer/BlocProvider/GetBuilder/GetX/Obx, которые будет ещё одним StatefulWidget. Класс StatefulWidget - это класс большего размера, чем StatelessWidget, который будет выделять больше оперативной памяти, и это может не иметь существенного значения между одним или двумя классами, но, безусловно, будет иметь место, когда у вас их 100! Если вам не нужно использовать миксин, например TickerProviderStateMixin, использовать StatefulWidget с Get совершенно не нужно. Вы можете вызывать все методы StatefulWidget прямо из GetBuilder. Например, если вам нужно вызвать метод initState() или dispose(), вы можете вызвать их напрямую; ```dart GetBuilder( initState: (_) => Controller.to.fetchApi(), dispose: (_) => Controller.to.closeStreams(), builder: (s) => Text('${s.username}'), ), ``` Гораздо лучший подход, чем этот, - использовать методы onInit() и onClose() непосредственно из вашего контроллера. ```dart @override void onInit() { fetchApi(); super.onInit(); } ``` - ПРИМЕЧАНИЕ: Если вы хотите запустить метод в момент первого вызова контроллера, вам НЕ НУЖНО использовать для этого конструкторы, на самом деле, используя ориентированный на производительность пакет, такой как Get, это граничит с плохой практикой, потому что отклоняется от логики, в которой контроллеры создаются или выделяются (если вы создаете экземпляр этого контроллера, конструктор будет вызываться немедленно, вы будете заполнять контроллер ещё до того, как он будет использован, вы выделяете память, не используя её , это определенно вредит принципам этой библиотеки). Методы onInit(); и onClose(); были созданы для этого, они будут вызываться при создании Контроллера или использоваться в первый раз, в зависимости от того, используете вы Get.lazyPut или нет. Если вы хотите, например, вызвать ваш API для заполнения данных, вы можете забыть о старомодном методе initState/dispose, просто начните свой вызов api в onInit, и если вам нужно выполнить любую команду как закрытие потоков, используйте для этого onClose(). ### Почему это существует Цель этого пакета - предоставить вам законченное решение для навигации по маршрутам, управления зависимостями и состояниями с использованием минимально возможных зависимостей с высокой степенью разделения. Get включает в себя все высокоуровневые и низкоуровневые API-интерфейсы Flutter, чтобы гарантировать, что вы работаете с наименьшими взаимозависимостями. Мы централизуем всё в одном пакете, чтобы гарантировать, что у вас нет никакой взаимозависимости в вашем проекте. Таким образом, вы можете поместить в свое представление только виджеты и оставить часть своей команды, которая работает с бизнес-логикой, свободной, чтобы работать с бизнес-логикой независимо от какого-либо элемента представления. Это обеспечивает гораздо более чистую рабочую среду, так что часть вашей команды работает только с виджетами, не беспокоясь об отправке данных на ваш контроллер, а часть вашей команды работает только с бизнес-логикой, независимо от какого-либо элемента представления. Итак, чтобы упростить это: вам не нужно вызывать методы в initState и отправлять их по параметрам на ваш контроллер или использовать для этого конструктор вашего контроллера, у вас есть метод onInit (), который вызывается в нужное время, чтобы вы могли начать ваши сервисы. Вам не нужно вызывать устройство, у вас есть метод onClose(), который будет вызываться именно в тот момент, когда ваш контроллер больше не нужен и будет удален из памяти. Таким образом, оставьте представления только для виджетов, воздерживаясь от какой-либо бизнес-логики. Не вызывайте метод удаления внутри GetxController, он ничего не сделает, помните, что контроллер не является виджетом, его не следует "удалять", и он будет автоматически и разумно удалён из памяти с помощью Get. Если вы использовали какой-либо поток и хотите закрыть его, просто вставьте его в метод close. Пример: ```dart class Controller extends GetxController { StreamController user = StreamController(); StreamController name = StreamController(); /// close stream = onClose method, not dispose. @override void onClose() { user.close(); name.close(); super.onClose(); } } ``` Жизненный цикл контроллера: - onInit() когда он создается. - onClose() когда он закрывается для внесения каких-либо изменений при подготовке к удалению. - удалено: у вас нет доступа к этому API, потому что он буквально удаляет контроллер из памяти. Он буквально удаляется, не оставляя следов. ### Другие способы использования Вы можете использовать экземпляр контроллера непосредственно со значением GetBuilder: ```dart GetBuilder( init: Controller(), builder: (value) => Text( '${value.counter}', //here ), ), ``` Вам также может понадобиться экземпляр вашего контроллера вне GetBuilder, и вы можете использовать эти подходы для достижения этой цели: ```dart class Controller extends GetxController { static Controller get to => Get.find(); [...] } // on you view: GetBuilder( init: Controller(), // use it only first time on each controller builder: (_) => Text( '${Controller.to.counter}', //here ) ), ``` или ```dart class Controller extends GetxController { // static Controller get to => Get.find(); // with no static get [...] } // on stateful/stateless class GetBuilder( init: Controller(), // use it only first time on each controller builder: (_) => Text( '${Get.find().counter}', //here ), ), ``` - Для этого можно использовать "неканоничные" подходы. Если вы используете какой-либо другой менеджер зависимостей, например get_it, modular и т.д., и просто хотите доставить экземпляр контроллера, вы можете сделать это: ```dart Controller controller = Controller(); [...] GetBuilder( init: controller, //here builder: (_) => Text( '${controller.counter}', // here ), ), ``` ### Уникальные идентификаторы Если вы хотите уточнить элемент управления обновлением виджета с помощью GetBuilder, вы можете назначить им уникальные идентификаторы: ```dart GetBuilder( id: 'text' init: Controller(), // use it only first time on each controller builder: (_) => Text( '${Get.find().counter}', //here ), ), ``` И обновите это следующим образом: ```dart update(['text']); ``` Также можно наложить условия на обновление: ```dart update(['text'], counter < 10); ``` GetX делает это автоматически и восстанавливает только виджет, который использует точную переменную, которая была изменена, если вы измените переменную на ту же самую, что и предыдущая, и это не означает изменения состояния, GetX не будет перестраивать виджет для экономии памяти и CPU (На экране отображается 3, и вы снова меняете переменную на 3. В большинстве менеджеров состояний это вызовет новую перестройку, но с GetX виджет будет перестраиваться снова только в том случае, если на самом деле его состояние изменилось). ## Смешивание двух менеджеров состояний Некоторые открыли запрос, так как они хотели использовать только один тип реактивной переменной и другой механизм, и для этого нужно было вставить Obx в GetBuilder. Подумав об этом, был создан MixinBuilder. Он позволяет как реактивные изменения путем изменения переменных ".obs", так и механические обновления через update(). Однако из 4 виджетов он - тот, который потребляет больше всего ресурсов, поскольку помимо подписки на получение событий изменений от своих дочерних элементов, он подписывается на метод обновления своего контроллера. Расширение GetxController важно, поскольку у них есть жизненные циклы, и они могут "запускать" и "завершать" события в своих методах onInit() и onClose(). Вы можете использовать для этого любой класс, но я настоятельно рекомендую вам использовать класс GetxController для размещения ваших переменных, независимо от того, наблюдаемы они или нет. ## GetBuilder vs GetX vs Obx vs MixinBuilder За десять лет работы с программированием я смог извлечь несколько ценных уроков. Мой первый контакт с реактивным программированием был таким: "Вау, это невероятно", и на самом деле реактивное программирование невероятно. Однако он подходит не для всех ситуаций. Часто всё, что вам нужно, - это изменить состояние 2 или 3 виджетов одновременно или кратковременное изменение состояния, и в этом случае реактивный подход неплох, но не подходит. Реактивное программирование требует более высокого потребления оперативной памяти, что может быть компенсировано отдельным рабочим процессом, который гарантирует, что только один виджет будет перестроен и при необходимости, но создание списка из 80 объектов, каждый с несколькими потоками, не является хорошей идеей. Откройте dart inspect и проверьте, сколько потребляет StreamBuilder, и вы поймёте, что я пытаюсь вам сказать. Имея это в виду, я создал простой менеджер состояний. Это просто, и это именно то, что вы должны от него требовать: обновление состояния в блоках простым и наиболее экономичным способом. GetBuilder очень экономичен в оперативной памяти, и вряд ли существует более экономичный подход, чем он (по крайней мере, я не могу представить его, если он существует, сообщите нам). Однако GetBuilder по-прежнему является механическим менеджером состояний, вам нужно вызвать update() так же, как вам нужно было бы вызвать notifyListeners() провайдера. Бывают и другие ситуации, когда реактивное программирование действительно интересно, и не работать с ним - все равно, что изобретать колесо. Имея это в виду, GetX был создан, чтобы предоставить всё самое современное и продвинутое в менеджере состояний. Он обновляет только то, что необходимо, и при необходимости, если у вас есть ошибка и вы отправляете 300 изменений состояния одновременно, GetX будет фильтровать и обновлять экран только в том случае, если состояние действительно изменяется. GetX по-прежнему более экономичен, чем любой другой менеджер реактивного состояния, но он потребляет немного больше оперативной памяти, чем GetBuilder. Думая об этом и стремясь максимизировать потребление ресурсов, Obx был создан. В отличие от GetX и GetBuilder, вы не сможете инициализировать контроллер внутри Obx, это просто виджет с StreamSubscription, который получает события изменения от ваших детей, вот и всё. Он более экономичен, чем GetX, но проигрывает GetBuilder, что и следовало ожидать, поскольку он является реактивным, а GetBuilder имеет самый упрощенный подход к хранению хэш-кода виджета и его StateSetter. С Obx вам не нужно писать свой тип контроллера, и вы можете услышать изменение от нескольких разных контроллеров, но его необходимо инициализировать перед этим, используя примерный подход в начале этого файла readme или используя класс Bindings. ================================================ FILE: documentation/tr_TR/dependency_management.md ================================================ # Dependency Management - [Dependency Management](#dependency-management) - [Örnek Metodlar](#örnek-metodlar) - [Get.put()](#getput) - [Get.lazyPut](#getlazyput) - [Get.putAsync](#getputasync) - [Get.create](#getcreate) - [Metodların/Sınıfların örneklerinin kullanılması](#metodların/sınıfların-örneklerinin-kullanılması) - [Alternatif bir instance tanımlama](#alternatif-bir-instance-tanımlama) - [Metodlar arasındaki farklılıklar](#metodlar-arasındaki-farklılıklar) - [Bindings](#bindings) - [Bindings class](#bindings-class) - [BindingsBuilder](#bindingsbuilder) - [SmartManagement](#smartmanagement) - [Nasıl değiştirilir?](#nasıl-değiştirilir) - [SmartManagement.full](#smartmanagementfull) - [SmartManagement.onlyBuilder](#smartmanagementonlybuilder) - [SmartManagement.keepFactory](#smartmanagementkeepfactory) - [Nasıl bindings yapılır?](#nasıl-bindings-yapılır) - [Notlar](#notlar) Get, yalnızca 1 satır kodla, Provider context'i olmadan, inheritedWidget olmadan Bloc veya Controller ile aynı sınıfı almanızı sağlayan basit ve güçlü bir dependency manager'a (bağımlılık yöneticisine) sahiptir: ```dart Controller controller = Get.put(Controller()); // Rather Controller controller = Controller(); ``` Sınıfınızı kullandığınız sınıf içinde somutlaştırmak yerine, onu uygulamanız genelinde kullanılabilir hale getirecek olan Get örneğinde somutlaştırıyorsunuz. Böylece denetleyicinizi (veya Bloc sınıfını) normal şekilde kullanabilirsiniz. - Not: Get's State Manager kullanıyorsanız, view'e controller'ı bağlamayı kolaylaştıracak olan [Bindings](#bindings) API'sine daha fazla dikkat edin. - Not²: Get dependency management (bağımlılık yönetimi) paketin diğer bölümlerinden ayrılmıştır, bu nedenle örneğin uygulamanız zaten bir state manager (durum yöneticisi) kullanıyorsa (herhangi biri, önemli değil), bunu değiştirmeniz gerekmez, dependency injection (bağımlılık enjeksiyonunu) kullanabilirsiniz. ## Örnek metodlar Metodlar ve configurable parameters (yapılandırılabilir parametreleri) şunlardır: ### Get.put() Dependency (bağımlılık) eklemenin en yaygın yolu. Örneğin; ```dart Get.put(SomeClass()); Get.put(LoginController(), permanent: true); Get.put(ListItemController, tag: "some unique string"); ``` Put kullanırken ayarlayabileceğiniz tüm seçenekler şunlardır: ```dart Get.put( // Zorunlu: Controller veya herhangi bir şey gibi kaydetmek istediğiniz sınıf // not: "S", herhangi bir türde bir sınıf olabileceği anlamına gelir S dependency // isteğe bağlı: bu, aynı türden birden çok sınıf içindir // normalde Get.find() kullanarak bir sınıf aldığınız için, // hangi örneğe ihtiyacınız olduğunu söylemek için "tag" kullanmanız gerekir // benzersiz dize olmalıdır String tag, // isteğe bağlı: varsayılan olarak, get artık kullanılmadıktan sonra örnekleri elden çıkarır (örneğin, // gizli bir view'in controller'ı), ancak instance'a ihtiyacınız olabilir // tüm uygulama boyunca orada tutulacak, Shared Preferences örneği veya başka bir şey gibi // yani bunu kullanıyorsun // varsayılan olarak false bool permanent = false, // isteğe bağlı: bir testte abstract(soyut) bir sınıf kullandıktan sonra, onu başka bir sınıfla değiştirmenize ve testi takip etmenize olanak tanır. // varsayılan olarak false bool overrideAbstract = false, // optional: allows you to create the dependency using function instead of the dependency itself. //isteğe bağlı: dependency'nin(bağımlılığın) kendisi yerine fonksiyonu kullanarak dependency(bağımlılık) oluşturmanıza olanak tanır. //bu yaygın olarak kullanılmaz InstanceBuilderCallback builder, ) ``` ### Get.lazyPut Bir bağımlılığı lazyLoad ile yalnızca kullanıldığında somutlaştırılacak şekilde yüklemek mümkündür. Hesaplamalı expensive sınıflar için veya birkaç sınıfı tek bir yerde başlatmak istiyorsanız (Bindings sınıfında olduğu gibi) çok kullanışlıdır ve o zaman o sınıfı kullanmayacağınızı bilirsiniz. ```dart /// ApiMock yalnızca Get.find'u ilk kez kullandığında çağrılacak Get.lazyPut(() => ApiMock()); Get.lazyPut( () { // ... gerekirse biraz mantık return FirebaseAuth(); }, tag: Math.random().toString(), fenix: true ) Get.lazyPut( () => Controller() ) ``` lazyPut'u kullanırken ayarlayabileceğiniz tüm seçenekler şunlardır: ```dart Get.lazyPut( // zorunlu: sınıfınız ilk kez çağrıldığında yürütülecek bir yöntem InstanceBuilderCallback builder, // isteğe bağlı: Get.put() ile aynı, aynı sınıfın birden çok farklı örneğini istediğinizde kullanılır // unique olmalı String tag, // isteğe bağlı: "Kalıcı" ile benzerdir, aradaki fark, instance şu durumlarda atılmasıdır. // kullanılmıyor, ancak tekrar kullanılması gerektiğinde Get, instance yeniden oluşturacak //bindings api'sindeki "SmartManagement.keepFactory" ile aynı // varsayılan olarak false bool fenix = false ) ``` ### Get.putAsync Eşzamansız bir instance kaydetmek istiyorsanız, `Get.putAsync` kullanabilirsiniz: ```dart Get.putAsync(() async { final prefs = await SharedPreferences.getInstance(); await prefs.setInt('counter', 12345); return prefs; }); Get.putAsync( () async => await YourAsyncClass() ) ``` putAsync kullanırken ayarlayabileceğiniz tüm seçenekler şunlardır: ```dart Get.putAsync( // zorunlu: sınıfınızın instance'ını oluşturmak için yürütülecek bir asenkron metod AsyncInstanceBuilderCallback builder, //isteğe bağlı: Get.put() ile aynı, aynı sınıfın birden çok farklı örneğini istediğinizde kullanılır // unique olmalı String tag, // isteğe bağlı: Get.put() ile aynı, bu instance tüm uygulamada canlı tutmanız gerektiğinde kullanılır // varsayılan olarak false bool permanent = false ) ``` ### Get.create Bu zor. Bunun ne olduğuna ve diğeri arasındaki farklara ilişkin ayrıntılı bir açıklama, [Differences between methods:](#differences-between-methods) bölümünde bulunabilir. ```dart Get.Create(() => SomeClass()); Get.Create(() => LoginController()); ``` Oluştururken ayarlayabileceğiniz tüm seçenekler şunlardır: ```dart Get.create( // gerekli: her seferinde "fabrikasyon" olacak bir sınıf döndüren bir işlev // `Get.find()` çağrılır // Örnek: Get.create(() => YourClass()) FcBuilderFunc builder, // isteğe bağlı: tıpkı Get.put() gibi, ancak birden çok örneğe ihtiyacınız olduğunda kullanılır // aynı sınıftan bir // Her öğenin kendi denetleyicisine ihtiyaç duyduğu bir listeniz varsa kullanışlıdır // benzersiz bir dize olması gerekir. String name, // isteğe bağlı: tıpkı int`Get.put()` gibi, // tüm uygulama boyunca canlı örnek. Fark Get.create'de // kalıcı, varsayılan olarak doğrudur bool permanent = true ``` ## Metodların/Sınıfların örneklerinin kullanılması Çok sayıda rotada gezindiğinizi ve kontrol cihazınızda geride bırakılan bir veriye ihtiyacınız olduğunu hayal edin, Sağlayıcı veya Get_it ile birleştirilmiş bir durum yöneticisine ihtiyacınız olacak, değil mi? Get ile değil. Controller için "find" seçeneğini sormanız yeterlidir, herhangi bir ek bağımlılığa ihtiyacınız yoktur: ```dart final controller = Get.find(); // veya Controller controller = Get.find(); // Evet, sihir gibi görünüyor, Controller'ı(Denetleyicinizi) bulacak ve size teslim edecek. // Instance edilmiş 1 milyon controller'a sahip olabilirsiniz, Get size her zaman doğru controller'ı verecektir. ``` Ve sonra orada elde edilen controller ile verilerinizi kurtarabileceksiniz: ```dart Text(controller.textFromApi); ``` Döndürülen değer normal bir sınıf olduğundan, istediğiniz her şeyi yapabilirsiniz: ```dart int count = Get.find().getInt('counter'); print(count); // out: 12345 ``` Get örneğini kaldırmak için: ```dart Get.delete(); //genellikle bunu yapmanız gerekmez çünkü GetX kullanılmayan controller'ları(denetleyicileri) zaten siler ``` ## Alternatif bir instance tanımlama Şu anda eklenen bir örnek, `replace` veya `lazyReplace` yöntemi kullanılarak benzer veya genişletilmiş bir sınıf örneğiyle değiştirilebilir. Bu daha sonra özgün sınıf kullanılarak alınabilir. ```dart abstract class BaseClass {} class ParentClass extends BaseClass {} class ChildClass extends ParentClass { bool isChild = true; } Get.put(ParentClass()); Get.replace(ChildClass()); final instance = Get.find(); print(instance is ChildClass); //true class OtherClass extends BaseClass {} Get.lazyReplace(() => OtherClass()); final instance = Get.find(); print(instance is ChildClass); // false print(instance is OtherClass); //true ``` ## Metodlar arasındaki farklılıklar İlk olarak Get.lazyPut'un `fenix`i ve diğer yöntemlerin `permanent`'larından bahsedelim. `permanent` ve `fenix` arasındaki temel fark, örneklerinizi nasıl depolamak istediğinizdir. Güçlendirme: Varsayılan olarak GetX, kullanımda değilken örnekleri siler. Bunun anlamı: Ekran 1'de controller 1 varsa ve ekran 2'de controller 2 varsa ve ilk rotayı stackten kaldırırsanız (`Get.off()` veya `Get.offNamed()` kullanıyorsanız) controller(denetleyici) 1 kaybolur kullanımı silinecektir. Ancak `permanent:true` kullanmayı tercih etmek istiyorsanız, bu geçişte controller kaybolmaz - bu, tüm uygulama boyunca canlı tutmak istediğiniz hizmetler için çok yararlıdır. `fenix`ise ekran değişiklikleri arasında kaybetme endişesi duymadığınız ancak o hizmete ihtiyaç duyduğunuzda canlı olmasını beklediğiniz hizmetler içindir. Temel olarak, kullanılmayan controller/service/class elden çıkaracak, ancak ihtiyacınız olduğunda yeni bir örneği "küllerden yeniden yaratacaktır". Metodlar arasındaki farklarla devam edelim: - Get.put ve Get.putAsync, ikincisinin eşzamansız bir yöntem kullanması farkıyla aynı oluşturma sırasını takip eder: bu iki yöntem, örneği oluşturur ve başlatır. Bu, `permanent: false` ve `isSingleton: true` parametreleriyle `insert` dahili yöntemi kullanılarak doğrudan belleğe eklenir (bu isSingleton parametresinin tek amacı, "bağımlılık" bağımlılığını kullanıp kullanmayacağını söylemektir. veya `FcBuilderFunc` bağımlılığını kullanacaksa). Bundan sonra, bellekteki örnekleri hemen başlatan `Get.find()` çağrılır. - Get.create: Adından da anlaşılacağı gibi, dependency'i (bağımlılığı) "create(oluşturacak)"! `Get.put()`a benzer şekilde, örneklemeye `insert` dahili yöntemini de çağırır. Ancak `permanent` doğru oldu ve`isSingleton` yanlış oldu (bağımlılığımızı "creating", bunun tek bir örnek olmasının bir yolu yok, bu yüzden yanlış). Ve `permanent: true` olduğu için, varsayılan olarak ekranlar arasında kaybetmeme avantajına sahibiz! Ayrıca `Get.find()` hemen çağrılmaz, çağrılacak ekranda kullanılmayı bekler. `permanent` parametresini kullanmak için bu şekilde yaratılmıştır, o zamandan beri, fark edilmeye değer `Get.create()`, örneğin bir bu liste için benzersiz bir örnek istiyorsanız - bu nedenle Get.create GetWidget ile birlikte kullanılmalıdır. - Get.lazyPut: Adından da anlaşılacağı gibi tembel bir işlemdir. Örnek yaratılır, ancak hemen kullanılmak üzere çağrılmaz, çağrılmayı bekler. Diğer yöntemlerin aksine burada `insert` denilmez. Bunun yerine, instance hafızanın başka bir bölümüne, örneğin yeniden oluşturulup oluşturulamayacağını söylemekle sorumlu bir kısma eklenir, buna "factory" diyelim. Daha sonra kullanılmak üzere bir şey yaratmak istersek, şu anda kullanılanlarla karıştırılmayacak. Ve işte burada `fenix` sihirleri devreye giriyor: `fenix: false` bırakmayı seçerseniz ve `smartManagement`ınız `keepFactory` değilse, o zaman `Get.find` kullanılırken örnek bellekteki yeri değiştirecektir. "factory"den ortak örnek bellek alanına. Bundan hemen sonra, varsayılan olarak "factory"den kaldırılır. Şimdi, `fenix: true` seçeneğini seçerseniz, örnek bu özel bölümde var olmaya devam eder, hatta gelecekte tekrar çağrılmak üzere ortak alana gider. ## Bindings Bu paketin en büyük farklılıklarından biri, belki de route'ların, state manager'in(durum yöneticisinin) ve dependency manager(bağımlılık yöneticisinin) tam entegrasyonu olasılığıdır. Stackten bir rota kaldırıldığında, onunla ilgili tüm controller'lar, değişkenler ve nesne örnekleri bellekten kaldırılır. Streams(Akışlar) veya timers(zamanlayıcılar) kullanıyorsanız, bunlar otomatik olarak kapatılır ve bunların hiçbiri için endişelenmenize gerek yoktur. 2.10 sürümünde Bindings API'sini tamamen uygulayın. Artık init metodunu kullanmanıza gerek yok. İstemiyorsanız controller yazmanız bile gerekmez. Bunun için uygun yerde controller ve servislerinizi başlatabilirsiniz. Binding sınıfı, state manager(durum yöneticisine) ve dependency manager(bağımlılık yöneticisine) giden rotaları "binding" ederken, dependency injection(bağımlılık enjeksiyonunu) ayıracak bir sınıftır. Bu, belirli bir controller(denetleyici) kullanıldığında hangi ekranın görüntülenmekte olduğunu ve bunun nerede ve nasıl imha edileceğini bilmenizi sağlar. Ayrıca Binding sınıfı, SmartManager yapılandırma kontrolüne sahip olmanızı sağlar. Stackten bir rota kaldırılırken veya onu kullanan pencere öğesi düzenlendiğinde veya hiçbirini yapmadığında düzenlenecek bağımlılıkları yapılandırabilirsiniz. Sizin için çalışan intelligent dependency management(akıllı bağımlılık yönetimine) sahip olacaksınız, ancak buna rağmen istediğiniz gibi yapılandırabilirsiniz. ### Bindings class - Bir sınıf oluşturun ve Binding'i uygulayın ```dart class HomeBinding implements Bindings {} ``` IDE'niz otomatik olarak sizden "dependencies(bağımlılıklar)" metodunu geçersiz kılmanızı isteyecektir ve sadece lambaya tıklamanız, metodu geçersiz kılmanız ve o rotada kullanacağınız tüm sınıfları eklemeniz yeterlidir: ```dart class HomeBinding implements Bindings { @override void dependencies() { Get.lazyPut(() => HomeController()); Get.put(()=> Api()); } } class DetailsBinding implements Bindings { @override void dependencies() { Get.lazyPut(() => DetailsController()); } } ``` Şimdi sadece rotanızı, route manager(rota yöneticisi), dependencies(bağımlılıklar) ve states(durumlar) arasında bağlantı kurmak için bu binding'i kullanacağınızı bildirmeniz gerekiyor. - Adlandırılmış yolları kullanma: ```dart getPages: [ GetPage( name: '/', page: () => HomeView(), binding: HomeBinding(), ), GetPage( name: '/details', page: () => DetailsView(), binding: DetailsBinding(), ), ]; ``` - Normal yolları kullanma: ```dart Get.to(Home(), binding: HomeBinding()); Get.to(DetailsView(), binding: DetailsBinding()) ``` Orada, artık uygulamanızın bellek yönetimi konusunda endişelenmenize gerek yok, Get bunu sizin için yapacak. Bir rota çağrıldığında Binding sınıfı çağrılır, oluşturulacak tüm bağımlılıkları eklemek için GetMaterialApp'ınızda bir "initialBinding" oluşturabilirsiniz. ```dart GetMaterialApp( initialBinding: SampleBind(), home: Home(), ); ``` ### BindingsBuilder Binding oluşturmanın varsayılan yolu, Binding'leri uygulayan bir sınıf oluşturmaktır. Ancak alternatif olarak, istediğiniz her şeyi somutlaştırmak için bir işlevi kullanabilmeniz için `BindingsBuilder` callback kullanabilirsiniz. Örnek: ```dart getPages: [ GetPage( name: '/', page: () => HomeView(), binding: BindingsBuilder(() { Get.lazyPut(() => ControllerX()); Get.put(()=> Api()); }), ), GetPage( name: '/details', page: () => DetailsView(), binding: BindingsBuilder(() { Get.lazyPut(() => DetailsController()); }), ), ]; ``` Bu şekilde, her rota için bir Binding sınıfı oluşturmaktan kaçınarak bunu daha da basitleştirebilirsiniz. Her iki şekilde de gayet iyi çalışıyor ve zevkinize en uygun olanı kullanmanızı istiyoruz. ### SmartManagement GetX, bir hata oluşsa ve onu kullanan bir pencere öğesi düzgün şekilde atılmamış olsa bile, varsayılan olarak kullanılmayan controller(denetleyicileri) bellekten atar. Bu, `full` dependency management(bağımlılık yönetimi) modu olarak adlandırılan şeydir. Ancak GetX'in sınıfların imhasını kontrol etme şeklini değiştirmek istiyorsanız, farklı davranışlar ayarlayabileceğiniz `SmartManagement` sınıfınız var. #### Nasıl değiştirilir? Bu yapılandırmayı (genellikle ihtiyacınız olmayan) şekilde değiştirmek istiyorsanız: ```dart void main () { runApp( GetMaterialApp( smartManagement: SmartManagement.onlyBuilder //burada home: Home(), ) ) } ``` #### SmartManagement.full Varsayılan olanıdır. Kullanılmayan ve kalıcı olarak ayarlanmamış sınıfları dispose edin. Çoğu durumda, bu yapılandırmayı el değmeden tutmak isteyeceksiniz. Eğer Get için yeniyseniz, bunu değiştirmeyin. #### SmartManagement.onlyBuilder Bu seçenekle, yalnızca `init:` ile başlatılan veya `Get.lazyPut()` ile bir Binding'e yüklenen controller(denetleyiciler) dispose edilecektir. `Get.put()` veya `Get.putAsync()` veya başka bir yaklaşım kullanırsanız, SmartManagement bu bağımlılığı dışlamak için izinlere sahip olmayacaktır. Varsayılan davranışla, SmartManagement.onlyBuilder'ın aksine "Get.put" ile örneklenen widget'lar bile kaldırılacaktır. #### SmartManagement.keepFactory SmartManagement.full gibi, artık kullanılmadığında bağımlılıklarını kaldıracaktır. Ancak, factory'leri koruyacak, yani bu örneğe tekrar ihtiyacınız olursa dependency(bağımlılığı) yeniden yaratacaktır. ### Nasıl bindings yapılır? Bindings, başka bir ekrana gitmek için tıkladığınız anda oluşturulan geçici factory'ler oluşturur ve ekran değişirken animasyon gerçekleşir gerçekleşmez yok edilir. Bu o kadar hızlı gerçekleşir ki analyzer onu kaydedemez bile. Bu ekrana tekrar gittiğinizde, yeni bir geçici factory çağrılır, bu nedenle SmartManagement.keepFactory kullanmak yerine bu tercih edilir, ancak Bindings oluşturmak istemiyorsanız veya tüm bağımlılıklarınızı aynı Binding üzerinde tutmak istiyorsanız, mutlaka size yardımcı olacaktır. Factory'ler çok az bellek kaplarlar, örnekleri tutmazlar, ancak istediğiniz sınıfın "shape" olan bir fonksiyona sahiptirler. Bunun bellekte maliyeti çok düşüktür, ancak bu kitaplığın amacı, minimum kaynakları kullanarak mümkün olan maksimum performansı elde etmek olduğundan, Get factory bile varsayılan olarak kaldırır. Hangisi sizin için daha uygunsa onu kullanın. ## Notlar - Birden çok Bindings kullanıyorsanız SmartManagement.keepFactory KULLANMAYIN. Bindings olmadan veya GetMaterialApp'in initialBinding'inde bağlantılı tek bir Binding ile kullanılmak üzere tasarlanmıştır. - Bindings kullanmak tamamen isteğe bağlıdır, isterseniz belirli bir controller(denetleyiciyi) kullanan sınıflarda `Get.put()` ve `Get.find()` kullanabilirsiniz. Ancak, Services veya başka bir abstract class ile çalışıyorsanız, daha iyi bir organizasyon için Bindings'i kullanmanızı öneririm. ================================================ FILE: documentation/tr_TR/route_management.md ================================================ - [Rota Yönetimi (Route Management)](#route-management) - [Nasıl kullanılır?](#nasıl-kullanılır) - [Adlandırılmış rotalar olmadan navigasyon](#adlandırılmış-rotalar-olmadan-navigasyon) - [Adlandırılmış rotalarla navigasyon](#adlandırılmış-rotalarla-navigasyon) - [Verileri adlandırılmış rotalara gönder](#verileri-adlandırılmış-rotalara-gönder) - [Dinamik URL bağlantıları](#dinamik-url-bağlantıları) - [Middleware](#middleware) - [Context olmadan Navigasyon](#context-olmadan-navigasyon) - [SnackBars](#snackbars) - [Dialogs](#dialogs) - [BottomSheets](#bottomsheets) - [Nested Navigasyon](#nested-navigasyon) # Route Management Konu rota yönetimi olduğunda Getx için gereken her şeyin tam açıklaması budur. ## Nasıl kullanılır? Bunu pubspec.yaml dosyanıza ekleyin: ```yaml dependencies: get: ``` Context olmadan routes/snackbars/dialogs/bottomsheets kullanacaksanız veya üst düzey Get API'lerini kullanacaksanız, `MaterialApp`'ınızdan önce `Get` eklemeniz, `GetMaterialApp`'a dönüştürmeniz ve keyfini çıkarmanız yeterlidir! ```dart GetMaterialApp( // Öncesi: MaterialApp( home: MyHome(), ) ``` ## Adlandırılmış rotalar olmadan navigasyon Yeni bir ekrana gitmek için: ```dart Get.to(NextScreen()); ``` Snackbar'ları, Dialog'ları, Bottomsheet'leri veya normalde kapatacağınız herhangi bir şeyi kapatmak için `Navigator.pop(context);` ```dart Get.back(); ``` Bir sonraki ekrana gittikten sonra önceki ekrana geri dönme seçeneğinin olmaması için (SplashScreens, Login ekranlarında vb. kullanım için) ```dart Get.off(NextScreen()); ``` Bir sonraki ekrana gittikten sonra önceki tüm rotaları iptal etmek için (Alışveriş sepetlerinde, Anketlerde ve Testlerde kullanışlıdır) ```dart Get.offAll(NextScreen()); ``` Bir sonraki rotaya gitmek ve geri döner dönmez verileri almak veya güncellemek için: ```dart var data = await Get.to(Payment()); ``` diğer ekrandan önceki rota için bir veri gönderin: ```dart Get.back(result: 'success'); ``` Ve kullanın: Örn: ```dart if(data == 'success') madeAnything(); ``` Sözdizimimizi öğrenmek istemiyor musun? Navigator'ı (büyük harf) navigator (küçük harf) olarak değiştirin ve bağlam(context) kullanmak zorunda kalmadan standart navigasyonun tüm işlevlerine sahip olacaksınız. Örnek: ```dart // Varsayılan Flutter Navigator Navigator.of(context).push( context, MaterialPageRoute( builder: (BuildContext context) { return HomePage(); }, ), ); // Bağlama(context) ihtiyaç duymadan Flutter sözdizimini(syntax) kullanın navigator.push( MaterialPageRoute( builder: (_) { return HomePage(); }, ), ); // Get syntax (Bu kullanım çok daha iyidir. Tabiki siz karşı çıkma hakkına sahipsiniz.) Get.to(HomePage()); ``` ### Adlandırılmış Rotalarla Navigasyon - NamedRoutes ile gezinmeyi tercih ederseniz, Get de bunu destekler. nextScreen'e gitmek için ```dart Get.toNamed("/NextScreen"); ``` Ağaçta ve önceki ekranda gezinmek için. ```dart Get.offNamed("/NextScreen"); ``` Ağaçta gezinmek ve önceki tüm ekranları kaldırmak için ```dart Get.offAllNamed("/NextScreen"); ``` Rotaları tanımlamak için `GetMaterialApp`'i kullanın: ```dart void main() { runApp( GetMaterialApp( initialRoute: '/', getPages: [ GetPage(name: '/', page: () => MyHomePage()), GetPage(name: '/second', page: () => Second()), GetPage( name: '/third', page: () => Third(), transition: Transition.zoom ), ], ) ); } ``` Tanımsız rotalara navigasyonu yönetmek için (404 hatası), `GetMaterialApp`'de bir `unknownRoute` sayfası tanımlayabilirsiniz. ```dart void main() { runApp( GetMaterialApp( unknownRoute: GetPage(name: '/notfound', page: () => UnknownRoutePage()), initialRoute: '/', getPages: [ GetPage(name: '/', page: () => MyHomePage()), GetPage(name: '/second', page: () => Second()), ], ) ); } ``` ### Verileri adlandırılmış Rotalara gönder Sadece argümanlar için istediğinizi gönderin. Get, burada bir String, Map, List veya hatta bir Class örneği olsun, her şeyi kabul eder. ```dart Get.toNamed("/NextScreen", arguments: 'Get is the best'); ``` sınıfınızda(class) veya denetleyicinizde(controller): ```dart print(Get.arguments); //print out: Get is the best ``` ### Dinamik URL bağlantıları Web'deki gibi gelişmiş dinamik url'ler sunun. Web geliştiricileri muhtemelen bu özelliği Flutter'da istemişlerdir ve büyük olasılıkla bir paketin bu özelliği vaat ettiğini ve bir URL'nin web'de bulunacağından tamamen farklı bir sözdizimi sunduğunu görmüşlerdir, ancak Get bunu da çözmektedir. ```dart Get.offAllNamed("/NextScreen?device=phone&id=354&name=Enzo"); ``` controller/bloc/stateful/stateless Sınıfınıza: ```dart print(Get.parameters['id']); // out: 354 print(Get.parameters['name']); // out: Enzo ``` Ayrıca Get ile Adlandırılmış Parametreleri kolayca alabilirsiniz: ```dart void main() { runApp( GetMaterialApp( initialRoute: '/', getPages: [ GetPage( name: '/', page: () => MyHomePage(), ), GetPage( name: '/profile/', page: () => MyProfile(), ), //Argümanlı rotalar için farklı bir sayfa ve argümansız başka bir sayfa tanımlayabilirsiniz, ancak bunun için yukarıdaki gibi argüman almayacak olan rotada '/' eğik çizgisini kullanmanız gerekir. GetPage( name: '/profile/:user', page: () => UserProfile(), ), GetPage( name: '/third', page: () => Third(), transition: Transition.cupertino ), ], ) ); } ``` Rota adına veri gönder ```dart Get.toNamed("/profile/34954"); ``` İkinci ekranda verileri parametreye göre alın ```dart print(Get.parameters['user']); // out: 34954 ``` veya bunun gibi birden çok parametre gönderin ```dart Get.toNamed("/profile/34954?flag=true&country=italy"); ``` veya ```dart var parameters = {"flag": "true","country": "italy",}; Get.toNamed("/profile/34954", parameters: parameters); ``` İkinci ekranda, verileri genellikle olduğu gibi parametrelere göre alın ```dart print(Get.parameters['user']); print(Get.parameters['flag']); print(Get.parameters['country']); // out: 34954 true italy ``` Ve şimdi tek yapmanız gereken, herhangi bir bağlam(context) olmaksızın adlandırılmış rotalarınızda gezinmek için Get.toNamed()'i kullanmaktır (rotalarınızı doğrudan BLoC veya Controller sınıfınızdan çağırabilirsiniz) ve uygulamanız web'de derlendiğinde, rotalar url'de görünecek <3 ### Middleware Eylemleri tetiklemek için olayları almak dinlemek istiyorsanız routingCallback'i kullanabilirsiniz. ```dart GetMaterialApp( routingCallback: (routing) { if(routing.current == '/second'){ openAds(); } } ) ``` GetMaterialApp kullanmıyorsanız, Middleware gözlemcisini(observer) eklemek için manuel API'yi kullanabilirsiniz. ```dart void main() { runApp( MaterialApp( onGenerateRoute: Router.generateRoute, initialRoute: "/", navigatorKey: Get.key, navigatorObservers: [ GetObserver(MiddleWare.observer), // Burası !!! ], ), ); } ``` Bir MiddleWare sınıfı oluşturun ```dart class MiddleWare { static observer(Routing routing) { /// Her ekranda rotalar, snackbarlar, diyaloglar ve bottomsheetleri ek olarak dinleyebilirsiniz. ///Bu 3 olaydan herhangi birini doğrudan buraya girmeniz gerekiyorsa, ///Yapmaya çalıştığınızdan daha fazla olayın olduğunu != kullanarak belirtmelisiniz. if (routing.current == '/second' && !routing.isSnackbar) { Get.snackbar("Hi", "You are on second route"); } else if (routing.current =='/third'){ print('last route called'); } } } ``` Şimdi, Get on kodunu kullanın: ```dart class First extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( leading: IconButton( icon: Icon(Icons.add), onPressed: () { Get.snackbar("hi", "i am a modern snackbar"); }, ), title: Text('First Route'), ), body: Center( child: ElevatedButton( child: Text('Open route'), onPressed: () { Get.toNamed("/second"); }, ), ), ); } } class Second extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( leading: IconButton( icon: Icon(Icons.add), onPressed: () { Get.snackbar("hi", "i am a modern snackbar"); }, ), title: Text('second Route'), ), body: Center( child: ElevatedButton( child: Text('Open route'), onPressed: () { Get.toNamed("/third"); }, ), ), ); } } class Third extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Third Route"), ), body: Center( child: ElevatedButton( onPressed: () { Get.back(); }, child: Text('Go back!'), ), ), ); } } ``` ## Context olmadan Navigasyon ### SnackBars Flutter ile basit bir SnackBar'a sahip olmak için Scaffold bağlamını(context) almalısınız veya Scaffold'unuza bağlı bir GlobalKey kullanmalısınız. ```dart final snackBar = SnackBar( content: Text('Hi!'), action: SnackBarAction( label: 'I am a old and ugly snackbar :(', onPressed: (){} ), ); // Widget ağacında Scaffold'u bulun ve kullanın // bir SnackBar göstermek için. Scaffold.of(context).showSnackBar(snackBar); ``` Get ile: ```dart Get.snackbar('Hi', 'i am a modern snackbar'); ``` Get ile, Yapmanız gereken tek şey kodunuzun herhangi bir yerinden Get.snackbar'ınızı aramak veya onu istediğiniz gibi özelleştirmek! ```dart Get.snackbar( "Hey i'm a Get SnackBar!", // title "It's unbelievable! I'm using SnackBar without context, without boilerplate, without Scaffold, it is something truly amazing!", // message icon: Icon(Icons.alarm), shouldIconPulse: true, onTap:(){}, barBlur: 20, isDismissible: true, duration: Duration(seconds: 3), ); ////////// ALL FEATURES ////////// // Color colorText, // Duration duration, // SnackPosition snackPosition, // Widget titleText, // Widget messageText, // bool instantInit, // Widget icon, // bool shouldIconPulse, // double maxWidth, // EdgeInsets margin, // EdgeInsets padding, // double borderRadius, // Color borderColor, // double borderWidth, // Color backgroundColor, // Color leftBarIndicatorColor, // List boxShadows, // Gradient backgroundGradient, // TextButton mainButton, // OnTap onTap, // bool isDismissible, // bool showProgressIndicator, // AnimationController progressIndicatorController, // Color progressIndicatorBackgroundColor, // Animation progressIndicatorValueColor, // SnackStyle snackStyle, // Curve forwardAnimationCurve, // Curve reverseAnimationCurve, // Duration animationDuration, // double barBlur, // double overlayBlur, // Color overlayColor, // Form userInputForm /////////////////////////////////// ``` Geleneksel snackbar'ı tercih ediyorsanız veya yalnızca bir satır eklemek de dahil olmak üzere sıfırdan özelleştirmek istiyorsanız (Get.snackbar zorunlu bir başlık ve mesaj kullanır), Get.snackbar'ın üzerine inşa edildiği RAW API'sini sağlayan `Get.rawSnackbar();` kullanabilirsiniz. ### Dialogs Dialog'u açmak için: ```dart Get.dialog(YourDialogWidget()); ``` Varsayılan dialog açmak için: ```dart Get.defaultDialog( onConfirm: () => print("Ok"), middleText: "Dialog made in 3 lines of code" ); ``` showGeneralDialog yerine Get.generalDialog'u da kullanabilirsiniz. Cupertinos dahil olmak üzere tüm diğer Flutter Dialog widget'ları için bağlam(context) yerine Get.overlayContext'i kullanabilir ve kodunuzun herhangi bir yerinde açabilirsiniz. Overlay kullanmayan widget'lar için Get.context'i kullanabilirsiniz. Bu iki bağlam(context), inheritedWidget'ın bir gezinme bağlamı(context) olmadan kullanıldığı durumlar dışında, kullanıcı arayüzünüzün bağlamını(context) değiştirmek için vakaların %99'unda çalışacaktır. ### BottomSheets Get.bottomSheet, showModalBottomSheet gibidir, ancak bağlama(context) ihtiyaç duymaz. ```dart Get.bottomSheet( Container( child: Wrap( children: [ ListTile( leading: Icon(Icons.music_note), title: Text('Music'), onTap: () {} ), ListTile( leading: Icon(Icons.videocam), title: Text('Video'), onTap: () {}, ), ], ), ) ); ``` ## Nested Navigasyon Flutter'ın iç içe gezinmesini daha da kolaylaştırın. İçeriğe ihtiyacınız yoktur ve navigasyon yığınınızı kimliğe(ID) göre bulacaksınız. - NOT: Paralel gezinme yığınları oluşturmak tehlikeli olabilir. İdeal olan, NestedNavigators'ı kullanmamak veya idareli kullanmaktır. Projeniz gerektiriyorsa, devam edin, ancak bellekte birden çok gezinme yığınını tutmanın RAM tüketimi için iyi bir fikir olmayabileceğini unutmayın. Bakın ne kadar basit: ```dart Navigator( key: Get.nestedKey(1), // index göre anahtar oluşturma initialRoute: '/', onGenerateRoute: (settings) { if (settings.name == '/') { return GetPageRoute( page: () => Scaffold( appBar: AppBar( title: Text("Main"), ), body: Center( child: TextButton( color: Colors.blue, onPressed: () { Get.toNamed('/second', id:1); // indexe göre iç içe geçmiş rotanıza göre gezinin }, child: Text("Go to second"), ), ), ), ); } else if (settings.name == '/second') { return GetPageRoute( page: () => Center( child: Scaffold( appBar: AppBar( title: Text("Main"), ), body: Center( child: Text("second") ), ), ), ); } } ), ``` ================================================ FILE: documentation/tr_TR/state_management.md ================================================ * [State Management](#state-management) + [Reactive State Manager](#reactive-state-manager) - [Avantajlar](#avantajlar) - [Maksimum Performans:](#maksimum-performans) - [Reaktif bir değişken bildirmek](#reaktif-bir-değişken-bildirmek) - [Reaktif bir state'e sahip olmak kolaydır.](#reaktif-bir-state'e-sahip-olmak-kolaydır) - [Görünümdeki değerleri kullanmak](#görünümdeki-değerleri-kullanmak) - [Yeniden oluşturulacak koşullar](#yeniden-oluşturulacak-koşullar) - [Nerede .obs kullanılabilir](#nerede-obs-kullanılabilir) - [Listeler hakkında not](#listeler-hakkında-not) - [Neden .value kullanmak zorundayım?](#neden-value-kullanmak-zorundayım?) - [Obx()](#obx) - [Çalışanlar](#Çalışanlar) + [Simple State Manager](#simple-state-manager) - [Avantajlar](#avantajlar-1) - [Kullanımı](#kullanımı) - [Controller'lar nasıl çalışır](#controller'lar-nasıl-çalışır) - [Artık StatefulWidget'lara ihtiyacınız olmayacak](#artık-statefulwidget'lara-ihtiyacınız-olmayacak) - [Neden var](#neden-var) - [Kullanmanın diğer yolları](#kullanmanın-diğer-yolları) - [Unique IDs-Benzersiz Kimlikler](#unique-ids-benzersiz-kimlikler) + [İki state managers ile Mixing](#İki-state-managers-ile-mixing) + [GetBuilder vs GetX vs Obx vs MixinBuilder](#getbuilder-vs-getx-vs-obx-vs-mixinbuilder) # State Management GetX, diğer State Management'ler (Durum Yöneticileri) gibi Streams veya ChangeNotifier kullanmaz. Niye? GetX ile android, iOS, web, linux, macos ve linux için uygulamalar oluşturmaya ek olarak, Flutter/GetX ile aynı syntax(sözdizimine) sahip server(sunucu) uygulamaları oluşturabilirsiniz. Yanıt süresini iyileştirmek ve RAM tüketimini azaltmak için düşük işletim maliyetiyle çok fazla performans sunan düşük gecikmeli çözümler olan GetValue ve GetStream'i oluşturduk. State Management (Durum Yönetimi) de dahil olmak üzere tüm kaynaklarımızı oluşturmak için bu temeli kullanıyoruz. * _Complexity_ (Karmaşıklık): Bazı state management'ler karmaşıktır ve çok fazla ortak özelliği vardır. GetX ile her olay için bir sınıf tanımlamanız gerekmez, kod son derece temiz ve nettir ve daha az yazarak çok daha fazlasını yaparsınız. Pek çok insan bu konu yüzünden Flutter'dan vazgeçti ve şimdi nihayet durumları yönetmek için basit bir çözüme sahipler. * _No code generators_ (Kod Oluşturucu Yok): Geliştirme zamanınızın yarısını uygulama mantığınızı yazmaya harcarsınız. Bazı state management'ler, minimum düzeyde okunabilir koda sahip olmak için kod oluşturuculara güvenir. Bir değişkeni değiştirmek ve build_runner'ı çalıştırmak verimsiz olabilir ve genellikle flutter clean'den sonraki bekleme süresi uzun olur ve çok fazla kahve içmeniz gerekir. GetX ile her şey reaktiftir ve hiçbir şey kod oluşturuculara bağlı değildir, bu da geliştirmenizin tüm yönlerinde üretkenliğinizi artırır. * _It does not depend on context(Context'e bağlı değil)_: Muhtemelen görünümünüzün context'ini (bağlam) bir denetleyiciye göndermeniz gerekiyordu, bu da görünümün iş mantığınızla bağlantısını yüksek hale getirdi. Muhtemelen context'i (bağlamı) olmayan bir yer için bir bağımlılık kullanmak zorunda kaldınız ve context'i(bağlamı) çeşitli sınıflar ve fonksiyonlardan geçirmek zorunda kaldınız.Bu sadece GetX ile mevcut değil. Controller'larınıza (Denetleyicilerinize) , controller'larınızın(denetleyicilerinizin) içinden herhangi bir context (bağlam) olmadan erişebilirsiniz. Kelimenin tam anlamıyla hiçbir şey için context'i(bağlamı) parametreye göre göndermeniz gerekmez. * _Granular control(Parçacıklı Kontrol)_: Çoğu state management(durum yöneticisi) ChangeNotifier'ı temel alır. ChangeNotifier, notifyListeners çağrıldığında kendisine bağlı olan tüm widget'ları bilgilendirecektir. Bir ekranda ChangeNotifier sınıfınızın bir değişkenine sahip 40 widget'ınız varsa, birini güncellediğinizde hepsi yeniden oluşturulacaktır. GetX ile iç içe geçmiş widget'lara bile saygı duyulur. Obx listview'inizi izliyorsa ve diğeri ListView içinde bir onay kutusu izliyorsa, CheckBox değerini değiştirirken yalnızca o onay kutusu güncellenir, Liste değerini değiştirirken yalnızca ListView güncellenir. * _It only reconstructs if its variable REALLY changes (Değişken değişirse GERÇEKTEN yeniden yapılandırır)_: GetX akış kontrolüne sahiptir, yani 'Paola' ile bir text(metin) görüntülerseniz, (observable)gözlemlenebilir değişkeni tekrar 'Paola' olarak değiştirirseniz, widget yeniden yapılandırılmayacaktır. Çünkü GetX, 'Paola'nın' zaten text'de(metinde) görüntülendiğini ve gereksiz rekonstrüksiyonlar yapmayacağını biliyor. Mevcut state management'lerin(durum yöneticilerin) çoğu (hepsi değilse de) ekranda yeniden oluşturulur. ## Reactive State Manager Reaktif programlama birçok insanı yabancılaştırabilir çünkü karmaşık olduğu söylenir. GetX reaktif programlamayı oldukça basit bir şeye dönüştürür: * Stream Controller oluşturmanıza gerek yoktur. * Her değişken için bir StreamBuilder oluşturmanız gerekmez. * Her state(durum) için bir sınıf oluşturmanız gerekmeyecektir. * Bir initial value(başlangıç değeri) için bir get oluşturmanız gerekmeyecektir. Get ile reaktif programlama, Setstate'i kullanmak kadar kolaydır. Bir ad değişkeniniz olduğunu ve her değiştirdiğinizde onu kullanan tüm widget'ların otomatik olarak değiştirilmesini istediğinizi düşünelim. Bu sizin count(sayım) değişkeninizdir: ``` dart var name = 'Jonatas Borges'; ``` Observable hale getirmek için, sonuna ".obs" eklemeniz gerekir: ``` dart var name = 'Jonatas Borges'.obs; ``` Hepsi bu. *Bu kadar basit* bir şey. Şu andan itibaren, bu reaktif-".obs"(ervables) değişkenlerine _Rx_ adını verebiliriz. Başlık altında ne yaptık? `String` lerin bir `Stream` oluşturduk, `"Jonatas Borges"` initial value'sunu(başlangıç değerini) atadık, `"Jonatas Borges"` kullanan tüm widget'lara artık bu değişkene "ait olduklarını" bildirdik ve _Rx_ değeri değiştiğinde de değişmeleri gerekecek. Bu, Dart'ın yetenekleri sayesinde **GetX'in büyüsüdür**. Ancak, bildiğimiz gibi, bir `Widget` yalnızca bir işlevin içindeyse değiştirilebilir, çünkü statik sınıflar "otomatik değiştirme" gücüne sahip değildir. Bir `StreamBuilder` oluşturmanız, değişiklikleri dinlemek için bu değişkene abone olmanız ve aynı kapsamdaki birkaç değişkeni değiştirmek istiyorsanız, iç içe geçmiş `StreamBuilder` bir "kaskad" oluşturmanız gerekir, değil mi? Hayır, bir `StreamBuilder`a ihtiyacınız yok, ancak statik sınıflar konusunda haklısınız. Pekala, görünüşe göre, belirli bir Widget'ı değiştirmek istediğimizde genellikle çok fazla ortak bilgimiz olur, bu Flutter yoludur. **GetX** ile bu ortak kod kodunu da unutabilirsiniz. `StreamBuilder( … )` ? `initialValue: …` ? `builder: …` ? Hayır, bu değişkeni bir `Obx()` Widget'ına yerleştirmeniz yeterlidir. ``` dart Obx (() => Text (controller.name)); ``` _Ezberlemek için neye ihtiyacın var?_Sadece `Obx(() =>` . Bu Widget'ı bir ok işlevinden bir `Obx()` (_Rx_'in "Observable") içine geçiriyorsunuz. `Obx` oldukça akıllıdır ve yalnızca `controller.name`nin değeri değiştiğinde değişecektir. `name`, `"John"` ise ve onu `"John"` ( `name.value = "John"` ) olarak değiştirirseniz, öncekiyle aynı `değer` olduğundan, ekranda hiçbir şey değişmeyecektir, ve 'Obx' , kaynakları kurtarmak için yeni değeri yok sayar ve Widget'ı yeniden oluşturmaz. **Harika değil mi?** > Peki ya bir `Obx` içinde 5 _Rx_ (observable) değişkenim varsa? Yalnızca **herhangi biri** değiştiğinde güncellenecektir. > Ve bir sınıfta 30 değişkenim varsa, birini güncellediğimde, o sınıftaki değişkenlerin **tümünü** günceller mi? Hayır, sadece bu _Rx_ değişkenini kullanan **belirli Widget**. Bu nedenle, **GetX** yalnızca _Rx_ değişkeni değerini değiştirdiğinde ekranı günceller. ``` final isOpen = false.obs; // NOTHING will happen... same value. void onButtonTap() => isOpen.value=false; ``` ### Avantajlar **GetX()**, güncellenen değişkenler üzerinde **ayrıntılı** kontrole ihtiyacınız olduğunda size yardımcı olur. Bir eylem gerçekleştirdiğinizde tüm değişkenleriniz değiştirileceğinden `unique IDs(benzersiz kimliklere)` ihtiyacınız yoksa, `GetBuilder`ı kullanın, çünkü Simple State Updater(Basit Durum Güncelleyicisi)'dir (`setState ()` gibi bloklar halinde), sadece birkaç kod satırında yapılır. En az CPU etkisine sahip olmak ve sadece tek bir amacı (_State_ rebuild) yerine getirmek ve mümkün olan en az kaynağı harcamak için basitleştirildi. **Güçlü** bir State Management (Durum Yöneticisi)'e ihtiyacınız varsa, **GetX** ile yanlış yapmış olamazsınız. Değişkenlerle çalışmaz, ancak __flows__, içindeki her şey başlık altındaki `Streams`dır. _rxDart_ ile birlikte kullanabilirsiniz, çünkü her şey `Streams`, her "_Rx_variable"ın 'event(olayını)' dinleyebilirsiniz, çünkü içindeki her şey `Streams`'dir. Kelimenin tam anlamıyla bir _BLoC_ yaklaşımıdır, _MobX_'den daha kolaydır ve kod oluşturucuları veya süslemeleri yoktur. **Herhangi bir şeyi** yalnızca bir `.obs` ile _"Observable"_ hale getirebilirsiniz. ### Maksimum Performans: State Management (Durum Yöneticisinin)'in değiştiğinden emin olmak için akıllı bir algoritmaya sahip olmanın yanı sıra **GetX** comparators kullanır. Uygulamanızda herhangi bir hatayla karşılaşırsanız ve yinelenen bir State(durum) değişikliği gönderirseniz, **GetX** çökmemesini sağlayacaktır. **GetX** ile State(Durum) yalnızca `value(değer)` değişirse değişir. Bu, **GetX** ile mobx_'den _ `computed` kullanımı arasındaki temel farktır. İki defa __observable__ 'da bir değişiklik yapıldığında; o _observable_ dinleyicisi de değişecektir. **GetX** ile, iki değişkeni birleştirirseniz, `GetX()` (`Observer()`a benzer) yalnızca gerçek bir State(Durum) değişikliği gerektiriyorsa yeniden oluşturacaktır. ### Reaktif bir değişken bildirmek Bir değişkeni "observable" hale getirmenin 3 yolu vardır. 1 - Birincisi **`Rx{Type}`** kullanmak. ``` dart // initial value önerilir, zorunlu değildir. final name = RxString(''); final isLogged = RxBool(false); final count = RxInt(0); final balance = RxDouble(0.0); final items = RxList([]); final myMap = RxMap({}); ``` 2 - İkincisi, **`Rx`** kullanmak ve Darts Generics, `Rx` kullanmaktır. ``` dart final name = Rx(''); final isLogged = Rx(false); final count = Rx(0); final balance = Rx(0.0); final number = Rx(0); final items = Rx>([]); final myMap = Rx>({}); // Custom classes - herhangi bir sınıf olabilir final user = Rx(); ``` 3 - Üçüncü, daha pratik, daha kolay ve tercih edilen yaklaşım,`value`'ya bir **`.obs`** ekleyin: ``` dart final name = ''.obs; final isLogged = false.obs; final count = 0.obs; final balance = 0.0.obs; final number = 0.obs; final items = [].obs; final myMap = {}.obs; // Custom classes - herhangi bir sınıf olabilir final user = User().obs; ``` ##### Reaktif bir state'e sahip olmak kolaydır. Bildiğimiz gibi, _Dart_ şimdi _null safety_ doğru gidiyor. Şu andan itibaren hazırlıklı olmak için, _Rx_ değişkenlerinizi her zaman bir **initial value** ile başlatmalısınız. > Bir değişkeni **GetX** ile _observable_ + _initial value_ değerine dönüştürmek en basit ve pratik yaklaşımdır. Kelimenin tam anlamıyla bir değişkeninizin sonuna bir " `.obs` " ekleyeceksiniz, ve **bu kadar**. Şimdi onu gözlemlenebilir hale getirdiniz, ve onun `.value(değer)`'i, _initial value_ olacaktır. ### Görünümdeki değerleri kullanmak ``` dart // controller dosyası final count1 = 0.obs; final count2 = 0.obs; int get sum => count1.value + count2.value; ``` ``` dart // view dosyası GetX( builder: (controller) { print("count 1 rebuild"); return Text('${controller.count1.value}'); }, ), GetX( builder: (controller) { print("count 2 rebuild"); return Text('${controller.count2.value}'); }, ), GetX( builder: (controller) { print("count 3 rebuild"); return Text('${controller.sum}'); }, ), ``` `count1.value++` değerini artırırsak, şunu yazdırır: * `count 1 rebuild` * `count 3 rebuild` `count1`, `1` değerine sahip olduğundan ve `1 + 0 = 1` olduğundan, `toplam` değeri değiştirilir. `count2.value++` değerini değiştirirsek, şunu yazdırır: * `count 2 rebuild` * `count 3 rebuild` çünkü `count2.value` değişti ve `sum`un sonucu şimdi `2` oldu. * NOT: Varsayılan olarak, ilk etkinlik aynı `value` olsa bile widget'ı yeniden oluşturacaktır. Bu durum boolean değişkenlerinde de mevcuttur. Bunu yaptığınızı hayal edin: ``` dart var isLogged = false.obs; ``` Ardından, bir kullanıcının `ever` içinde bir olayı tetiklemek için `isLogged` olup olmadığını kontrol ettiniz. ``` dart @override onInit() async { ever(isLogged, fireRoute); isLogged.value = await Preferences.hasToken(); } fireRoute(logged) { if (logged) { Get.off(Home()); } else { Get.off(Login()); } } ``` `hasToken` `false` olsaydı, `isLogged`da herhangi bir değişiklik olmazdı, bu nedenle `ever()` asla çağrılmazdı. Bu tür davranışlardan kaçınmak için, bir _observable_ öğesindeki ilk değişiklik her zaman bir olayı tetikleyecektir, aynı `.value` değerini içerse bile. İsterseniz bu davranışı kullanarak kaldırabilirsiniz: `isLogged.firstRebuild = false;` ### Yeniden oluşturulacak koşullar Ek olarak, Get gelişmiş durum kontrolü sağlar. Bir olayı (listeye nesne ekleme gibi) belirli bir koşulda koşullandırabilirsiniz. ``` dart // İlk parametre: koşul, true veya false döndürmelidir. // İkinci parametre: koşul doğruysa yeni değer uygulanacaktır. list.addIf(item < limit, item); ``` Süslemesiz, kod oluşturucusuz, komplikasyonsuz :smile: Flutter'ın sayaç uygulamasını biliyor musunuz? Controller sınıfınız şöyle görünebilir: ``` dart class CountController extends GetxController { final count = 0.obs; } ``` Basit bir şekilde: ``` dart controller.count.value++ ``` Kullanıcı arabiriminizdeki sayaç değişkenini nerede depolandığına bakılmaksızın güncelleştirebilirsiniz. ### Nerede .obs kullanılabilir Obs üzerindeki her şeyi dönüştürebilirsiniz. İşte bunu yapmanın iki yolu: * Sınıf değerlerinizi obs'ye dönüştürebilirsiniz ``` dart class RxUser { final name = "Camila".obs; final age = 18.obs; } ``` * veya tüm sınıfı observable hale getirebilirsiniz. ``` dart class User { User({String name, int age}); var name; var age; } // örnek: final user = User(name: "Camila", age: 18).obs; ``` ### Listeler hakkında not Listeler, içindeki nesneler gibi tamamen gözlemlenebilir. Bu şekilde, bir listeye bir değer eklerseniz, onu kullanan widget'ları otomatik olarak yeniden oluşturur. Ayrıca listelerde ".value" kullanmanıza gerek yok, harika dart api'ları bunu kaldırmamıza izin verdi. Ne yazık ki, String ve int gibi ilkel türler genişletilemez, bu da kullanımını sağlar. Değer zorunludur, ancak bunlar için get ve setter'larla çalışıyorsanız bu bir sorun olmayacaktır. ``` dart // On the controller final String title = 'User Info:'.obs final list = List().obs; // on the view Text(controller.title.value), // .value olması gerekir ListView.builder ( itemCount: controller.list.length // listelerin buna ihtiyacı yok ) ``` Kendi sınıflarınızı observable hale getirirken, bunları güncellemenin farklı bir yolu vardır: ``` dart // model dosyasında // her bir öznitelik yerine tüm sınıfı observable hale getireceğiz class User() { User({this.name = '', this.age = 0}); String name; int age; } // Controller dosyası final user = User().obs; // User değişkenini güncellemeniz gerektiğinde: user.update( (user) { // bu parametre, güncellemek istediğiniz sınıfın kendisidir. user.name = 'Jonny'; user.age = 18; }); // user değişkenini güncellemenin alternatif bir yolu: user(User(name: 'João', age: 35)); // on view: Obx(()=> Text("Name ${user.value.name}: Age: ${user.value.age}")) // model değerlerine .value olmadan da erişebilirsiniz: user().name; ``` İstemiyorsanız setlerle çalışmak zorunda değilsiniz. "assign" ve "assignAll" api'sini kullanabilirsiniz. "assign" api'si listenizi temizler ve oradan başlatmak istediğiniz tek bir nesneyi ekler. "assignAll" api, mevcut listeyi temizleyecek ve ona enjekte ettiğiniz yinelenebilir nesneleri ekleyecektir. ### Neden .value kullanmak zorundayım? Basit bir decoration ve code generator ile `String` ve `int` için 'value' kullanma zorunluluğunu kaldırabiliriz, ancak bu kütüphanenin amacı kesinlikle dış bağımlılıklardan kaçınmaktır. Temelleri (route, dependencies ve state management) içeren, harici bir pakete ihtiyaç duymadan basit, hafif ve performanslı bir şekilde programlamaya hazır bir ortam sunmak istiyoruz. Pubspec'inize (get) tam anlamıyla 3 harf ve iki nokta üst üste ekleyebilir ve programlamaya başlayabilirsiniz. Rota yönetiminden durum yönetimine kadar varsayılan olarak dahil edilen tüm çözümler kolaylık, üretkenlik ve performansı hedefler. Bu kitaplığın toplam ağırlığı, eksiksiz bir çözüm olmasına rağmen tek bir state manager'den daha azdır. Eğer `.value` dan rahatsızsanız MobX harika bir alternatiftir ve Get ile birlikte kullanabilirsiniz. MobX code generator ile bir sorununuz yoksa veya BLoC ilgili bir sorununuz yoksa Get ile route'u kullanabilirsiniz. Get SEM ve RSM ile doğdu, şirketimin 90'dan fazla controller'a sahip bir projesi var.Büyük bir projeniz varsa, oldukça iyi bir makinede bir Flutter Clean'den sonra görevlerini tamamlaması 30 dakikadan fazla sürdü. 5, 10, 15 controller, herhangi bir state manager size yardımcı olacaktır. Büyük bir projeniz varsa ve code generator sizin için bir sorunsa, bu çözüm size verildi. Açıkçası, birisi projeye katkıda bulunmak ve bir code generator veya benzeri bir şey oluşturmak istiyorsa, bunu readme'de alternatif olarak bağlantı ekleyeceğim, şimdilik diyorum ki, bunu zaten yapan iyi çözümler var, MobX gibi. ### Obx() Bindings kullanarak Get yazmak gereksizdir. Yalnızca bir pencere öğesi oluşturan anonim işlevi alan GetX yerine Obx pencere aracını kullanabilirsiniz. Açıkçası, bir tür kullanmıyorsanız, değişkenleri kullanmak için denetleyicinizin bir örneğine sahip olmanız veya değeri almak için `Get.find()` .value veya Controller.to.value öğesini kullanmanız gerekir. . ### Çalışanlar Bir olay meydana geldiğinde belirli geri aramaları tetikleyerek size yardımcı olacaktır. ``` dart /// 'Count1' her değiştiğinde çağrılır. ever(count1, (_) => print("$_ has been changed")); /// $_ değişkeni yalnızca ilk kez değiştirildiğinde çağrılır. once(count1, (_) => print("$_ was changed once")); /// Anti DDoS - Örneğin, kullanıcı 1 saniye boyunca yazmayı her durdurduğunda çağrılır. debounce(count1, (_) => print("debouce$_"), time: Duration(seconds: 1)); /// 1 saniye içinde tüm değişiklikleri yok sayın. interval(count1, (_) => print("interval $_"), time: Duration(seconds: 1)); ``` Tüm çalışanlar (`debounce` dışında), "bool" veya "bool" döndüren bir callback olabilen bir "Koşul" adlı parametreye sahiptir. Bu "koşul", "callback" işlevinin ne zaman yürütüleceğini tanımlar. Tüm çalışanlar, çalışanı iptal etmek için ( `dispose()` aracılığıyla) kullanabileceğiniz bir 'Worker' örneği döndürür. * **`ever`** _Rx_ değişkeni her yeni bir değer yaydığında çağrılır. * **`everAll`** `ever` gibi, ancak değişkeni her değiştirildiğinde çağrılan _Rx_ değerlerinin bir `List`'ini alır. Bu kadar. * **`once`** 'once', yalnızca değişken ilk değiştirildiğinde çağrılır. * **`debounce`** 'debounce', yalnızca kullanıcı yazmayı bitirdiğinde API'nin çağrılmasını istediğiniz arama işlevlerinde çok kullanışlıdır. Kullanıcı "Jonny" yazarsa, apı'lerde J, o, n, n ve y harfleriyle 5 aramanız olur. Get ile bu olmaz, çünkü yalnızca yazmanın sonunda tetiklenecek bir "debounce" çalışanınız olur. * **`interval`** 'interval' debouce'dan farklıdır. debouce kullanıcı 1 saniye içinde bir değişkene 1000 değişiklik yaparsa, öngörülen zamanlayıcıdan sonra yalnızca sonuncusunu gönderir (varsayılan değer 800 milisaniyedir). Interval bunun yerine, öngörülen süre boyunca tüm kullanıcı eylemlerini yoksayar. Olayları saniyede 1000 olmak üzere 1 dakika boyunca gönderirseniz, debounce yalnızca kullanıcı olayları engellemeyi bıraktığında size sonuncusunu gönderir. aralık, her saniye olayları teslim eder ve 3 saniyeye ayarlanırsa, o dakika 20 olay teslim eder. Bu, kullanıcının bir şeye hızlı bir şekilde tıklayabileceği ve bir avantaj elde edebileceği işlevlerde kötüye kullanımı önlemek için önerilir (kullanıcının bir şeye tıklayarak para kazanabileceğini düşünün, aynı dakikada 300 kez tıklarsa, 300 jetona sahip olur, aralığı kullanarak, bir zaman dilimi ayarlayabilirsiniz 3 saniye boyunca ve hatta 300 veya bin kez tıklandığında, 1 dakika içinde alacağı maksimum 20 jeton, 300 veya 1 milyon kez tıklanır). Debounce, anti-DDoS için, Onchange'deki her değişikliğin apı'nizde bir sorguya neden olacağı arama gibi işlevler için uygundur. Debounce, kullanıcının isteği yapmak için adı yazmayı bırakmasını bekleyecektir. Yukarıda belirtilen jeton senaryosunda kullanılmış olsaydı, kullanıcı yalnızca belirlenen süre boyunca "durakladığında" çalıştırıldığı için kullanıcı yalnızca 1 jeton kazanırdı. * NOT: Çalışanlar her zaman bir Controller veya Class başlatırken kullanılmalıdır, bu nedenle her zaman onInit (önerilen), Class oluşturucu veya statefulwidget'in initState üzerinde olmalıdır (bu uygulama çoğu durumda önerilmez, ancak herhangi bir yan etkisi olmamalıdır). ## Simple State Manager(Basit Durum Yöneticisi) Get'in son derece hafif ve kolay, ChangeNotifier kullanmayan, özellikle Flutter'a yeni başlayanların ihtiyacını karşılayacak ve büyük uygulamalar için sorun yaratmayacak bir state manager'i var. GetBuilder tam olarak çoklu state controller'a yöneliktir. Bir sepete 30 ürün eklediğinizi, birini sil'i tıklattığınızı, aynı zamanda listenin güncellendiğini, fiyatın güncellendiğini ve alışveriş sepetindeki rozetin daha küçük bir sayıya güncellendiğini düşünün. Bunu GetBuilder yapar, çünkü durumları gruplandırır ve bunun için herhangi bir "hesaplama mantığı" olmadan hepsini bir kerede değiştirir. GetBuilder, bu tür bir durum göz önünde bulundurularak oluşturuldu, çünkü geçici durum değişikliği için Setstate'i kullanabilirsiniz ve bunun için bir state manager'e ihtiyacınız olmaz. Bu şekilde, tek bir controller'a ihtiyacınız varsa, bunun için ID'ler atayabilir veya getx'i kullanabilirsiniz. Bu size kalmış, sahip olduğunuz daha fazla "individual" widget'ın, getx'in performansının o kadar fazla öne çıkacağını, Getbuilder'ın performansının ise birden fazla durum değişikliği olduğunda üstün olması gerektiğini unutmayın. ### Avantajlar 1. Yalnızca gerekli widget'ları günceller. 2. ChangeNotifier kullanmaz, daha az bellek kullanan (0mb'ye yakın) durum yöneticisidir. 3. StatefulWidget'ı unutun! Get ile buna asla ihtiyacınız olmayacak. Diğer state manager'lar ile (BLoC, MobX Controller vb.) muhtemelen bir StatefulWidget kullanmanız gerekecek. Stateless Widget mı? Öyleyse, yalnızca state bilgisi olan Widget'ın durumunu kaydedebiliyorsanız, neden tüm sınıfın durumunu kurtarın? Get bunu da çözer. Stateless bir sınıf oluşturun, her şeyi Stateless yapın. Tek bir bileşeni güncellemeniz gerekiyorsa, onu GetBuilder ile sarın. 4. Projenizi gerçek anlamda düzenleyin! Denetleyiciler UI'nizde bulunmamalı, TextEditController'ınızı veya kullandığınız herhangi bir denetleyiciyi Controller sınıfınıza yerleştirmemelidir. 5.Bir widget'ı oluşturulduğu anda güncellemek için bir olayı tetiklemeniz mi gerekiyor? GetBuilder, StatefulWidget gibi "initState" özelliğine sahiptir ve initState'inize daha fazla event yerleştirilmeden, doğrudan denetleyicinizden event'leri çağırabilirsiniz. 6. Stream, timer vb. kapatmak gibi bir eylemi tetiklemeniz gerekiyor mu? Get Builder ayrıca, widget yok edilir edilmez olayları çağırabileceğiniz dispose özelliğine de sahiptir. 7. Stream'leri yalnızca gerektiğinde kullanın. Stream Controller controller içinde normal olarak kullanabilir ve Streambuilder'ı da normal olarak kullanabilirsiniz, ancak unutmayın, bir stream makul bir şekilde bellek tüketir, reaktif programlama güzeldir, ancak kötüye kullanmamalısınız. aynı anda açılan 30 stream, Changenotifier'den daha kötü olabilir (ve changeNotifier çok kötüdür). 8. Ram harcamadan widget'ları güncelleyin. Get yalnızca Get Builderlder içerik oluşturucu kimliğini ve gerektiğinde GetBuilder güncelleştirmelerini depolar. Bellekte get ID depolama bellek tüketimi bile GetBuilders binlerce çok düşüktür. Yeni bir GetBuilder oluşturduğunuzda, aslında bir içerik oluşturucu kimliği olan GetBuilder durumunu paylaşıyorsunuz demektir. Her GetBuilder için büyük uygulamalar için çok fazla ram tasarrufu sağlayan yeni bir durum oluşturulmaz. Temel olarak uygulamanız tamamen Stateless olacak ve State Bilgisi olan birkaç Widget (GetBuilder içinde) tek bir duruma sahip olacak ve bu nedenle birini güncellemek hepsini güncelleyecektir. State sadece bir tanesidir. 9. Get her şeyi bilir ve çoğu durumda bir denetleyiciyi bellekten çıkarma zamanını tam olarak bilir. Bir denetleyiciyi ne zaman elden çıkaracağınız konusunda endişelenmemelisiniz, Bunu yapmak için en iyi zamanı öğrenin. ### Kullanımı ``` dart // Controller sınıfı oluşturun ve GetxController'ı extends edin class Controller extends GetxController { int counter = 0; void increment() { counter++; update(); // artış çağrıldığında kullanıcı arayüzünde sayaç değişkenini güncellemek için update() işlevini kullanın } } // Stateless/Stateful sınıfınızda, artış çağrıldığında Metni güncellemek için Get Builder'ı kullanın GetBuilder( init: Controller(), // INIT IT ONLY THE FIRST TIME builder: (_) => Text( '${_.counter}', ), ) //Initialize yalnızca ilk kez başlatın. Aynı controller için ReBuilder'ı ikinci kez kullanıyorsanız, tekrar kullanmayın. Controller'ınız, onu 'init' olarak işaretleyen pencere öğesi yerleştirildiği anda bellekten otomatik olarak kaldırılacaktır. Bunun için endişelenmenize gerek yok, Get bunu otomatik olarak yapacak, sadece aynı contrroller'ın iki kez başlatmadığınızdan emin olun. ``` **Tamamlandı!** * Get ile durumları nasıl yöneteceğinizi öğrendiniz. * Not: Daha büyük bir organizasyon isteyebilirsiniz ve init özelliğini kullanmayabilirsiniz. Bunun için bir sınıf oluşturup Bindings sınıfını extends edebilir ve bunun içinde o rotada oluşturulacak controller'dan bahsedebilirsiniz. Controller'lar o anda oluşturulmaz, tam tersine, bu sadece bir ifadedir, böylece bir Controller'ı ilk kullandığınızda, nereye bakacağınızı bilecek. Get lazyLoad olarak kalacak ve artık ihtiyaç duyulmadığında Controller'ları elden çıkarmaya devam edecek. Nasıl çalıştığını görmek için pub.dev örneğine bakın. Birçok rotada gezinirseniz ve daha önce kullandığınız controller'ınızda bulunan verilere ihtiyacınız varsa, GetBuilder tekrar (init olmadan) kullanmanız yeterlidir: ``` dart class OtherClass extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( body: Center( child: GetBuilder( builder: (s) => Text('${s.counter}'), ), ), ); } ``` Controller'ınızı GetBuilder dışında birçok başka yerde kullanmanız gerekiyorsa, controller'ınıza bir get oluşturun ve kolayca elde edin. (veya `Get.find()` kullanın) ``` dart class Controller extends GetxController { /// Buna ihtiyacın yok. Sadece syntax kolaylığı için kullanmanızı öneririm. /// statik yöntemle: Controller.to.increment(); /// statik yöntem olmadan: Get.find().increment(); /// Her iki syntax kullanmanın herhangi bir yan etkisi veya performans farkı yoktur. Yalnızca birinin türe ihtiyacı yoktur ve diğeri IDE tarafından otomatik olarak tamamlanır. static Controller get to => Get.find(); // bu satırı ekleyin int counter = 0; void increment() { counter++; update(); } } ``` Ve sonra controller'a doğrudan bu şekilde erişebilirsiniz: ``` dart FloatingActionButton( onPressed: () { Controller.to.increment(), } // Bu inanılmaz derecede basit! child: Text("${Controller.to.counter}"), ), ``` FloatingActionButton tuşuna bastığınızda, 'counter' değişkenini dinleyen tüm widget'lar otomatik olarak güncellenir. ### Controller'lar nasıl çalışır Diyelim ki elimizde bu var: `Class a => Class B (has controller X) => Class C (has controller X)` A sınıfında controller henüz bellekte değil çünkü henüz kullanmadınız (Get is lazyLoad). B sınıfında controller kullandınız ve belleğe girdi. C sınıfında, B sınıfındakiyle aynı controller'ı kullandınız, Get, controller B'nin durumunu C controller'ı ile paylaşacak ve aynı controller hala bellekte kalacaktır. C ekranını ve B ekranını kapatırsanız, Get, A Sınıfı controller'ı kullanmadığından otomatik olarak X controller'ını bellekten alır ve kaynakları boşaltır. Tekrar B'ye giderseniz, X controller'ı tekrar belleğe girer, C sınıfına gitmek yerine tekrar A sınıfına dönerseniz Get, controller aynı şekilde bellekten çıkarır. C sınıfı controller'ı kullanmadıysa ve B sınıfını bellekten çıkardıysanız, hiçbir sınıf controller'ı X kullanmayacak ve aynı şekilde imha edilecektir. Get ile bulaşabilecek tek istisna, B'yi rotadan beklenmedik bir şekilde kaldırırsanız ve controller'ı C'de kullanmaya çalışırsanız. Bu durumda, controller'ın B'deki ID silindi ve Get şu şekilde programlandı: ID olmayan her controller'ı bellekten kaldırın. Bunu yapmayı düşünüyorsanız, "autoRemove: false" işaretini B sınıfının GetBuilder'ına ekleyin ve adoptID = true; yapın C sınıfında GetBuilder kullanın. ### Artık StatefulWidget'lara ihtiyacınız olmayacak Stateful Widget kullanmak, tüm ekranların durumunu gereksiz yere depolamak anlamına gelir, çünkü bir widget'ı minimum düzeyde yeniden oluşturmanız gerekse bile, onu başka bir Stateful Widget olacak olan bir Consumer/Observer/BlocProvider/GetBuilder/GetX/Obx içine gömeceksiniz. StatefulWidget sınıfı, daha fazla RAM tahsis edecek olan StatelessWidget'ten daha büyük bir sınıftır ve bu, bir veya iki sınıf arasında önemli bir fark yaratmayabilir, ancak 100 tanesine sahip olduğunuzda kesinlikle yapacaktır! TickerProviderStateMixin gibi bir mixin kullanmanız gerekmiyorsa, Get ile bir StatefulWidget kullanmak tamamen gereksiz olacaktır. StatefulWidget'ın tüm yöntemlerini doğrudan bir GetBuilder'dan çağırabilirsiniz. Örneğin, initState() veya Dispose() yöntemini çağırmanız gerekiyorsa, bunları doğrudan çağırabilirsiniz; ``` dart GetBuilder( initState: (_) => Controller.to.fetchApi(), dispose: (_) => Controller.to.closeStreams(), builder: (s) => Text('${s.username}'), ), ``` Bundan çok daha iyi bir yaklaşım, doğrudan controller'dan onInit() ve onClose() yöntemini kullanmaktır. ``` dart @override void onInit() { fetchApi(); super.onInit(); } ``` * NOT: Controller ilk kez çağrıldığı anda bir metot başlatmak istiyorsanız, bunun için constructors kullanmanıza GEREK YOKTUR, aslında Get gibi performans odaklı bir paket kullanarak bu sorunu çözebilirsiniz. Controller'ların oluşturulduğu veya tahsis edildiği mantıktan saptığı için (bu controller'ın bir örneğini oluşturursanız, constructor hemen çağrılır, bir controller kullanılmadan önce bellek ayırırsınız ve belleği doldurursunuz. Bu kesinlikle bu kütüphanenin ilkelerine zarar verir. onInit() yöntemleri; ve onClose(); için oluşturulduysa, Get.lazyPut kullanıp kullanmadığınıza bağlı olarak Controller oluşturulduğunda veya ilk kez kullanıldığında çağrılırlar. Örneğin, verileri doldurmak için API'nize bir çağrı yapmak istiyorsanız, eski moda initState/dispose yöntemini unutabilirsiniz, sadece onInit'te api'ye çağrınızı başlatın ve herhangi bir komutu çalıştırmanız gerekirse akışları kapatmak gibi, bunun için onClose()'u kullanın. ### Neden var Bu paketin amacı, mümkün olan en az bağımlılıkları kullanarak, yüksek derecede ayrıştırma ile rotaların gezinmesi, bağımlılıkların ve durumların yönetimi için eksiksiz bir çözüm sunmaktır. Get, mümkün olan en az bağlantıyla çalıştığınızdan emin olmak için tüm yüksek ve düşük seviyeli Flutter API'lerini kendi içinde çalıştırır. Projenizde herhangi bir bağlantı olmadığından emin olmak için her şeyi tek bir pakette merkezileştiriyoruz. Bu şekilde, görünümünüze yalnızca widget'lar koyabilir ve ekibinizin iş mantığıyla çalışan bölümünü, Görünümün herhangi bir öğesine bağlı kalmadan iş mantığıyla çalışmak üzere serbest bırakabilirsiniz. Bu, çok daha temiz bir çalışma ortamı sağlar, böylece ekibinizin bir kısmı controller'a veri göndermekten endişe etmeden yalnızca widget'larla çalışır ve ekibinizin bir kısmı, görünümün hiçbir öğesine bağlı kalmadan yalnızca genişliğindeki iş mantığıyla çalışır. Yani bunu basitleştirirsek: Initstate'deki yöntemleri çağırmanız ve bunları controller'ınıza parametre ile göndermeniz veya bunun için controller içerisinde constructor kullanmanız gerekmez, doğru zamanda çağrılan onInit () yöntemine sahip olursunuz. Cihaz aramak zorunda değilsiniz, gerektiğinde tam zamanında close() yöntemi ile hafızasından silinecektir. Bu şekilde, yalnızca widget'lar için görünümler bırakın, her türlü iş mantığından kaçının. GetxController içinde bir dispose yöntemi çağırmayın, hiçbir şey yapmaz, controller'ın bir Widget olmadığını, "dispose" gerektiğini ve Get tarafından bellekten otomatik ve akıllıca kaldırılacağını unutmayın. Üzerinde herhangi bir akış kullandıysanız ve kapatmak istiyorsanız, onu close yöntemine eklemeniz yeterlidir. Örnek: ``` dart class Controller extends GetxController { StreamController user = StreamController(); StreamController name = StreamController(); /// close stream = onClose yöntemi, dispose değil. @override void onClose() { user.close(); name.close(); super.onClose(); } } ``` Controller life cycle(yaşam döngüsü): * onInit() oluşturulduğu yer. * onClose() close yöntemine hazırlanırken herhangi bir değişiklik yapmak için kapatıldığı yer. * deleted: Controller bellekten tam anlamıyla kaldırdığı için bu API'ye erişiminiz olmaz. Herhangi bir iz bırakmadan kelimenin tam anlamıyla silinir. ### Kullanmanın diğer yolları Controller'ı doğrudan GetBuilder ile kullanabilirsiniz: ``` dart GetBuilder( init: Controller(), builder: (value) => Text( '${value.counter}', //here ), ), ``` Controller'ınızı GetBuilder dışında bir örneğine de ihtiyacınız olabilir ve bunu başarmak için bu yaklaşımları kullanabilirsiniz: ``` dart class Controller extends GetxController { static Controller get to => Get.find(); [...] } // görünümde: GetBuilder( init: Controller(), // Her controller'ı bir kez kullanın builder: (_) => Text( '${Controller.to.counter}', //burada ) ), ``` or ``` dart class Controller extends GetxController { // static Controller get to => Get.find(); // static olmadan [...] } // on stateful/stateless class GetBuilder( init: Controller(), // Her controller'ı bir kez kullanın builder: (_) => Text( '${Get.find().counter}', //burada ), ), ``` * Bunu yapmak için "non-canonical" yaklaşımları kullanabilirsiniz. Get_it, modular vb. Gibi başka bir dependency manager kullanıyorsanız ve sadece controller instance etmek istiyorsanız, bunu yapabilirsiniz: ``` dart Controller controller = Controller(); [...] GetBuilder( init: controller, //burada builder: (_) => Text( '${controller.counter}', // burada ), ), ``` ### Unique IDs-Benzersiz Kimlikler Bir widget'ın controller'ını güncellemek istiyorsanız GetBuilder onlara benzersiz kimlikler atayabilirsiniz: ``` dart GetBuilder( id: 'text' init: Controller(), // Her controller'ı bir kez kullanın builder: (_) => Text( '${Get.find().counter}', //burada ), ), ``` Ve bu formu güncelleyin: ``` dart update(['text']); ``` Güncelleme için koşullar da uygulayabilirsiniz: ``` dart update(['text'], counter < 10); ``` GetX bunu otomatik olarak yapar ve yalnızca değiştirilen değişkeni tam olarak kullanan widget'ı yeniden yapılandırır, bir değişkeni öncekiyle aynı olacak şekilde değiştirirseniz ve bu state değişikliği anlamına gelmezse GetX widget'ı bellek ve CPU döngülerinden tasarruf etmek için yeniden oluşturmaz (ekranda 3 görüntüleniyor ve değişkeni tekrar 3 olarak değiştirirsiniz. Çoğu state manager, bu yeni bir yeniden yapılanmaya neden olur, ancak Get ile widget yalnızca state değiştiyse yeniden oluşturulur). ## İki state managers ile Mixing Bazı insanlar, yalnızca bir tür reaktif değişken ve diğer mekanikleri kullanmak istediklerinden ve bunun için bir GetBuilder'a bir Obx eklemeleri gerektiğinden bir özellik request'i açtı. Bunu düşünerek MixinBuilder oluşturuldu. Hem ".obs" değişkenlerini değiştirerek reaktif değişikliklere hem de update() aracılığıyla mekanik güncellemelere izin verildi. Bununla birlikte, 4 widget'tan en çok kaynak tüketendir, çünkü children'larda değişiklik olaylarını anlaması için sahip olmasının yanı sıra, controller'ın güncelleme yöntemine sahip olur. GetxController'ı extends etmek önemlidir, çünkü yaşam döngüleri vardır ve olayları onInit() ve onClose() yöntemlerinde "başlatabilir" ve "bitebilir". Bunun için herhangi bir sınıfı kullanabilirsiniz, ancak değişkenlerinizi observable olsun ya da olmasın yerleştirmek için GetxController sınıfını kullanmanızı şiddetle tavsiye ederim. ## StateMixin `UI` state'ini ele almanın başka bir yolu da `StateMixin` kullanmaktır. Bunu uygulamak için, `StateMixin` ile `with`i kullanın. bir controller'a T modelinizi ekleyin. ``` dart class Controller extends GetController with StateMixin{} ``` `change()` yöntemi istediğimiz zaman State'i değiştirir. Sadece verileri ve state'i bu şekilde iletin: ```dart change(data, status: RxStatus.success()); ``` RxStatus şu duruma izin verir: ``` dart RxStatus.loading(); RxStatus.success(); RxStatus.empty(); RxStatus.error('message'); ``` UI'da bu şekilde kullanın: ```dart class OtherClass extends GetView { @override Widget build(BuildContext context) { return Scaffold( body: controller.obx( (state)=>Text(state.name), // burada özel yükleme göstergenizi koyabilirsiniz, ancak // varsayılan olarak Center(child:CircularProgressIndicator()) olacaktır. onLoading: CustomLoadingIndicator(), onEmpty: Text('No data found'), // burada ayrıca kendi hata widget'ınızı ayarlayabilirsiniz, ancak // default birCenter(child:Text(error)) olacaktır. onError: (error)=>Text(error), ), ); } ``` ## GetBuilder vs GetX vs Obx vs MixinBuilder Programlamayla geçen on yılda bazı değerli dersler öğrenebildim. Reaktif programlama ile ilk temasım çok "vay be, bu inanılmaz" oldu ve aslında reaktif programlama inanılmaz. Ancak, tüm durumlar için uygun değildir. Çoğu zaman tek ihtiyacınız olan, aynı anda 2 veya 3 parçacığın durumunu değiştirmek veya geçici bir durum değişikliğidir, bu durumda reaktif programlama kötü değildir, ancak uygun değildir. Reaktif programlama, bireysel iş akışıyla telafi edilebilecek daha yüksek bir RAM tüketimine sahiptir; bu, yalnızca bir widget'ın yeniden oluşturulmasını ve gerektiğinde yapılmasını sağlar, ancak her biri birkaç akışa sahip 80 nesneden oluşan bir liste oluşturmak iyi bir fikir değildir. Dartı açın ve bir StreamBuilder'ın ne kadar tükettiğini kontrol edin ve size ne söylemeye çalıştığımı anlayacaksınız. Bunu akılda tutarak, basit bir state manager yarattım. Bu basittir ve ondan tam olarak talep etmeniz gereken şey budur: State'i bloklar halinde basit bir şekilde ve en ekonomik şekilde güncellemek. GetBuilder RAM'de çok ekonomiktir ve ondan daha ekonomik bir yaklaşım yoktur (en azından ben bir tane hayal edemiyorum, varsa lütfen bize bildirin). Ancak GetBuilder hala mekanik bir state manager'dir, tıpkı Provider'ın notifyListeners() işlevini çağırmanız gerektiği gibi update() öğesini çağırmanız gerekir. Reaktif programlamanın gerçekten ilginç olduğu başka durumlar da vardır ve onunla çalışmamak, tekerleği yeniden icat etmekle aynı şeydir. Bunu akılda tutarak GetX, bir State Manager'de en modern ve gelişmiş olan her şeyi sağlamak için oluşturuldu. Sadece gerekli olanı günceller ve gerektiğinde, bir hatanız varsa ve aynı anda 300 durum değişikliği gönderirseniz GetX, yalnızca durum gerçekten değiştiğinde ekranı filtreler ve günceller. GetX, diğer herhangi bir reaktif durum yöneticisinden hala daha ekonomiktir, ancak GetBuilder'dan biraz daha fazla RAM tüketir. Bunu düşünerek ve Obx'in yarattığı kaynakların tüketimini en üst düzeye çıkarmayı hedefleyerek. GetX ve GetBuilder'dan farklı olarak, bir Obx içinde bir controller başlatamayacaksınız, bu sadece children'larda değişiklik olaylarını alan bir StreamSubscription'a sahip bir Widget'tır, hepsi bu. GetX'ten daha ekonomiktir, ancak reaktif olduğu için beklendiği gibi GetBuilder'a kaydeder ve GetBuilder, bir parçacığın hashcode'unu ve StateSetter'ını depolamak için var olan en basit yaklaşıma sahiptir. Obx ile controller türünüzü yazmanız gerekmez ve değişikliği birden çok farklı controller'dan ulaşabilir ve dinleyebilirsiniz, ancak bu readme dosyasının başındaki örnek yaklaşım kullanılarak veya Bindings classı kullanılarak daha önce başlatılması gerekir. ================================================ FILE: documentation/vi_VI/dependency_management.md ================================================ # Quản lý dependency - [Quản lý dependency](#dependency-management) - [Instancing methods](#instancing-methods) - [Get.put()](#getput) - [Get.lazyPut](#getlazyput) - [Get.putAsync](#getputasync) - [Get.create](#getcreate) - [Sử dụng các phương thức / class](#using-instantiated-methodsclasses) - [Khác nhau giữa phương thức (methods)](#differences-between-methods) - [Bindings](#bindings) - [Cách sử dụng](#how-to-use) - [BindingsBuilder](#bindingsbuilder) - [SmartManagement](#smartmanagement) - [Cách thay đổi](#How-to-change) - [SmartManagement.full](#smartmanagementfull) - [SmartManagement.onlyBuilders](#smartmanagementonlybuilders) - [SmartManagement.keepFactory](#smartmanagementkeepfactory) - [Cách bindings làm việc ngầm](#how-bindings-work-under-the-hood) - [Chí ú](#notes) Get có một trình quản lý dependency đơn giản và mạnh mẽ cho phép bạn truy xuất cùng một class với Blocs hoặc Controller của bạn chỉ với 1 dòng mã, không có "context", không có InheritedWidget ```dart Controller controller = Get.put(Controller()); // Rather Controller controller = Controller(); ``` Thay vì khởi tạo class của bạn trong class bạn đang sử dụng, bạn đang khởi tạo nó trong phiên bản Get, điều này sẽ làm cho nó có sẵn trên toàn bộ Ứng dụng của bạn. Vì vậy, bạn có thể sử dụng controller (hoặc class Blocs) của mình một cách bình thường - Note: Nếu bạn đang sử dụng Get's State Manager, hãy chú ý hơn đến [Bindings](#bindings) api, điều này sẽ giúp kết nối chế độ xem với controller của bạn dễ dàng hơn. - Note²: Quản lý state của Get được tách biệt khỏi các phần khác của gói, vì vậy, nếu ví dụ: nếu ứng dụng của bạn đã sử dụng trình quản lý state (bất kỳ cái nào, không quan trọng), bạn không cần phải thay đổi điều đó, bạn có thể sử dụng phần dependency này người quản lý không có vấn đề gì cả ## Instancing methods Các phương thức và các tham số có thể định cấu hình của nó là: ### Get.put() Cách phổ biến nhất để chèn một dependency, là một điều tốt cho controller của View của bạn. ```dart Get.put(SomeClass()); Get.put(LoginController(), permanent: true); Get.put(ListItemController, tag: "some unique string"); ``` Đây là tùy chọn mà bạn có thể đặt lệnh: ```dart Get.put( // mandatory: the class that you want to get to save, like a controller or anything // note: "S" means that it can be a class of any type S dependency // optional: this is for when you want multiple classess that are of the same type // since you normally get a class by using Get.find(), // you need to use tag to tell which instance you need // must be unique string String tag, // optional: by default, get will dispose instances after they are not used anymore (example, // the controller of a view that is closed), but you might need that the instance // to be kept there throughout the entire app, like an instance of sharedPreferences or something // so you use this // defaults to false bool permanent = false, // optional: allows you after using an abstract class in a test, replace it with another one and follow the test. // defaults to false bool overrideAbstract = false, // optional: allows you to create the dependency using function instead of the dependency itself. // this one is not commonly used InstanceBuilderCallback builder, ) ``` ### Get.lazyPut Có thể lazyLoad một dependecy để nó chỉ được khởi tạo khi được sử dụng. Rất hữu ích cho các class ngốn nhiều tài nguyên hoặc nếu bạn muốn khởi tạo một số class chỉ ở một nơi (như trong class Bindings) và bạn biết rằng mình sẽ không sử dụng class đó tại thời điểm nhất định. ```dart /// ApiMock will only be called when someone uses Get.find for the first time Get.lazyPut(() => ApiMock()); Get.lazyPut( () { // ... some logic if needed return FirebaseAuth(); }, tag: Math.random().toString(), fenix: true ) Get.lazyPut( () => Controller() ) ``` Đây là các tùy chọn bạn có thể đặt lệnh: ```dart Get.lazyPut( // mandatory: a method that will be executed when your class is called for the first time InstanceBuilderCallback builder, // optional: same as Get.put(), it is used for when you want multiple different instance of a same class // must be unique String tag, // optional: It is similar to "permanent", the difference is that the instance is discarded when // is not being used, but when it's use is needed again, Get will recreate the instance // just the same as "SmartManagement.keepFactory" in the bindings api // defaults to false bool fenix = false ) ``` ### Get.putAsync Đây là khi bạn muốn xài asynchronize code `Get.putAsync`: ```dart Get.putAsync(() async { final prefs = await SharedPreferences.getInstance(); await prefs.setInt('counter', 12345); return prefs; }); Get.putAsync( () async => await YourAsyncClass() ) ``` Đây là tùy chọn bạn có thể đặt lệnh với putAsync: ```dart Get.putAsync( // mandatory: an async method that will be executed to instantiate your class AsyncInstanceBuilderCallback builder, // optional: same as Get.put(), it is used for when you want multiple different instance of a same class // must be unique String tag, // optional: same as in Get.put(), used when you need to maintain that instance alive in the entire app // defaults to false bool permanent = false ) ``` ### Get.create Cái này hơi khó giải thích, nhưng sự khác nhau giữa chúng có thể được tìm thấy trên mục [Differences between methods:](#differences-between-methods) ```dart Get.Create(() => SomeClass()); Get.Create(() => LoginController()); ``` Tùy chọn có thể sử dụng: ```dart Get.create( // required: a function that returns a class that will be "fabricated" every // time `Get.find()` is called // Example: Get.create(() => YourClass()) FcBuilderFunc builder, // optional: just like Get.put(), but it is used when you need multiple instances // of a of a same class // Useful in case you have a list that each item need it's own controller // needs to be a unique string. Just change from tag to name String name, // optional: just like int`Get.put()`, it is for when you need to keep the // instance alive thoughout the entire app. The difference is in Get.create // permanent is true by default bool permanent = true ``` ## Sử dụng các phương thức / class Hãy tưởng tượng rằng bạn đã điều hướng qua nhiều route và bạn cần một dữ liệu còn sót trong controller của mình, bạn sẽ cần một trình quản lý state kết hợp với Provider hoặc Get_it, phải hem? Với Get, bạn chỉ cần yêu cầu Get to "find" cho controller của mình và thế là xong: ```dart final controller = Get.find(); // OR Controller controller = Get.find(); // Yes, it looks like Magic, Get will find your controller, and will deliver it to you. // You can have 1 million controllers instantiated, Get will always give you the right controller. ``` Và sau đó, bạn sẽ có thể khôi phục dữ liệu controller của mình đã lấy được ở đó: ```dart Text(controller.textFromApi); ``` Vì giá trị trả về là một class bình thường, bạn có thể làm bất cứ điều gì bạn muốn: ```dart int count = Get.find().getInt('counter'); print(count); // out: 12345 ``` Để xóa một controller đang chạy ngầm của Get: ```dart Get.delete(); //thường thì Get tự xóa, bạn không cần phải đặt lệnh này. ``` ## Khác nhau giữa phương thức (methods) Đầu tiên, hãy nói về `fenix` của Get.lazyPut và `permanent`của các phương thức khác. Sự khác biệt cơ bản giữa `permanent` và `fenix` là cách bạn muốn lưu trữ các cá thể của mình. Củng cố: theo mặc định, GetX xóa các trường hợp khi chúng không được sử dụng. Có nghĩa là: Nếu màn hình 1 có controller 1 và màn hình 2 có controller 2 và bạn xóa route đầu tiên khỏi stack, (chẳng hạn như nếu bạn sử dụng `` Get.off () 'hoặc' `Get.offNamed()``) thì controller 1 bị mất việc sử dụng nó vì vậy nó sẽ bị xóa. Nhưng nếu bạn muốn chọn sử dụng `permanent: true`, thì controller sẽ không bị mất trong quá trình chuyển đổi này - điều này rất hữu ích cho các dịch vụ mà bạn muốn duy trì hoạt động trong toàn bộ ứng dụng. Mặt khác, `fenix` dành cho các dịch vụ mà bạn không lo bị mất giữa các lần thay đổi màn hình, nhưng khi bạn cần dịch vụ đó, bạn hy vọng rằng nó vẫn tồn tại. Vì vậy, về cơ bản, nó sẽ loại bỏ controller / service / class không sử dụng, nhưng khi bạn cần, nó sẽ "tạo lại từ đống tro tàn" ở một trường hợp (instance) mới. Tiếp tục với sự khác biệt giữa các phương pháp: - Get.put và Get.putAsync tuân theo cùng một thứ tự tạo, với sự khác biệt là một cái sử dụng phương thức không đồng bộ: hai phương thức đó đều tạo và khởi tạo các trường hợp. Cái sử dụng không đồng bộ được chèn trực tiếp vào bộ nhớ, bằng cách sử dụng phương thức nội bộ `insert` với các tham số `permanent: false` và` isSingleton: true` (tham số isSingleton này chỉ nhằm mục đích cho biết liệu nó có sử dụng dependency vào `dependency` hay không hoặc nếu nó được sử dụng dependency vào `FcBuilderFunc`). Sau đó, `Get.find ()` được gọi để khởi tạo ngay lập tức các các trường hợp trên bộ nhớ. - Get.create: Như tên của nó, nó sẽ "tạo ra" sự dependency cho bạn! Tương tự như `Get.put ()`, nó cũng gọi phương thức nội bộ là `insert` để các trường hợp. Nhưng `permanent` trở thành true và` isSingleton` trở thành false (vì chúng ta đang "tạo" dependency của mình, không có cách nào để nó là một instace singleton, đó là lý do tại sao lại là false). Và bởi vì nó có `permanent: true`, chúng tôi mặc định có lợi ích là không bị mất nó giữa các màn hình! Ngoài ra, `` Get.find () 'không được gọi ngay lập tức, nó phải chờ được sử dụng trong màn hình để được gọi. Nó được tạo ra theo cách này để sử dụng tham số `permanent ', vì vậy, đáng chú ý là` Get.create () `được tạo ra với mục tiêu tạo ra các phiên bản không được chia sẻ, nhưng không bị loại bỏ, như ví dụ: trong listView, mà bạn muốn có một phiên bản duy nhất cho danh sách đó - do đó, Get.create phải được sử dụng cùng với GetWidget. - Get.lazyPut: Như tên của nó, nó là một quy trình lười biếng. Cá thể được tạo, nhưng nó không được gọi để sử dụng ngay lập tức, nó vẫn đang chờ được gọi. Trái ngược với các phương thức khác, `insert` không được gọi ở đây. Thay vào đó, cá thể được chèn vào một phần khác của bộ nhớ, một phần chịu trách nhiệm cho biết liệu cá thể đó có thể được tạo lại hay không, chúng ta hãy gọi nó là "nhà máy". Nếu chúng ta muốn tạo ra thứ gì đó để sử dụng sau này, nó sẽ không bị trộn lẫn với những thứ đã được sử dụng ngay bây giờ. Và đây là nơi phép thuật của `fenix` đi vào: nếu bạn chọn bỏ` fenix: false`, và `smartManagement` của bạn không phải là` keepFactory`, thì khi sử dụng `Get.find`, instance sẽ thay đổi vị trí trong bộ nhớ từ "nhà máy" đến vùng bộ nhớ cá thể chung. Ngay sau đó, theo mặc định, nó được xóa khỏi "nhà máy". Bây giờ, nếu bạn chọn `fenix: true`, cá thể vẫn tiếp tục tồn tại trong phần dành riêng này, thậm chí sẽ chuyển sang vùng chung, sẽ được gọi lại trong tương lai. ## Bindings Có lẽ, một trong những điểm khác biệt lớn của gói này là khả năng tích hợp đầy đủ các route, trình quản lý state và trình quản lý dependency. Khi một route bị xóa khỏi stack, tất cả các controller, biến và phiên bản của các đối tượng liên quan đến nó sẽ bị xóa khỏi bộ nhớ. Nếu bạn đang sử dụng luồng hoặc bộ hẹn giờ, chúng sẽ tự động bị đóng và bạn không phải lo lắng về bất kỳ điều gì trong số đó. Trong phiên bản 2.10 Được triển khai hoàn toàn API bindings. Bây giờ bạn không cần sử dụng phương thức init nữa. Bạn thậm chí không cần phải nhập controller của mình nếu bạn không muốn. Bạn có thể khởi động controller và dịch vụ của mình ở nơi thích hợp cho việc đó. Lớp Binding là một class sẽ tách riêng việc tiêm dependency, trong khi "bindings" các route đường tới trình quản lý state và trình quản lý dependency. Điều này cho phép Nhận biết màn hình nào đang được hiển thị khi một controller cụ thể được sử dụng và biết vị trí và cách vứt bỏ nó. Ngoài ra, class Binding sẽ cho phép bạn kiểm soát cấu hình SmartManager. Bạn có thể định cấu hình các phần dependency được sắp xếp khi xóa một route khỏi ngăn xếp hoặc khi widget con đã sử dụng nó được bố trí hoặc không. Bạn sẽ có quản lý dependency thông minh làm việc cho bạn, nhưng ngay cả như vậy, bạn có thể định cấu hình nó theo ý muốn. ### Bindings class - Tạo một class và implements Binding ```dart class HomeBinding implements Bindings {} ``` IDE của bạn sẽ tự động yêu cầu bạn ghi đè phương thức "dependency" và bạn chỉ cần nhấp vào đèn, ghi đè phương thức và chèn tất cả các class bạn sẽ sử dụng trên route đó: ```dart class HomeBinding implements Bindings { @override void dependencies() { Get.lazyPut(() => HomeController()); Get.put(()=> Api()); } } class DetailsBinding implements Bindings { @override void dependencies() { Get.lazyPut(() => DetailsController()); } } ``` Bây giờ bạn chỉ cần thông báo route của mình, rằng bạn sẽ sử dụng bindings đó để tạo kết nối giữa trình quản lý route, các dependency và state. - Sử dụng routes có tên: ```dart getPages: [ GetPage( name: '/', page: () => HomeView(), binding: HomeBinding(), ), GetPage( name: '/details', page: () => DetailsView(), binding: DetailsBinding(), ), ]; ``` - Sử dụng routes thường: ```dart Get.to(Home(), binding: HomeBinding()); Get.to(DetailsView(), binding: DetailsBinding()) ``` Ở đó, bạn không phải lo lắng về việc quản lý bộ nhớ của ứng dụng của mình nữa, Get sẽ thay bạn làm điều đó. Lớp Binding được gọi khi một route được gọi, bạn có thể tạo một "InitialBinding trong GetMaterialApp của mình để chèn tất cả các dependency sẽ được tạo. ```dart GetMaterialApp( initialBinding: SampleBind(), home: Home(), ); ``` ### BindingsBuilder Cách mặc định để tạo bindings là tạo một class thực hiện các bindings. Nhưng cách khác, bạn có thể sử dụng lệnh gọi lại `BindingsBuilder` để bạn có thể chỉ cần sử dụng một hàm để khởi tạo bất cứ thứ gì bạn muốn. Example: ```dart getPages: [ GetPage( name: '/', page: () => HomeView(), binding: BindingsBuilder(() { Get.lazyPut(() => ControllerX()); Get.put(()=> Api()); }), ), GetPage( name: '/details', page: () => DetailsView(), binding: BindingsBuilder(() { Get.lazyPut(() => DetailsController()); }), ), ]; ``` Bằng cách đó, bạn có thể tránh tạo một class Binding cho mỗi routes, làm cho việc này trở nên đơn giản hơn. Cả hai cách làm việc đều hoàn toàn tốt và chúng tôi muốn bạn sử dụng những gì phù hợp với sở thích của bạn nhất. ### SmartManagement GetX theo mặc định loại bỏ controller không sử dụng khỏi bộ nhớ, ngay cả khi xảy ra lỗi và widget con sử dụng nó không được xử lý đúng cách. Đây được gọi là chế độ quản lý dependency `` đầy đủ`. Nhưng nếu bạn muốn thay đổi cách GetX kiểm soát việc xử lý các class, bạn có class `SmartManagement` để bạn có thể thiết lập các hành vi khác nhau. #### Cách thay đổi Nếu bạn muốn thay đổi cấu hình này (mà bạn thường không cần) thì đây là cách: ```dart void main () { runApp( GetMaterialApp( smartManagement: SmartManagement.onlyBuilders //here home: Home(), ) ) } ``` #### SmartManagement.full Nó là một trong những mặc định. Loại bỏ các class không được sử dụng và không được đặt thành vĩnh viễn. Trong phần lớn các trường hợp, bạn sẽ muốn giữ nguyên cấu hình này. Nếu bạn mới sử dụng GetX thì đừng thay đổi điều này. #### SmartManagement.onlyBuilders Với tùy chọn này, chỉ những controller bắt đầu trong `init: 'hoặc được tải vào Binding với` `Get.lazyPut ()` mới được xử lý. Nếu bạn sử dụng `Get.put () 'hoặc' Get.putAsync ()` hoặc bất kỳ cách tiếp cận nào khác, SmartManagement sẽ không có quyền loại trừ sự dependency này. Với hành vi mặc định, ngay cả các widget con được khởi tạo bằng "Get.put" sẽ bị xóa, không giống như SmartManagement.onlyBuilders. #### SmartManagement.keepFactory Cũng giống như SmartManagement.full, nó sẽ loại bỏ các phần dependency của nó khi nó không được sử dụng nữa. Tuy nhiên, nó sẽ giữ nguyên chế độ factory của họ, có nghĩa là nó sẽ tạo lại phần dependency nếu bạn cần lại phiên bản đó. ### Cách bindings làm việc ngầm Các liên kết tạo ra các factory tạm thời, được tạo ra ngay khi bạn nhấp để chuyển sang màn hình khác và sẽ bị phá hủy ngay sau khi hoạt ảnh thay đổi màn hình xảy ra. Điều này xảy ra quá nhanh đến nỗi máy phân tích thậm chí sẽ không thể đăng ký nó. Khi bạn điều hướng đến màn hình này một lần nữa, một factory tạm thời mới sẽ được gọi, vì vậy điều này thích hợp hơn khi sử dụng SmartManagement.keepFactory, nhưng nếu bạn không muốn tạo Bindings hoặc muốn giữ tất cả các dependency của mình trên cùng một Binding, thì chắc chắn sẽ giúp ích cho bạn. Các factory chiếm ít bộ nhớ, chúng không chứa các cá thể mà là một chức năng có "hình dạng" của class đó mà bạn muốn. Điều này có chi phí bộ nhớ rất thấp, nhưng vì mục đích của lib này là để đạt được hiệu suất tối đa có thể bằng cách sử dụng tài nguyên tối thiểu, Get xóa ngay cả các factory theo mặc định. Sử dụng cái nào thuận tiện nhất cho bạn. ## Chí ú - KHÔNG SỬ DỤNG SmartManagement.keepFactory nếu bạn đang sử dụng nhiều Binding. Nó được thiết kế để sử dụng mà không có Bindings, hoặc với một Bindings duy nhất được liên kết trong `initialBinding` của GetMaterialApp. - Việc sử dụng Bindings là hoàn toàn tùy chọn, nếu muốn, bạn có thể sử dụng `Get.put () 'và' Get.find()` trên các class sử dụng controller nhất định mà không gặp bất kỳ vấn đề gì. Tuy nhiên, nếu bạn làm việc với Service hoặc bất kỳ abstract nào khác, tôi khuyên bạn nên sử dụng Bindings để tổ chức tốt hơn. ================================================ FILE: documentation/vi_VI/route_management.md ================================================ - [Quản lý route](#route-management) - [Hướng dẫn sử dụng trước khi dùng](#how-to-use) - [Điều hướng không cần tên](#navigation-without-named-routes) - [Điều hướng cần tên](#navigation-with-named-routes) - [Gửi data cho route có tên](#send-data-to-named-routes) - [Dynamic urls links](#dynamic-urls-links) - [Middleware](#middleware) - [Điều hướng không cần context](#navigation-without-context) - [SnackBars](#snackbars) - [Dialogs](#dialogs) - [BottomSheets](#bottomsheets) - [Điều hướng lồng (Nested Navigation)](#nested-navigation) # Quản lý route Đây là lời giải thích đầy đủ về tất cả những gì có cho Getx khi vấn đề là quản lý routes. ## Hướng dẫn sử dụng trước khi dùng Thêm cái này vào file pubspec.yaml của bạn: ```yaml dependencies: get: ``` Nếu bạn định sử dụng các routes / snackbars / dialogs / bottomsheets mà không có "context" hoặc sử dụng các API cấp cao, bạn chỉ cần thêm Get trước MaterialApp của mình, biến nó thành GetMaterialApp và tung hành! ```dart GetMaterialApp( // Before: MaterialApp( home: MyHome(), ) ``` ## Điều hướng không cần tên Để điều hướng đến một màn hình mới: ```dart Get.to(NextScreen()); ``` Để đóng snackbars, dialog, bottomsheets hoặc bất cứ thứ gì bạn thường đóng bằng Navigator.pop (context); ```dart Get.back(); ``` Để chuyển đến màn hình tiếp theo và không có tùy chọn nào để quay lại màn hình trước đó (để sử dụng trong SplashScreens, màn hình đăng nhập, v.v.) ```dart Get.off(NextScreen()); ``` Để chuyển đến màn hình tiếp theo và hủy tất cả các lộ trình trước đó (hữu ích trong giỏ hàng, polls và test) ```dart Get.offAll(NextScreen()); ``` Để điều hướng đến routes tiếp theo và nhận hoặc cập nhật dữ liệu ngay sau khi bạn trở về từ routes đó: ```dart var data = await Get.to(Payment()); ``` trên màn hình khác, gửi dữ liệu cho routes trước đó: ```dart Get.back(result: 'success'); ``` And use it: ex: ```dart if(data == 'success') madeAnything(); ``` Bạn không muốn học cú pháp của chúng tôi? Chỉ cần thay đổi Navigator (chữ in hoa) thành navigator (chữ thường) và bạn sẽ có tất cả các chức năng của điều hướng tiêu chuẩn mà không cần phải sử dụng "context" Thí dụ: ```dart // Default Flutter navigator Navigator.of(context).push( context, MaterialPageRoute( builder: (BuildContext context) { return HomePage(); }, ), ); // Get using Flutter syntax without needing context navigator.push( MaterialPageRoute( builder: (_) { return HomePage(); }, ), ); // Get syntax (It is much better, but you have the right to disagree) Get.to(HomePage()); ``` ## Điều hướng cần tên - Nếu bạn thích điều hướng bằng tên, Get cũng hỗ trợ điều này. To navigate to nextScreen ```dart Get.toNamed("/NextScreen"); ``` Để điều hướng và xóa màn hình trước đó khỏi cây widget. ```dart Get.offNamed("/NextScreen"); ``` Để điều hướng và xóa tất cả các màn hình trước đó khỏi cây widget. ```dart Get.offAllNamed("/NextScreen"); ``` Để định dạng routes, sử dụng GetMaterialApp: ```dart void main() { runApp( GetMaterialApp( initialRoute: '/', getPages: [ GetPage(name: '/', page: () => MyHomePage()), GetPage(name: '/second', page: () => Second()), GetPage( name: '/third', page: () => Third(), transition: Transition.zoom ), ], ) ); } ``` Để xử lý điều hướng đến các routes không được xác định (lỗi 404), bạn có thể xác định trang 'không xác định' trong GetMaterialApp. ```dart void main() { runApp( GetMaterialApp( unknownRoute: GetPage(name: '/notfound', page: () => UnknownRoutePage()), initialRoute: '/', getPages: [ GetPage(name: '/', page: () => MyHomePage()), GetPage(name: '/second', page: () => Second()), ], ) ); } ``` ### Gửi data cho route có tên Chỉ cần gửi những gì bạn muốn cho các đối số (arguments). Get chấp nhận bất kỳ thứ gì ở đây, cho dù đó là String, Map, List hay thậm chí là một class trường hợp. ```dart Get.toNamed("/NextScreen", arguments: 'Get is the best'); ``` Trong class controller của bạn: ```dart print(Get.arguments); //print out: Get is the best ``` ### Dynamic urls links Get hỗ trợ các url động nâng cao giống như trên Web. Các nhà phát triển web có lẽ đã muốn tính năng này trên Flutter và rất có thể đã thấy một gói hứa hẹn tính năng này và cung cấp một cú pháp hoàn toàn khác so với một URL sẽ có trên web, và Get cũng giải quyết được điều này. ```dart Get.offAllNamed("/NextScreen?device=phone&id=354&name=Enzo"); ``` trong controller/bloc/stateful/stateless của class: ```dart print(Get.parameters['id']); // out: 354 print(Get.parameters['name']); // out: Enzo ``` Bạn có thể đặt NamedParameters với Get dễ dàng: ```dart void main() { runApp( GetMaterialApp( initialRoute: '/', getPages: [ GetPage( name: '/', page: () => MyHomePage(), ), GetPage( name: '/profile/', page: () => MyProfile(), ), //You can define a different page for routes with arguments, and another without arguments, but for that you must use the slash '/' on the route that will not receive arguments as above. GetPage( name: '/profile/:user', page: () => UserProfile(), ), GetPage( name: '/third', page: () => Third(), transition: Transition.cupertino ), ], ) ); } ``` Gửi data bằng tên ```dart Get.toNamed("/profile/34954"); ``` Trên màn hình thứ hai, lấy dữ liệu theo tham số (parameters) ```dart print(Get.parameters['user']); // out: 34954 ``` hoặc gửi nhiều tham số như thế này ```dart Get.toNamed("/profile/34954?flag=true&country=italy"); ``` or ```dart var parameters = {"flag": "true","country": "italy",}; Get.toNamed("/profile/34954", parameters: parameters); ``` Trên màn hình thứ hai, lấy dữ liệu theo các tham số như thường lệ ```dart print(Get.parameters['user']); print(Get.parameters['flag']); print(Get.parameters['country']); // out: 34954 true italy ``` Và bây giờ, tất cả những gì bạn cần làm là sử dụng Get.toNamed () để điều hướng các routes đã đặt tên của bạn mà không cần bất kỳ "context" nào (bạn có thể gọi các routes của mình trực tiếp từ BLoC hoặc lớp Bộ điều khiển) và khi ứng dụng của bạn được biên dịch lên web, các routes sẽ xuất hiện trong url <3 ### Middleware Nếu bạn muốn nghe Get events để kích hoạt các hành động, bạn có thể sử dụng routingCallback cho nó ```dart GetMaterialApp( routingCallback: (routing) { if(routing.current == '/second'){ openAds(); } } ) ``` Nếu bạn không sử dụng GetMaterialApp, bạn có thể sử dụng API thủ công để đính kèm trình quan sát Middleware. ```dart void main() { runApp( MaterialApp( onGenerateRoute: Router.generateRoute, initialRoute: "/", navigatorKey: Get.key, navigatorObservers: [ GetObserver(MiddleWare.observer), // HERE !!! ], ), ); } ``` Tạo một MiddleWare class ```dart class MiddleWare { static observer(Routing routing) { /// You can listen in addition to the routes, the snackbars, dialogs and bottomsheets on each screen. ///If you need to enter any of these 3 events directly here, ///you must specify that the event is != Than you are trying to do. if (routing.current == '/second' && !routing.isSnackbar) { Get.snackbar("Hi", "You are on second route"); } else if (routing.current =='/third'){ print('last route called'); } } } ``` Bây giờ, hãy sử dụng Get trên code của bạn: ```dart class First extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( leading: IconButton( icon: Icon(Icons.add), onPressed: () { Get.snackbar("hi", "i am a modern snackbar"); }, ), title: Text('First Route'), ), body: Center( child: ElevatedButton( child: Text('Open route'), onPressed: () { Get.toNamed("/second"); }, ), ), ); } } class Second extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( leading: IconButton( icon: Icon(Icons.add), onPressed: () { Get.snackbar("hi", "i am a modern snackbar"); }, ), title: Text('second Route'), ), body: Center( child: ElevatedButton( child: Text('Open route'), onPressed: () { Get.toNamed("/third"); }, ), ), ); } } class Third extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Third Route"), ), body: Center( child: ElevatedButton( onPressed: () { Get.back(); }, child: Text('Go back!'), ), ), ); } } ``` ## Điều hướng không cần context ### SnackBars Để có một SnackBar đơn giản với Flutter, bạn phải lấy context của Scaffold, hoặc bạn phải sử dụng GlobalKey được gắn vào Scaffold của bạn ```dart final snackBar = SnackBar( content: Text('Hi!'), action: SnackBarAction( label: 'I am a old and ugly snackbar :(', onPressed: (){} ), ); // Find the Scaffold in the widget tree and use // it to show a SnackBar. Scaffold.of(context).showSnackBar(snackBar); ``` With Get: ```dart Get.snackbar('Hi', 'i am a modern snackbar'); ``` Với Get, tất cả những gì bạn phải làm là gọi thanh Get.snackbar từ bất kỳ đâu trong code của bạn hoặc tùy chỉnh nó theo cách bạn muốn! ```dart Get.snackbar( "Hey i'm a Get SnackBar!", // title "It's unbelievable! I'm using SnackBar without context, without boilerplate, without Scaffold, it is something truly amazing!", // message icon: Icon(Icons.alarm), shouldIconPulse: true, onTap:(){}, barBlur: 20, isDismissible: true, duration: Duration(seconds: 3), ); ////////// ALL FEATURES ////////// // Color colorText, // Duration duration, // SnackPosition snackPosition, // Widget titleText, // Widget messageText, // bool instantInit, // Widget icon, // bool shouldIconPulse, // double maxWidth, // EdgeInsets margin, // EdgeInsets padding, // double borderRadius, // Color borderColor, // double borderWidth, // Color backgroundColor, // Color leftBarIndicatorColor, // List boxShadows, // Gradient backgroundGradient, // TextButton mainButton, // OnTap onTap, // bool isDismissible, // bool showProgressIndicator, // AnimationController progressIndicatorController, // Color progressIndicatorBackgroundColor, // Animation progressIndicatorValueColor, // SnackStyle snackStyle, // Curve forwardAnimationCurve, // Curve reverseAnimationCurve, // Duration animationDuration, // double barBlur, // double overlayBlur, // Color overlayColor, // Form userInputForm /////////////////////////////////// ``` Nếu bạn thích snackbar truyền thống hoặc muốn tùy chỉnh nó từ đầu, bao gồm chỉ thêm một dòng (Get.snackbar sử dụng tiêu đề và thông báo bắt buộc), bạn có thể sử dụng `Get.rawSnackbar ()`; 'cung cấp API RAW trên đó Get. ### Dialogs To open dialog: ```dart Get.dialog(YourDialogWidget()); ``` To open default dialog: ```dart Get.defaultDialog( onConfirm: () => print("Ok"), middleText: "Dialog made in 3 lines of code" ); ``` Bạn cũng có thể sử dụng Get.generalDialog thay vì showGeneralDialog. Đối với tất cả các tiện ích hộp thoại Flutter khác, bao gồm cả cupertinos, bạn có thể sử dụng Get.overlayContext thay vì context và mở nó ở bất kỳ đâu trong mã của bạn. Đối với các widget không sử dụng Overlay, bạn có thể sử dụng Get.context. Hai context này sẽ hoạt động trong 99% trường hợp để thay thế context của UI của bạn, ngoại trừ các trường hợp trong đó inheritWidget được sử dụng mà không có context điều hướng. ### BottomSheets Get.bottomSheet giống như showModalBottomSheet, nhưng không cần context. ```dart Get.bottomSheet( Container( child: Wrap( children: [ ListTile( leading: Icon(Icons.music_note), title: Text('Music'), onTap: () {} ), ListTile( leading: Icon(Icons.videocam), title: Text('Video'), onTap: () {}, ), ], ), ) ); ``` ## Điều hướng lồng (Nested Navigation) Làm cho điều hướng lồng (nested navigation) của Flutter thậm chí còn dễ dàng hơn. Bạn không cần context và bạn sẽ tìm thấy stack điều hướng của mình theo Id. - CHÍ Ú: Việc tạo các stack điều hướng song song có thể gây nguy hiểm. Lý tưởng nhất là không sử dụng NestedNavigators, hoặc sử dụng một cách tối thiểu. Nếu dự án của bạn yêu cầu, hãy tiếp tục, nhưng hãy nhớ rằng việc giữ nhiều stack điều hướng trong bộ nhớ có thể không phải là một ý tưởng hay cho việc tiêu thụ RAM. Xem nó code đơn giản nè: ```dart Navigator( key: Get.nestedKey(1), // create a key by index initialRoute: '/', onGenerateRoute: (settings) { if (settings.name == '/') { return GetPageRoute( page: () => Scaffold( appBar: AppBar( title: Text("Main"), ), body: Center( child: TextButton( color: Colors.blue, onPressed: () { Get.toNamed('/second', id:1); // navigate by your nested route by index }, child: Text("Go to second"), ), ), ), ); } else if (settings.name == '/second') { return GetPageRoute( page: () => Center( child: Scaffold( appBar: AppBar( title: Text("Main"), ), body: Center( child: Text("second") ), ), ), ); } } ), ``` ================================================ FILE: documentation/vi_VI/state_management.md ================================================ * [Quản lý State](#state-management) + [Quản lý Reactive State](#reactive-state-manager) - [Lợi thế](#advantages) - [Hiệu suất tối đa:](#maximum-performance) - [Khai báo một biến phản ứng (reactive variable)](#declaring-a-reactive-variable) - [Thât dễ khi có reactive state.](#having-a-reactive-state-is-easy) - [Sử dụng values trong View](#using-the-values-in-the-view) - [Điều kiện để tái tạo lại](#conditions-to-rebuild) - [Nơi .obs có thể dùng](#where-obs-can-be-used) - [Chí ú về Lists](#note-about-lists) - [Tại sao tôi phải dùng .value](#why-i-have-to-use-value) - [Obx()](#obx) - [Workers](#workers) + [Quản lý State đơn giản](#simple-state-manager) - [Lợi thế](#advantages-1) - [Sử dụng](#usage) - [Cách GetX sử dụng controllers](#how-it-handles-controllers) - [Không cần StatefulWidget nữa!](#you-wont-need-statefulwidgets-anymore) - [Tại sao GetX tồn tại?](#why-it-exists) - [Cách sử dụng khác](#other-ways-of-using-it) - [IDs độc nhất](#unique-ids) + [Trộn hai trình quản lý state](#mixing-the-two-state-managers) + [GetBuilder vs GetX vs Obx vs MixinBuilder](#getbuilder-vs-getx-vs-obx-vs-mixinbuilder) # Quản lý State GetX không sử dụng Streams hoặc ChangeNotifier như các quản lý state khác. Tại sao? Ngoài việc xây dựng các ứng dụng cho android, iOS, web, linux, macos và linux, với GetX bạn có thể xây dựng các ứng dụng máy chủ với cú pháp tương tự như Flutter / GetX. Để cải thiện thời gian phản hồi và giảm mức tiêu thụ RAM, chúng tôi đã tạo GetValue và GetStream, là các giải pháp có độ trễ thấp mang lại nhiều hiệu suất với chi phí vận hành thấp. Chúng tôi sử dụng cơ sở này để xây dựng tất cả các nguồn lực của mình, bao gồm cả quản lý state. * _Phức hợp_: Một số quản lý state rất phức tạp và có rất nhiều cơ sở hạ tầng. Với GetX, bạn không phải xác định một class cho mỗi event, code rất rõ ràng và rõ ràng, và bạn làm được nhiều việc hơn bằng cách viết ít hơn. Nhiều người đã từ bỏ Flutter vì chủ đề này, và cuối cùng họ đã có một giải pháp đơn giản đến mức đần độn để quản lý các state. * _Không trình tạo mã_: Bạn dành một nửa thời gian phát triển để viết logic ứng dụng của mình. Một số quản lý state dựa vào trình tạo mã để có mã có thể đọc được ở mức tối thiểu. Việc thay đổi một biến và phải chạy build_runner có thể gây mất hiệu quả, chuyện này rất ngốn thời gian chờ đợi sau khi quét sạch sẽ rất lâu và bạn phải uống rất nhiều cà phê. Với GetX, mọi thứ đều hoạt động và độc lập với trình tạo mã, giúp tăng năng suất của bạn trong mọi khía cạnh phát triển của bạn. * _Không phụ thuộc vào context_: Có thể bạn đã cần gửi context của chế độ xem của mình tới controller, làm cho khả năng kết hợp của View với business logic của bạn cao hơn. Bạn có thể phải sử dụng một dependency cho một nơi không có context và phải chuyển context qua các class và hàm khác nhau. Điều này không tồn tại với GetX. Bạn có quyền truy cập vào controller của mình từ bên trong controller mà không cần bất kỳ context nào. Bạn không cần phải gửi context theo tham số vì không có gì theo nghĩa đen. * _Kiểm soát hạt_: Hầu hết các quản lý state đều dựa trên ChangeNotifier. ChangeNotifier sẽ thông báo cho tất cả các widget phụ thuộc vào nó khi thông báo cho các widget được gọi. Nếu bạn có 40 widget con trên một màn hình, trong đó có một biến thuộc class ChangeNotifier của bạn, khi bạn cập nhật một widget con, tất cả chúng sẽ được xây dựng lại. Với GetX, ngay cả các widget lồng nhau cũng được tôn trọng. Nếu bạn có Obx đang xem ListView của bạn và người khác đang xem hộp kiểm bên trong ListView, thì khi thay đổi giá trị CheckBox, chỉ nó mới được cập nhật, khi thay đổi giá trị List, chỉ ListView sẽ được cập nhật. * _Chỉ tái tạo lại nếu biến CẦN thay đổi_: GetX có tính năng kiểm soát streams, điều đó có nghĩa là nếu bạn hiển thị Text là 'Kaiser', nếu bạn thay đổi lại biến có thể quan sát thành 'Kaiser', widget sẽ không được tạo lại. Đó là bởi vì GetX biết rằng 'Kaiser' đã được hiển thị trong Văn bản và sẽ không thực hiện các thao tác tái tạo không cần thiết. Hầu hết (nếu không phải tất cả) các trình quản lý state hiện tại sẽ xây dựng lại trên màn hình. ## Quản lý Reactive State Lập trình phản ứng (Reactive programming) có thể khiến nhiều người xa lánh vì nó được cho là phức tạp. GetX biến lập trình phản ứng thành một thứ khá đơn giản: * Bạn sẽ không cần tạo StreamControllers. * Bạn sẽ không cần tạo StreamBuilder cho mỗi biến * Bạn sẽ không cần phải tạo một class cho mỗi state. * Bạn sẽ không cần tạo get cho một giá trị ban đầu. Lập trình phản ứng với Get dễ dàng như sử dụng setState. Hãy tưởng tượng rằng bạn có một biến tên và muốn rằng mỗi khi bạn thay đổi nó, tất cả các widget sử dụng nó sẽ được tự động thay đổi. Đây là count variable của bạn: ``` dart var name = 'Khang Huỳnh'; ``` Để làm cho nó có thể quan sát được, bạn chỉ cần thêm ".obs" vào cuối nó: ``` dart var name = 'Khang Huỳnh'.obs; ``` Chỉ vậy thôi, chỉ *vậy thôi* người ơi~ Từ bây giờ, chúng ta có thể tham chiếu đến các biến reactive - ". Obs" (có thể thay thế) này là _Rx_. Chúng tôi đã làm gì phía dưới class code? Chúng tôi đã tạo một `Stream` của `String`, được gán giá trị ban đầu `"Khang Huỳnh"`, chúng tôi đã thông báo cho tất cả các widget con sử dụng `"Khang Huỳnh"` rằng chúng hiện "thuộc về" biến này và khi giá trị _Rx_ thay đổi, chúng phải thay đổi theo. Đây là **phép màu của GetX**, nhờ vào khả năng của Dart. Tuy nhiên, như chúng ta đã biết, một `Widget` chỉ có thể được thay đổi nếu nó nằm bên trong một hàm, bởi vì các class tĩnh không có quyền" tự động thay đổi ". Bạn sẽ cần tạo một `StreamBuilder`, đăng ký biến này để lắng nghe các thay đổi và tạo một "stream" các` StreamBuilder` lồng nhau nếu bạn muốn thay đổi một số biến trong cùng một phạm vi, phải không? Không, bạn không cần `StreamBuilder`, nhưng bạn đã đúng về các class tĩnh. Theo quan điểm, chúng ta thường có rất nhiều bảng soạn sẵn khi chúng ta muốn thay đổi một Widget cụ thể, đó là cách Flutter. Với ** GetX **, bạn cũng có thể quên mã soạn sẵn này. `StreamBuilder (…)`? `initialValue:…`? `builder:…`? Không, bạn chỉ cần đặt biến này bên trong Widget `Obx ()`. ``` dart Obx (() => Text (controller.name)); ``` _Bạn cần nhớ gì?_ Chỉ `Obx(() =>` . Bạn chỉ đang chuyển Widget đó thông qua một hàm mũi tên vào một `Obx ()` ("Observer" của _Rx_). `Obx` khá thông minh và sẽ chỉ thay đổi nếu giá trị của `controller.name` thay đổi. Nếu `name` là` "Kaiser" `và bạn thay đổi nó thành` "Kaiser" `(` name.value = "Kaiser" `), vì nó giống như` giá trị` như trước, sẽ không có gì thay đổi trên màn hình, và `Obx`, để tiết kiệm tài nguyên, sẽ đơn giản bỏ qua giá trị mới và không xây dựng lại Widget. **Tuyệt vời ông mặt trời chứ?** > So, what if I have 5 _Rx_ (observable) variables within an `Obx` ? Nó sẽ chỉ cập nhật khi ** bất kỳ ** nào trong số chúng thay đổi. > And if I have 30 variables in a class, when I update one, will it update **all** the variables that are in that class? Không, chỉ **Widget cụ thể** sử dụng biến _Rx_ đó. Vì vậy, **GetX** chỉ cập nhật màn hình, khi biến _Rx_ thay đổi giá trị của nó. ``` final isOpen = false.obs; // NOTHING will happen... same value. void onButtonTap() => isOpen.value=false; ``` ### Lợi thế **GetX()** giúp bạn khi bạn cần kiểm soát **chi tiết** đối với những gì đang được cập nhật. Nếu bạn không cần `ID duy nhất`, vì tất cả các biến của bạn sẽ được sửa đổi khi bạn thực hiện một hành động, thì hãy sử dụng` GetBuilder`, bởi vì nó là một Trình cập nhật state đơn giản (trong các khối, như `setState ()` '), được tạo chỉ trong một vài dòng mã. Nó được làm đơn giản, ít ảnh hưởng đến CPU nhất và chỉ để thực hiện một mục đích duy nhất (xây dựng lại _State_) và sử dụng tài nguyên tối thiểu có thể. Nếu bạn cần một Trình quản lý state **mạnh mẽ**, bạn không thể làm sai với **GetX**. Nó không hoạt động với các biến, nhưng __flows__, mọi thứ trong đó đều là `Streams`. Bạn có thể sử dụng _rxDart_ kết hợp với nó, vì mọi thứ đều là `Luồng`, bạn có thể nghe `event` của từng" biến _Rx_ ", bởi vì mọi thứ trong đó đều là `Streams`. Nó thực sự là một cách tiếp cận _BLoC_, dễ dàng hơn _MobX_ và không có trình tạo code hoặc decorations. Bạn có thể biến **mọi thứ** thành một _"Observable" _ chỉ với một `.obs`. ### Hiệu suất tối đa: Ngoài việc có một thuật toán thông minh để xây dựng lại tối thiểu, **GetX** sử dụng trình so sánh để đảm bảo rằng Bang đã thay đổi. Nếu bạn gặp bất kỳ lỗi nào trong ứng dụng của mình và gửi một bản thay đổi state, **GetX** sẽ đảm bảo rằng nó sẽ không gặp sự cố. Với **GetX** State chỉ thay đổi nếu `giá trị` thay đổi. Đó là sự khác biệt chính giữa **GetX** và việc sử dụng _ `computed` từ MobX_. Khi kết hợp hai __observables__, và một thay đổi; trình nghe của _observable_ đó cũng sẽ thay đổi. Với **GetX**, nếu bạn nối hai biến, `GetX ()` (tương tự như `Observer ()`) sẽ chỉ xây dựng lại nếu nó ngụ ý thay đổi state thực sự. ### Khai báo một biến phản ứng (reactive variable) Bạn có 3 cách để thay đổi variable thành "observable". 1 - Sử dụng **`Rx{Type}`**. ``` dart // initial value is recommended, but not mandatory final name = RxString(''); final isLogged = RxBool(false); final count = RxInt(0); final balance = RxDouble(0.0); final items = RxList([]); final myMap = RxMap({}); ``` 2 - Sử dụng **`Rx`** và dùng Darts Generics, `Rx` ``` dart final name = Rx(''); final isLogged = Rx(false); final count = Rx(0); final balance = Rx(0.0); final number = Rx(0); final items = Rx>([]); final myMap = Rx>({}); // Custom classes - it can be any class, literally final user = Rx(); ``` 3 - Cách tối ưu nhất, thêm **`.obs`** ở `value` : ``` dart final name = ''.obs; final isLogged = false.obs; final count = 0.obs; final balance = 0.0.obs; final number = 0.obs; final items = [].obs; final myMap = {}.obs; // Custom classes - it can be any class, literally final user = User().obs; ``` ##### Thât dễ khi có reactive state. Như chúng ta biết, _Dart_ đang hướng tới _null safety_. Để chuẩn bị, từ bây giờ, bạn phải luôn bắt đầu các biến _Rx_ của mình bằng một **initial value**. > Transforming a variable into an _observable_ + _initial value_ with **GetX** is the simplest, and most practical approach. Theo đúng nghĩa đen, bạn sẽ thêm một "` .obs` "vào cuối biến của mình và **vậy thôi người ơi~**, bạn đã làm cho nó có thể quan sát được, và `.value` của nó sẽ là _initial value_). ### Sử dụng values trong View ``` dart // controller file final count1 = 0.obs; final count2 = 0.obs; int get sum => count1.value + count2.value; ``` ``` dart // view file GetX( builder: (controller) { print("count 1 rebuild"); return Text('${controller.count1.value}'); }, ), GetX( builder: (controller) { print("count 2 rebuild"); return Text('${controller.count2.value}'); }, ), GetX( builder: (controller) { print("count 3 rebuild"); return Text('${controller.sum}'); }, ), ``` Nếu chúng ta cộng `count1.value++` , nó sẽ in: * `count 1 rebuild` * `count 3 rebuild` bởi vì `count1` có giá trị là` 1` và `1 + 0 = 1`, thay đổi giá trị getter` sum`. Nếu ta thay đổi `count2.value++` , nó sẽ in: * `count 2 rebuild` * `count 3 rebuild` bởi vì `count2.value` đã thay đổi, và kết quả của` sum` bây giờ là `2`. * LƯU Ý: Theo mặc định, event đầu tiên sẽ xây dựng lại widget con, ngay cả khi nó là cùng một `giá trị`. Hành vi này tồn tại do các biến Boolean. Ví dụ, bạn code thế này: ``` dart var isLogged = false.obs; ``` Và sau đó, bạn đã kiểm tra xem người dùng có "đăng nhập" để kích hoạt event trong `ever` không. ``` dart @override onInit(){ ever(isLogged, fireRoute); isLogged.value = await Preferences.hasToken(); } fireRoute(logged) { if (logged) { Get.off(Home()); } else { Get.off(Login()); } } ``` nếu `hasToken` là` false`, sẽ không có thay đổi thành `isLogged`, vì vậy `ever ()` sẽ không bao giờ được gọi. Để tránh loại hành vi này, thay đổi đầu tiên đối với _observable_ sẽ luôn kích hoạt một event, ngay cả khi nó chứa cùng một `.value`. Bạn có thể xóa hành vi này nếu muốn, bằng cách sử dụng: `isLogged.firstRebuild = false;` ### Điều kiện để tái tạo lại Ngoài ra, Get cung cấp khả năng kiểm soát state đã được tinh chỉnh. Bạn có thể điều kiện một event (chẳng hạn như thêm một đối tượng vào danh sách), với một điều kiện nhất định. ``` dart // First parameter: condition, must return true or false. // Second parameter: the new value to apply if the condition is true. list.addIf(item < limit, item); ``` Không có decoration, không có trình tạo mã, không có phức tạp hóa vấn đề: smile: Bạn có biết ứng dụng counter của Flutter không? Class controller của bạn có thể trông giống như sau: ``` dart class CountController extends GetxController { final count = 0.obs; } ``` Đơn giản hơn: ``` dart controller.count.value++ ``` Bạn có thể cập nhật counter trong UI của mình, bất kể nó được lưu trữ ở đâu. ### Nơi .obs có thể dùng Bạn có thể biến đổi bất cứ thứ gì trên obs. Đây là hai cách để làm điều đó: * Bạn có thể chuyển đổi các giá trị class của mình thành obs ``` dart class RxUser { final name = "Camila".obs; final age = 18.obs; } ``` * hoặc bạn có thể biến cả 1 class thành observable ``` dart class User { User({String name, int age}); var name; var age; } // when instantianting: final user = User(name: "Camila", age: 18).obs; ``` ### Chí ú về Lists List hoàn toàn có thể quan sát được cũng như các đối tượng bên trong nó. Bằng cách đó, nếu bạn thêm một giá trị vào danh sách, nó sẽ tự động xây dựng lại các widget con sử dụng nó. Bạn cũng không cần phải sử dụng ".value" với các danh sách, api phi tiêu tuyệt vời đã cho phép chúng tôi loại bỏ điều đó. Tiếc thay, các kiểu nguyên thủy như String và int không thể được mở rộng, khiến việc sử dụng .value là bắt buộc, nhưng điều đó sẽ không thành vấn đề nếu bạn làm việc với getters và setters cho những thứ này. ``` dart // On the controller final String title = 'User Info:'.obs final list = List().obs; // on the view Text(controller.title.value), // String need to have .value in front of it ListView.builder ( itemCount: controller.list.length // lists don't need it ) ``` Khi bạn đang làm cho các class của riêng mình có thể quan sát được, có một cách khác để cập nhật chúng: ``` dart // on the model file // we are going to make the entire class observable instead of each attribute class User() { User({this.name = '', this.age = 0}); String name; int age; } // on the controller file final user = User().obs; // when you need to update the user variable: user.update( (user) { // this parameter is the class itself that you want to update user.name = 'Jonny'; user.age = 18; }); // an alternative way of update the user variable: user(User(name: 'João', age: 35)); // on view: Obx(()=> Text("Name ${user.value.name}: Age: ${user.value.age}")) // you can also access the model values without the .value: user().name; // notice that is the user variable, not the class (variable has lowercase u) ``` Bạn không cần phải làm việc với các bộ nếu bạn không muốn. bạn có thể sử dụng api "assign" và "assignAll". Api "assign" sẽ xóa danh sách của bạn và thêm một đối tượng duy nhất mà bạn muốn bắt đầu ở đó. Api "allowAll" sẽ xóa danh sách hiện có và thêm bất kỳ đối tượng có thể lặp lại nào mà bạn đưa vào đó. ### Tại sao tôi phải dùng .value Chúng ta có thể loại bỏ việc sử dụng 'value' đối với `String` và` int` bằng một trình tạo mã và decoration đơn giản, nhưng mục đích của thư viện này chính là tránh các dependency bên ngoài. Chúng tôi muốn cung cấp một môi trường sẵn sàng cho việc lập trình, liên quan đến các yếu tố cần thiết (quản lý các route, dependency và state), theo cách đơn giản, nhẹ và hiệu quả mà không cần gói bên ngoài. Theo nghĩa đen, bạn có thể thêm 3 chữ cái vào pubspec (get) của mình và dấu hai chấm và bắt đầu lập trình. Tất cả các giải pháp được bao gồm theo mặc định, từ quản lý route đến quản lý state, nhằm mục đích dễ dàng, năng suất và hiệu suất. Tổng trọng lượng của thư viện này ít hơn của một trình quản lý state duy nhất, mặc dù nó là một giải pháp hoàn chỉnh và đó là những gì bạn phải hiểu. Nếu bạn bị làm phiền bởi `.value`, và giống như một trình tạo mã, MobX là một giải pháp thay thế tuyệt vời và bạn có thể sử dụng nó cùng với Get. Đối với những người muốn thêm một gói dependency duy nhất vào pubspec và bắt đầu lập trình mà không cần lo lắng về phiên bản của gói không tương thích với gói khác hoặc nếu lỗi cập nhật state đến từ trình quản lý state hoặc dependency, hoặc vẫn không muốn lo lắng về sự sẵn có của controller, cho dù theo nghĩa đen là "chỉ là lập trình", GetX là lựa chọn hoàn hảo. Nếu bạn không gặp vấn đề gì với trình tạo mã MobX hoặc không gặp vấn đề gì với bảng soạn sẵn BLoC, bạn có thể chỉ cần sử dụng Get cho các route và quên rằng nó có trình quản lý state. Get SEM và RSM ra đời không cần thiết, công ty của tôi có một dự án với hơn 90 controller và trình tạo mã chỉ mất hơn 30 phút để hoàn thành nhiệm vụ của nó sau khi Flutter Clean trên một máy khá tốt, nếu dự án của bạn có 5, 10, 15 controller, bất kỳ nhà quản lý state sẽ cung cấp cho bạn tốt. Nếu bạn có một dự án lớn đến mức ngớ ngẩn và trình tạo mã là một vấn đề đối với bạn, thì bạn đã được trao giải pháp này. Rõ ràng, nếu ai đó muốn đóng góp vào dự án và tạo trình tạo mã, hoặc thứ gì đó tương tự, tôi sẽ liên kết trong readme này như một giải pháp thay thế, nhu cầu của tôi không phải là nhu cầu của tất cả các nhà phát triển, nhưng ý tôi là thế, đã có những giải pháp tốt đã làm được điều đó, như MobX. ### Obx() Nhập vào Get bằng cách sử dụng Bindings là không cần thiết. bạn có thể sử dụng widget Obx thay vì GetX, widget chỉ nhận được chức năng ẩn danh tạo widget. Rõ ràng, nếu bạn không sử dụng một kiểu, bạn sẽ cần phải có một phiên bản của controller để sử dụng các biến hoặc sử dụng `Get.find ()` .value hoặc Controller.to.value để truy xuất giá trị . ### Workers Workers sẽ hỗ trợ bạn, kích hoạt các lệnh gọi lại cụ thể khi một event xảy ra. ``` dart /// Called every time `count1` changes. ever(count1, (_) => print("$_ has been changed")); /// Called only first time the variable $_ is changed once(count1, (_) => print("$_ was changed once")); /// Anti DDos - Called every time the user stops typing for 1 second, for example. debounce(count1, (_) => print("debouce$_"), time: Duration(seconds: 1)); /// Ignore all changes within 1 second. interval(count1, (_) => print("interval $_"), time: Duration(seconds: 1)); ``` Tất cả các workers (except `debounce` ) có `condition` tham số được đặt tên, mà có thể là loại `bool` hoặc lệnh gọi lại trả về một `bool` . `condition` này mô tả khi `callback` kích hoạt. Tất cả các workers đều trả về trường hợp `Worker`, mà bạn có thể đóng ( thông qua `dispose()` ) của worker. * **`ever`** được gọi mỗi khi biến _Rx_ tạo ra một giá trị mới. * **`everAll`** Giống như `ever`, nhưng nó có một` List` gồm các giá trị _Rx_ Được gọi mỗi khi biến của nó bị thay đổi. Chỉ vậy thôi người ơi~ 😊 * **`once`** 'once' chỉ được gọi lần đầu tiên biến được thay đổi. * **`debounce`** 'debounce' rất hữu ích trong các hàm tìm kiếm, nơi bạn chỉ muốn API được gọi khi người dùng nhập xong. Nếu người dùng nhập "Kaiser", bạn sẽ có 6 tìm kiếm trong các API, theo ký tự K, a, i, s, e và r. Với Get, điều này không xảy ra, bởi vì bạn sẽ có một Worker "debounce" sẽ chỉ được kích hoạt khi kết thúc nhập. * **`interval`** 'interval' khác với debounce. Debounce xảy ra nếu người dùng thực hiện 1000 thay đổi đối với một biến trong vòng 1 giây, y sẽ chỉ gửi biến cuối cùng sau bộ hẹn giờ quy định (mặc định là 800 mili giây). Thay vào đó, interval sẽ bỏ qua tất cả các hành động của người dùng trong interval quy định. Nếu bạn gửi event trong 1 phút, 1000 mỗi giây, tính năng gỡ lỗi sẽ chỉ gửi cho bạn event cuối cùng, khi người dùng ngừng phân chia event. interval sẽ phân phối các event mỗi giây và nếu được đặt thành 3 giây, nó sẽ phân phối 20 event trong phút đó. Điều này được khuyến nghị để tránh lạm dụng, trong các chức năng mà người dùng có thể nhanh chóng nhấp vào một thứ gì đó và có được một số lợi thế (hãy tưởng tượng rằng người dùng có thể kiếm được xu bằng cách nhấp vào thứ gì đó, nếu y nhấp 300 lần trong cùng một phút, y sẽ có 300 xu, bằng cách sử dụng interval, bạn có thể đặt khung thời gian trong 3 giây, và thậm chí sau đó nhấp vào 300 hoặc một nghìn lần, tối đa y sẽ nhận được trong 1 phút sẽ là 20 xu, nhấp 300 hoặc 1 triệu lần). Việc gỡ lỗi này phù hợp cho việc chống DDos, cho các chức năng như tìm kiếm trong đó mỗi thay đổi đối với onChange sẽ gây ra một truy vấn tới api của bạn. Debounce sẽ đợi người dùng ngừng nhập tên để thực hiện yêu cầu. Nếu nó được sử dụng trong kịch bản đồng xu được đề cập ở trên, người dùng sẽ chỉ giành được 1 đồng xu, bởi vì nó chỉ được thực thi, khi người dùng "tạm dừng" trong thời gian đã thiết lập. * CHÍ Ú: Workers phải luôn được sử dụng khi khởi động Controller hoặc Class, vì vậy nó phải luôn ở trên onInit (được khuyến nghị), phương thức khởi tạo Class hoặc initState của StatefulWidget (phương pháp này không được khuyến khích trong hầu hết các trường hợp, nhưng nó không nên có hiệu ứng phụ nào khác). ## Quản lý State đơn giản Get có một trình quản lý state cực kỳ nhẹ và dễ dàng, không sử dụng ChangeNotifier, sẽ đáp ứng nhu cầu đặc biệt cho những người mới sử dụng Flutter và sẽ không gây ra sự cố cho các ứng dụng lớn. GetBuilder nhắm chính xác vào việc kiểm soát nhiều state. Hãy tưởng tượng rằng bạn đã thêm 30 sản phẩm vào giỏ hàng, bạn nhấp vào xóa một sản phẩm, đồng thời danh sách được cập nhật, giá được cập nhật và huy hiệu trong giỏ hàng được cập nhật thành số lượng nhỏ hơn. Kiểu tiếp cận này khiến GetBuilder trở thành kẻ giết người, bởi vì nó nhóm các state và thay đổi tất cả chúng cùng một lúc mà không có bất kỳ "logic tính toán" nào cho điều đó. GetBuilder được tạo ra với loại tình huống này, vì để thay đổi state tạm thời, bạn có thể sử dụng setState và bạn sẽ không cần trình quản lý state cho việc này. Bằng cách đó, nếu bạn muốn một controller riêng lẻ, bạn có thể gán ID cho controller đó hoặc sử dụng GetX. Điều này là tùy thuộc vào bạn, hãy nhớ rằng bạn càng có nhiều widget "riêng lẻ" thì hiệu suất của GetX càng nổi bật, trong khi hiệu suất của GetBuilder phải vượt trội hơn khi có nhiều thay đổi state. ### Lợi thế 1. Chỉ cập nhật các widget được yêu cầu. 2. Không sử dụng changeNotifier, đó là trình quản lý state sử dụng ít bộ nhớ hơn (gần như bằng 0mb). 3. Quên StatefulWidget! Với Get, bạn sẽ không bao giờ cần đến nó. Với các trình quản lý state khác, bạn có thể sẽ phải sử dụng StatefulWidget để lấy phiên bản của Provider, BLoC, MobX, v.v. Nhưng bạn đã bao giờ nghĩ rằng AppBar, Scaffole và hầu hết các widget trong class của bạn là stateless? Vậy tại sao phải lưu state của toàn bộ class, nếu bạn chỉ có thể lưu state của Widget là stateful? Get giải quyết được điều đó bằng cách tạo một class Stateless, làm cho mọi thứ trở nên vô trạng. Nếu bạn cần cập nhật một thành phần riêng lẻ, hãy bọc nó bằng GetBuilder và state của nó sẽ được duy trì. 4. Tái cơ cấu cho dự án của bạn xanh, sạch và đẹp! Controller không được nằm trong UI của bạn, hãy đặt TextEditController của bạn hoặc bất kỳ controller nào bạn sử dụng trong class Controller của mình. 5. Bạn có cần kích hoạt event để cập nhật widget con ngay khi nó được hiển thị không? GetBuilder có thuộc tính "initState", giống như StatefulWidget và bạn có thể gọi các event từ controller của mình, trực tiếp từ nó, không có thêm event nào được đặt trong initState của bạn. 6. Bạn có cần phải kích hoạt một hành động như đóng streams, hẹn giờ, v.v. không? GetBuilder cũng có dispose property, nơi bạn có thể gọi các event ngay khi widget đó bị phá hủy. 7. Chỉ sử dụng các streams nếu cần thiết. Bạn có thể sử dụng StreamControllers bên trong controller của mình một cách bình thường và sử dụng StreamBuilder cũng bình thường, nhưng hãy nhớ rằng, một streams tiêu thụ bộ nhớ một kha khá, lập trình phản ứng rất đẹp, nhưng bạn không nên lạm dụng nó. 30 streams mở cùng lúc có thể tệ hơn changeNotifier (và changeNotifier đã rất là tệ). 8. Cập nhật các widgets mà không tốn ram. Chỉ lưu trữ ID người tạo GetBuilder và cập nhật GetBuilder đó khi cần thiết. Mức tiêu thụ bộ nhớ của get ID trong bộ nhớ là rất thấp ngay cả đối với hàng nghìn GetBuilders. Khi bạn tạo GetBuilder mới, bạn thực sự đang chia sẻ state GetBuilder có ID người tạo. Một state mới không được tạo cho mỗi GetBuilder, giúp tiết kiệm RẤT NHIỀU ram cho các ứng dụng lớn. Về cơ bản, ứng dụng của bạn sẽ hoàn toàn là Không state và một số ít Tiện ích sẽ có state (trong GetBuilder) sẽ có một state duy nhất, và do đó cập nhật một sẽ cập nhật tất cả. Nhà nước chỉ là một. 9. Get là toàn trí và trong hầu hết các trường hợp, nó biết chính xác thời gian để lấy controller ra khỏi bộ nhớ. Bạn không nên lo lắng về việc khi nào nên vứt bỏ controller, Get biết thời điểm tốt nhất để thực hiện việc này. ### Sử dụng ``` dart // Create controller class and extends GetxController class Controller extends GetxController { int counter = 0; void increment() { counter++; update(); // use update() to update counter variable on UI when increment be called } } // On your Stateless/Stateful class, use GetBuilder to update Text when increment be called GetBuilder( init: Controller(), // INIT IT ONLY THE FIRST TIME builder: (_) => Text( '${_.counter}', ), ) //Initialize your controller only the first time. The second time you are using ReBuilder for the same controller, do not use it again. Your controller will be automatically removed from memory as soon as the widget that marked it as 'init' is deployed. You don't have to worry about that, Get will do it automatically, just make sure you don't start the same controller twice. ``` **OK, giải thích xong rồi!** * Bạn đã học cách quản lý state với Get. * Lưu ý: Bạn có thể muốn một tổ chức lớn hơn và không sử dụng thuộc tính init. Vì vậy, bạn có thể tạo một class và mở rộng class Bindings và trong đó đề cập đến các controller sẽ được tạo trong route đó. Khi đó các Controllers sẽ không được tạo, ngược lại, đây chỉ là một câu lệnh, để lần đầu sử dụng Controller, Get sẽ biết cần tìm ở đâu. Get sẽ vẫn là lazyLoad và sẽ tiếp tục loại bỏ Controller khi chúng không còn cần thiết nữa. Hãy xem ví dụ pub.dev để xem nó hoạt động như thế nào. Nếu bạn điều hướng nhiều route và cần dữ liệu trong controller đã sử dụng trước đó, bạn chỉ cần sử dụng GetBuilder Again (không có init): ``` dart class OtherClass extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( body: Center( child: GetBuilder( builder: (s) => Text('${s.counter}'), ), ), ); } ``` Nếu bạn cần sử dụng controller của mình ở nhiều nơi khác và bên ngoài GetBuilder, chỉ cần tạo quyền truy cập vào controller của bạn và có nó một cách dễ dàng. (hoặc sử dụng `Get.find ()`) ``` dart class Controller extends GetxController { /// You do not need that. I recommend using it just for ease of syntax. /// with static method: Controller.to.increment(); /// with no static method: Get.find().increment(); /// There is no difference in performance, nor any side effect of using either syntax. Only one does not need the type, and the other the IDE will autocomplete it. static Controller get to => Get.find(); // add this line int counter = 0; void increment() { counter++; update(); } } ``` Sau đó, truy cập thẳng vào controller của bạn: ``` dart FloatingActionButton( onPressed: () { Controller.to.increment(), } // This is incredibly simple! child: Text("${Controller.to.counter}"), ), ``` Khi bạn nhấn FloatingActionButton, tất cả các widget đang lắng nghe biến 'counter' sẽ được cập nhật tự động. ### Cách GetX sử dụng controllers Ví dụ: `Class a => Class B (has controller X) => Class C (has controller X)` Trong class A, controller chưa có trong bộ nhớ, vì bạn chưa sử dụng nó (Get là lazyLoad). Trong class B, bạn đã sử dụng controller và nó đã vào bộ nhớ. Trong class C, bạn đã sử dụng cùng một controller như trong class B, Get sẽ chia sẻ state của controller B với controller C, và controller tương tự vẫn còn trong bộ nhớ. Nếu bạn đóng màn hình C và màn hình B, Get sẽ tự động lấy controller X ra khỏi bộ nhớ và giải phóng tài nguyên, vì Class A không sử dụng controller. Nếu bạn điều hướng đến B một lần nữa, controller X sẽ nhập lại bộ nhớ, nếu thay vì đi đến class C, bạn quay lại class A một lần nữa, Get sẽ đưa controller ra khỏi bộ nhớ theo cách tương tự. Nếu class C không sử dụng controller và bạn đã lấy class B ra khỏi bộ nhớ, thì sẽ không có class nào sử dụng controller X và tương tự như vậy, nó sẽ bị loại bỏ. Ngoại lệ duy nhất có thể gây rắc rối với Get là nếu bạn xóa B khỏi route một cách bất ngờ và cố gắng sử dụng controller trong C. Trong trường hợp này, ID người tạo của controller ở B đã bị xóa và Get được lập trình để xóa nó khỏi bộ nhớ mọi controller không có ID người tạo. Nếu bạn dự định làm điều này, hãy thêm flag "autoRemove: false" vào GetBuilder của class B và sử dụng adoptID = true trong GetBuilder của class C. ### Không cần StatefulWidget nữa! Sử dụng StatefulWidgets có nghĩa là lưu trữ state của toàn bộ màn hình một cách không cần thiết, ngay cả khi bạn cần xây dựng lại một cách tối thiểu widget, bạn sẽ nhúng nó vào Consumer / Observer / BlocProvider / GetBuilder / GetX / Obx, đây sẽ là một StatefulWidget khác. Class StatefulWidget là một class lớn hơn StatelessWidget, class này sẽ phân bổ nhiều RAM hơn và điều này có thể không tạo ra sự khác biệt đáng kể giữa một hoặc hai class, nhưng chắc chắn nó sẽ làm được khi bạn có 100 class trong số chúng! Trừ khi bạn cần sử dụng một mixin, như TickerProviderStateMixin, thì việc sử dụng StatefulWidget với Get là hoàn toàn không cần thiết. Bạn có thể gọi trực tiếp tất cả các phương thức của StatefulWidget từ GetBuilder. Nếu bạn cần gọi phương thức initState () hoặc dispose () chẳng hạn, bạn có thể gọi chúng trực tiếp; ``` dart GetBuilder( initState: (_) => Controller.to.fetchApi(), dispose: (_) => Controller.to.closeStreams(), builder: (s) => Text('${s.username}'), ), ``` Một cách tiếp cận tốt hơn nhiều so với cách này là sử dụng phương thức onInit () và onClose () trực tiếp từ controller của bạn. ``` dart @override void onInit() { fetchApi(); super.onInit(); } ``` * CHÍ Ú: Nếu bạn muốn bắt đầu một phương thức tại thời điểm controller được gọi lần đầu tiên, bạn KHÔNG CẦN sử dụng các hàm tạo cho việc này, trên thực tế, bằng cách sử dụng gói hướng hiệu suất như Get, điều này không phù hợp với thực tiễn xấu, bởi vì nó lệch khỏi logic trong đó controller được tạo hoặc chỉ định (nếu bạn tạo một phiên bản của controller này, hàm tạo sẽ được gọi ngay lập tức, bạn sẽ điền controller trước khi nó được sử dụng, bạn đang cấp phát bộ nhớ mà không sử dụng nó , điều này chắc chắn làm hỏng các nguyên tắc của thư viện này). Các phương thức onInit(); và onClose(); được tạo ra cho mục đích này, chúng sẽ được gọi khi Controller được tạo hoặc được sử dụng lần đầu tiên, tùy thuộc vào việc bạn có đang sử dụng Get.lazyPut hay không. Ví dụ: nếu bạn muốn thực hiện lệnh gọi tới API của mình để điền dữ liệu, bạn có thể quên phương thức cũ của initState / dispose, chỉ cần bắt đầu lệnh gọi tới api trong onInit. Nếu bạn cần thực thi bất kỳ lệnh nào, như đóng streams, hãy sử dụng onClose() cho việc đó. ### Tại sao GetX tồn tại? Mục đích của gói này chính xác là cung cấp cho bạn một giải pháp hoàn chỉnh để điều hướng các route, quản lý các dependency và state, sử dụng các dependency ít nhất có thể, với mức độ tách biệt cao. Nhận tất cả các API Flutter cấp cao và cấp thấp trong chính nó, để đảm bảo rằng bạn làm việc với ít khớp nối nhất có thể. Chúng tôi tập trung mọi thứ trong một gói duy nhất, để đảm bảo rằng bạn không có bất kỳ loại khớp nối nào trong dự án của mình. Bằng cách đó, bạn có thể chỉ đặt các widget trong chế độ xem của mình và để phần của nhóm làm việc với logic nghiệp vụ tự do làm việc với logic nghiệp vụ độc lập với View. Điều này cung cấp một môi trường làm việc sạch hơn nhiều, để một phần nhóm của bạn chỉ hoạt động với các widget mà không phải lo lắng về việc gửi dữ liệu đến controller của bạn và một phần nhóm của bạn chỉ làm việc với logic nghiệp vụ trong phạm vi bề rộng của nó mà không dependency vào bất kỳ yếu tố View. Vì vậy, để đơn giản hóa điều này: Bạn không cần gọi các phương thức trong initState và gửi chúng theo tham số đến controller của mình, cũng như không sử dụng phương thức khởi tạo controller cho việc đó, bạn có phương thức onInit() được gọi vào đúng thời điểm để bạn khởi động các dịch vụ của mình. Bạn không cần phải gọi thiết bị, bạn có phương thức onClose() sẽ được gọi vào thời điểm chính xác khi controller của bạn không còn cần thiết nữa và sẽ bị xóa khỏi bộ nhớ. Bằng cách đó, chỉ để lại chế độ xem cho các widget, tránh bất kỳ loại logic nghiệp vụ nào từ nó. Đừng gọi một phương thức vứt bỏ bên trong GetxController, nó sẽ không làm được gì cả, hãy nhớ rằng controller không phải là một Widget, bạn không nên "vứt bỏ" nó, và nó sẽ được Get tự động và thông minh xóa khỏi bộ nhớ. Nếu bạn đã sử dụng bất kỳ streams nào trên đó và muốn đóng streams đó, chỉ cần chèn streams đó vào phương thức đóng. Thí dụ: ``` dart class Controller extends GetxController { StreamController user = StreamController(); StreamController name = StreamController(); /// close stream = onClose method, not dispose. @override void onClose() { user.close(); name.close(); super.onClose(); } } ``` Vòng đời của controller: * onInit() nơi nó được tạo. * onClose() nơi nó được đóng để thực hiện bất kỳ thay đổi nào nhằm chuẩn bị cho phương thức xóa * deleted: bạn không có quyền truy cập vào API này vì nó sẽ xóa controller khỏi bộ nhớ theo đúng nghĩa đen. Nó được xóa theo đúng nghĩa đen, mà không để lại bất kỳ dấu vết nào. ### Cách sử dụng khác Bạn có thể sử dụng phiên bản Controller trực tiếp trên giá trị GetBuilder: ``` dart GetBuilder( init: Controller(), builder: (value) => Text( '${value.counter}', //here ), ), ``` Bạn cũng có thể cần một phiên bản của controller bên ngoài GetBuilder và bạn có thể sử dụng các phương pháp này để đạt được điều này: ``` dart class Controller extends GetxController { static Controller get to => Get.find(); [...] } // on you view: GetBuilder( init: Controller(), // use it only first time on each controller builder: (_) => Text( '${Controller.to.counter}', //here ) ), ``` or ``` dart class Controller extends GetxController { // static Controller get to => Get.find(); // with no static get [...] } // on stateful/stateless class GetBuilder( init: Controller(), // use it only first time on each controller builder: (_) => Text( '${Get.find().counter}', //here ), ), ``` * Bạn có thể sử dụng các phương pháp tiếp cận "không chuẩn" để thực hiện việc này. Nếu bạn đang sử dụng một số trình quản lý dependency khác, như get_it, modular, v.v. và chỉ muốn cung cấp phiên bản controller, bạn có thể thực hiện điều này: ``` dart Controller controller = Controller(); [...] GetBuilder( init: controller, //here builder: (_) => Text( '${controller.counter}', // here ), ), ``` ### IDs độc nhất Nếu bạn muốn tinh chỉnh kiểm soát cập nhật của widget con với GetBuilder, bạn có thể gán cho chúng các ID độc: ``` dart GetBuilder( id: 'text' init: Controller(), // use it only first time on each controller builder: (_) => Text( '${Get.find().counter}', //here ), ), ``` Và cập nhật nó vào biểu mẫu này: ``` dart update(['text']); ``` Bạn cũng có thể áp đặt các điều kiện cho bản cập nhật: ``` dart update(['text'], counter < 10); ``` GetX thực hiện điều này tự động và chỉ cấu trúc lại widget con sử dụng biến chính xác đã được thay đổi, nếu bạn thay đổi một biến thành giống với biến trước đó và điều đó không ngụ ý thay đổi state, GetX sẽ không xây dựng lại widget con để tiết kiệm bộ nhớ và Chu kỳ CPU ( 3 đang được hiển thị trên màn hình và bạn lại thay đổi biến thành 3. Trong hầu hết các trình quản lý state, điều này sẽ gây ra việc xây dựng lại mới, nhưng với GetX, widget sẽ chỉ được xây dựng lại, nếu trên thực tế state của nó đã thay đổi ). ## Trộn hai trình quản lý state Một số người đã mở một yêu cầu tính năng, vì họ chỉ muốn sử dụng một loại biến phản ứng và cơ chế khác và cần chèn Obx vào GetBuilder cho việc này. Suy nghĩ về nó MixinBuilder đã được tạo ra. Nó cho phép cả những thay đổi phản ứng bằng cách thay đổi các biến ".obs" và cập nhật thủ công thông qua update(). Tuy nhiên, trong số 4 widget, nó là widget tiêu tốn nhiều tài nguyên nhất, vì ngoài việc có Subscription để nhận các event thay đổi từ con mình, nó còn đăng ký phương thức cập nhật của controller của mình. Việc mở rộng GetxController rất quan trọng, vì chúng có vòng đời và có thể "bắt đầu" và "kết thúc" các event trong các phương thức onInit() và onClose() của chúng. Bạn có thể sử dụng bất kỳ lớp nào cho việc này, nhưng tôi thực sự khuyên bạn nên sử dụng lớp GetxController để đặt các biến của bạn, cho dù chúng có thể quan sát được hay không. ## StateMixin Một cách khác để xử lý state `UI` của bạn là sử dụng` StateMixin `. Để triển khai nó, hãy sử dụng dấu `với` để thêm` StateMixin ` bộ điều khiển của bạn cho phép một mô hình T. ``` dart class Controller extends GetController with StateMixin{} ``` Phương thức `change()` thay đổi state bất cứ khi nào chúng ta muốn. Chỉ cần truyền dữ liệu và state theo cách này: ```dart change(data, status: RxStatus.success()); ``` RxStatus cho phép các state này: ``` dart RxStatus.loading(); RxStatus.success(); RxStatus.empty(); RxStatus.error('message'); ``` Để diễn tả nó trên UI, sử dụng: ```dart class OtherClass extends GetView { @override Widget build(BuildContext context) { return Scaffold( body: controller.obx( (state)=>Text(state.name), // here you can put your custom loading indicator, but // by default would be Center(child:CircularProgressIndicator()) onLoading: CustomLoadingIndicator(), onEmpty: Text('No data found'), // here also you can set your own error widget, but by // default will be an Center(child:Text(error)) onError: (error)=>Text(error), ), ); } ``` ## GetBuilder vs GetX vs Obx vs MixinBuilder Trong một thập kỷ làm việc với lập trình, tôi đã có thể học được một số bài học quý giá. Lần đầu tiên tôi tiếp xúc với lập trình phản ứng là rất "Trời thần ơi, tuyệt vời ông mặt trời!" và trên thực tế, lập trình phản ứng là không thể tin được. Tuy nhiên, nó không phải là thích hợp cho tất cả các trường hợp. Thông thường, tất cả những gì bạn cần là thay đổi state của 2 hoặc 3 widget cùng lúc, hoặc thay đổi state trong thời gian ngắn, trong trường hợp này, lập trình phản ứng không phải là xấu, nhưng nó không phù hợp. Lập trình phản ứng có mức tiêu thụ RAM cao hơn có thể được bù đắp bởi quy trình làm việc riêng lẻ, điều này sẽ đảm bảo rằng chỉ một widget con được xây dựng lại và khi cần thiết, nhưng tạo danh sách với 80 đối tượng, mỗi đối tượng có nhiều streams không phải là một ý kiến hay . Mở thanh kiểm tra phi tiêu và kiểm tra xem StreamBuilder tiêu thụ bao nhiêu và bạn sẽ hiểu những gì tôi đang cố gắng nói với bạn. Với ý nghĩ đó, tôi đã tạo trình quản lý state đơn giản. Nó đơn giản, và đó chính xác là những gì bạn cần ở nó: cập nhật state trong các khối theo cách đơn giản và tiết kiệm nhất. GetBuilder rất tiết kiệm RAM và khó có cách tiếp cận nào tiết kiệm hơn nó (ít nhất là tôi không thể tưởng tượng được, nếu đã tồn tại cách khác, vui lòng cho chúng tôi biết). Tuy nhiên, GetBuilder vẫn là một trình quản lý state cơ học, bạn cần phải gọi update () giống như bạn sẽ cần gọi tới Provider's InformListaries (). Có những tình huống khác mà lập trình phản ứng thực sự thú vị và nếu không dùng nó đồng nghĩa như đang phát minh lại cái bánh xe. Với suy nghĩ đó, GetX được tạo ra để cung cấp mọi thứ hiện đại và tiên tiến nhất trong một trình quản lý state. Nó chỉ cập nhật những gì cần thiết và khi cần thiết, nếu bạn gặp lỗi và gửi 300 state thay đổi đồng thời, GetX sẽ lọc và cập nhật màn hình chỉ khi state thực sự thay đổi. GetX vẫn tiết kiệm hơn bất kỳ trình quản lý state phản ứng nào khác, nhưng nó tiêu tốn nhiều RAM hơn GetBuilder một chút. Suy nghĩ về điều đó và hướng tới việc tiêu thụ tối đa tài nguyên mà Obx đã tạo ra. Không giống như GetX và GetBuilder, bạn sẽ không thể khởi tạo controller bên trong Obx, nó chỉ là một Widget với StreamSubscription nhận các event thay đổi từ con bạn, vậy thôi. Nó tiết kiệm hơn GetX, nhưng thua GetBuilder, điều được mong đợi, vì nó có tính phản ứng và GetBuilder có cách tiếp cận đơn giản nhất tồn tại, đó là lưu trữ hashCode của widget con và StateSetter của nó. Với Obx, bạn không cần phải viết loại controller của mình và bạn có thể nghe thấy sự thay đổi từ nhiều controller khác nhau, nhưng nó cần được khởi tạo trước đó, sử dụng phương pháp ví dụ ở đầu readme này hoặc sử dụng class Bindings. ================================================ FILE: documentation/zh_CN/dependency_management.md ================================================ # #依赖管理 - [依赖管理](#依赖管理) - [实例方法](#实例方法) - [Get.put()](#Get.put()) - [Get.lazyPut](#Get.lazyPut) - [Get.putAsync](#Get.putAsync) - [Get.create](#Get.create) - [使用实例化方法/类](#使用实例化方法/类) - [方法之间的差异](#方法之间的差异) - [Bindings](#Bindings) - [Bindings类](#Bindings类) - [BindingsBuilder](#BindingsBuilder) - [智能管理](#智能管理) - [如何改变](#如何改变) - [SmartManagement.full](#smartmanagementfull) - [SmartManagement.onlyBuilders](#SmartManagement.onlyBuilders) - [SmartManagement.keepFactory](#smartmanagementkeepFactory) - [Bindings的工作原理](#Bindings的工作原理) - [注释](#注释) Get有一个简单而强大的依赖管理器,它允许你只用1行代码就能检索到与你的Bloc或Controller相同的类,无需Provider上下文,无需 inheritedWidget。 ```dart Controller controller = Get.put(Controller()); // 而不是 Controller controller = Controller(); ``` 你是在Get实例中实例化它,而不是在你正在使用的类中实例化你的类,这将使它在整个App中可用。 所以你可以正常使用你的控制器(或Bloc类)。 - 注意:如果你使用的是Get的状态管理器,请多关注[Bindings](#Bindings)api,这将使你的视图更容易连接到你的控制器。 - 注意事项²。Get的依赖管理与包中的其他部分是分离的,所以如果你的应用已经使用了一个状态管理器(任何一个,都没关系),你不需要修改也可以同时使用这个依赖注入管理器,完全没有问题。 ## 实例方法 这些方法和它的可配置参数是: ### Get.put() 最常见的插入依赖关系的方式。例如,对于你的视图的控制器来说: ```dart Get.put(SomeClass()); Get.put(LoginController(), permanent: true); Get.put(ListItemController, tag: "some unique string"); ``` 这是你使用put时可以设置的所有选项。 ```dart Get.put( // 必备:你想得到保存的类,比如控制器或其他东西。 // 注:"S "意味着它可以是任何类型的类。 S dependency // 可选:当你想要多个相同类型的类时,可以用这个方法。 // 因为你通常使用Get.find()来获取一个类。 // 你需要使用标签来告诉你需要哪个实例。 // 必须是唯一的字符串 String tag, // 可选:默认情况下,get会在实例不再使用后进行销毁 // (例如:一个已经销毁的视图的Controller) // 但你可能需要这个实例在整个应用生命周期中保留在那里,就像一个sharedPreferences的实例或其他东西。 //所以你设置这个选项 // 默认值为false bool permanent = false, // 可选:允许你在测试中使用一个抽象类后,用另一个抽象类代替它,然后再进行测试。 // 默认为false bool overrideAbstract = false, // 可选:允许你使用函数而不是依赖(dependency)本身来创建依赖。 // 这个不常用 InstanceBuilderCallback builder, ) ``` ### Get.lazyPut 可以懒加载一个依赖,这样它只有在使用时才会被实例化。这对于计算代价高的类来说非常有用,或者如果你想在一个地方实例化几个类(比如在Bindings类中),而且你知道你不会在那个时候使用这个类。 ```dart ///只有当第一次使用Get.find时,ApiMock才会被调用。 Get.lazyPut(() => ApiMock()); Get.lazyPut( () { // ... some logic if needed return FirebaseAuth(); }, tag: Math.random().toString(), fenix: true ) Get.lazyPut( () => Controller() ) ``` 这是你在使用lazyPut时可以设置的所有选项。 ```dart Get.lazyPut( // 强制性:当你的类第一次被调用时,将被执行的方法。 InstanceBuilderCallback builder, // 可选:和Get.put()一样,当你想让同一个类有多个不同的实例时,就会用到它。 // 必须是唯一的 String tag, // 可选:类似于 "永久", // 不同的是,当不使用时,实例会被丢弃,但当再次需要使用时,Get会重新创建实例, // 就像 bindings api 中的 "SmartManagement.keepFactory "一样。 // 默认值为false bool fenix = false ) ``` ### Get.putAsync 如果你想注册一个异步实例,你可以使用`Get.putAsync`。 ```dart Get.putAsync(() async { final prefs = await SharedPreferences.getInstance(); await prefs.setInt('counter', 12345); return prefs; }); Get.putAsync( () async => await YourAsyncClass() ) ``` 这都是你在使用putAsync时可以设置的选项。 ```dart Get.putAsync( // 必备:一个将被执行的异步方法,用于实例化你的类。 AsyncInstanceBuilderCallback builder, // 可选:和Get.put()一样,当你想让同一个类有多个不同的实例时,就会用到它。 // 必须是唯一的 String tag, // 可选:与Get.put()相同,当你需要在整个应用程序中保持该实例的生命时使用。 // 默认值为false bool permanent = false ) ``` ### Get.create 这个就比较棘手了。关于这个是什么和其他的区别,可以在[方法之间的差异](#方法之间的差异)部分找到详细的解释。 ```dart Get.Create(() => SomeClass()); Get.Create(() => LoginController()); ``` 这是你在使用create时可以设置的所有选项。 ```dart Get.create( // 需要:一个返回每次调用"Get.find() "都会被新建的类的函数。 // 示例: Get.create(()=>YourClass()) FcBuilderFunc builder, // 可选:就像Get.put()一样,但当你需要多个同类的实例时,会用到它。 // 当你有一个列表,每个项目都需要自己的控制器时,这很有用。 // 需要是一个唯一的字符串。只要把标签改成名字 String name, // 可选:就像 Get.put() 一样, // 它是为了当你需要在整个应用中保活实例的时候 // 区别在于 Get.create 的 permanent默认为true bool permanent = true ``` ## 使用实例化方法/类 想象一下,你已经浏览了无数条路由,现在你需要拿到一个被遗留在控制器中的数据,那么你会需要一个状态管理器与Provider或Get_it相结合,对吗?用Get则不然,你只需要让Get为你的控制器自动 "寻找",你不需要任何额外的依赖关系。 ```dart final controller = Get.find(); // 或者 Controller controller = Get.find(); // 是的,它看起来像魔术,Get会找到你的控制器,并将其提供给你。 // 你可以实例化100万个控制器,Get总会给你正确的控制器。 ``` 然后你就可以恢复你在后面获得的控制器数据。 ```dart Text(controller.textFromApi); ``` 由于返回的值是一个正常的类,你可以做任何你想做的事情。 ```dart int count = Get.find().getInt('counter'); print(count); // out: 12345 ``` 移除一个Get实例: ```dart Get.delete(); //通常你不需要这样做,因为GetX已经删除了未使用的控制器。 ``` ## 方法之间的差异 首先,让我们来看看Get.lazyPut的 "fenix "和其他方法的 "permanent"。 `permanent`和`fenix`的根本区别在于你想如何存储实例。 强化:默认情况下,GetX会在不使用实例时删除它们。 这意味着 如果页面1有控制器1,页面2有控制器2,而你从堆栈中删除了第一个路由,(比如你使用`Get.off()`或`Get.offNamed()`)控制器1失去了它的使用,所以它将被删除。 但是如果你想选择使用`permanent:true`,那么控制器就不会在这个过渡中丢失--这对于你想在整个应用程序中保持生命的服务来说非常有用。 `fenix`则是针对那些你不担心在页面变化之间丢失的服务,但当你需要该服务时,你希望它还活着。所以基本上,它会处理未使用的控制器/服务/类,但当你需要它时,它会 "从灰烬中重新创建 "一个新的实例。 继续说说方法之间的区别: - Get.put和Get.putAsync的创建顺序是一样的,不同的是,第二个方法使用的是异步方法创建和初始化实例。put是直接插入内存,使用内部方法`insert`,参数`permanent: false`和`isSingleton: true`(这个isSingleton参数只是告诉它是使用`dependency`上的依赖,还是使用`FcBuilderFunc`上的依赖),之后,调用`Get.find()`,立即初始化内存中的实例。 - Get.create。顾名思义,它将 "创建 "你的依赖关系!类似于`Get.put()`。与`Get.put()`类似,它也会调用内部方法`insert`来实例化。但是`permanent`变成了true,而`isSingleton`变成了false(因为我们是在 "创建 "我们的依赖关系,所以它没有办法成为一个单例,这就是为什么是false)。因为它有`permanent: true`,所以我们默认的好处是不会在页面跳转之间销毁它。另外,`Get.find()`并不是立即被调用,而是等待在页面中被调用,这样创建是为了利用 "permanent "这个参数,值得注意的是,`Get.create()`的目标是创建不共享的实例,但不会被销毁,比如listView中的一个按钮,你想为该列表创建一个唯一的实例--正因为如此,Get.create必须和GetWidget一起使用。 - Get.lazyPut。顾名思义,这是一个懒加载的过程。实例被创建了,但它并没有被调用来立即使用,而是一直等待被调用。与其他方法相反,这里没有调用 "insert"。取而代之的是,实例被插入到内存的另一个部分,这个部分负责判断实例是否可以被重新创建,我们称之为 "工厂"。如果我们想创建一些以后使用的东西,它不会和现在使用的东西混在一起。这就是 "fenix "的魔力所在:如果你选择留下 "fenix: false",并且你的 "smartManagement "不是 "keepFactory",那么当使用 "Get.find "时,实例将把内存中的位置从 "工厂 "改为普通实例内存区域。紧接着,默认情况下,它将从 "工厂 "中移除。现在,如果你选择 "fenix: true",实例将继续存在这个专用的部分,甚至进入公共区域,以便将来再次被调用。 ## Bindings 这个包最大的区别之一,也许就是可以将路由、状态管理器和依赖管理器完全集成。 当一个路由从Stack中移除时,所有与它相关的控制器、变量和对象的实例都会从内存中移除。如果你使用的是流或定时器,它们会自动关闭,你不必担心这些。 在2.10版本中,Get完全实现了Bindings API。 现在你不再需要使用init方法了。如果你不想的话,你甚至不需要键入你的控制器。你可以在适当的地方启动你的控制器和服务来实现。 Binding类是一个将解耦依赖注入的类,同时 "Bindings "路由到状态管理器和依赖管理器。 这使得Get可以知道当使用某个控制器时,哪个页面正在显示,并知道在哪里以及如何销毁它。 此外,Binding类将允许你拥有SmartManager配置控制。你可以配置依赖关系,当从堆栈中删除一个路由时,或者当使用它的widget被布置时,或者两者都不布置。你将有智能依赖管理为你工作,但即使如此,你也可以按照你的意愿进行配置。 ### Bindings类 - 创建一个类并实现Binding ```dart class HomeBinding implements Bindings {} ``` 你的IDE会自动要求你重写 "dependencies"方法,然后插入你要在该路由上使用的所有类。 ```dart class HomeBinding implements Bindings { @override void dependencies() { Get.lazyPut(() => HomeController()); Get.put(()=> Api()); } } class DetailsBinding implements Bindings { @override void dependencies() { Get.lazyPut(() => DetailsController()); } } ``` 现在你只需要通知你的路由,你将使用该 Binding 来建立路由管理器、依赖关系和状态之间的连接。 - 使用别名路由: ```dart getPages: [ GetPage( name: '/', page: () => HomeView(), binding: HomeBinding(), ), GetPage( name: '/details', page: () => DetailsView(), binding: DetailsBinding(), ), ]; ``` - 使用正常路由。 ```dart Get.to(Home(), binding: HomeBinding()); Get.to(DetailsView(), binding: DetailsBinding()) ``` 至此,你不必再担心你的应用程序的内存管理,Get将为你做这件事。 Binding类在调用路由时被调用,你可以在你的GetMaterialApp中创建一个 "initialBinding "来插入所有将要创建的依赖关系。 ```dart GetMaterialApp( initialBinding: SampleBind(), home: Home(), ); ``` ### BindingsBuilder 创建Bindings的默认方式是创建一个实现Bindings的类,但是,你也可以使用`BindingsBuilder`回调,这样你就可以简单地使用一个函数来实例化任何你想要的东西。 例子: ```dart getPages: [ GetPage( name: '/', page: () => HomeView(), binding: BindingsBuilder(() { Get.lazyPut(() => ControllerX()); Get.put(()=> Api()); }), ), GetPage( name: '/details', page: () => DetailsView(), binding: BindingsBuilder(() { Get.lazyPut(() => DetailsController()); }), ), ]; ``` 这样一来,你就可以避免为每条路径创建一个 Binding 类,使之更加简单。 两种方式都可以完美地工作,我们希望您使用最适合您的风格。 ### 智能管理 GetX 默认情况下会将未使用的控制器从内存中移除。 但是如果你想改变GetX控制类的销毁方式,你可以用`SmartManagement`类设置不同的行为。 #### 如何改变 如果你想改变这个配置(你通常不需要),就用这个方法。 ```dart void main () { runApp( GetMaterialApp( smartManagement: SmartManagement.onlyBuilders //这里 home: Home(), ) ) } ``` #### SmartManagement.full 这是默认的。销毁那些没有被使用的、没有被设置为永久的类。在大多数情况下,你会希望保持这个配置不受影响。如果你是第一次使用GetX,那么不要改变这个配置。 #### SmartManagement.onlyBuilders 使用该选项,只有在`init:`中启动的控制器或用`Get.lazyPut()`加载到Binding中的控制器才会被销毁。 如果你使用`Get.put()`或`Get.putAsync()`或任何其他方法,SmartManagement将没有权限移除这个依赖。 在默认行为下,即使是用 "Get.put "实例化的widget也会被移除,这与SmartManagement.onlyBuilders不同。 #### SmartManagement.keepFactory 就像SmartManagement.full一样,当它不再被使用时,它将删除它的依赖关系,但它将保留它们的工厂,这意味着如果你再次需要该实例,它将重新创建该依赖关系。 ### Bindings的工作原理 Bindings会创建过渡性工厂,在你点击进入另一个页面的那一刻,这些工厂就会被创建,一旦换屏动画发生,就会被销毁。 这种情况发生得非常快,以至于分析器甚至都来不及注册。 当你再次导航到这个页面时,一个新的临时工厂将被调用,所以这比使用SmartManagement.keepFactory更可取,但如果你不想创建Bindings,或者想让你所有的依赖关系都在同一个Binding上,它肯定会帮助你。 Factories占用的内存很少,它们并不持有实例,而是一个具有你想要的那个类的 "形状 "的函数。 这在内存上的成本很低,但由于这个库的目的是用最少的资源获得最大的性能,所以Get连工厂都默认删除。 请使用对你来说最方便的方法。 ## 注释 - 如果你使用多个Bindings,不要使用SmartManagement.keepFactory。它被设计成在没有Bindings的情况下使用,或者在GetMaterialApp的初始Binding中链接一个Binding。 - 使用Bindings是完全可选的,你也可以在使用给定控制器的类上使用`Get.put()`和`Get.find()`。 然而,如果你使用Services或任何其他抽象,我建议使用Bindings来更好地组织。 ================================================ FILE: documentation/zh_CN/route_management.md ================================================ - [路由管理](#路由管理) - [如何使用](#如何使用) - [普通路由导航](#普通路由导航) - [别名路由导航](#别名路由导航) - [发送数据到别名路由](#发送数据到别名路由) - [动态网页链接](#动态网页链接) - [中间件](#中间件) - [免context导航](#免context导航) - [SnackBars](#SnackBars) - [Dialogs](#dialogs) - [BottomSheets](#bottomSheets) - [嵌套导航](#嵌套导航) # 路由管理 这是关于Getx在路由管理方面的完整解释。 ## 如何使用 将此添加到你的pubspec.yaml文件中。 ```yaml dependencies: get: ``` 如果你要在没有context的情况下使用路由/SnackBars/Dialogs/BottomSheets,或者使用高级的Get API,你只需要在你的MaterialApp前面加上 "Get",就可以把它变成GetMaterialApp,享受吧! ```dart GetMaterialApp( // Before: MaterialApp( home: MyHome(), ) ``` ## 普通路由导航 导航到新的页面。 ```dart Get.to(NextScreen()); ``` 关闭SnackBars、Dialogs、BottomSheets或任何你通常会用Navigator.pop(context)关闭的东西。 ```dart Get.back(); ``` 进入下一个页面,但没有返回上一个页面的选项(用于SplashScreens,登录页面等)。 ```dart Get.off(NextScreen()); ``` 进入下一个界面并取消之前的所有路由(在购物车、投票和测试中很有用)。 ```dart Get.offAll(NextScreen()); ``` 要导航到下一条路由,并在返回后立即接收或更新数据。 ```dart var data = await Get.to(Payment()); ``` 在另一个页面上,发送前一个路由的数据。 ```dart Get.back(result: 'success'); ``` 并使用它,例: ```dart if(data == 'success') madeAnything(); ``` 你不想学习我们的语法吗? 只要把 Navigator(大写)改成 navigator(小写),你就可以拥有标准导航的所有功能,而不需要使用context,例如: ```dart // 默认的Flutter导航 Navigator.of(context).push( context, MaterialPageRoute( builder: (BuildContext context) { return HomePage(); }, ), ); // 使用Flutter语法获得,而不需要context。 navigator.push( MaterialPageRoute( builder: (_) { return HomePage(); }, ), ); // get语法 (这要好得多) Get.to(HomePage()); ``` ## 别名路由导航 - 如果你喜欢用别名路由导航,Get也支持。 导航到下一个页面 ```dart Get.toNamed("/NextScreen"); ``` 浏览并删除前一个页面。 ```dart Get.offNamed("/NextScreen"); ``` 浏览并删除所有以前的页面。 ```dart Get.offAllNamed("/NextScreen"); ``` 要定义路由,使用GetMaterialApp。 ```dart void main() { runApp( GetMaterialApp( initialRoute: '/', getPages: [ GetPage(name: '/', page: () => MyHomePage()), GetPage(name: '/second', page: () => Second()), GetPage( name: '/third', page: () => Third(), transition: Transition.zoom ), ], ) ); } ``` 要处理到未定义路线的导航(404错误),可以在GetMaterialApp中定义unknownRoute页面。 ```dart void main() { runApp( GetMaterialApp( unknownRoute: GetPage(name: '/notfound', page: () => UnknownRoutePage()), initialRoute: '/', getPages: [ GetPage(name: '/', page: () => MyHomePage()), GetPage(name: '/second', page: () => Second()), ], ) ); } ``` ### 发送数据到别名路由 只要发送你想要的参数即可。Get在这里接受任何东西,无论是一个字符串,一个Map,一个List,甚至一个类的实例。 ```dart Get.toNamed("/NextScreen", arguments: 'Get is the best'); ``` 在你的类或控制器上: ```dart print(Get.arguments); //print out: Get is the best ``` ### 动态网页链接 Get提供高级动态URL,就像在Web上一样。Web开发者可能已经在Flutter上想要这个功能了,Get也解决了这个问题。 ```dart Get.offAllNamed("/NextScreen?device=phone&id=354&name=Enzo"); ``` 在你的controller/bloc/stateful/stateless类上: ```dart print(Get.parameters['id']); // out: 354 print(Get.parameters['name']); // out: Enzo ``` 你也可以用Get轻松接收NamedParameters。 ```dart void main() { runApp( GetMaterialApp( initialRoute: '/', getPages: [ GetPage( name: '/', page: () => MyHomePage(), ), GetPage( name: '/profile/', page: () => MyProfile(), ), //你可以为有参数的路由定义一个不同的页面,也可以为没有参数的路由定义一个不同的页面,但是你必须在不接收参数的路由上使用斜杠"/",就像上面说的那样。 GetPage( name: '/profile/:user', page: () => UserProfile(), ), GetPage( name: '/third', page: () => Third(), transition: Transition.cupertino ), ], ) ); } ``` 发送别名路由数据 ```dart Get.toNamed("/second/34954"); ``` 在第二个页面上,通过参数获取数据 ```dart print(Get.parameters['user']); // out: 34954 ``` 或像这样发送多个参数 ```dart Get.toNamed("/profile/34954?flag=true"); ``` 在第二个屏幕上,通常按参数获取数据 ```dart print(Get.parameters['user']); print(Get.parameters['flag']); // out: 34954 true ``` 现在,你需要做的就是使用Get.toNamed()来导航你的别名路由,不需要任何context(你可以直接从你的BLoC或Controller类中调用你的路由),当你的应用程序被编译到web时,你的路由将出现在URL中。 ### 中间件 如果你想通过监听Get事件来触发动作,你可以使用routingCallback来实现。 ```dart GetMaterialApp( routingCallback: (routing) { if(routing.current == '/second'){ openAds(); } } ) ``` 如果你没有使用GetMaterialApp,你可以使用手动API来附加Middleware观察器。 ```dart void main() { runApp( MaterialApp( onGenerateRoute: Router.generateRoute, initialRoute: "/", navigatorKey: Get.key, navigatorObservers: [ GetObserver(MiddleWare.observer), // HERE !!! ], ), ); } ``` 创建一个MiddleWare类 ```dart class MiddleWare { static observer(Routing routing) { ///你除了可以监听路由外,还可以监听每个页面上的SnackBars、Dialogs和Bottomsheets。 if (routing.current == '/second' && !routing.isSnackbar) { Get.snackbar("Hi", "You are on second route"); } else if (routing.current =='/third'){ print('last route called'); } } } ``` 现在,在你的代码上使用Get: ```dart class First extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( leading: IconButton( icon: Icon(Icons.add), onPressed: () { Get.snackbar("hi", "i am a modern snackbar"); }, ), title: Text('First Route'), ), body: Center( child: ElevatedButton( child: Text('Open route'), onPressed: () { Get.toNamed("/second"); }, ), ), ); } } class Second extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( leading: IconButton( icon: Icon(Icons.add), onPressed: () { Get.snackbar("hi", "i am a modern snackbar"); }, ), title: Text('second Route'), ), body: Center( child: ElevatedButton( child: Text('Open route'), onPressed: () { Get.toNamed("/third"); }, ), ), ); } } class Third extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Third Route"), ), body: Center( child: ElevatedButton( onPressed: () { Get.back(); }, child: Text('Go back!'), ), ), ); } } ``` ## 免context导航 ### SnackBars 用Flutter创建一个简单的SnackBar,你必须获得Scaffold的context,或者你必须使用一个GlobalKey附加到你的Scaffold上。 ```dart final snackBar = SnackBar( content: Text('Hi!'), action: SnackBarAction( label: 'I am a old and ugly snackbar :(', onPressed: (){} ), ); // 在小组件树中找到脚手架并使用它显示一个SnackBars。 Scaffold.of(context).showSnackBar(snackBar); ``` 用Get: ```dart Get.snackbar('Hi', 'i am a modern snackbar'); ``` 有了Get,你所要做的就是在你代码的任何地方调用你的Get.snackbar,或者按照你的意愿定制它。 ```dart Get.snackbar( "Hey i'm a Get SnackBar!", // title "It's unbelievable! I'm using SnackBar without context, without boilerplate, without Scaffold, it is something truly amazing!", // message icon: Icon(Icons.alarm), shouldIconPulse: true, onTap:(){}, barBlur: 20, isDismissible: true, duration: Duration(seconds: 3), ); ////////// ALL FEATURES ////////// // Color colorText, // Duration duration, // SnackPosition snackPosition, // Widget titleText, // Widget messageText, // bool instantInit, // Widget icon, // bool shouldIconPulse, // double maxWidth, // EdgeInsets margin, // EdgeInsets padding, // double borderRadius, // Color borderColor, // double borderWidth, // Color backgroundColor, // Color leftBarIndicatorColor, // List boxShadows, // Gradient backgroundGradient, // TextButton mainButton, // OnTap onTap, // bool isDismissible, // bool showProgressIndicator, // AnimationController progressIndicatorController, // Color progressIndicatorBackgroundColor, // Animation progressIndicatorValueColor, // SnackStyle snackStyle, // Curve forwardAnimationCurve, // Curve reverseAnimationCurve, // Duration animationDuration, // double barBlur, // double overlayBlur, // Color overlayColor, // Form userInputForm /////////////////////////////////// ``` 如果您喜欢传统的SnackBars,或者想从头开始定制,包括只添加一行(Get.snackbar使用了一个强制性的标题和信息),您可以使用 `Get.rawSnackbar();`它提供了建立Get.snackbar的RAW API。 ### Dialogs 打开Dialogs: ```dart Get.dialog(YourDialogWidget()); ``` 打开默认Dialogs: ```dart Get.defaultDialog( onConfirm: () => print("Ok"), middleText: "Dialog made in 3 lines of code" ); ``` 你也可以用Get.generalDialog代替showGeneralDialog。 对于所有其他的FlutterDialogs小部件,包括cupertinos,你可以使用Get.overlayContext来代替context,并在你的代码中任何地方打开它。 对于不使用Overlay的小组件,你可以使用Get.context。 这两个context在99%的情况下都可以代替你的UIcontext,除了在没有导航context的情况下使用 inheritedWidget的情况。 ### BottomSheets Get.bottomSheet类似于showModalBottomSheet,但不需要context: ```dart Get.bottomSheet( Container( child: Wrap( children: [ ListTile( leading: Icon(Icons.music_note), title: Text('Music'), onTap: () {} ), ListTile( leading: Icon(Icons.videocam), title: Text('Video'), onTap: () {}, ), ], ), ) ); ``` ## 嵌套导航 Get让Flutter的嵌套导航更加简单。 你不需要context,而是通过Id找到你的导航栈。 - 注意:创建平行导航堆栈可能是危险的。理想的情况是不要使用NestedNavigators,或者尽量少用。如果你的项目需要它,请继续,但请记住,在内存中保持多个导航堆栈可能不是一个好主意(消耗RAM)。 看看它有多简单: ```dart Navigator( key: Get.nestedKey(1), // create a key by index initialRoute: '/', onGenerateRoute: (settings) { if (settings.name == '/') { return GetPageRoute( page: () => Scaffold( appBar: AppBar( title: Text("Main"), ), body: Center( child: TextButton( color: Colors.blue, onPressed: () { Get.toNamed('/second', id:1); // navigate by your nested route by index }, child: Text("Go to second"), ), ), ), ); } else if (settings.name == '/second') { return GetPageRoute( page: () => Center( child: Scaffold( appBar: AppBar( title: Text("Main"), ), body: Center( child: Text("second") ), ), ), ); } } ), ``` ================================================ FILE: documentation/zh_CN/state_management.md ================================================ - [状态管理](#状态管理) - [响应式状态管理器](#响应式状态管理器) - [优点](#优势) - [声明一个响应式变量](#声明一个响应式变量) - [使用视图中的值](#使用视图中的值) - [什么时候重建](#什么时候重建) - [可以使用.obs的地方](#可以使用.obs的地方) - [关于List的说明](#关于List的说明) - [为什么使用.value](#为什么使用.value) - [Obx()](#obx) - [Workers](#Workers) - [简单状态管理器](#简单状态管理器) - [优点](#优点) - [用法](#用法) - [如何处理controller](#如何处理controller) - [无需StatefulWidgets](#无需StatefulWidgets) - [为什么它存在](#为什么它存在) - [其他使用方法](#其他使用方法) - [唯一的ID](#唯一的ID) - [与其他状态管理器混用](#与其他状态管理器混用) - [GetBuilder vs GetX vs Obx vs MixinBuilder](#GetBuilder-vs-GetX-vs-Obx-vs-MixinBuilder) # 状态管理 目前,Flutter有几种状态管理器。但是,它们中的大多数都涉及到使用ChangeNotifier来更新widget,这对于中大型应用的性能来说是一个糟糕的方法。你可以在Flutter官方文档中查到,[ChangeNotifier应该使用1个或最多2个监听器](https://api.flutter.dev/flutter/foundation/ChangeNotifier-class.html),这使得它实际上无法用于任何中等或大型应用。 其他的状态管理器也不错,但有其细微的差别。 - BLoC非常安全和高效,但是对于初学者来说非常复杂,这使得人们无法使用Flutter进行开发。 - MobX比BLoC更容易,而且是响应式的,几乎是完美的,但是你需要使用一个代码生成器,对于大型应用来说,这降低了生产力,因为你需要喝很多咖啡,直到你的代码在`flutter clean`之后再次准备好(这不是MobX的错,而是codegen真的很慢!)。 - Provider使用InheritedWidget来传递相同的监听器,以此来解决上面报告的ChangeNotifier的问题,这意味着对其ChangeNotifier类的任何访问都必须在widget树内。 Get并不是比任何其他状态管理器更好或更差,而是说你应该分析这些要点以及下面的要点来选择是只用Get,还是与其他状态管理器结合使用。Get不是其他状态管理器的敌人,因为Get是一个微框架,而不仅仅是一个状态管理器,它的状态管理功能既可以单独使用,也可以与其他状态管理器结合使用。 ## 响应式状态管理器 响应式编程可能会让很多人感到陌生,因为它很复杂,但是GetX将响应式编程变得非常简单。 - 你不需要创建StreamControllers. - 你不需要为每个变量创建一个StreamBuilder。 - 你不需要为每个状态创建一个类。 - 你不需要为一个初始值创建一个get。 使用 Get 的响应式编程就像使用 setState 一样简单。 让我们想象一下,你有一个名称变量,并且希望每次你改变它时,所有使用它的小组件都会自动刷新。 这是你的计数变量: ```dart var name = 'Jonatas Borges'; ``` 要想让它变得可观察,你只需要在它的末尾加上".obs"。 ```dart var name = 'Jonatas Borges'.obs; ``` 就这么简单! 我们把这个reactive-".obs"(ervables)变量称为 _Rx_ 。 我们做了什么?我们创建了一个 "Stream "的 "String",分配了初始值 "Jonatas Borges",我们通知所有使用 "Jonatas Borges "的widgets,它们现在 "属于 "这个变量,当_Rx_的值发生变化时,它们也要随之改变。 这就是GetX **的** 魔力,这要归功于Dart的能力。 但是,我们知道,一个`Widget`只有在函数里面才能改变,因为静态类没有 "自动改变 "的能力。 你需要创建一个`StreamBuilder`,订阅这个变量来监听变化,如果你想在同一个范围内改变几个变量,就需要创建一个 "级联 "的嵌套`StreamBuilder`,对吧? 不,你不需要一个`StreamBuilder`,但你对静态类的理解是对的。 在视图中,当我们想改变一个特定的Widget时,我们通常有很多Flutter方式的模板。 有了**GetX**,你也可以忘记这些模板代码了。 `StreamBuilder( ... )`? `initialValue: ...`? `builder: ...`? 不,你只需要把这个变量放在`Obx()`这个Widget里面就可以了。 ```dart Obx (() => Text (controller.name)); ``` 你只需记住 `Obx(()=>` 你只需将Widget通过一个箭头函数传递给 `Obx()`( _Rx_ 的 "观察者")。 `Obx`是相当聪明的,只有当`controller.name`的值发生变化时才会改变。 如果`name`是`"John"`,你把它改成了`"John"`(`name.value="John"`),因为它和之前的`value`是一样的,所以界面上不会有任何变化,而`Obx`为了节省资源,会直接忽略新的值,不重建Widget。**这是不是很神奇**? > 那么,如果我在一个`Obx`里有5个 _Rx_ (可观察的)变量呢? 当其中**任何**一个变量发生变化时,它就会更新。 > 如果我在一个类中有 30 个变量,当我更新其中一个变量时,它会更新该类中**所有**的变量吗? 不会,只会更新使用那个 _Rx_ 变量的**特定 Widget**。 所以,只有当 _Rx_ 变量的值发生变化时,**GetX**才会更新界面。 ``` final isOpen = false.obs; //什么都不会发生......相同的值。 void onButtonTap() => isOpen.value=false; ``` ### 优势 **当你需要对更新的内容进行**精细的控制时,**GetX()** 可以帮助你。 如果你不需要 "unique IDs",比如当你执行一个操作时,你的所有变量都会被修改,那么就使用`GetBuilder`。 因为它是一个简单的状态更新器(以块为单位,比如`setState()`),只用几行代码就能完成。 它做得很简单,对CPU的影响最小,只是为了完成一个单一的目的(一个_State_ Rebuild),并尽可能地花费最少的资源。 如果你需要一个**强大的**状态管理器,用**GetX**是不会错的。 它不能和变量一起工作,除了 __flows__ ,它里面的东西本质都是`Streams`。 你可以将 _rxDart_ 与它结合使用,因为所有的东西都是`Streams`。 你可以监听每个" _Rx_ 变量 "的 "事件"。 因为里面的所有东西都是 "Streams"。 这实际上是一种 _BLoC_ 方法,比 _MobX_ 更容易,而且没有代码生成器或装饰。 你可以把**任何东西**变成一个 _"Observable"_ ,只需要在它末尾加上`.obs`。 ### 最高性能 除了有一个智能的算法来实现最小化的重建,**GetX**还使用了比较器以确保状态已经改变。 如果你的应用程序中遇到错误,并发送重复的状态变更,**GetX**将确保它不会崩溃。 使用**GetX**,只有当`value`改变时,状态才会改变。 这就是**GetX**,和使用MobX _的_ `computed`的主要区别。 当加入两个 __observable__ ,其中一个发生变化时,该 _observable_ 的监听器也会发生变化。 使用**GetX**,如果你连接了两个变量,`GetX()`(类似于`Observer()`)只有在它的状态真正变化时才会重建。 ### 声明一个响应式变量 你有3种方法可以把一个变量变成是 "可观察的"。 1 - 第一种是使用 **`Rx{Type}`**。 ```dart // 建议使用初始值,但不是强制性的 final name = RxString(''); final isLogged = RxBool(false); final count = RxInt(0); final balance = RxDouble(0.0); final items = RxList([]); final myMap = RxMap({}); ``` 2 - 第二种是使用 **`Rx`**,规定泛型 `Rx`。 ```dart final name = Rx(''); final isLogged = Rx(false); final count = Rx(0); final balance = Rx(0.0); final number = Rx(0) final items = Rx>([]); final myMap = Rx>({}); // 自定义类 - 可以是任何类 final user = Rx(); ``` 3 - 第三种更实用、更简单、更可取的方法,只需添加 **`.obs`** 作为`value`的属性。 ```dart final name = ''.obs; final isLogged = false.obs; final count = 0.obs; final balance = 0.0.obs; final number = 0.obs; final items = [].obs; final myMap = {}.obs; // 自定义类 - 可以是任何类 final user = User().obs; ``` ##### 有一个反应的状态,很容易。 我们知道, _Dart_ 现在正朝着 _null safety_ 的方向发展。 为了做好准备,从现在开始,你应该总是用一个**初始值**来开始你的 _Rx_ 变量。 > 用**GetX**将一个变量转化为一个 _observable_ + _initial value_ 是最简单,也是最实用的方法。 你只需在变量的末尾添加一个"`.obs`",即可把它变成可观察的变量, 然后它的`.value`就是 _初始值_ )。 ### 使用视图中的值 ```dart // controller final count1 = 0.obs; final count2 = 0.obs; int get sum => count1.value + count2.value; ``` ```dart // 视图 GetX( builder: (controller) { print("count 1 rebuild"); return Text('${controller.count1.value}'); }, ), GetX( builder: (controller) { print("count 2 rebuild"); return Text('${controller.count2.value}'); }, ), GetX( builder: (controller) { print("count 3 rebuild"); return Text('${controller.sum}'); }, ), ``` 如果我们把`count1.value++`递增,就会打印出来: - `count 1 rebuild` - `count 3 rebuild` 如果我们改变`count2.value++`,就会打印出来。 - `count 2 rebuild` - `count 3 rebuild` 因为`count2.value`改变了,`sum`的结果现在是`2`。 - 注意:默认情况下,第一个事件将重建小组件,即使是相同的`值`。 这种行为是由于布尔变量而存在的。 想象一下你这样做。 ```dart var isLogged = false.obs; ``` 然后,你检查用户是否 "登录",以触发`ever`的事件。 ```dart @override onInit(){ ever(isLogged, fireRoute); isLogged.value = await Preferences.hasToken(); } fireRoute(logged) { if (logged) { Get.off(Home()); } else { Get.off(Login()); } } ``` 如果 "hasToken "是 "false","isLogged "就不会有任何变化,所以 "ever() "永远不会被调用。 为了避免这种问题,_observable_的第一次变化将总是触发一个事件,即使它包含相同的`.value`。 如果你想删除这种行为,你可以使用: `isLogged.firstRebuild = false;`。 ### 什么时候重建 此外,Get还提供了精细的状态控制。你可以根据特定的条件对一个事件进行条件控制(比如将一个对象添加到List中)。 ```dart // 第一个参数:条件,必须返回true或false。 // 第二个参数:如果条件为真,则为新的值。 list.addIf(item < limit, item); ``` 没有装饰,没有代码生成器,没有复杂的程序: 爽歪歪! 你知道Flutter的计数器应用吗?你的Controller类可能是这样的。 ```dart class CountController extends GetxController { final count = 0.obs; } ``` 用一个简单的。 ```dart controller.count.value++ ``` 你可以在你的UI中更新计数器变量,不管它存储在哪里。 ### 可以使用.obs的地方 你可以在 obs 上转换任何东西,这里有两种方法: * 可以将你的类值转换为 obs ```dart class RxUser { final name = "Camila".obs; final age = 18.obs; } ``` * 或者可以将整个类转换为一个可观察的类。 ```dart class User { User({String name, int age}); var name; var age; } //实例化时。 final user = User(name: "Camila", age: 18).obs; ``` ### 关于List的说明 List和它里面的对象一样,是完全可以观察的。这样一来,如果你在List中添加一个值,它会自动重建使用它的widget。 你也不需要在List中使用".value",神奇的dart api允许我们删除它。 不幸的是,像String和int这样的原始类型不能被扩展,使得.value的使用是强制性的,但是如果你使用get和setter来处理这些类型,这将不是一个问题。 ```dart // controller final String title = 'User Info:'.obs final list = List().obs; // view Text(controller.title.value), // 字符串后面需要有.value。 ListView.builder ( itemCount: controller.list.length // List不需要它 ) ``` 当你在使自己的类可观察时,有另外一种方式来更新它们: ```dart // model // 我们将使整个类成为可观察的,而不是每个属性。 class User{ User({this.name = '', this.age = 0}); String name; int age; } // controller final user = User().obs; //当你需要更新user变量时。 user.update( (user) { // 这个参数是你要更新的类本身。 user.name = 'Jonny'; user.age = 18; }); // 更新user变量的另一种方式。 user(User(name: 'João', age: 35)); // view Obx(()=> Text("Name ${user.value.name}: Age: ${user.value.age}")); // 你也可以不使用.value来访问模型值。 user().name; // 注意是user变量,而不是类变量(首字母是小写的)。 ``` 你可以使用 "assign "和" assignAll "。 "assign "会清除你的List,并添加一个单个对象。 "assignAll "将清除现有的List,并添加任何可迭代对象。 ### 为什么使用.value 我们可以通过一个简单的装饰和代码生成器来消除使用"String "和 "int "的值的义务,但这个库的目的正是为了避免外部依赖。我们希望提供一个准备好的编程的环境(路由、依赖和状态的管理),以一种简单、轻量级和高性能的方式,而不需要一个外部包。 你可以在你的pubspec中添加3个字母(get)和一个冒号,然后开始编程。从路由管理到状态管理,所有的解决方案都是默认的,目的是为了方便、高效和高性能。 这个库的总大小还不如一个状态管理器,尽管它是一个完整的解决方案。 如果你被`.value`困扰,喜欢代码生成器,MobX是一个很好的选择,也可以和Get一起使用。对于那些想在pubspec中添加一个单一的依赖,然后开始编程,而不用担心一个包的版本与另一个包不兼容,也不用担心状态更新的错误来自于状态管理器或依赖,还是不想担心控制器的可用性,get都是刚刚好。 如果你对MobX代码生成器或者BLoC模板熟悉,你可以直接用Get来做路由,而忘记它有状态管理器。如果有一个项目有90多个控制器,MobX代码生成器在标准机器上进行Flutter Clean后,需要30多分钟才能完成任务。如果你的项目有5个、10个、15个控制器,任何一个状态管理器都能很好的满足你。如果你的项目大得离谱,代码生成器对你来说是个问题,但你已经获得了Get这个解决方案。 显然,如果有人想为项目做贡献,创建一个代码生成器,或者类似的东西,我将在这个readme中链接,作为一个替代方案,我的需求并不是所有开发者的需求,但现在我要说的是,已经有很好的解决方案,比如MobX。 ### Obx() 在Get中使用Bindings的类型是不必要的。你可以使用Obx widget代替GetX,GetX只接收创建widget的匿名函数。 如果你不使用类型,你将需要有一个控制器的实例来使用变量,或者使用`Get.find()`.value或Controller.to.value来检索值。 ### Workers Workers将协助你在事件发生时触发特定的回调。 ```dart ///每次`count1`变化时调用。 ever(count1, (_) => print("$_ has been changed")); ///只有在变量$_第一次被改变时才会被调用。 once(count1, (_) => print("$_ was changed once")); ///防DDos - 每当用户停止输入1秒时调用,例如。 debounce(count1, (_) => print("debouce$_"), time: Duration(seconds: 1)); ///忽略1秒内的所有变化。 interval(count1, (_) => print("interval $_"), time: Duration(seconds: 1)); ``` 所有Workers(除 "debounce "外)都有一个名为 "condition"的参数,它可以是一个 "bool "或一个返回 "bool "的回调。 这个`condition`定义了`callback`函数何时执行。 所有worker都会返回一个`Worker`实例,你可以用它来取消(通过`dispose()`)worker。 - **`ever`** 每当 _Rx_ 变量发出一个新的值时,就会被调用。 - **`everAll`** 和 "ever "很像,但它需要一个 _Rx_ 值的 "List",每次它的变量被改变时都会被调用。就是这样。 - **`once`** 'once'只在变量第一次被改变时被调用。 - **`debounce`** 'debounce'在搜索函数中非常有用,你只希望API在用户完成输入时被调用。如果用户输入 "Jonny",你将在API中进行5次搜索,分别是字母J、o、n、n和y。使用Get不会发生这种情况,因为你将有一个 "debounce "Worker,它只会在输入结束时触发。 - **`interval`** 'interval'与debouce不同,debouce如果用户在1秒内对一个变量进行了1000次修改,他将在规定的计时器(默认为800毫秒)后只发送最后一次修改。Interval则会忽略规定时间内的所有用户操作。如果你发送事件1分钟,每秒1000个,那么当用户停止DDOS事件时,debounce将只发送最后一个事件。建议这样做是为了避免滥用,在用户可以快速点击某样东西并获得一些好处的功能中(想象一下,用户点击某样东西可以赚取硬币,如果他在同一分钟内点击300次,他就会有300个硬币,使用间隔,你可以设置时间范围为3秒,无论是点击300次或100万次,1分钟内他最多获得20个硬币)。debounce适用于防DDOS,适用于搜索等功能,每次改变onChange都会调用你的api进行查询。Debounce会等待用户停止输入名称,进行请求。如果在上面提到的投币场景中使用它,用户只会赢得1个硬币,因为只有当用户 "暂停 "到既定时间时,它才会被执行。 - 注意:Worker应该总是在启动Controller或Class时使用,所以应该总是在onInit(推荐)、Class构造函数或StatefulWidget的initState(大多数情况下不推荐这种做法,但应该不会有任何副作用)。 ## 简单状态管理器 Get有一个极其轻巧简单的状态管理器,它不使用ChangeNotifier,可以满足特别是对Flutter新手的需求,而且不会给大型应用带来问题。 GetBuilder正是针对多状态控制的。想象一下,你在购物车中添加了30个产品,你点击删除一个,同时List更新了,价格更新了,购物车中的徽章也更新为更小的数字。这种类型的方法使GetBuilder成为杀手锏,因为它将状态分组并一次性改变,而无需为此进行任何 "计算逻辑"。GetBuilder就是考虑到这种情况而创建的,因为对于短暂的状态变化,你可以使用setState,而不需要状态管理器。 这样一来,如果你想要一个单独的控制器,你可以为其分配ID,或者使用GetX。这取决于你,记住你有越多的 "单独 "部件,GetX的性能就越突出,而当有多个状态变化时,GetBuilder的性能应该更优越。 ### 优点 1. 只更新需要的小部件。 2. 不使用changeNotifier,状态管理器使用较少的内存(接近0mb)。 3. 忘掉StatefulWidget! 使用Get你永远不会需要它。对于其他的状态管理器,你可能需要使用StatefulWidget来获取你的Provider、BLoC、MobX控制器等的实例。但是你有没有停下来想一想,你的appBar,你的脚手架,以及你的类中的大部分widget都是无状态的?那么如果你只能保存有状态的Widget的状态,为什么要保存整个类的状态呢?Get也解决了这个问题。创建一个无状态类,让一切都成为无状态。如果你需要更新单个组件,就用GetBuilder把它包起来,它的状态就会被维护。 4. 真正的解耦你的项目! 控制器一定不要在你的UI中,把你的TextEditController,或者你使用的任何控制器放在你的Controller类中。 5. 你是否需要触发一个事件来更新一个widget,一旦它被渲染?GetBuilder有一个属性 "initState",就像StatefulWidget一样,你可以从你的控制器中调用事件,直接从控制器中调用,不需要再在你的initState中放置事件。 6. 你是否需要触发一个动作,比如关闭流、定时器等?GetBuilder也有dispose属性,只要该widget被销毁,你就可以调用事件。 7. 仅在必要时使用流。你可以在你的控制器里面正常使用你的StreamControllers,也可以正常使用StreamBuilder,但是请记住,一个流消耗合理的内存,响应式编程很美,但是你不应该滥用它。30个流同时打开会比changeNotifier更糟糕(而且changeNotifier非常糟糕)。 8. 更新widgets而不需要为此花费ram。Get只存储GetBuilder的创建者ID,必要时更新该GetBuilder。get ID存储在内存中的消耗非常低,即使是成千上万的GetBuilders。当你创建一个新的GetBuilder时,你实际上是在共享拥有创建者ID的GetBuilder的状态。不会为每个GetBuilder创建一个新的状态,这为大型应用节省了大量的内存。基本上你的应用程序将是完全无状态的,而少数有状态的Widgets(在GetBuilder内)将有一个单一的状态,因此更新一个状态将更新所有的状态。状态只是一个。 9. Get是全知全能的,在大多数情况下,它很清楚地知道从内存中取出一个控制器的时机,你不需要担心什么时候移除一个控制器,Get知道最佳的时机。 ### 用法 ```dart // 创建控制器类并扩展GetxController。 class Controller extends GetxController { int counter = 0; void increment() { counter++; update(); // 当调用增量时,使用update()来更新用户界面上的计数器变量。 } } // 在你的Stateless/Stateful类中,当调用increment时,使用GetBuilder来更新Text。 GetBuilder( init: Controller(), // 首次启动 builder: (_) => Text( '${_.counter}', ), ) //只在第一次时初始化你的控制器。第二次使用ReBuilder时,不要再使用同一控制器。一旦将控制器标记为 "init "的部件部署完毕,你的控制器将自动从内存中移除。你不必担心这个问题,Get会自动做到这一点,只是要确保你不要两次启动同一个控制器。 ``` **完成!** - 你已经学会了如何使用Get管理状态。 - 注意:你可能想要一个更大的规模,而不是使用init属性。为此,你可以创建一个类并扩展Bindings类,并在其中添加将在该路由中创建的控制器。控制器不会在那个时候被创建,相反,这只是一个声明,这样你第一次使用Controller时,Get就会知道去哪里找。Get会保持懒加载,当不再需要Controller时,会自动移除它们。请看pub.dev的例子来了解它是如何工作的。 如果你导航了很多路由,并且需要之前使用的Controller中的数据,你只需要再用一次GetBuilder(没有init)。 ```dart class OtherClass extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( body: Center( child: GetBuilder( builder: (s) => Text('${s.counter}'), ), ), ); } ``` 如果你需要在许多其他地方使用你的控制器,并且在GetBuilder之外,只需在你的控制器中创建一个get,就可以轻松地拥有它。(或者使用`Get.find()`) ```dart class Controller extends GetxController { /// 你不需要这个,我推荐使用它只是为了方便语法。 /// 用静态方法:Controller.to.increment()。 /// 没有静态方法的情况下:Get.find().increment(); /// 使用这两种语法在性能上没有区别,也没有任何副作用。一个不需要类型,另一个IDE会自动完成。 static Controller get to => Get.find(); // 添加这一行 int counter = 0; void increment() { counter++; update(); } } ``` 然后你可以直接访问你的控制器,这样: ```dart FloatingActionButton( onPressed: () { Controller.to.increment(), } // 是不是贼简单! child: Text("${Controller.to.counter}"), ), ``` 当你按下FloatingActionButton时,所有监听'counter'变量的widget都会自动更新。 ### 如何处理controller 比方说,我们有这样的情况。 `Class a => Class B (has controller X) => Class C (has controller X)` 在A类中,控制器还没有进入内存,因为你还没有使用它(Get是懒加载)。在类B中,你使用了控制器,并且它进入了内存。在C类中,你使用了与B类相同的控制器,Get会将控制器B的状态与控制器C共享,同一个控制器还在内存中。如果你关闭C屏和B屏,Get会自动将控制器X从内存中移除,释放资源,因为a类没有使用该控制器。如果你再次导航到B,控制器X将再次进入内存,如果你没有去C类,而是再次回到a类,Get将以同样的方式将控制器从内存中移除。如果类C没有使用控制器,你把类B从内存中移除,就没有类在使用控制器X,同样也会被处理掉。唯一能让Get乱了阵脚的例外情况,是如果你意外地从路由中删除了B,并试图使用C中的控制器,在这种情况下,B中的控制器的创建者ID被删除了,Get被设计为从内存中删除每一个没有创建者ID的控制器。如果你打算这样做,在B类的GetBuilder中添加 "autoRemove: false "标志,并在C类的GetBuilder中使用adopID = true; ### 无需StatefulWidgets 使用StatefulWidgets意味着不必要地存储整个界面的状态,甚至因为如果你需要最小化地重建一个widget,你会把它嵌入一个Consumer/Observer/BlocProvider/GetBuilder/GetX/Obx中,这将是另一个StatefulWidget。 StatefulWidget类是一个比StatelessWidget大的类,它将分配更多的RAM,只使用一两个类之间可能不会有明显的区别,但当你有100个类时,它肯定会有区别! 除非你需要使用混合器,比如TickerProviderStateMixin,否则完全没有必要使用StatefulWidget与Get。 你可以直接从GetBuilder中调用StatefulWidget的所有方法。 例如,如果你需要调用initState()或dispose()方法,你可以直接调用它们。 ```dart GetBuilder( initState: (_) => Controller.to.fetchApi(), dispose: (_) => Controller.to.closeStreams(), builder: (s) => Text('${s.username}'), ), ``` 比这更好的方法是直接从控制器中使用onInit()和onClose()方法。 ```dart @override void onInit() { fetchApi(); super.onInit(); } ``` - 注意:如果你想在控制器第一次被调用的那一刻启动一个方法,你不需要为此使用构造函数,使用像Get这样面向性能的包,这样做反而是糟糕的做法,因为它偏离了控制器被创建或分配的逻辑(如果你创建了这个控制器的实例,构造函数会立即被调用,你会在控制器还没有被使用之前就填充了一个控制器,你在没有被使用的情况下就分配了内存,这绝对违背这个库的原则)。onInit();和onClose();方法就是为此而创建的,它们会在Controller被创建,或者第一次使用时被调用,这取决于你是否使用Get.lazyPut。例如,如果你想调用你的API来填充数据,你可以忘掉老式的initState/dispose方法,只需在onInit中开始调用api,如果你需要执行任何命令,如关闭流,使用onClose()来实现。 ### 为什么它存在 这个包的目的正是为了给你提供一个完整的解决方案,用于导航路线,管理依赖和状态,使用尽可能少的依赖,高度解耦。Get将所有高低级别的Flutter API都纳入自身,以确保你在工作中尽可能减少耦合。我们将所有的东西集中在一个包中,以确保你在你的项目中没有任何形式的耦合。这样一来,你就可以只在视图中放置小组件,而让你的团队中负责业务逻辑的那部分人自由地工作,不需要依赖视图中的任何元素来处理业务逻辑。这样就提供了一个更加干净的工作环境,这样你的团队中的一部分人只用widget工作,而不用担心将数据发送到你的controller,你的团队中的一部分人只用业务逻辑工作,而不依赖于视图的任何元素。 所以为了简化这个问题。 你不需要在initState中调用方法,然后通过参数发送给你的控制器,也不需要使用你的控制器构造函数,你有onInit()方法,在合适的时间被调用,以启动你的服务。 你不需要调用设备,你有onClose()方法,它将在确切的时刻被调用,当你的控制器不再需要时,将从内存中删除。这样一来,只给widget留下视图,不要从中进行任何形式的业务逻辑。 不要在GetxController里面调用dispose方法,它不会有任何作用,记住控制器不是Widget,你不应该 "dispose "它,它会被Get自动智能地从内存中删除。如果你在上面使用了任何流,想关闭它,只要把它插入到close方法中就可以了。例如 ```dart class Controller extends GetxController { StreamController user = StreamController(); StreamController name = StreamController(); ///关闭流用onClose方法,而不是dispose @override void onClose() { user.close(); name.close(); super.onClose(); } } ``` 控制器的生命周期。 - onInit()是创建控制器的地方。 - onClose(),关闭控制器,为删除方法做准备。 - deleted: 你不能访问这个API,因为它实际上是将控制器从内存中删除。它真的被删除了,不留任何痕迹。 ### 其他使用方法 你可以直接在GetBuilder值上使用Controller实例。 ```dart GetBuilder( init: Controller(), builder: (value) => Text( '${value.counter}', //here ), ), ``` 你可能还需要在GetBuilder之外的控制器实例,你可以使用这些方法来实现。 ```dart class Controller extends GetxController { static Controller get to => Get.find(); [...] } //view GetBuilder( init: Controller(), // 每个控制器只用一次 builder: (_) => Text( '${Controller.to.counter}', //here ) ), ``` 或者 ```dart class Controller extends GetxController { // static Controller get to => Get.find(); // with no static get [...] } // on stateful/stateless class GetBuilder( init: Controller(), // 每个控制器只用一次 builder: (_) => Text( '${Get.find().counter}', //here ), ), ``` - 你可以使用 "非规范 "的方法来做这件事。如果你正在使用一些其他的依赖管理器,比如get_it、modular等,并且只想交付控制器实例,你可以这样做。 ```dart Controller controller = Controller(); [...] GetBuilder( init: controller, //here builder: (_) => Text( '${controller.counter}', // here ), ), ``` ### 唯一的ID 如果你想用GetBuilder完善一个widget的更新控件,你可以给它们分配唯一的ID。 ```dart GetBuilder( id: 'text', //这里 init: Controller(), // 每个控制器只用一次 builder: (_) => Text( '${Get.find().counter}', //here ), ), ``` 并更新它: ```dart update(['text']); ``` 您还可以为更新设置条件。 ```dart update(['text'], counter < 10); ``` GetX会自动进行重建,并且只重建使用被更改的变量的小组件,如果您将一个变量更改为与之前相同的变量,并且不意味着状态的更改,GetX不会重建小组件以节省内存和CPU周期(界面上正在显示3,而您再次将变量更改为3。在大多数状态管理器中,这将导致一个新的重建,但在GetX中,如果事实上他的状态已经改变,那么widget将只被再次重建) ## 与其他状态管理器混用 有人开了一个功能请求,因为他们只想使用一种类型的响应式变量,而其他的则手动去更新,需要为此在GetBuilder中插入一个Obx。思来想去,MixinBuilder应运而生。它既可以通过改变".obs "变量进行响应式改变,也可以通过update()进行手动更新。然而,在4个widget中,他是消耗资源最多的一个,因为除了有一个Subscription来接收来自他的子代的变化事件外,他还订阅了他的控制器的update方法。 扩展GetxController是很重要的,因为它们有生命周期,可以在它们的onInit()和onClose()方法中 "开始 "和 "结束 "事件。你可以使用任何类来实现这一点,但我强烈建议你使用GetxController类来放置你的变量,无论它们是否是可观察的。 ## GetBuilder vs GetX vs Obx vs MixinBuilder 在十年的编程工作中,我能够学到一些宝贵的经验。 我第一次接触到响应式编程的时候,是那么的 "哇,这太不可思议了",事实上响应式编程是不可思议的。 但是,它并不适合所有情况。通常情况下,你需要的是同时改变2、3个widget的状态,或者是短暂的状态变化,这种情况下,响应式编程不是不好,而是不适合。 响应式编程对RAM的消耗比较大,可以通过单独的工作流来弥补,这样可以保证只有一个widget被重建,而且是在必要的时候,但是创建一个有80个对象的List,每个对象都有几个流,这不是一个好的想法。打开dart inspect,查看一个StreamBuilder的消耗量,你就会明白我想告诉你什么。 考虑到这一点,我创建了简单的状态管理器。它很简单,这正是你应该对它提出的要求:以一种简单的方式,并且以最高效的方式更新块中的状态。 GetBuilder在RAM中是非常高效的,几乎没有比他更高效的方法(至少我无法想象,如果存在,请告诉我们)。 然而,GetBuilder仍然是一个手动的状态管理器,你需要调用update(),就像你需要调用Provider的notifyListeners()一样。 还有一些情况下,响应式编程真的很有趣,不使用它就等于重新发明轮子。考虑到这一点,GetX的创建是为了提供状态管理器中最现代和先进的一切。它只在必要的时候更新必要的东西,如果你出现了错误,同时发送了300个状态变化,GetX只在状态真正发生变化时才会过滤并更新界面。 GetX比其他响应式状态管理器还是比较高效的,但它比GetBuilder多消耗一点内存。思前想后,以最大限度地消耗资源为目标,Obx应运而生。与GetX和GetBuilder不同的是,你将无法在Obx内部初始化一个控制器,它只是一个带有StreamSubscription的Widget,接收来自你的子代的变化事件,仅此而已。它比GetX更高效,但输给了GetBuilder,这是意料之中的,因为它是响应式的,而且GetBuilder有最简单的方法,即存储widget的hashcode和它的StateSetter。使用Obx,你不需要写你的控制器类型,你可以从多个不同的控制器中监听到变化,但它需要在之前进行初始化,或者使用本readme开头的示例方法,或者使用Bindings类。 ================================================ FILE: example/.gitignore ================================================ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ # IntelliJ related *.iml *.ipr *.iws .idea/ # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. #.vscode/ # Flutter/Dart/Pub related **/doc/api/ **/ios/Flutter/.last_build_id .dart_tool/ .flutter-plugins .flutter-plugins-dependencies .packages .pub-cache/ .pub/ /linux/ /ios/ /android/ /web/ /macos/ /windows/ # Web related # Symbolication related app.*.symbols # Obfuscation related app.*.map.json ================================================ FILE: example/.metadata ================================================ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # # This file should be version controlled and should not be manually edited. version: revision: "35c388afb57ef061d06a39b537336c87e0e3d1b1" channel: "stable" project_type: app # Tracks metadata for the flutter migrate command migration: platforms: - platform: root create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 - platform: web create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 # User provided section # List of Local paths (relative to this file) that should be # ignored by the migrate tool. # # Files that are not part of the templates will be ignored by default. unmanaged_files: - 'lib/main.dart' - 'ios/Runner.xcodeproj/project.pbxproj' ================================================ FILE: example/README.md ================================================ # example A new Flutter project. ## Getting Started This project is a starting point for a Flutter application. A few resources to get you started if this is your first Flutter project: - [Lab: Write your first Flutter app](https://flutter.dev/documentation/get-started/codelab) - [Cookbook: Useful Flutter samples](https://flutter.dev/documentation/cookbook) For help getting started with Flutter, view our [online documentation](https://flutter.dev/docs), which offers tutorials, samples, guidance on mobile development, and a full API reference. ================================================ FILE: example/analysis_options.yaml ================================================ # This file configures the analyzer, which statically analyzes Dart code to # check for errors, warnings, and lints. # # The issues identified by the analyzer are surfaced in the UI of Dart-enabled # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be # invoked from the command line by running `flutter analyze`. # The following line activates a set of recommended lints for Flutter apps, # packages, and plugins designed to encourage good coding practices. linter: # The lint rules applied to this project can be customized in the # section below to disable rules from the `package:flutter_lints/flutter.yaml` # included above or to enable additional rules. A list of all available lints # and their documentation is published at # https://dart-lang.github.io/linter/lints/index.html. # # Instead of disabling a lint rule for the entire project in the # section below, it can also be suppressed for a single line of code # or a specific dart file by using the `// ignore: name_of_lint` and # `// ignore_for_file: name_of_lint` syntax on the line or in the file # producing the lint. rules: camel_case_types: false constant_identifier_names: false # avoid_print: false # Uncomment to disable the `avoid_print` rule # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options ================================================ FILE: example/example.md ================================================ # GetX codelab In this example you will learn the basics of GetX. You will see how much easier it is to code with this framework, and you will know what problems GetX proposes to solve. If the default Flutter application were rewritten with Getx, it would have only a few lines of code. The Getx state manager is easier than using setState. You just need to add a ".obs" at the end of your variable, and wrap the widget you want to change within a Obx(). ```dart void main() => runApp(MaterialApp(home: Home())); class Home extends StatelessWidget { var count = 0.obs; @override Widget build(context) => Scaffold( appBar: AppBar(title: Text("counter")), body: Center( child: Obx(() => Text("$count")), ), floatingActionButton: FloatingActionButton( child: Icon(Icons.add), onPressed: () => count ++, )); } ``` However, this simple example deals with ephemeral state management. If you need to access this data in several places in your application, you will need global state management. The most common way to do this is to separate the business logic from its visualization. I know, you've heard this concept before, and it might have been scary for you, but with Getx, it's stupidly simple. Getx provides you with a class capable of initializing data, and removing it when it is no longer needed, and its use is very simple: Just create a class by extending GetxController and insert ALL your variables and functions there. ```dart class Controller extends GetxController { var count = 0; void increment() { count++; update(); } } ``` Here you can choose between simple state management, or reactive state management. The simple one will update its variable on the screen whenever update() is called. It is used with a widget called "GetBuilder". ```dart class Home extends StatelessWidget { final controller = Get.put(Controller()); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text("counter")), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ GetBuilder( builder: (_) => Text( 'clicks: ${controller.count}', )), ElevatedButton( child: Text('Next Route'), onPressed: () { Get.to(Second()); }, ), ], ), ), floatingActionButton: FloatingActionButton( child: Icon(Icons.add), onPressed: controller.increment(), ), ); } } class Second extends StatelessWidget { final Controller ctrl = Get.find(); @override Widget build(context){ return Scaffold(body: Center(child: Text("${ctrl.count}"))); } } ``` When instantiating your controller, you may have noticed that we use `Get.put(Controller())`. This is enough to make your controller available to other pages as long as it is in memory. You may have noticed that you have a new button using `Get.to(Second())`. This is enough to navigate to another page. You don't need a ```dart Navigator.of(context).push(context, MaterialPageRoute(context, builder: (context){ return Second(); }, ); ``` all that huge code, it's reduced to a simple `Get.to(Second())`. Isn't that incredible? You may also have noticed that on the other page you simply used Get.find (), and the framework knows which controller you registered previously, and returns it effortlessly, you don't need to type the find with `Get.find()` so that he knows what you need. However, this is simple state management. You may want to control the output of each change of state, you may want to use and manipulate streams, you may want a variable to only be changed on the screen, when it definitely changes, you may need a debounce on changing the state, and to these and other needs, Getx has powerful, intelligent, and lightweight state management that can address any need, regardless of the size of your project. And its use is even easier than the previous one. In your controller, you can remove the update method, Getx is reactive, so when a variable changes, it will automatically change on the screen. You just need to add a ".obs" in front of your variable, and that's it, it's already reactive. ```dart class Controller extends GetxController { var count = 0.obs; void increment() { count++; } } ``` Now you just need to change GetBuilder for GetX and that's it ```dart class Home extends StatelessWidget { final controller = Get.put(Controller()); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text("counter")), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ GetX( builder: (_) => Text( 'clicks: ${controller.count}', )), ElevatedButton( child: Text('Next Route'), onPressed: () { Get.to(Second()); }, ), ], ), ), floatingActionButton: FloatingActionButton( child: Icon(Icons.add), onPressed: controller.increment(), ), ); } } class Second extends StatelessWidget { final Controller ctrl = Get.find(); @override Widget build(context){ return Scaffold(body: Center(child: Text("${ctrl.count}"))); } } ``` GetX is a useful widget when you want to inject the controller into the init property, or when you want to retrieve an instance of the controller within the widget itself. In other cases, you can insert your widget into an Obx, which receives a widget function. This looks much easier and clearer, just like the first example ```dart class Home extends StatelessWidget { final controller = Get.put(Controller()); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text("counter")), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Obx(() => Text( 'clicks: ${controller.count}', )), ElevatedButton( child: Text('Next Route'), onPressed: () { Get.to(Second()); }, ), ], ), ), floatingActionButton: FloatingActionButton( child: Icon(Icons.add), onPressed: controller.increment(), ), ); } } class Second extends StatelessWidget { final Controller ctrl = Get.find(); @override Widget build(context){ return Scaffold(body: Center(child: Text("${ctrl.count}"))); } } ``` If you are a more demanding user, you must have said: BLoC separates the View from the business logic. But what about the presentation logic? Will I be obliged to attach it to the visualization? Will I be totally dependent on the context for everything I want to do? Will I have to insert a bunch of variables, TextEditingControllers in my view? If I need to hear the Scroll on my screen, do I need to insert an initState and a function into my view? If I need to trigger an action when this scroll reaches the end, do I insert it into the view, or in the bloc/changeNotifier class? Well, these are common architectural questions, and most of the time the solution to them is ugly. With Getx you have no doubts when your architecture, if you need a function, it must be on your Controller, if you need to trigger an event, it needs to be on your controller, your view is generally a StatelessWidget free from any dirt. This means that if someone has already done something you’re looking for, you can copy the controller entirely from someone else, and it will work for you, this level of standardization is what Flutter lacked, and it’s because of this that Getx has become so popular in the last few days. Flutter is amazing, and has minor one-off problems. Getx came to solve these specific problems. Flutter provides powerful APIs, and we turn them into an easy, clean, clear, and concise API for you to build applications in a fast, performance and highly scalable way. If you have already asked yourself some of these questions above, you have certainly found the solution to your problems. Getx is able to completely separate any logic, be it presentation or business, and you will only have pure widgets in your visualization layer. No variables, no functions, just widgets. This will facilitate the work of your team working with the UI, as well as your team working with your logic. They won't depend on initState to do anything, their controller has onInit. Your code can be tested in isolation, the way it is. But what about dependency injection? Will I have it attached to my visualization? If you've used any state manager, you've probably heard of "multiAnything", or something like that. You have probably already inserted dozens of ChangeNotifier or Blocs classes in a widget, just to have it all over the tree. This level of coupling is yet another problem that Getx came to solve. For this, in this ecosystem we use BINDINGS. Bindings are dependency injection classes. They are completely outside your widget tree, making your code cleaner, more organized, and allowing you to access it anywhere without context. You can initialize dozens of controllers in your Bindings, when you need to know what is being injected into your view, just open the Bindings file on your page and that's it, you can clearly see what has been prepared to be initialized in your View. Bindings is the first step towards having a scalable application, you can visualize what will be injected into your page, and decouple the dependency injection from your visualization layer. To create a Binding, simply create a class and implement Bindings ```dart class SampleBind extends Binding { @override List dependencies() { return [ Bind.lazyPut(() => Controller()), Bind.lazyPut(() => Controller2()), Bind.lazyPut(() => Controller3()), ]; } } ``` You can use with named routes (preferred) ```dart void main() { runApp(GetMaterialApp( initialRoute: '/home', getPages: [ GetPage(name: '/home', page: () => First(), binding: SampleBind()), ], )); } ``` Or unnamed ```dart Get.to(Second(), binding: SampleBind()); ``` There is a trick that can clear your View even more. Instead of extending StatelessWidget, you can extend GetView, which is a StatelessWidget with a "controller" property. See the example and see how clean your code can be using this approach. The standard Flutter counter has almost 100 lines, it would be summarized to: on main.dart ```dart void main() { runApp(GetMaterialApp( initialRoute: '/home', getPages: [ GetPage(name: '/home', page: () => HomeView(), binding: HomeBinding()), ], )); } ``` on home_bindings.dart ```dart class HomeBinding implements Bindings { @override void dependencies() { Get.lazyPut(() => HomeController()); } } ``` on home_controller.dart ```dart class HomeController extends GetxController { var count = 0.obs; void increment() => count++; } ``` on home_view.dart ```dart class Home extends GetView { @override Widget build(context) => Scaffold( appBar: AppBar(title: Text("counter")), body: Center( child: Obx(() => Text("${controller.counter}")), ), floatingActionButton: FloatingActionButton( child: Icon(Icons.add), onPressed: controller.increment, )); } ``` What did you do: He built an example of the counter, (with less code than the original), decoupling its visualization, its business logic, its dependency injection, in a clean, scalable way, facilitating code maintenance and reusability. If you need an accountant on another project, or your developer friend does, you can just share the content of the controller file with him, and everything will work perfectly. As the view has only widgets, you can use a view for android, and another for iOS, taking advantage of 100% of your business logic, your view has only widgets! you can change them however you want, without affecting your application in any way. However, some examples like internationalization, Snackbars without context, validators, responsiveness and other Getx resources, were not explored (and it would not even be possible to explore all resources in such a simple example), so below is an example not very complete, but trying demonstrate how to use internationalization, reactive custom classes, reactive lists, snackbars contextless, workers etc. ```dart import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; void main() { runApp(GetMaterialApp( // It is not mandatory to use named routes, but dynamic urls are interesting. initialRoute: '/home', defaultTransition: Transition.native, translations: MyTranslations(), locale: Locale('pt', 'BR'), getPages: [ //Simple GetPage GetPage(name: '/home', page: () => First()), // GetPage with custom transitions and bindings GetPage( name: '/second', page: () => Second(), customTransition: SizeTransitions(), binding: SampleBind(), ), // GetPage with default transitions GetPage( name: '/third', transition: Transition.cupertino, page: () => Third(), ), ], )); } class MyTranslations extends Translations { @override Map> get keys => { 'en': { 'title': 'Hello World %s', }, 'en_US': { 'title': 'Hello World from US', }, 'pt': { 'title': 'Olá de Portugal', }, 'pt_BR': { 'title': 'Olá do Brasil', }, }; } class Controller extends GetxController { int count = 0; void increment() { count++; // use update method to update all count variables update(); } } class First extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( leading: IconButton( icon: Icon(Icons.add), onPressed: () { Get.snackbar("Hi", "I'm modern snackbar"); }, ), title: Text("title".trArgs(['John'])), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ GetBuilder( init: Controller(), // You can initialize your controller here the first time. Don't use init in your other GetBuilders of same controller builder: (_) => Text( 'clicks: ${_.count}', )), ElevatedButton( child: Text('Next Route'), onPressed: () { Get.toNamed('/second'); }, ), ElevatedButton( child: Text('Change locale to English'), onPressed: () { Get.updateLocale(Locale('en', 'UK')); }, ), ], ), ), floatingActionButton: FloatingActionButton( child: Icon(Icons.add), onPressed: () { Get.find().increment(); }), ); } } class Second extends GetView { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('second Route'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Obx( () { print("count1 rebuild"); return Text('${controller.count1}'); }, ), Obx( () { print("count2 rebuild"); return Text('${controller.count2}'); }, ), Obx(() { print("sum rebuild"); return Text('${controller.sum}'); }), Obx( () => Text('Name: ${controller.user.value?.name}'), ), Obx( () => Text('Age: ${controller.user.value?.age}'), ), ElevatedButton( child: Text("Go to last page"), onPressed: () { Get.toNamed('/third', arguments: 'arguments of second'); }, ), ElevatedButton( child: Text("Back page and open snackbar"), onPressed: () { Get.back(); Get.snackbar( 'User 123', 'Successfully created', ); }, ), ElevatedButton( child: Text("Increment"), onPressed: () { controller.increment(); }, ), ElevatedButton( child: Text("Increment"), onPressed: () { controller.increment2(); }, ), ElevatedButton( child: Text("Update name"), onPressed: () { controller.updateUser(); }, ), ElevatedButton( child: Text("Dispose worker"), onPressed: () { controller.disposeWorker(); }, ), ], ), ), ); } } class Third extends GetView { @override Widget build(BuildContext context) { return Scaffold( floatingActionButton: FloatingActionButton(onPressed: () { controller.incrementList(); }), appBar: AppBar( title: Text("Third ${Get.arguments}"), ), body: Center( child: Obx(() => ListView.builder( itemCount: controller.list.length, itemBuilder: (context, index) { return Text("${controller.list[index]}"); }))), ); } } class SampleBind extends Binding { @override void dependencies() { Get.lazyPut(() => ControllerX()); } } class User { User({this.name = 'Name', this.age = 0}); String name; int age; } class ControllerX extends GetxController { final count1 = 0.obs; final count2 = 0.obs; final list = [56].obs; final user = User().obs; updateUser() { user.update((value) { value!.name = 'Jose'; value.age = 30; }); } /// Once the controller has entered memory, onInit will be called. /// It is preferable to use onInit instead of class constructors or initState method. /// Use onInit to trigger initial events like API searches, listeners registration /// or Workers registration. /// Workers are event handlers, they do not modify the final result, /// but it allows you to listen to an event and trigger customized actions. /// Here is an outline of how you can use them: /// made this if you need cancel you worker late Worker _ever; @override onInit() { /// Called every time the variable $_ is changed _ever = ever(count1, (_) => print("$_ has been changed (ever)")); everAll([count1, count2], (_) => print("$_ has been changed (everAll)")); /// Called first time the variable $_ is changed once(count1, (_) => print("$_ was changed once (once)")); /// Anti DDos - Called every time the user stops typing for 1 second, for example. debounce(count1, (_) => print("debouce$_ (debounce)"), time: Duration(seconds: 1)); /// Ignore all changes within 1 second. interval(count1, (_) => print("interval $_ (interval)"), time: Duration(seconds: 1)); } int get sum => count1.value + count2.value; increment() => count1.value++; increment2() => count2.value++; disposeWorker() { _ever.dispose(); // or _ever(); } incrementList() => list.add(75); } class SizeTransitions extends CustomTransition { @override Widget buildTransition( BuildContext context, Curve? curve, Alignment? alignment, Animation animation, Animation secondaryAnimation, Widget child) { return Align( alignment: Alignment.center, child: SizeTransition( sizeFactor: CurvedAnimation( parent: animation, curve: curve!, ), child: child, ), ); } } ``` ================================================ FILE: example/lib/lang/en_US.dart ================================================ // ignore_for_file: file_names const Map en_US = { 'update_language': 'Update language to Portuguese', 'number_of_prizes': 'Number of prizes', 'average_age_of_laureates': 'Average age of laureates', 'details': 'Details', 'nobel_by_country': 'Nobel by country', }; ================================================ FILE: example/lib/lang/pt_BR.dart ================================================ // ignore_for_file: file_names const Map pt_BR = { 'update_language': 'Atualizar idioma para Inglês', 'number_of_prizes': 'Número de prêmios', 'average_age_of_laureates': 'Idade média dos laureados', 'details': 'Detalhes', 'nobel_by_country': 'Nobel por país', }; ================================================ FILE: example/lib/lang/translation_service.dart ================================================ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'en_US.dart'; import 'pt_BR.dart'; class TranslationService extends Translations { static Locale? get locale => Get.deviceLocale; static const fallbackLocale = Locale('en', 'US'); @override Map> get keys => { 'en_US': en_US, 'pt_BR': pt_BR, }; } ================================================ FILE: example/lib/main.dart ================================================ import 'package:flutter/material.dart'; import 'package:get/get.dart'; // import 'lang/translation_service.dart'; // import 'routes/app_pages.dart'; // import 'shared/logger/logger_utils.dart'; // void main() { // runApp(const MyApp()); // } // class MyApp extends StatelessWidget { // const MyApp({Key? key}) : super(key: key); // @override // Widget build(BuildContext context) { // return GetMaterialApp( // theme: ThemeData(useMaterial3: true), // debugShowCheckedModeBanner: false, // enableLog: true, // logWriterCallback: Logger.write, // initialRoute: AppPages.INITIAL, // getPages: AppPages.routes, // locale: TranslationService.locale, // fallbackLocale: TranslationService.fallbackLocale, // translations: TranslationService(), // ); // } // } /// Nav 2 snippet void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return GetMaterialApp( getPages: [ GetPage( participatesInRootNavigator: true, name: '/first', page: () => const First()), GetPage( name: '/second', page: () => const Second(), transition: Transition.downToUp, ), GetPage( name: '/third', page: () => const Third(), ), GetPage( name: '/fourth', page: () => const Fourth(), ), ], debugShowCheckedModeBanner: false, ); } } class FirstController extends GetxController { @override void onClose() { print('on close first'); super.onClose(); } } class First extends StatelessWidget { const First({Key? key}) : super(key: key); @override Widget build(BuildContext context) { print('First rebuild'); Get.put(FirstController()); return Scaffold( appBar: AppBar( title: const Text('page one'), leading: IconButton( icon: const Icon(Icons.more), onPressed: () { Get.snackbar( 'title', "message", mainButton: TextButton(onPressed: () {}, child: const Text('button')), isDismissible: true, duration: Duration(seconds: 5), snackbarStatus: (status) => print(status), ); // print('THEME CHANGED'); // Get.changeTheme( // Get.isDarkMode ? ThemeData.light() : ThemeData.dark()); }, ), ), body: Center( child: SizedBox( height: 300, width: 300, child: ElevatedButton( onPressed: () { Get.toNamed('/second?id=123'); }, child: const Text('next screen'), ), ), ), ); } } class SecondController extends GetxController { final textEdit = TextEditingController(); @override void onClose() { print('on close second'); textEdit.dispose(); super.onClose(); } } class Second extends StatelessWidget { const Second({Key? key}) : super(key: key); @override Widget build(BuildContext context) { final controller = Get.put(SecondController()); print('second rebuild'); return PopScope( canPop: false, onPopInvokedWithResult: (didPop, result) => print('pop invoked'), child: Scaffold( appBar: AppBar( title: Text('page two ${Get.parameters["id"]}'), ), body: Center( child: Column( children: [ Expanded( child: TextField( controller: controller.textEdit, )), SizedBox( height: 300, width: 300, child: ElevatedButton( onPressed: () { Get.toNamed('/third'); }, child: const Text('next screen'), ), ), ], ), ), ), ); } } class Third extends StatelessWidget { const Third({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.red, appBar: AppBar( title: const Text('page three'), ), body: Center( child: SizedBox( height: 300, width: 300, child: ElevatedButton( onPressed: () { Get.offNamedUntil('/fourth', (route) { return Get.currentRoute == '/first'; }); }, child: const Text('go to first screen'), ), ), ), ); } } class Fourth extends StatelessWidget { const Fourth({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.red, appBar: AppBar( title: const Text('page four'), ), body: Center( child: SizedBox( height: 300, width: 300, child: ElevatedButton( onPressed: () { Get.back(); }, child: const Text('go to first screen'), ), ), ), ); } } ================================================ FILE: example/lib/pages/home/bindings/details_binding.dart ================================================ import 'package:get/get.dart'; import '../presentation/controllers/details_controller.dart'; class DetailsBinding extends Binding { @override List dependencies() { return [ Bind.lazyPut(() => DetailsController(homeRepository: Get.find())), ]; } } ================================================ FILE: example/lib/pages/home/bindings/home_binding.dart ================================================ import 'package:get/get.dart'; import '../data/home_api_provider.dart'; import '../data/home_repository.dart'; import '../domain/adapters/repository_adapter.dart'; import '../presentation/controllers/home_controller.dart'; class HomeBinding extends Binding { @override List dependencies() { return [ Bind.lazyPut(() => HomeProvider()), Bind.lazyPut(() => HomeRepository(provider: Get.find())), Bind.lazyPut(() => HomeController(homeRepository: Get.find())), ]; } } ================================================ FILE: example/lib/pages/home/data/home_api_provider.dart ================================================ import 'package:get/get.dart'; import '../../../shared/constants/endpoints.dart'; import '../domain/entity/country_model.dart'; // ignore: one_member_abstracts abstract class IHomeProvider { Future>> getCountries(); Future> getCountry(String path); } class HomeProvider extends GetConnect implements IHomeProvider { @override void onInit() { httpClient.baseUrl = API_URL; super.onInit(); } @override Future>> getCountries() { return get( '/countries', decoder: (data) => (data as List).map((item) => CountriesItem.fromJson(item)).toList(), ); } Future> getCountry(String path) async { return get('/country/$path', decoder: (data) => Country.fromJson(data)); } } ================================================ FILE: example/lib/pages/home/data/home_repository.dart ================================================ import '../domain/adapters/repository_adapter.dart'; import '../domain/entity/country_model.dart'; import 'home_api_provider.dart'; class HomeRepository implements IHomeRepository { HomeRepository({required this.provider}); final IHomeProvider provider; @override Future> getCountries() async { final cases = await provider.getCountries(); if (cases.status.hasError) { return Future.error(cases.statusText!); } else { return cases.body!; } } Future getCountry(String path) async { final country = await provider.getCountry(path); if (country.status.hasError) { return Future.error(country.statusText!); } else { return country.body!; } } } ================================================ FILE: example/lib/pages/home/domain/adapters/repository_adapter.dart ================================================ // ignore: one_member_abstracts import '../entity/country_model.dart'; abstract class IHomeRepository { Future> getCountries(); Future getCountry(String path); } ================================================ FILE: example/lib/pages/home/domain/entity/country_model.dart ================================================ // Models class Country { final String name; final String countryCode; final int numberOfPrizes; final double averageAgeOfLaureates; const Country({ required this.name, required this.countryCode, required this.numberOfPrizes, required this.averageAgeOfLaureates, }); factory Country.fromJson(Map json) { return Country( name: json['Country'], countryCode: json['CountryCode'], numberOfPrizes: json['Number of prizes'], averageAgeOfLaureates: json['Average age of laureates'].toDouble(), ); } } class CountriesItem { final String country; final String countryCode; const CountriesItem({ required this.country, required this.countryCode, }); factory CountriesItem.fromJson(Map json) { return CountriesItem( country: json['Country'], countryCode: json['CountryCode'], ); } } ================================================ FILE: example/lib/pages/home/presentation/controllers/details_controller.dart ================================================ import 'package:get/get.dart'; import '../../domain/adapters/repository_adapter.dart'; import '../../domain/entity/country_model.dart'; class DetailsController extends StateController { DetailsController({required this.homeRepository}); final IHomeRepository homeRepository; late CountriesItem? country; @override void onInit() { super.onInit(); country = Get.arguments; final countryName = country?.country; if (countryName == null) { change(GetStatus.error('Country not found')); } else { //Loading, Success, Error handle with 1 line of code futurize(() => homeRepository.getCountry(countryName)); } } } ================================================ FILE: example/lib/pages/home/presentation/controllers/home_controller.dart ================================================ import 'package:get/get.dart'; import '../../domain/adapters/repository_adapter.dart'; import '../../domain/entity/country_model.dart'; class HomeController extends StateController> { HomeController({required this.homeRepository}); final IHomeRepository homeRepository; @override void onInit() { super.onInit(); futurize(homeRepository.getCountries); } Future getCountryByName(String name) async { final country = await homeRepository.getCountry(name); return country; } } ================================================ FILE: example/lib/pages/home/presentation/views/details_view.dart ================================================ import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:get_demo/pages/home/domain/entity/country_model.dart'; import '../controllers/details_controller.dart'; class DetailsView extends GetView { const DetailsView({Key? key}) : super(key: key); @override Widget build(BuildContext context) { final args = context.arguments as CountriesItem; return controller.obx((country) { return Container( decoration: BoxDecoration( gradient: LinearGradient( colors: [Colors.blueAccent, Colors.lightBlueAccent], begin: Alignment.topLeft, end: Alignment.bottomRight, ), image: DecorationImage( fit: BoxFit.cover, colorFilter: ColorFilter.mode( Colors.black.withValues(alpha: 0.2), BlendMode.darken, ), image: NetworkImage( "https://flagpedia.net/data/flags/normal/${args.countryCode.toLowerCase()}.png", ), ), ), child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 8.0, sigmaY: 8.0), child: Scaffold( backgroundColor: Colors.transparent, appBar: AppBar( title: Text( args.country, style: TextStyle( fontWeight: FontWeight.bold, fontSize: 24, ), ), backgroundColor: Colors.transparent, elevation: 0, centerTitle: true, toolbarHeight: 70, shape: RoundedRectangleBorder( borderRadius: BorderRadius.vertical( bottom: Radius.circular(30), ), ), ), body: SafeArea( child: Center( child: Card( elevation: 12, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(25), ), shadowColor: Colors.blueAccent.withValues(alpha: 0.5), color: Colors.white.withValues(alpha: 0.85), child: Padding( padding: const EdgeInsets.all(32.0), child: Column( mainAxisSize: MainAxisSize.min, children: [ InfoRow( label: 'number_of_prizes'.tr, value: '${country.numberOfPrizes}', ), SizedBox(height: 20), InfoRow( label: 'average_age_of_laureates'.tr, value: '${country.averageAgeOfLaureates}', ), ], ), ), ), ), ), ), ), ); }); } } class InfoRow extends StatelessWidget { const InfoRow({ super.key, required this.label, required this.value, }); final String label; final String value; @override Widget build(BuildContext context) { return Column( children: [ Text( label, style: TextStyle( fontSize: 20, color: Colors.blueGrey[700], fontWeight: FontWeight.w200, ), ), SizedBox(height: 6), Text( value, style: TextStyle( fontSize: 30, fontWeight: FontWeight.w600, color: Colors.blueGrey[900], ), ), ], ); } } ================================================ FILE: example/lib/pages/home/presentation/views/home_view.dart ================================================ import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import '../controllers/home_controller.dart'; class HomeView extends GetView { const HomeView({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Material( child: Container( decoration: const BoxDecoration( image: DecorationImage( fit: BoxFit.cover, image: NetworkImage( "https://upload.wikimedia.org/wikipedia/en/e/ed/Nobel_Prize.png", ), ), ), child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0), child: Scaffold( backgroundColor: Colors.black.withValues(alpha: 0.6), extendBodyBehindAppBar: true, appBar: AppBar( title: Text( 'nobel_by_country'.tr, style: TextStyle( fontSize: 28, fontWeight: FontWeight.w200, color: Colors.white, letterSpacing: 1.2, ), ), backgroundColor: Colors.transparent, elevation: 0, centerTitle: true, leading: IconButton( icon: const Icon(Icons.add, color: Colors.white, size: 28), onPressed: () { Get.snackbar( 'New Feature', 'Coming soon!', snackPosition: SnackPosition.bottom, backgroundColor: Colors.white.withValues(alpha: 0.9), colorText: Colors.black, borderRadius: 10, duration: Duration(seconds: 3), animationDuration: Duration(milliseconds: 500), boxShadows: [ BoxShadow( color: Colors.black.withValues(alpha: 0.2), spreadRadius: 1, blurRadius: 5, offset: Offset(0, 3), ), ], ); }, ), ), body: SafeArea( child: Padding( padding: const EdgeInsets.fromLTRB(16, 32, 16, 16), child: Column( children: [ ElevatedButton( style: ElevatedButton.styleFrom( foregroundColor: Colors.white, backgroundColor: Colors.blueAccent.withValues(alpha: 0.8), padding: EdgeInsets.symmetric(horizontal: 24, vertical: 16), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(30), ), elevation: 8, shadowColor: Colors.blueAccent.withValues(alpha: 0.5), ), onPressed: () { Get.updateLocale(Get.locale?.languageCode == 'en' ? const Locale('pt', 'BR') : const Locale('en', 'EN')); }, child: Text( 'update_language'.tr, style: TextStyle( fontWeight: FontWeight.w200, fontSize: 18, letterSpacing: 1.1, ), ), ), const SizedBox(height: 32), Expanded( child: controller.obx( (state) { return ListView.builder( itemCount: state.length, itemBuilder: (context, index) { final country = state[index]; return Card( elevation: 8, margin: EdgeInsets.symmetric( vertical: 12, horizontal: 4), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), ), color: Colors.white.withValues(alpha: 0.9), child: ListTile( onTap: () async { final data = await Get.toNamed( '/home/details', arguments: country); if (data != null) Get.log(data); }, contentPadding: EdgeInsets.symmetric( horizontal: 20, vertical: 16), leading: Hero( tag: 'flag_${country.countryCode}', child: CircleAvatar( radius: 32, backgroundImage: NetworkImage( "https://flagpedia.net/data/flags/normal/${country.countryCode.toLowerCase()}.png", ), ), ), title: Text( country.country, style: TextStyle( fontWeight: FontWeight.w200, fontSize: 20, color: Colors.black87, ), ), trailing: Icon(Icons.arrow_forward_ios, color: Colors.blueAccent, size: 24), ), ); }, ); }, onLoading: Center( child: CircularProgressIndicator( color: Colors.white, strokeWidth: 3, ), ), onError: (error) => Center( child: Text( 'Error: $error', style: TextStyle( color: Colors.white, fontSize: 18, ), textAlign: TextAlign.center, ), ), ), ), ], ), ), ), ), ), ), ); } } ================================================ FILE: example/lib/routes/app_pages.dart ================================================ import 'package:get/get.dart'; import 'package:get_demo/pages/home/bindings/details_binding.dart'; import '../pages/home/bindings/home_binding.dart'; import '../pages/home/presentation/views/details_view.dart'; import '../pages/home/presentation/views/home_view.dart'; part 'app_routes.dart'; // ignore: avoid_classes_with_only_static_members class AppPages { static const INITIAL = Routes.HOME; static final routes = [ GetPage( name: Routes.HOME, page: () => const HomeView(), binding: HomeBinding(), children: [ GetPage( name: Routes.DETAILS, page: () => const DetailsView(), binding: DetailsBinding()), ], ), ]; } ================================================ FILE: example/lib/routes/app_routes.dart ================================================ part of 'app_pages.dart'; abstract class Routes { static const HOME = '/home'; static const COUNTRY = '/country'; static const DETAILS = '/details'; static const DASHBOARD = '/dashboard'; static const PROFILE = '/profile'; static const PRODUCTS = '/products'; } ================================================ FILE: example/lib/shared/constants/endpoints.dart ================================================ const API_URL = 'https://nobels.jonatasdev.workers.dev'; ================================================ FILE: example/lib/shared/logger/logger_utils.dart ================================================ mixin Logger { // Sample of abstract logging function static void write(String text, {bool isError = false}) { // ignore: avoid_print Future.microtask(() => print('** $text. isError: [$isError]')); } } ================================================ FILE: example/pubspec.yaml ================================================ name: get_demo description: A new Flutter project. # The following line prevents the package from being accidentally published to # pub.dev using `pub publish`. This is preferred for private packages. publish_to: "none" # Remove this line if you wish to publish to pub.dev # The following defines the version and build number for your application. # A version number is three numbers separated by dots, like 1.2.43 # followed by an optional build number separated by a +. # Both the version and the builder number may be overridden in flutter # build by specifying --build-name and --build-number, respectively. # In Android, build-name is used as versionName while build-number used as versionCode. # Read more about Android versioning at https://developer.android.com/studio/publish/versioning # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html version: 1.0.0+1 environment: sdk: ">=3.0.1 <4.0.0" dependency_overrides: get: path: ../ dependencies: flutter: sdk: flutter google_fonts: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. get: path: ../ #get_test: ^3.13.3 dev_dependencies: flutter_test: sdk: flutter get_test: 4.0.1 flutter_lints: ^2.0.1 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec # The following section is specific to Flutter. flutter: # The following line ensures that the Material Icons font is # included with your application, so that you can use the icons in # the material Icons class. uses-material-design: true # To add assets to your application, add an assets section, like this: # assets: # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware. # For details regarding adding assets from package dependencies, see # https://flutter.dev/assets-and-images/#from-packages # To add custom fonts to your application, add a fonts section here, # in this "flutter" section. Each entry in this list should have a # "family" key with the font family name, and a "fonts" key with a # list giving the asset and other descriptors for the font. For # example: # fonts: # - family: Schyler # fonts: # - asset: fonts/Schyler-Regular.ttf # - asset: fonts/Schyler-Italic.ttf # style: italic # - family: Trajan Pro # fonts: # - asset: fonts/TrajanPro.ttf # - asset: fonts/TrajanPro_Bold.ttf # weight: 700 # # For details regarding fonts from package dependencies, # see https://flutter.dev/custom-fonts/#from-packages ================================================ FILE: example/test/main_test.dart ================================================ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:get/get.dart'; import 'package:get/get_navigation/src/routes/test_kit.dart'; import 'package:get_demo/pages/home/domain/adapters/repository_adapter.dart'; import 'package:get_demo/pages/home/domain/entity/country_model.dart'; import 'package:get_demo/pages/home/presentation/controllers/details_controller.dart'; import 'package:get_demo/pages/home/presentation/controllers/home_controller.dart'; // Mock data const country1 = CountriesItem( country: 'Lalaland', countryCode: 'LA', ); const country2 = CountriesItem( country: 'Lololand', countryCode: 'LO', ); // Mock repository for success class MockRepositorySuccess implements IHomeRepository { @override Future> getCountries() async => [country1, country2]; @override Future getCountry(String path) async => Country( name: 'Lalaland', countryCode: 'LA', numberOfPrizes: 3, averageAgeOfLaureates: 4, ); } // Mock repository for failure class MockRepositoryFailure implements IHomeRepository { @override Future> getCountries() async => Future.error(FetchException('Failed to load countries')); @override Future getCountry(String path) async => Future.error(FetchException('Failed to load country')); } class FetchException implements Exception { final String message; FetchException(this.message); } // Custom bindings class TestHomeBinding extends Binding { final IHomeRepository repository; TestHomeBinding({required this.repository}); @override List dependencies() => [ Bind.lazyPut(() => repository), Bind.lazyPut( () => HomeController(homeRepository: Get.find()), ), ]; } class TestDetailsBinding extends Binding { final IHomeRepository repository; TestDetailsBinding({required this.repository}); @override List dependencies() => [ Bind.lazyPut(() => repository), Bind.lazyPut( () => DetailsController(homeRepository: Get.find()), ), ]; } void main() { WidgetsFlutterBinding.ensureInitialized(); setUpAll(() { HttpOverrides.global = null; GetTestMode.active = true; }); setUp(() => Get.reset()); group('HomeController Tests', () { test('Success Scenario', () async { TestHomeBinding(repository: MockRepositorySuccess()).dependencies(); final controller = Get.find(); expect(controller.initialized, isTrue); await Future.delayed(const Duration(milliseconds: 200)); expect(controller.status.isSuccess, isTrue); expect(controller.state.length, 2); expect(controller.state, containsAll([country1, country2])); }); test('Failure Scenario', () async { TestHomeBinding(repository: MockRepositoryFailure()).dependencies(); final controller = Get.find(); expect(controller.initialized, isTrue); await Future.delayed(const Duration(milliseconds: 200)); expect(controller.status.isError, isTrue); expect(controller.status.error, isA()); }); }); group('DetailsController Tests', () { test('Success Scenario', () async { TestDetailsBinding(repository: MockRepositorySuccess()).dependencies(); GetTestMode.setTestArguments(country1); final controller = Get.find(); expect(controller.initialized, isTrue); await Future.delayed(const Duration(milliseconds: 200)); expect(controller.status.isSuccess, isTrue); expect(controller.state.name, 'Lalaland'); expect(controller.state.countryCode, 'LA'); expect(controller.state.numberOfPrizes, 3); expect(controller.state.averageAgeOfLaureates, 4); }); test('Failure Scenario', () async { TestDetailsBinding(repository: MockRepositoryFailure()).dependencies(); GetTestMode.setTestArguments(country1); final controller = Get.find(); expect(controller.initialized, isTrue); await Future.delayed(const Duration(milliseconds: 200)); expect(controller.status.isError, isTrue); expect(controller.status.error, isA()); }); }); } ================================================ FILE: example/test/widget_test.dart ================================================ // This is a basic Flutter widget test. // // To perform an interaction with a widget in your test, use the WidgetTester // utility in the flutter_test package. For example, you can send tap and scroll // gestures. You can also use WidgetTester to find child widgets in the widget // tree, read text, and verify that the values of widget properties are correct. import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:get_demo/main.dart'; void main() { testWidgets('Counter increments smoke test', (WidgetTester tester) async { // Build our app and trigger a frame. await tester.pumpWidget(const MyApp()); // Verify that our counter starts at 0. expect(find.text('0'), findsOneWidget); expect(find.text('1'), findsNothing); // Tap the '+' icon and trigger a frame. await tester.tap(find.byIcon(Icons.add)); await tester.pump(); // Verify that our counter has incremented. expect(find.text('0'), findsNothing); expect(find.text('1'), findsOneWidget); }); } ================================================ FILE: example_nav2/.gitignore ================================================ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ # IntelliJ related *.iml *.ipr *.iws .idea/ # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. #.vscode/ # Flutter/Dart/Pub related **/doc/api/ **/ios/Flutter/.last_build_id .dart_tool/ .flutter-plugins .flutter-plugins-dependencies .packages .pub-cache/ .pub/ /build/ # Web related lib/generated_plugin_registrant.dart # Symbolication related app.*.symbols # Obfuscation related app.*.map.json # Android Studio will place build artifacts here /android/app/debug /android/app/profile /android/app/release ================================================ FILE: example_nav2/.metadata ================================================ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # # This file should be version controlled and should not be manually edited. version: revision: "5f77df9269acb57aea28082203b62b7e16d38c29" channel: "master" project_type: app # Tracks metadata for the flutter migrate command migration: platforms: - platform: root create_revision: 5f77df9269acb57aea28082203b62b7e16d38c29 base_revision: 5f77df9269acb57aea28082203b62b7e16d38c29 - platform: android create_revision: 5f77df9269acb57aea28082203b62b7e16d38c29 base_revision: 5f77df9269acb57aea28082203b62b7e16d38c29 # User provided section # List of Local paths (relative to this file) that should be # ignored by the migrate tool. # # Files that are not part of the templates will be ignored by default. unmanaged_files: - 'lib/main.dart' - 'ios/Runner.xcodeproj/project.pbxproj' ================================================ FILE: example_nav2/README.md ================================================ # example_nav2 A new Flutter project. ## Getting Started This project is a starting point for a Flutter application. A few resources to get you started if this is your first Flutter project: - [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) For help getting started with Flutter, view our [online documentation](https://flutter.dev/docs), which offers tutorials, samples, guidance on mobile development, and a full API reference. ================================================ FILE: example_nav2/analysis_options.yaml ================================================ # This file configures the analyzer, which statically analyzes Dart code to # check for errors, warnings, and lints. # # The issues identified by the analyzer are surfaced in the UI of Dart-enabled # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be # invoked from the command line by running `flutter analyze`. # The following line activates a set of recommended lints for Flutter apps, # packages, and plugins designed to encourage good coding practices. include: package:flutter_lints/flutter.yaml linter: # The lint rules applied to this project can be customized in the # section below to disable rules from the `package:flutter_lints/flutter.yaml` # included above or to enable additional rules. A list of all available lints # and their documentation is published at # https://dart-lang.github.io/linter/lints/index.html. # # Instead of disabling a lint rule for the entire project in the # section below, it can also be suppressed for a single line of code # or a specific dart file by using the `// ignore: name_of_lint` and # `// ignore_for_file: name_of_lint` syntax on the line or in the file # producing the lint. rules: # avoid_print: false # Uncomment to disable the `avoid_print` rule # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options ================================================ FILE: example_nav2/android/.gitignore ================================================ gradle-wrapper.jar /.gradle /captures/ /gradlew /gradlew.bat /local.properties GeneratedPluginRegistrant.java .cxx/ # Remember to never publicly share your keystore. # See https://flutter.dev/to/reference-keystore key.properties **/*.keystore **/*.jks ================================================ FILE: example_nav2/android/app/build.gradle.kts ================================================ plugins { id("com.android.application") id("kotlin-android") // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. id("dev.flutter.flutter-gradle-plugin") } android { namespace = "com.example.example_nav2" compileSdk = flutter.compileSdkVersion ndkVersion = flutter.ndkVersion compileOptions { sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 } kotlinOptions { jvmTarget = JavaVersion.VERSION_11.toString() } defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId = "com.example.example_nav2" // You can update the following values to match your application needs. // For more information, see: https://flutter.dev/to/review-gradle-config. minSdk = flutter.minSdkVersion targetSdk = flutter.targetSdkVersion versionCode = flutter.versionCode versionName = flutter.versionName } buildTypes { release { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. signingConfig = signingConfigs.getByName("debug") } } } flutter { source = "../.." } ================================================ FILE: example_nav2/android/app/src/debug/AndroidManifest.xml ================================================ ================================================ FILE: example_nav2/android/app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: example_nav2/android/app/src/main/kotlin/com/example/example_nav2/MainActivity.kt ================================================ package com.example.example_nav2 import io.flutter.embedding.android.FlutterActivity class MainActivity : FlutterActivity() ================================================ FILE: example_nav2/android/app/src/main/res/drawable/launch_background.xml ================================================ ================================================ FILE: example_nav2/android/app/src/main/res/drawable-v21/launch_background.xml ================================================ ================================================ FILE: example_nav2/android/app/src/main/res/values/styles.xml ================================================ ================================================ FILE: example_nav2/android/app/src/main/res/values-night/styles.xml ================================================ ================================================ FILE: example_nav2/android/app/src/profile/AndroidManifest.xml ================================================ ================================================ FILE: example_nav2/android/build.gradle.kts ================================================ allprojects { repositories { google() mavenCentral() } } val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get() rootProject.layout.buildDirectory.value(newBuildDir) subprojects { val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) project.layout.buildDirectory.value(newSubprojectBuildDir) } subprojects { project.evaluationDependsOn(":app") } tasks.register("clean") { delete(rootProject.layout.buildDirectory) } ================================================ FILE: example_nav2/android/gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip ================================================ FILE: example_nav2/android/gradle.properties ================================================ org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true ================================================ FILE: example_nav2/android/local.properties ================================================ sdk.dir=/Users/jonatasborges/Library/Android/sdk flutter.sdk=/Users/jonatasborges/flutter flutter.buildMode=debug flutter.versionName=1.0.0 flutter.versionCode=1 ================================================ FILE: example_nav2/android/settings.gradle.kts ================================================ pluginManagement { val flutterSdkPath = run { val properties = java.util.Properties() file("local.properties").inputStream().use { properties.load(it) } val flutterSdkPath = properties.getProperty("flutter.sdk") require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } flutterSdkPath } includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") repositories { google() mavenCentral() gradlePluginPortal() } } plugins { id("dev.flutter.flutter-plugin-loader") version "1.0.0" id("com.android.application") version "8.7.0" apply false id("org.jetbrains.kotlin.android") version "1.8.22" apply false } include(":app") ================================================ FILE: example_nav2/ios/.gitignore ================================================ *.mode1v3 *.mode2v3 *.moved-aside *.pbxuser *.perspectivev3 **/*sync/ .sconsign.dblite .tags* **/.vagrant/ **/DerivedData/ Icon? **/Pods/ **/.symlinks/ profile xcuserdata **/.generated/ Flutter/App.framework Flutter/Flutter.framework Flutter/Flutter.podspec Flutter/Generated.xcconfig Flutter/ephemeral/ Flutter/app.flx Flutter/app.zip Flutter/flutter_assets/ Flutter/flutter_export_environment.sh ServiceDefinitions.json Runner/GeneratedPluginRegistrant.* # Exceptions to above rules. !default.mode1v3 !default.mode2v3 !default.pbxuser !default.perspectivev3 ================================================ FILE: example_nav2/ios/Flutter/AppFrameworkInfo.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable App CFBundleIdentifier io.flutter.flutter.app CFBundleInfoDictionaryVersion 6.0 CFBundleName App CFBundlePackageType FMWK CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 MinimumOSVersion 11.0 ================================================ FILE: example_nav2/ios/Flutter/Debug.xcconfig ================================================ #include "Generated.xcconfig" ================================================ FILE: example_nav2/ios/Flutter/Generated.xcconfig ================================================ // This is a generated file; do not edit or check into version control. FLUTTER_ROOT=/Users/jonatasborges/flutter FLUTTER_APPLICATION_PATH=/Users/jonatasborges/newgetx/getx/example_nav2 COCOAPODS_PARALLEL_CODE_SIGN=true FLUTTER_TARGET=lib/main.dart FLUTTER_BUILD_DIR=build FLUTTER_BUILD_NAME=1.0.0 FLUTTER_BUILD_NUMBER=1 EXCLUDED_ARCHS[sdk=iphonesimulator*]=i386 EXCLUDED_ARCHS[sdk=iphoneos*]=armv7 DART_OBFUSCATION=false TRACK_WIDGET_CREATION=true TREE_SHAKE_ICONS=false PACKAGE_CONFIG=.dart_tool/package_config.json ================================================ FILE: example_nav2/ios/Flutter/Release.xcconfig ================================================ #include "Generated.xcconfig" ================================================ FILE: example_nav2/ios/Flutter/flutter_export_environment.sh ================================================ #!/bin/sh # This is a generated file; do not edit or check into version control. export "FLUTTER_ROOT=/Users/jonatasborges/flutter" export "FLUTTER_APPLICATION_PATH=/Users/jonatasborges/newgetx/getx/example_nav2" export "COCOAPODS_PARALLEL_CODE_SIGN=true" export "FLUTTER_TARGET=lib/main.dart" export "FLUTTER_BUILD_DIR=build" export "FLUTTER_BUILD_NAME=1.0.0" export "FLUTTER_BUILD_NUMBER=1" export "DART_OBFUSCATION=false" export "TRACK_WIDGET_CREATION=true" export "TREE_SHAKE_ICONS=false" export "PACKAGE_CONFIG=.dart_tool/package_config.json" ================================================ FILE: example_nav2/ios/Runner/AppDelegate.swift ================================================ import UIKit import Flutter @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } } ================================================ FILE: example_nav2/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@3x.png", "scale" : "3x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@3x.png", "scale" : "3x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@3x.png", "scale" : "3x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@2x.png", "scale" : "2x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@3x.png", "scale" : "3x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@1x.png", "scale" : "1x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@1x.png", "scale" : "1x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@1x.png", "scale" : "1x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@2x.png", "scale" : "2x" }, { "size" : "83.5x83.5", "idiom" : "ipad", "filename" : "Icon-App-83.5x83.5@2x.png", "scale" : "2x" }, { "size" : "1024x1024", "idiom" : "ios-marketing", "filename" : "Icon-App-1024x1024@1x.png", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: example_nav2/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "LaunchImage.png", "scale" : "1x" }, { "idiom" : "universal", "filename" : "LaunchImage@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "LaunchImage@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: example_nav2/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md ================================================ # Launch Screen Assets You can customize the launch screen with your own desired assets by replacing the image files in this directory. 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. ================================================ FILE: example_nav2/ios/Runner/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: example_nav2/ios/Runner/Base.lproj/Main.storyboard ================================================ ================================================ FILE: example_nav2/ios/Runner/GeneratedPluginRegistrant.h ================================================ // // Generated file. Do not edit. // // clang-format off #ifndef GeneratedPluginRegistrant_h #define GeneratedPluginRegistrant_h #import NS_ASSUME_NONNULL_BEGIN @interface GeneratedPluginRegistrant : NSObject + (void)registerWithRegistry:(NSObject*)registry; @end NS_ASSUME_NONNULL_END #endif /* GeneratedPluginRegistrant_h */ ================================================ FILE: example_nav2/ios/Runner/GeneratedPluginRegistrant.m ================================================ // // Generated file. Do not edit. // // clang-format off #import "GeneratedPluginRegistrant.h" @implementation GeneratedPluginRegistrant + (void)registerWithRegistry:(NSObject*)registry { } @end ================================================ FILE: example_nav2/ios/Runner/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName example_nav2 CFBundlePackageType APPL CFBundleShortVersionString $(FLUTTER_BUILD_NAME) CFBundleSignature ???? CFBundleVersion $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIViewControllerBasedStatusBarAppearance CADisableMinimumFrameDurationOnPhone UIApplicationSupportsIndirectInputEvents ================================================ FILE: example_nav2/ios/Runner/Runner-Bridging-Header.h ================================================ #import "GeneratedPluginRegistrant.h" ================================================ FILE: example_nav2/ios/Runner.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 54; objects = { /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 9740EEB31CF90195004384FC /* Generated.xcconfig */, ); name = Flutter; sourceTree = ""; }; 97C146E51CF9000F007C117D = { isa = PBXGroup; children = ( 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, ); sourceTree = ""; }; 97C146EF1CF9000F007C117D /* Products */ = { isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, ); name = Products; sourceTree = ""; }; 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, ); path = Runner; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, ); buildRules = ( ); dependencies = ( ); name = Runner; productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 1430; ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; LastSwiftMigration = 1100; }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 97C146E51CF9000F007C117D; productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Run Script"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 97C146FB1CF9000F007C117D /* Base */, ); name = Main.storyboard; sourceTree = ""; }; 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( 97C147001CF9000F007C117D /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 249021D3217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Profile; }; 249021D4217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.get.exampleNav2; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Profile; }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; }; 97C147061CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.get.exampleNav2; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; }; 97C147071CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.get.exampleNav2; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147031CF9000F007C117D /* Debug */, 97C147041CF9000F007C117D /* Release */, 249021D3217E4FDB00AE95B9 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147061CF9000F007C117D /* Debug */, 97C147071CF9000F007C117D /* Release */, 249021D4217E4FDB00AE95B9 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } ================================================ FILE: example_nav2/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: example_nav2/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: example_nav2/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings ================================================ PreviewsEnabled ================================================ FILE: example_nav2/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme ================================================ ================================================ FILE: example_nav2/ios/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: example_nav2/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: example_nav2/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings ================================================ PreviewsEnabled ================================================ FILE: example_nav2/ios/RunnerTests/RunnerTests.swift ================================================ import Flutter import UIKit import XCTest class RunnerTests: XCTestCase { func testExample() { // If you add code to the Runner application, consider adding tests here. // See https://developer.apple.com/documentation/xctest for more information about using XCTest. } } ================================================ FILE: example_nav2/lib/app/middleware/auth_middleware.dart ================================================ import 'package:get/get.dart'; import '../../services/auth_service.dart'; import '../routes/app_pages.dart'; class EnsureAuthMiddleware extends GetMiddleware { @override Future redirectDelegate(RouteDecoder route) async { // you can do whatever you want here // but it's preferable to make this method fast // await Future.delayed(Duration(milliseconds: 500)); if (!AuthService.to.isLoggedInValue) { final newRoute = Routes.LOGIN_THEN(route.pageSettings!.name); return RouteDecoder.fromRoute(newRoute); } return await super.redirectDelegate(route); } } class EnsureNotAuthedMiddleware extends GetMiddleware { @override Future redirectDelegate(RouteDecoder route) async { if (AuthService.to.isLoggedInValue) { //NEVER navigate to auth screen, when user is already authed return null; //OR redirect user to another screen //return RouteDecoder.fromRoute(Routes.PROFILE); } return await super.redirectDelegate(route); } } ================================================ FILE: example_nav2/lib/app/modules/dashboard/bindings/dashboard_binding.dart ================================================ import 'package:get/get.dart'; import '../controllers/dashboard_controller.dart'; class DashboardBinding extends Binding { @override List dependencies() { return [ Bind.lazyPut( () => DashboardController(), ) ]; } } ================================================ FILE: example_nav2/lib/app/modules/dashboard/controllers/dashboard_controller.dart ================================================ import 'dart:async'; import 'package:get/get.dart'; class DashboardController extends GetxController { final now = DateTime.now().obs; @override void onReady() { super.onReady(); Timer.periodic( const Duration(seconds: 1), (timer) { now.value = DateTime.now(); }, ); } } ================================================ FILE: example_nav2/lib/app/modules/dashboard/views/dashboard_view.dart ================================================ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import '../controllers/dashboard_controller.dart'; class DashboardView extends GetView { const DashboardView({super.key}); @override Widget build(BuildContext context) { return Scaffold( body: Center( child: Obx( () => Column( mainAxisSize: MainAxisSize.min, children: [ const Text( 'DashboardView is working', style: TextStyle(fontSize: 20), ), Text('Time: ${controller.now.value.toString()}'), ], ), ), ), ); } } ================================================ FILE: example_nav2/lib/app/modules/home/bindings/home_binding.dart ================================================ import 'package:get/get.dart'; import '../controllers/home_controller.dart'; class HomeBinding extends Binding { @override List dependencies() { return [ Bind.lazyPut( () => HomeController(), ) ]; } } ================================================ FILE: example_nav2/lib/app/modules/home/controllers/home_controller.dart ================================================ import 'package:get/get.dart'; class HomeController extends GetxController {} ================================================ FILE: example_nav2/lib/app/modules/home/views/home_view.dart ================================================ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import '../../../routes/app_pages.dart'; import '../controllers/home_controller.dart'; class HomeView extends GetView { const HomeView({super.key}); @override Widget build(BuildContext context) { return Column( children: [ Container( color: Colors.yellow, width: double.infinity, height: 25, ), Expanded( child: GetRouterOutlet.builder( route: Routes.home, builder: (context) { return Scaffold( body: GetRouterOutlet( initialRoute: Routes.dashboard, anchorRoute: Routes.home, ), bottomNavigationBar: IndexedRouteBuilder( routes: const [ Routes.dashboard, Routes.profile, Routes.products ], builder: (context, routes, index) { final delegate = context.delegate; return BottomNavigationBar( currentIndex: index, onTap: (value) => delegate.toNamed(routes[value]), items: const [ // _Paths.HOME + [Empty] BottomNavigationBarItem( icon: Icon(Icons.home), label: 'Home', ), // _Paths.HOME + Routes.PROFILE BottomNavigationBarItem( icon: Icon(Icons.account_box_rounded), label: 'Profile', ), // _Paths.HOME + _Paths.PRODUCTS BottomNavigationBarItem( icon: Icon(Icons.account_box_rounded), label: 'Products', ), ], ); }), ); }, ), ), ], ); } } ================================================ FILE: example_nav2/lib/app/modules/login/bindings/login_binding.dart ================================================ import 'package:get/get.dart'; import '../controllers/login_controller.dart'; class LoginBinding extends Binding { @override List dependencies() { return [ Bind.lazyPut(() => LoginController()), ]; } } ================================================ FILE: example_nav2/lib/app/modules/login/controllers/login_controller.dart ================================================ import 'package:get/get.dart'; class LoginController extends GetxController {} ================================================ FILE: example_nav2/lib/app/modules/login/views/login_view.dart ================================================ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import '../../../../services/auth_service.dart'; import '../../../routes/app_pages.dart'; import '../controllers/login_controller.dart'; class LoginView extends GetView { const LoginView({super.key}); @override Widget build(BuildContext context) { return Scaffold( body: Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Obx( () { final isLoggedIn = AuthService.to.isLoggedInValue; return Text( 'You are currently:' ' ${isLoggedIn ? "Logged In" : "Not Logged In"}' "\nIt's impossible to enter this " "route when you are logged in!", ); }, ), MaterialButton( child: const Text( 'Do LOGIN !!', style: TextStyle(color: Colors.blue, fontSize: 20), ), onPressed: () { AuthService.to.login(); final thenTo = context.params['then']; Get.offNamed(thenTo ?? Routes.home); }, ), ], ), ), ); } } ================================================ FILE: example_nav2/lib/app/modules/product_details/bindings/product_details_binding.dart ================================================ import 'package:get/get.dart'; import '../controllers/product_details_controller.dart'; class ProductDetailsBinding extends Binding { @override List dependencies() { return [ Bind.spawn( () => ProductDetailsController( Get.parameters['productId'] ?? '', ), ) ]; } } ================================================ FILE: example_nav2/lib/app/modules/product_details/controllers/product_details_controller.dart ================================================ import 'package:get/get.dart'; class ProductDetailsController extends GetxController { final String productId; ProductDetailsController(this.productId); @override void onInit() { super.onInit(); Get.log('ProductDetailsController created with id: $productId'); } @override void onClose() { Get.log('ProductDetailsController close with id: $productId'); super.onClose(); } } ================================================ FILE: example_nav2/lib/app/modules/product_details/views/product_details_view.dart ================================================ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import '../controllers/product_details_controller.dart'; class ProductDetailsView extends GetWidget { const ProductDetailsView({super.key}); @override Widget build(BuildContext context) { return Scaffold( body: Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ const Text( 'ProductDetailsView is working', style: TextStyle(fontSize: 20), ), Text('ProductId: ${controller.productId}') ], ), ), ); } } ================================================ FILE: example_nav2/lib/app/modules/products/bindings/products_binding.dart ================================================ import 'package:get/get.dart'; import '../controllers/products_controller.dart'; class ProductsBinding extends Binding { @override List dependencies() { return [ Bind.lazyPut( () => ProductsController(), ) ]; } } ================================================ FILE: example_nav2/lib/app/modules/products/controllers/products_controller.dart ================================================ import 'package:get/get.dart'; import '../../../../models/demo_product.dart'; class ProductsController extends GetxController { final products = [].obs; void loadDemoProductsFromSomeWhere() { products.add( DemoProduct( name: 'Product added on: ${DateTime.now().toString()}', id: DateTime.now().millisecondsSinceEpoch.toString(), ), ); } @override void onReady() { super.onReady(); loadDemoProductsFromSomeWhere(); } @override void onClose() { Get.printInfo(info: 'Products: onClose'); super.onClose(); } } ================================================ FILE: example_nav2/lib/app/modules/products/views/products_view.dart ================================================ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import '../../../routes/app_pages.dart'; import '../controllers/products_controller.dart'; class ProductsView extends GetView { const ProductsView({super.key}); @override Widget build(BuildContext context) { return Scaffold( floatingActionButton: FloatingActionButton.extended( onPressed: () => controller.loadDemoProductsFromSomeWhere(), label: const Text('Add'), ), body: Column( children: [ const Hero( tag: 'heroLogo', child: FlutterLogo(), ), Expanded( child: Obx( () => RefreshIndicator( onRefresh: () async { controller.products.clear(); controller.loadDemoProductsFromSomeWhere(); }, child: ListView.builder( itemCount: controller.products.length, itemBuilder: (context, index) { final item = controller.products[index]; return ListTile( onTap: () { Get.toNamed(Routes.PRODUCT_DETAILS(item.id)); }, title: Text(item.name), subtitle: Text(item.id), ); }, ), ), ), ), ], ), ); } } ================================================ FILE: example_nav2/lib/app/modules/profile/bindings/profile_binding.dart ================================================ import 'package:get/get.dart'; import '../controllers/profile_controller.dart'; class ProfileBinding extends Binding { @override List dependencies() { return [ Bind.lazyPut( () => ProfileController(), ) ]; } } ================================================ FILE: example_nav2/lib/app/modules/profile/controllers/profile_controller.dart ================================================ import 'package:get/get.dart'; class ProfileController extends GetxController {} ================================================ FILE: example_nav2/lib/app/modules/profile/views/profile_view.dart ================================================ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import '../../../routes/app_pages.dart'; import '../controllers/profile_controller.dart'; class ProfileView extends GetView { const ProfileView({super.key}); @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.amber, body: Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ const Text( 'ProfileView is working', style: TextStyle(fontSize: 20), ), const Hero( tag: 'heroLogo', child: FlutterLogo(), ), MaterialButton( child: const Text('Show a test dialog'), onPressed: () { //shows a dialog Get.defaultDialog( title: 'Test Dialog !!', barrierDismissible: true, ); }, ), MaterialButton( child: const Text('Show a test dialog in Home router outlet'), onPressed: () { //shows a dialog Get.defaultDialog( title: 'Test Dialog In Home Outlet !!', barrierDismissible: true, id: Routes.home, // navigatorKey: Get.nestedKey(Routes.HOME), ); }, ) ], ), ), ); } } ================================================ FILE: example_nav2/lib/app/modules/root/bindings/root_binding.dart ================================================ import 'package:get/get.dart'; import '../controllers/root_controller.dart'; class RootBinding extends Binding { @override List dependencies() { return [ Bind.lazyPut( () => RootController(), ) ]; } } ================================================ FILE: example_nav2/lib/app/modules/root/controllers/root_controller.dart ================================================ import 'package:get/get.dart'; class RootController extends GetxController { final count = 0.obs; @override void onInit() { super.onInit(); } @override void onClose() {} void increment() => count.value++; } ================================================ FILE: example_nav2/lib/app/modules/root/views/drawer.dart ================================================ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import '../../../../services/auth_service.dart'; import '../../../routes/app_pages.dart'; class DrawerWidget extends StatelessWidget { const DrawerWidget({ super.key, }); @override Widget build(BuildContext context) { return Drawer( child: Column( children: [ Container( height: 100, color: Colors.red, ), ListTile( title: const Text('Home'), onTap: () { Get.toNamed(Routes.home); //to close the drawer Navigator.of(context).pop(); }, ), ListTile( title: const Text('Settings'), onTap: () { Get.toNamed(Routes.settings); //to close the drawer Navigator.of(context).pop(); }, ), if (AuthService.to.isLoggedInValue) ListTile( title: const Text( 'Logout', style: TextStyle( color: Colors.red, ), ), onTap: () { AuthService.to.logout(); Get.toNamed(Routes.login); //to close the drawer Navigator.of(context).pop(); }, ), if (!AuthService.to.isLoggedInValue) ListTile( title: const Text( 'Login', style: TextStyle( color: Colors.blue, ), ), onTap: () { Get.toNamed(Routes.login); //to close the drawer Navigator.of(context).pop(); }, ), ], ), ); } } ================================================ FILE: example_nav2/lib/app/modules/root/views/root_view.dart ================================================ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import '../../../routes/app_pages.dart'; import '../controllers/root_controller.dart'; import 'drawer.dart'; class RootView extends GetView { const RootView({super.key}); @override Widget build(BuildContext context) { return Scaffold( drawer: const DrawerWidget(), appBar: AppBar( title: RouterListener(builder: (context) { final title = context.location; return Text(title); }), centerTitle: true, ), //body: HomeView(), body: GetRouterOutlet( initialRoute: Routes.home, anchorRoute: '/', ), ); } } ================================================ FILE: example_nav2/lib/app/modules/settings/bindings/settings_binding.dart ================================================ import 'package:get/get.dart'; import '../controllers/settings_controller.dart'; class SettingsBinding extends Binding { @override List dependencies() { return [ Bind.lazyPut( () => SettingsController(), ) ]; } } ================================================ FILE: example_nav2/lib/app/modules/settings/controllers/settings_controller.dart ================================================ import 'package:get/get.dart'; class SettingsController extends GetxController { final count = 0.obs; @override void onInit() { super.onInit(); } @override void onClose() {} void increment() => count.value++; } ================================================ FILE: example_nav2/lib/app/modules/settings/views/settings_view.dart ================================================ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import '../controllers/settings_controller.dart'; class SettingsView extends GetView { const SettingsView({super.key}); @override Widget build(BuildContext context) { return const Scaffold( body: Center( child: Text( 'SettingsView is working', style: TextStyle(fontSize: 20), ), ), ); } } ================================================ FILE: example_nav2/lib/app/routes/app_pages.dart ================================================ import 'dart:developer'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import '../middleware/auth_middleware.dart'; import '../modules/dashboard/bindings/dashboard_binding.dart'; import '../modules/dashboard/views/dashboard_view.dart'; import '../modules/home/bindings/home_binding.dart'; import '../modules/home/views/home_view.dart'; import '../modules/login/bindings/login_binding.dart'; import '../modules/login/views/login_view.dart'; import '../modules/product_details/bindings/product_details_binding.dart'; import '../modules/product_details/views/product_details_view.dart'; import '../modules/products/bindings/products_binding.dart'; import '../modules/products/views/products_view.dart'; import '../modules/profile/bindings/profile_binding.dart'; import '../modules/profile/views/profile_view.dart'; import '../modules/root/bindings/root_binding.dart'; import '../modules/root/views/root_view.dart'; import '../modules/settings/bindings/settings_binding.dart'; import '../modules/settings/views/settings_view.dart'; part 'app_routes.dart'; class AppPages { AppPages._(); static const initial = Routes.home; static final routes = [ GetPage( name: '/', page: () => const RootView(), bindings: [RootBinding()], participatesInRootNavigator: true, preventDuplicates: true, children: [ GetPage( middlewares: [ //only enter this route when not authed EnsureNotAuthedMiddleware(), ], name: _Paths.login, page: () => const LoginView(), bindings: [LoginBinding()], ), GetPage( preventDuplicates: true, name: _Paths.home, page: () => const HomeView(), bindings: [ HomeBinding(), ], title: null, children: [ GetPage( name: _Paths.dashboard, page: () => const DashboardView(), bindings: [ DashboardBinding(), ], ), GetPage( middlewares: [ //only enter this route when authed EnsureAuthMiddleware(), ], name: _Paths.profile, page: () => const ProfileView(), title: 'Profile', transition: Transition.size, bindings: [ProfileBinding()], ), GetPage( name: _Paths.products, page: () => const ProductsView(), title: 'Products', transition: Transition.cupertino, showCupertinoParallax: true, participatesInRootNavigator: false, bindings: [ProductsBinding(), ProductDetailsBinding()], children: [ GetPage( name: _Paths.productDetails, transition: Transition.cupertino, showCupertinoParallax: true, page: () => const ProductDetailsView(), bindings: const [], middlewares: [ //only enter this route when authed EnsureAuthMiddleware(), ], ), ], ), ], ), GetPage( name: _Paths.settings, page: () => const SettingsView(), bindings: [ SettingsBinding(), ], ), ], ), ]; } class MainMiddleware extends GetMiddleware { @override void onPageDispose() { log('MainMiddleware onPageDispose'); super.onPageDispose(); } @override Widget onPageBuilt(Widget page) { log('MainMiddleware onPageBuilt'); return super.onPageBuilt(page); } @override GetPage? onPageCalled(GetPage? page) { log('MainMiddleware onPageCalled for route: ${page?.name}'); return super.onPageCalled(page); } @override List? onBindingsStart(List? bindings) { log('MainMiddleware onBindingsStart'); return super.onBindingsStart(bindings); } @override GetPageBuilder? onPageBuildStart(GetPageBuilder? page) { log('MainMiddleware onPageBuildStart'); return super.onPageBuildStart(page); } } ================================================ FILE: example_nav2/lib/app/routes/app_routes.dart ================================================ // ignore_for_file: non_constant_identifier_names part of 'app_pages.dart'; // DO NOT EDIT. This is code generated via package:get_cli/get_cli.dart abstract class Routes { static const home = _Paths.home; static const profile = _Paths.home + _Paths.profile; static const settings = _Paths.settings; static const products = _Paths.home + _Paths.products; static const login = _Paths.login; static const dashboard = _Paths.home + _Paths.dashboard; Routes._(); static String LOGIN_THEN(String afterSuccessfulLogin) => '$login?then=${Uri.encodeQueryComponent(afterSuccessfulLogin)}'; static String PRODUCT_DETAILS(String productId) => '$products/$productId'; } abstract class _Paths { static const home = '/home'; static const products = '/products'; static const profile = '/profile'; static const settings = '/settings'; static const productDetails = '/:productId'; static const login = '/login'; static const dashboard = '/dashboard'; } ================================================ FILE: example_nav2/lib/main.dart ================================================ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import './services/auth_service.dart'; import 'app/routes/app_pages.dart'; void main() { runApp( GetMaterialApp( title: "Application", binds: [ Bind.put(AuthService()), ], getPages: AppPages.routes, initialRoute: AppPages.initial, // builder: (context, child) { // return FutureBuilder( // key: ValueKey('initFuture'), // future: Get.find().init(), // builder: (context, snapshot) { // if (snapshot.connectionState == ConnectionState.done) { // return child ?? SizedBox.shrink(); // } // return SplashView(); // }, // ); // }, // routeInformationParser: GetInformationParser( // // initialRoute: Routes.HOME, // ), // routerDelegate: GetDelegate( // backButtonPopMode: PopMode.History, // preventDuplicateHandlingMode: // PreventDuplicateHandlingMode.ReorderRoutes, // ), ), ); } ================================================ FILE: example_nav2/lib/models/demo_product.dart ================================================ class DemoProduct { final String name; final String id; DemoProduct({ required this.name, required this.id, }); } ================================================ FILE: example_nav2/lib/services/auth_service.dart ================================================ import 'package:get/get.dart'; class AuthService extends GetxService { static AuthService get to => Get.find(); /// Mocks a login process final isLoggedIn = false.obs; bool get isLoggedInValue => isLoggedIn.value; void login() { isLoggedIn.value = true; } void logout() { isLoggedIn.value = false; } } ================================================ FILE: example_nav2/linux/.gitignore ================================================ flutter/ephemeral ================================================ FILE: example_nav2/linux/CMakeLists.txt ================================================ # Project-level configuration. cmake_minimum_required(VERSION 3.10) project(runner LANGUAGES CXX) # The name of the executable created for the application. Change this to change # the on-disk name of your application. set(BINARY_NAME "example_nav2") # The unique GTK application identifier for this application. See: # https://wiki.gnome.org/HowDoI/ChooseApplicationID set(APPLICATION_ID "com.get.example_nav2") # Explicitly opt in to modern CMake behaviors to avoid warnings with recent # versions of CMake. cmake_policy(SET CMP0063 NEW) # Load bundled libraries from the lib/ directory relative to the binary. set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") # Root filesystem for cross-building. if(FLUTTER_TARGET_PLATFORM_SYSROOT) set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) endif() # Define build configuration options. if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Flutter build mode" FORCE) set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Profile" "Release") endif() # Compilation settings that should be applied to most targets. # # Be cautious about adding new options here, as plugins use this function by # default. In most cases, you should add new options to specific targets instead # of modifying this function. function(APPLY_STANDARD_SETTINGS TARGET) target_compile_features(${TARGET} PUBLIC cxx_std_14) target_compile_options(${TARGET} PRIVATE -Wall -Werror) target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") endfunction() # Flutter library and tool build rules. set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") add_subdirectory(${FLUTTER_MANAGED_DIR}) # System-level dependencies. find_package(PkgConfig REQUIRED) pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") # Define the application target. To change its name, change BINARY_NAME above, # not the value here, or `flutter run` will no longer work. # # Any new source files that you add to the application should be added here. add_executable(${BINARY_NAME} "main.cc" "my_application.cc" "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" ) # Apply the standard set of build settings. This can be removed for applications # that need different build settings. apply_standard_settings(${BINARY_NAME}) # Add dependency libraries. Add any application-specific dependencies here. target_link_libraries(${BINARY_NAME} PRIVATE flutter) target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) # Run the Flutter tool portions of the build. This must not be removed. add_dependencies(${BINARY_NAME} flutter_assemble) # Only the install-generated bundle's copy of the executable will launch # correctly, since the resources must in the right relative locations. To avoid # people trying to run the unbundled copy, put it in a subdirectory instead of # the default top-level location. set_target_properties(${BINARY_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" ) # Generated plugin build rules, which manage building the plugins and adding # them to the application. include(flutter/generated_plugins.cmake) # === Installation === # By default, "installing" just makes a relocatable bundle in the build # directory. set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) endif() # Start with a clean build bundle directory every time. install(CODE " file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") " COMPONENT Runtime) set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" COMPONENT Runtime) install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) install(FILES "${bundled_library}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) endforeach(bundled_library) # Fully re-copy the assets directory on each build to avoid having stale files # from a previous install. set(FLUTTER_ASSET_DIR_NAME "flutter_assets") install(CODE " file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") " COMPONENT Runtime) install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) # Install the AOT library on non-Debug builds only. if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) endif() ================================================ FILE: example_nav2/linux/flutter/CMakeLists.txt ================================================ # This file controls Flutter-level build steps. It should not be edited. cmake_minimum_required(VERSION 3.10) set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") # Configuration provided via flutter tool. include(${EPHEMERAL_DIR}/generated_config.cmake) # TODO: Move the rest of this into files in ephemeral. See # https://github.com/flutter/flutter/issues/57146. # Serves the same purpose as list(TRANSFORM ... PREPEND ...), # which isn't available in 3.10. function(list_prepend LIST_NAME PREFIX) set(NEW_LIST "") foreach(element ${${LIST_NAME}}) list(APPEND NEW_LIST "${PREFIX}${element}") endforeach(element) set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) endfunction() # === Flutter Library === # System-level dependencies. find_package(PkgConfig REQUIRED) pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") # Published to parent scope for install step. set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) list(APPEND FLUTTER_LIBRARY_HEADERS "fl_basic_message_channel.h" "fl_binary_codec.h" "fl_binary_messenger.h" "fl_dart_project.h" "fl_engine.h" "fl_json_message_codec.h" "fl_json_method_codec.h" "fl_message_codec.h" "fl_method_call.h" "fl_method_channel.h" "fl_method_codec.h" "fl_method_response.h" "fl_plugin_registrar.h" "fl_plugin_registry.h" "fl_standard_message_codec.h" "fl_standard_method_codec.h" "fl_string_codec.h" "fl_value.h" "fl_view.h" "flutter_linux.h" ) list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") add_library(flutter INTERFACE) target_include_directories(flutter INTERFACE "${EPHEMERAL_DIR}" ) target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") target_link_libraries(flutter INTERFACE PkgConfig::GTK PkgConfig::GLIB PkgConfig::GIO ) add_dependencies(flutter flutter_assemble) # === Flutter tool backend === # _phony_ is a non-existent file to force this command to run every time, # since currently there's no way to get a full input/output list from the # flutter tool. add_custom_command( OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} ${CMAKE_CURRENT_BINARY_DIR}/_phony_ COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} VERBATIM ) add_custom_target(flutter_assemble DEPENDS "${FLUTTER_LIBRARY}" ${FLUTTER_LIBRARY_HEADERS} ) ================================================ FILE: example_nav2/linux/flutter/generated_plugin_registrant.cc ================================================ // // Generated file. Do not edit. // // clang-format off #include "generated_plugin_registrant.h" void fl_register_plugins(FlPluginRegistry* registry) { } ================================================ FILE: example_nav2/linux/flutter/generated_plugin_registrant.h ================================================ // // Generated file. Do not edit. // // clang-format off #ifndef GENERATED_PLUGIN_REGISTRANT_ #define GENERATED_PLUGIN_REGISTRANT_ #include // Registers Flutter plugins. void fl_register_plugins(FlPluginRegistry* registry); #endif // GENERATED_PLUGIN_REGISTRANT_ ================================================ FILE: example_nav2/linux/flutter/generated_plugins.cmake ================================================ # # Generated file, do not edit. # list(APPEND FLUTTER_PLUGIN_LIST ) list(APPEND FLUTTER_FFI_PLUGIN_LIST ) set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) endforeach(ffi_plugin) ================================================ FILE: example_nav2/linux/main.cc ================================================ #include "my_application.h" int main(int argc, char** argv) { g_autoptr(MyApplication) app = my_application_new(); return g_application_run(G_APPLICATION(app), argc, argv); } ================================================ FILE: example_nav2/linux/my_application.cc ================================================ #include "my_application.h" #include #ifdef GDK_WINDOWING_X11 #include #endif #include "flutter/generated_plugin_registrant.h" struct _MyApplication { GtkApplication parent_instance; char** dart_entrypoint_arguments; }; G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) // Implements GApplication::activate. static void my_application_activate(GApplication* application) { MyApplication* self = MY_APPLICATION(application); GtkWindow* window = GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); // Use a header bar when running in GNOME as this is the common style used // by applications and is the setup most users will be using (e.g. Ubuntu // desktop). // If running on X and not using GNOME then just use a traditional title bar // in case the window manager does more exotic layout, e.g. tiling. // If running on Wayland assume the header bar will work (may need changing // if future cases occur). gboolean use_header_bar = TRUE; #ifdef GDK_WINDOWING_X11 GdkScreen* screen = gtk_window_get_screen(window); if (GDK_IS_X11_SCREEN(screen)) { const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); if (g_strcmp0(wm_name, "GNOME Shell") != 0) { use_header_bar = FALSE; } } #endif if (use_header_bar) { GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); gtk_widget_show(GTK_WIDGET(header_bar)); gtk_header_bar_set_title(header_bar, "example_nav2"); gtk_header_bar_set_show_close_button(header_bar, TRUE); gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); } else { gtk_window_set_title(window, "example_nav2"); } gtk_window_set_default_size(window, 1280, 720); gtk_widget_show(GTK_WIDGET(window)); g_autoptr(FlDartProject) project = fl_dart_project_new(); fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); FlView* view = fl_view_new(project); gtk_widget_show(GTK_WIDGET(view)); gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); fl_register_plugins(FL_PLUGIN_REGISTRY(view)); gtk_widget_grab_focus(GTK_WIDGET(view)); } // Implements GApplication::local_command_line. static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { MyApplication* self = MY_APPLICATION(application); // Strip out the first argument as it is the binary name. self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); g_autoptr(GError) error = nullptr; if (!g_application_register(application, nullptr, &error)) { g_warning("Failed to register: %s", error->message); *exit_status = 1; return TRUE; } g_application_activate(application); *exit_status = 0; return TRUE; } // Implements GObject::dispose. static void my_application_dispose(GObject* object) { MyApplication* self = MY_APPLICATION(object); g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); G_OBJECT_CLASS(my_application_parent_class)->dispose(object); } static void my_application_class_init(MyApplicationClass* klass) { G_APPLICATION_CLASS(klass)->activate = my_application_activate; G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; G_OBJECT_CLASS(klass)->dispose = my_application_dispose; } static void my_application_init(MyApplication* self) {} MyApplication* my_application_new() { return MY_APPLICATION(g_object_new(my_application_get_type(), "application-id", APPLICATION_ID, "flags", G_APPLICATION_NON_UNIQUE, nullptr)); } ================================================ FILE: example_nav2/linux/my_application.h ================================================ #ifndef FLUTTER_MY_APPLICATION_H_ #define FLUTTER_MY_APPLICATION_H_ #include G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, GtkApplication) /** * my_application_new: * * Creates a new Flutter-based application. * * Returns: a new #MyApplication. */ MyApplication* my_application_new(); #endif // FLUTTER_MY_APPLICATION_H_ ================================================ FILE: example_nav2/linux/runner/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.13) project(runner LANGUAGES CXX) # Define the application target. To change its name, change BINARY_NAME in the # top-level CMakeLists.txt, not the value here, or `flutter run` will no longer # work. # # Any new source files that you add to the application should be added here. add_executable(${BINARY_NAME} "main.cc" "my_application.cc" "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" ) # Apply the standard set of build settings. This can be removed for applications # that need different build settings. apply_standard_settings(${BINARY_NAME}) # Add preprocessor definitions for the application ID. add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") # Add dependency libraries. Add any application-specific dependencies here. target_link_libraries(${BINARY_NAME} PRIVATE flutter) target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") ================================================ FILE: example_nav2/linux/runner/main.cc ================================================ #include "my_application.h" int main(int argc, char** argv) { g_autoptr(MyApplication) app = my_application_new(); return g_application_run(G_APPLICATION(app), argc, argv); } ================================================ FILE: example_nav2/linux/runner/my_application.cc ================================================ #include "my_application.h" #include #ifdef GDK_WINDOWING_X11 #include #endif #include "flutter/generated_plugin_registrant.h" struct _MyApplication { GtkApplication parent_instance; char** dart_entrypoint_arguments; }; G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) // Implements GApplication::activate. static void my_application_activate(GApplication* application) { MyApplication* self = MY_APPLICATION(application); GtkWindow* window = GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); // Use a header bar when running in GNOME as this is the common style used // by applications and is the setup most users will be using (e.g. Ubuntu // desktop). // If running on X and not using GNOME then just use a traditional title bar // in case the window manager does more exotic layout, e.g. tiling. // If running on Wayland assume the header bar will work (may need changing // if future cases occur). gboolean use_header_bar = TRUE; #ifdef GDK_WINDOWING_X11 GdkScreen* screen = gtk_window_get_screen(window); if (GDK_IS_X11_SCREEN(screen)) { const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); if (g_strcmp0(wm_name, "GNOME Shell") != 0) { use_header_bar = FALSE; } } #endif if (use_header_bar) { GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); gtk_widget_show(GTK_WIDGET(header_bar)); gtk_header_bar_set_title(header_bar, "example_nav2"); gtk_header_bar_set_show_close_button(header_bar, TRUE); gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); } else { gtk_window_set_title(window, "example_nav2"); } gtk_window_set_default_size(window, 1280, 720); gtk_widget_show(GTK_WIDGET(window)); g_autoptr(FlDartProject) project = fl_dart_project_new(); fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); FlView* view = fl_view_new(project); gtk_widget_show(GTK_WIDGET(view)); gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); fl_register_plugins(FL_PLUGIN_REGISTRY(view)); gtk_widget_grab_focus(GTK_WIDGET(view)); } // Implements GApplication::local_command_line. static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { MyApplication* self = MY_APPLICATION(application); // Strip out the first argument as it is the binary name. self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); g_autoptr(GError) error = nullptr; if (!g_application_register(application, nullptr, &error)) { g_warning("Failed to register: %s", error->message); *exit_status = 1; return TRUE; } g_application_activate(application); *exit_status = 0; return TRUE; } // Implements GApplication::startup. static void my_application_startup(GApplication* application) { //MyApplication* self = MY_APPLICATION(object); // Perform any actions required at application startup. G_APPLICATION_CLASS(my_application_parent_class)->startup(application); } // Implements GApplication::shutdown. static void my_application_shutdown(GApplication* application) { //MyApplication* self = MY_APPLICATION(object); // Perform any actions required at application shutdown. G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); } // Implements GObject::dispose. static void my_application_dispose(GObject* object) { MyApplication* self = MY_APPLICATION(object); g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); G_OBJECT_CLASS(my_application_parent_class)->dispose(object); } static void my_application_class_init(MyApplicationClass* klass) { G_APPLICATION_CLASS(klass)->activate = my_application_activate; G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; G_APPLICATION_CLASS(klass)->startup = my_application_startup; G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; G_OBJECT_CLASS(klass)->dispose = my_application_dispose; } static void my_application_init(MyApplication* self) {} MyApplication* my_application_new() { // Set the program name to the application ID, which helps various systems // like GTK and desktop environments map this running application to its // corresponding .desktop file. This ensures better integration by allowing // the application to be recognized beyond its binary name. g_set_prgname(APPLICATION_ID); return MY_APPLICATION(g_object_new(my_application_get_type(), "application-id", APPLICATION_ID, "flags", G_APPLICATION_NON_UNIQUE, nullptr)); } ================================================ FILE: example_nav2/linux/runner/my_application.h ================================================ #ifndef FLUTTER_MY_APPLICATION_H_ #define FLUTTER_MY_APPLICATION_H_ #include G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, GtkApplication) /** * my_application_new: * * Creates a new Flutter-based application. * * Returns: a new #MyApplication. */ MyApplication* my_application_new(); #endif // FLUTTER_MY_APPLICATION_H_ ================================================ FILE: example_nav2/macos/.gitignore ================================================ # Flutter-related **/Flutter/ephemeral/ **/Pods/ # Xcode-related **/dgph **/xcuserdata/ ================================================ FILE: example_nav2/macos/Flutter/Flutter-Debug.xcconfig ================================================ #include "ephemeral/Flutter-Generated.xcconfig" ================================================ FILE: example_nav2/macos/Flutter/Flutter-Release.xcconfig ================================================ #include "ephemeral/Flutter-Generated.xcconfig" ================================================ FILE: example_nav2/macos/Flutter/GeneratedPluginRegistrant.swift ================================================ // // Generated file. Do not edit. // import FlutterMacOS import Foundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { } ================================================ FILE: example_nav2/macos/Runner/AppDelegate.swift ================================================ import Cocoa import FlutterMacOS @main class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } } ================================================ FILE: example_nav2/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "size" : "16x16", "idiom" : "mac", "filename" : "app_icon_16.png", "scale" : "1x" }, { "size" : "16x16", "idiom" : "mac", "filename" : "app_icon_32.png", "scale" : "2x" }, { "size" : "32x32", "idiom" : "mac", "filename" : "app_icon_32.png", "scale" : "1x" }, { "size" : "32x32", "idiom" : "mac", "filename" : "app_icon_64.png", "scale" : "2x" }, { "size" : "128x128", "idiom" : "mac", "filename" : "app_icon_128.png", "scale" : "1x" }, { "size" : "128x128", "idiom" : "mac", "filename" : "app_icon_256.png", "scale" : "2x" }, { "size" : "256x256", "idiom" : "mac", "filename" : "app_icon_256.png", "scale" : "1x" }, { "size" : "256x256", "idiom" : "mac", "filename" : "app_icon_512.png", "scale" : "2x" }, { "size" : "512x512", "idiom" : "mac", "filename" : "app_icon_512.png", "scale" : "1x" }, { "size" : "512x512", "idiom" : "mac", "filename" : "app_icon_1024.png", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: example_nav2/macos/Runner/Base.lproj/MainMenu.xib ================================================ ================================================ FILE: example_nav2/macos/Runner/Configs/AppInfo.xcconfig ================================================ // Application-level settings for the Runner target. // // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the // future. If not, the values below would default to using the project name when this becomes a // 'flutter create' template. // The application's name. By default this is also the title of the Flutter window. PRODUCT_NAME = example_nav2 // The application's bundle identifier PRODUCT_BUNDLE_IDENTIFIER = com.get.exampleNav2 // The copyright displayed in application information PRODUCT_COPYRIGHT = Copyright © 2023 com.get. All rights reserved. ================================================ FILE: example_nav2/macos/Runner/Configs/Debug.xcconfig ================================================ #include "../../Flutter/Flutter-Debug.xcconfig" #include "Warnings.xcconfig" ================================================ FILE: example_nav2/macos/Runner/Configs/Release.xcconfig ================================================ #include "../../Flutter/Flutter-Release.xcconfig" #include "Warnings.xcconfig" ================================================ FILE: example_nav2/macos/Runner/Configs/Warnings.xcconfig ================================================ WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings GCC_WARN_UNDECLARED_SELECTOR = YES CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE CLANG_WARN__DUPLICATE_METHOD_MATCH = YES CLANG_WARN_PRAGMA_PACK = YES CLANG_WARN_STRICT_PROTOTYPES = YES CLANG_WARN_COMMA = YES GCC_WARN_STRICT_SELECTOR_MATCH = YES CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES GCC_WARN_SHADOW = YES CLANG_WARN_UNREACHABLE_CODE = YES ================================================ FILE: example_nav2/macos/Runner/DebugProfile.entitlements ================================================ com.apple.security.app-sandbox com.apple.security.cs.allow-jit com.apple.security.network.server ================================================ FILE: example_nav2/macos/Runner/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIconFile CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString $(FLUTTER_BUILD_NAME) CFBundleVersion $(FLUTTER_BUILD_NUMBER) LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) NSHumanReadableCopyright $(PRODUCT_COPYRIGHT) NSMainNibFile MainMenu NSPrincipalClass NSApplication ================================================ FILE: example_nav2/macos/Runner/MainFlutterWindow.swift ================================================ import Cocoa import FlutterMacOS class MainFlutterWindow: NSWindow { override func awakeFromNib() { let flutterViewController = FlutterViewController.init() let windowFrame = self.frame self.contentViewController = flutterViewController self.setFrame(windowFrame, display: true) RegisterGeneratedPlugins(registry: flutterViewController) super.awakeFromNib() } } ================================================ FILE: example_nav2/macos/Runner/Release.entitlements ================================================ com.apple.security.app-sandbox ================================================ FILE: example_nav2/macos/Runner.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 54; objects = { /* Begin PBXAggregateTarget section */ 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { isa = PBXAggregateTarget; buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; buildPhases = ( 33CC111E2044C6BF0003C045 /* ShellScript */, ); dependencies = ( ); name = "Flutter Assemble"; productName = FLX; }; /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 33CC10E52044A3C60003C045 /* Project object */; proxyType = 1; remoteGlobalIDString = 33CC111A2044C6BA0003C045; remoteInfo = FLX; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 33CC110E2044A8840003C045 /* Bundle Framework */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( ); name = "Bundle Framework"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; 33CC10ED2044A3C60003C045 /* example_nav2.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "example_nav2.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 33CC10EA2044A3C60003C045 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 33BA886A226E78AF003329D5 /* Configs */ = { isa = PBXGroup; children = ( 33E5194F232828860026EE4D /* AppInfo.xcconfig */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, ); path = Configs; sourceTree = ""; }; 33CC10E42044A3C60003C045 = { isa = PBXGroup; children = ( 33FAB671232836740065AC1E /* Runner */, 33CEB47122A05771004F2AC0 /* Flutter */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, ); sourceTree = ""; }; 33CC10EE2044A3C60003C045 /* Products */ = { isa = PBXGroup; children = ( 33CC10ED2044A3C60003C045 /* example_nav2.app */, ); name = Products; sourceTree = ""; }; 33CC11242044D66E0003C045 /* Resources */ = { isa = PBXGroup; children = ( 33CC10F22044A3C60003C045 /* Assets.xcassets */, 33CC10F42044A3C60003C045 /* MainMenu.xib */, 33CC10F72044A3C60003C045 /* Info.plist */, ); name = Resources; path = ..; sourceTree = ""; }; 33CEB47122A05771004F2AC0 /* Flutter */ = { isa = PBXGroup; children = ( 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, ); path = Flutter; sourceTree = ""; }; 33FAB671232836740065AC1E /* Runner */ = { isa = PBXGroup; children = ( 33CC10F02044A3C60003C045 /* AppDelegate.swift */, 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, 33E51913231747F40026EE4D /* DebugProfile.entitlements */, 33E51914231749380026EE4D /* Release.entitlements */, 33CC11242044D66E0003C045 /* Resources */, 33BA886A226E78AF003329D5 /* Configs */, ); path = Runner; sourceTree = ""; }; D73912EC22F37F3D000D13A0 /* Frameworks */ = { isa = PBXGroup; children = ( ); name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 33CC10EC2044A3C60003C045 /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( 33CC10E92044A3C60003C045 /* Sources */, 33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, ); buildRules = ( ); dependencies = ( 33CC11202044C79F0003C045 /* PBXTargetDependency */, ); name = Runner; productName = Runner; productReference = 33CC10ED2044A3C60003C045 /* example_nav2.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 33CC10E52044A3C60003C045 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 33CC10EC2044A3C60003C045 = { CreatedOnToolsVersion = 9.2; LastSwiftMigration = 1100; ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.Sandbox = { enabled = 1; }; }; }; 33CC111A2044C6BA0003C045 = { CreatedOnToolsVersion = 9.2; ProvisioningStyle = Manual; }; }; }; buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 33CC10E42044A3C60003C045; productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 33CC10EC2044A3C60003C045 /* Runner */, 33CC111A2044C6BA0003C045 /* Flutter Assemble */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 33CC10EB2044A3C60003C045 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 3399D490228B24CF009A79C7 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( ); outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; }; 33CC111E2044C6BF0003C045 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( Flutter/ephemeral/FlutterInputs.xcfilelist, ); inputPaths = ( Flutter/ephemeral/tripwire, ); outputFileListPaths = ( Flutter/ephemeral/FlutterOutputs.xcfilelist, ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 33CC10E92044A3C60003C045 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { isa = PBXVariantGroup; children = ( 33CC10F52044A3C60003C045 /* Base */, ); name = MainMenu.xib; path = Runner; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 338D0CE9231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; }; name = Profile; }; 338D0CEA231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; }; name = Profile; }; 338D0CEB231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Manual; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Profile; }; 33CC10F92044A3C60003C045 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 33CC10FA2044A3C60003C045 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; }; name = Release; }; 33CC10FC2044A3C60003C045 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; }; name = Debug; }; 33CC10FD2044A3C60003C045 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; }; name = Release; }; 33CC111C2044C6BA0003C045 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Manual; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; 33CC111D2044C6BA0003C045 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 33CC10F92044A3C60003C045 /* Debug */, 33CC10FA2044A3C60003C045 /* Release */, 338D0CE9231458BD00FA5F75 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 33CC10FC2044A3C60003C045 /* Debug */, 33CC10FD2044A3C60003C045 /* Release */, 338D0CEA231458BD00FA5F75 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { isa = XCConfigurationList; buildConfigurations = ( 33CC111C2044C6BA0003C045 /* Debug */, 33CC111D2044C6BA0003C045 /* Release */, 338D0CEB231458BD00FA5F75 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 33CC10E52044A3C60003C045 /* Project object */; } ================================================ FILE: example_nav2/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: example_nav2/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme ================================================ ================================================ FILE: example_nav2/macos/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: example_nav2/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: example_nav2/macos/RunnerTests/RunnerTests.swift ================================================ import Cocoa import FlutterMacOS import XCTest class RunnerTests: XCTestCase { func testExample() { // If you add code to the Runner application, consider adding tests here. // See https://developer.apple.com/documentation/xctest for more information about using XCTest. } } ================================================ FILE: example_nav2/pubspec.yaml ================================================ name: example_nav2 version: 1.0.0+1 publish_to: none description: A new Flutter project. environment: sdk: ">=3.0.0 <4.0.0" dependencies: cupertino_icons: ^1.0.2 # get: ^4.1.4 get: path: ../ flutter: sdk: flutter flutter_lints: ^5.0.0 dev_dependencies: flutter_test: sdk: flutter flutter: uses-material-design: true ================================================ FILE: example_nav2/test/widget_test.dart ================================================ // This is a basic Flutter widget test. // // To perform an interaction with a widget in your test, use the WidgetTester // utility in the flutter_test package. For example, you can send tap and scroll // gestures. You can also use WidgetTester to find child widgets in the widget // tree, read text, and verify that the values of widget properties are correct. import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { testWidgets('Counter increments smoke test', (WidgetTester tester) async { // Build our app and trigger a frame. // await tester.pumpWidget(const MyApp()); // Verify that our counter starts at 0. expect(find.text('0'), findsOneWidget); expect(find.text('1'), findsNothing); // Tap the '+' icon and trigger a frame. await tester.tap(find.byIcon(Icons.add)); await tester.pump(); // Verify that our counter has incremented. expect(find.text('0'), findsNothing); expect(find.text('1'), findsOneWidget); }); } ================================================ FILE: example_nav2/web/index.html ================================================ example_nav2 ================================================ FILE: example_nav2/web/manifest.json ================================================ { "name": "example_nav2", "short_name": "example_nav2", "start_url": ".", "display": "standalone", "background_color": "#0175C2", "theme_color": "#0175C2", "description": "A new Flutter project.", "orientation": "portrait-primary", "prefer_related_applications": false, "icons": [ { "src": "icons/Icon-192.png", "sizes": "192x192", "type": "image/png" }, { "src": "icons/Icon-512.png", "sizes": "512x512", "type": "image/png" }, { "src": "icons/Icon-maskable-192.png", "sizes": "192x192", "type": "image/png", "purpose": "maskable" }, { "src": "icons/Icon-maskable-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" } ] } ================================================ FILE: example_nav2/windows/.gitignore ================================================ flutter/ephemeral/ # Visual Studio user-specific files. *.suo *.user *.userosscache *.sln.docstates # Visual Studio build-related files. x64/ x86/ # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ ================================================ FILE: example_nav2/windows/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.15) project(example_nav2 LANGUAGES CXX) set(BINARY_NAME "example_nav2") cmake_policy(SET CMP0063 NEW) set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") # Configure build options. get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) if(IS_MULTICONFIG) set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" CACHE STRING "" FORCE) else() if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Flutter build mode" FORCE) set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Profile" "Release") endif() endif() set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") # Use Unicode for all projects. add_definitions(-DUNICODE -D_UNICODE) # Compilation settings that should be applied to most targets. function(APPLY_STANDARD_SETTINGS TARGET) target_compile_features(${TARGET} PUBLIC cxx_std_17) target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") target_compile_options(${TARGET} PRIVATE /EHsc) target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") endfunction() set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") # Flutter library and tool build rules. add_subdirectory(${FLUTTER_MANAGED_DIR}) # Application build add_subdirectory("runner") # Generated plugin build rules, which manage building the plugins and adding # them to the application. include(flutter/generated_plugins.cmake) # === Installation === # Support files are copied into place next to the executable, so that it can # run in place. This is done instead of making a separate bundle (as on Linux) # so that building and running from within Visual Studio will work. set(BUILD_BUNDLE_DIR "$") # Make the "install" step default, as it's required to run. set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) endif() set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" COMPONENT Runtime) install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) if(PLUGIN_BUNDLED_LIBRARIES) install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) endif() # Fully re-copy the assets directory on each build to avoid having stale files # from a previous install. set(FLUTTER_ASSET_DIR_NAME "flutter_assets") install(CODE " file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") " COMPONENT Runtime) install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) # Install the AOT library on non-Debug builds only. install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" CONFIGURATIONS Profile;Release COMPONENT Runtime) ================================================ FILE: example_nav2/windows/flutter/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.15) set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") # Configuration provided via flutter tool. include(${EPHEMERAL_DIR}/generated_config.cmake) # TODO: Move the rest of this into files in ephemeral. See # https://github.com/flutter/flutter/issues/57146. set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") # === Flutter Library === set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") # Published to parent scope for install step. set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) list(APPEND FLUTTER_LIBRARY_HEADERS "flutter_export.h" "flutter_windows.h" "flutter_messenger.h" "flutter_plugin_registrar.h" "flutter_texture_registrar.h" ) list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") add_library(flutter INTERFACE) target_include_directories(flutter INTERFACE "${EPHEMERAL_DIR}" ) target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") add_dependencies(flutter flutter_assemble) # === Wrapper === list(APPEND CPP_WRAPPER_SOURCES_CORE "core_implementations.cc" "standard_codec.cc" ) list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") list(APPEND CPP_WRAPPER_SOURCES_PLUGIN "plugin_registrar.cc" ) list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") list(APPEND CPP_WRAPPER_SOURCES_APP "flutter_engine.cc" "flutter_view_controller.cc" ) list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") # Wrapper sources needed for a plugin. add_library(flutter_wrapper_plugin STATIC ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} ) apply_standard_settings(flutter_wrapper_plugin) set_target_properties(flutter_wrapper_plugin PROPERTIES POSITION_INDEPENDENT_CODE ON) set_target_properties(flutter_wrapper_plugin PROPERTIES CXX_VISIBILITY_PRESET hidden) target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) target_include_directories(flutter_wrapper_plugin PUBLIC "${WRAPPER_ROOT}/include" ) add_dependencies(flutter_wrapper_plugin flutter_assemble) # Wrapper sources needed for the runner. add_library(flutter_wrapper_app STATIC ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_APP} ) apply_standard_settings(flutter_wrapper_app) target_link_libraries(flutter_wrapper_app PUBLIC flutter) target_include_directories(flutter_wrapper_app PUBLIC "${WRAPPER_ROOT}/include" ) add_dependencies(flutter_wrapper_app flutter_assemble) # === Flutter tool backend === # _phony_ is a non-existent file to force this command to run every time, # since currently there's no way to get a full input/output list from the # flutter tool. set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) add_custom_command( OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} ${CPP_WRAPPER_SOURCES_APP} ${PHONY_OUTPUT} COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" windows-x64 $ VERBATIM ) add_custom_target(flutter_assemble DEPENDS "${FLUTTER_LIBRARY}" ${FLUTTER_LIBRARY_HEADERS} ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} ${CPP_WRAPPER_SOURCES_APP} ) ================================================ FILE: example_nav2/windows/flutter/generated_plugin_registrant.cc ================================================ // // Generated file. Do not edit. // // clang-format off #include "generated_plugin_registrant.h" void RegisterPlugins(flutter::PluginRegistry* registry) { } ================================================ FILE: example_nav2/windows/flutter/generated_plugin_registrant.h ================================================ // // Generated file. Do not edit. // // clang-format off #ifndef GENERATED_PLUGIN_REGISTRANT_ #define GENERATED_PLUGIN_REGISTRANT_ #include // Registers Flutter plugins. void RegisterPlugins(flutter::PluginRegistry* registry); #endif // GENERATED_PLUGIN_REGISTRANT_ ================================================ FILE: example_nav2/windows/flutter/generated_plugins.cmake ================================================ # # Generated file, do not edit. # list(APPEND FLUTTER_PLUGIN_LIST ) list(APPEND FLUTTER_FFI_PLUGIN_LIST ) set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) endforeach(ffi_plugin) ================================================ FILE: example_nav2/windows/runner/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.15) project(runner LANGUAGES CXX) add_executable(${BINARY_NAME} WIN32 "flutter_window.cpp" "main.cpp" "run_loop.cpp" "utils.cpp" "win32_window.cpp" "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" "Runner.rc" "runner.exe.manifest" ) apply_standard_settings(${BINARY_NAME}) target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") add_dependencies(${BINARY_NAME} flutter_assemble) ================================================ FILE: example_nav2/windows/runner/Runner.rc ================================================ // Microsoft Visual C++ generated resource script. // #pragma code_page(65001) #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include "winres.h" ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // English (United States) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE BEGIN "resource.h\0" END 2 TEXTINCLUDE BEGIN "#include ""winres.h""\r\n" "\0" END 3 TEXTINCLUDE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Icon // // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. IDI_APP_ICON ICON "resources\\app_icon.ico" ///////////////////////////////////////////////////////////////////////////// // // Version // #ifdef FLUTTER_BUILD_NUMBER #define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER #else #define VERSION_AS_NUMBER 1,0,0 #endif #ifdef FLUTTER_BUILD_NAME #define VERSION_AS_STRING #FLUTTER_BUILD_NAME #else #define VERSION_AS_STRING "1.0.0" #endif VS_VERSION_INFO VERSIONINFO FILEVERSION VERSION_AS_NUMBER PRODUCTVERSION VERSION_AS_NUMBER FILEFLAGSMASK VS_FFI_FILEFLAGSMASK #ifdef _DEBUG FILEFLAGS VS_FF_DEBUG #else FILEFLAGS 0x0L #endif FILEOS VOS__WINDOWS32 FILETYPE VFT_APP FILESUBTYPE 0x0L BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904e4" BEGIN VALUE "CompanyName", "com.get" "\0" VALUE "FileDescription", "A new Flutter project." "\0" VALUE "FileVersion", VERSION_AS_STRING "\0" VALUE "InternalName", "example_nav2" "\0" VALUE "LegalCopyright", "Copyright (C) 2021 com.get. All rights reserved." "\0" VALUE "OriginalFilename", "example_nav2.exe" "\0" VALUE "ProductName", "example_nav2" "\0" VALUE "ProductVersion", VERSION_AS_STRING "\0" END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x409, 1252 END END #endif // English (United States) resources ///////////////////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED ================================================ FILE: example_nav2/windows/runner/flutter_window.cpp ================================================ #include "flutter_window.h" #include #include "flutter/generated_plugin_registrant.h" FlutterWindow::FlutterWindow(RunLoop* run_loop, const flutter::DartProject& project) : run_loop_(run_loop), project_(project) {} FlutterWindow::~FlutterWindow() {} bool FlutterWindow::OnCreate() { if (!Win32Window::OnCreate()) { return false; } RECT frame = GetClientArea(); // The size here must match the window dimensions to avoid unnecessary surface // creation / destruction in the startup path. flutter_controller_ = std::make_unique( frame.right - frame.left, frame.bottom - frame.top, project_); // Ensure that basic setup of the controller was successful. if (!flutter_controller_->engine() || !flutter_controller_->view()) { return false; } RegisterPlugins(flutter_controller_->engine()); run_loop_->RegisterFlutterInstance(flutter_controller_->engine()); SetChildContent(flutter_controller_->view()->GetNativeWindow()); return true; } void FlutterWindow::OnDestroy() { if (flutter_controller_) { run_loop_->UnregisterFlutterInstance(flutter_controller_->engine()); flutter_controller_ = nullptr; } Win32Window::OnDestroy(); } LRESULT FlutterWindow::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { // Give Flutter, including plugins, an opportunity to handle window messages. if (flutter_controller_) { std::optional result = flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, lparam); if (result) { return *result; } } switch (message) { case WM_FONTCHANGE: flutter_controller_->engine()->ReloadSystemFonts(); break; } return Win32Window::MessageHandler(hwnd, message, wparam, lparam); } ================================================ FILE: example_nav2/windows/runner/flutter_window.h ================================================ #ifndef RUNNER_FLUTTER_WINDOW_H_ #define RUNNER_FLUTTER_WINDOW_H_ #include #include #include #include "run_loop.h" #include "win32_window.h" // A window that does nothing but host a Flutter view. class FlutterWindow : public Win32Window { public: // Creates a new FlutterWindow driven by the |run_loop|, hosting a // Flutter view running |project|. explicit FlutterWindow(RunLoop* run_loop, const flutter::DartProject& project); virtual ~FlutterWindow(); protected: // Win32Window: bool OnCreate() override; void OnDestroy() override; LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept override; private: // The run loop driving events for this window. RunLoop* run_loop_; // The project to run. flutter::DartProject project_; // The Flutter instance hosted by this window. std::unique_ptr flutter_controller_; }; #endif // RUNNER_FLUTTER_WINDOW_H_ ================================================ FILE: example_nav2/windows/runner/main.cpp ================================================ #include #include #include #include "flutter_window.h" #include "run_loop.h" #include "utils.h" int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, _In_ wchar_t *command_line, _In_ int show_command) { // Attach to console when present (e.g., 'flutter run') or create a // new console when running with a debugger. if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { CreateAndAttachConsole(); } // Initialize COM, so that it is available for use in the library and/or // plugins. ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); RunLoop run_loop; flutter::DartProject project(L"data"); std::vector command_line_arguments = GetCommandLineArguments(); project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); FlutterWindow window(&run_loop, project); Win32Window::Point origin(10, 10); Win32Window::Size size(1280, 720); if (!window.CreateAndShow(L"example_nav2", origin, size)) { return EXIT_FAILURE; } window.SetQuitOnClose(true); run_loop.Run(); ::CoUninitialize(); return EXIT_SUCCESS; } ================================================ FILE: example_nav2/windows/runner/resource.h ================================================ //{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. // Used by Runner.rc // #define IDI_APP_ICON 101 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 102 #define _APS_NEXT_COMMAND_VALUE 40001 #define _APS_NEXT_CONTROL_VALUE 1001 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif ================================================ FILE: example_nav2/windows/runner/run_loop.cpp ================================================ #include "run_loop.h" #include #include RunLoop::RunLoop() {} RunLoop::~RunLoop() {} void RunLoop::Run() { bool keep_running = true; TimePoint next_flutter_event_time = TimePoint::clock::now(); while (keep_running) { std::chrono::nanoseconds wait_duration = std::max(std::chrono::nanoseconds(0), next_flutter_event_time - TimePoint::clock::now()); ::MsgWaitForMultipleObjects( 0, nullptr, FALSE, static_cast(wait_duration.count() / 1000), QS_ALLINPUT); bool processed_events = false; MSG message; // All pending Windows messages must be processed; MsgWaitForMultipleObjects // won't return again for items left in the queue after PeekMessage. while (::PeekMessage(&message, nullptr, 0, 0, PM_REMOVE)) { processed_events = true; if (message.message == WM_QUIT) { keep_running = false; break; } ::TranslateMessage(&message); ::DispatchMessage(&message); // Allow Flutter to process messages each time a Windows message is // processed, to prevent starvation. next_flutter_event_time = std::min(next_flutter_event_time, ProcessFlutterMessages()); } // If the PeekMessage loop didn't run, process Flutter messages. if (!processed_events) { next_flutter_event_time = std::min(next_flutter_event_time, ProcessFlutterMessages()); } } } void RunLoop::RegisterFlutterInstance( flutter::FlutterEngine* flutter_instance) { flutter_instances_.insert(flutter_instance); } void RunLoop::UnregisterFlutterInstance( flutter::FlutterEngine* flutter_instance) { flutter_instances_.erase(flutter_instance); } RunLoop::TimePoint RunLoop::ProcessFlutterMessages() { TimePoint next_event_time = TimePoint::max(); for (auto instance : flutter_instances_) { std::chrono::nanoseconds wait_duration = instance->ProcessMessages(); if (wait_duration != std::chrono::nanoseconds::max()) { next_event_time = std::min(next_event_time, TimePoint::clock::now() + wait_duration); } } return next_event_time; } ================================================ FILE: example_nav2/windows/runner/run_loop.h ================================================ #ifndef RUNNER_RUN_LOOP_H_ #define RUNNER_RUN_LOOP_H_ #include #include #include // A runloop that will service events for Flutter instances as well // as native messages. class RunLoop { public: RunLoop(); ~RunLoop(); // Prevent copying RunLoop(RunLoop const&) = delete; RunLoop& operator=(RunLoop const&) = delete; // Runs the run loop until the application quits. void Run(); // Registers the given Flutter instance for event servicing. void RegisterFlutterInstance( flutter::FlutterEngine* flutter_instance); // Unregisters the given Flutter instance from event servicing. void UnregisterFlutterInstance( flutter::FlutterEngine* flutter_instance); private: using TimePoint = std::chrono::steady_clock::time_point; // Processes all currently pending messages for registered Flutter instances. TimePoint ProcessFlutterMessages(); std::set flutter_instances_; }; #endif // RUNNER_RUN_LOOP_H_ ================================================ FILE: example_nav2/windows/runner/runner.exe.manifest ================================================ PerMonitorV2 ================================================ FILE: example_nav2/windows/runner/utils.cpp ================================================ #include "utils.h" #include #include #include #include #include void CreateAndAttachConsole() { if (::AllocConsole()) { FILE *unused; if (freopen_s(&unused, "CONOUT$", "w", stdout)) { _dup2(_fileno(stdout), 1); } if (freopen_s(&unused, "CONOUT$", "w", stderr)) { _dup2(_fileno(stdout), 2); } std::ios::sync_with_stdio(); FlutterDesktopResyncOutputStreams(); } } std::vector GetCommandLineArguments() { // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. int argc; wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); if (argv == nullptr) { return std::vector(); } std::vector command_line_arguments; // Skip the first argument as it's the binary name. for (int i = 1; i < argc; i++) { command_line_arguments.push_back(Utf8FromUtf16(argv[i])); } ::LocalFree(argv); return command_line_arguments; } std::string Utf8FromUtf16(const wchar_t* utf16_string) { if (utf16_string == nullptr) { return std::string(); } int target_length = ::WideCharToMultiByte( CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, -1, nullptr, 0, nullptr, nullptr); if (target_length == 0) { return std::string(); } std::string utf8_string; utf8_string.resize(target_length); int converted_length = ::WideCharToMultiByte( CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, -1, utf8_string.data(), target_length, nullptr, nullptr); if (converted_length == 0) { return std::string(); } return utf8_string; } ================================================ FILE: example_nav2/windows/runner/utils.h ================================================ #ifndef RUNNER_UTILS_H_ #define RUNNER_UTILS_H_ #include #include // Creates a console for the process, and redirects stdout and stderr to // it for both the runner and the Flutter library. void CreateAndAttachConsole(); // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string // encoded in UTF-8. Returns an empty std::string on failure. std::string Utf8FromUtf16(const wchar_t* utf16_string); // Gets the command line arguments passed in as a std::vector, // encoded in UTF-8. Returns an empty std::vector on failure. std::vector GetCommandLineArguments(); #endif // RUNNER_UTILS_H_ ================================================ FILE: example_nav2/windows/runner/win32_window.cpp ================================================ #include "win32_window.h" #include #include "resource.h" namespace { constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; // The number of Win32Window objects that currently exist. static int g_active_window_count = 0; using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); // Scale helper to convert logical scaler values to physical using passed in // scale factor int Scale(int source, double scale_factor) { return static_cast(source * scale_factor); } // Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. // This API is only needed for PerMonitor V1 awareness mode. void EnableFullDpiSupportIfAvailable(HWND hwnd) { HMODULE user32_module = LoadLibraryA("User32.dll"); if (!user32_module) { return; } auto enable_non_client_dpi_scaling = reinterpret_cast( GetProcAddress(user32_module, "EnableNonClientDpiScaling")); if (enable_non_client_dpi_scaling != nullptr) { enable_non_client_dpi_scaling(hwnd); FreeLibrary(user32_module); } } } // namespace // Manages the Win32Window's window class registration. class WindowClassRegistrar { public: ~WindowClassRegistrar() = default; // Returns the singleton registar instance. static WindowClassRegistrar* GetInstance() { if (!instance_) { instance_ = new WindowClassRegistrar(); } return instance_; } // Returns the name of the window class, registering the class if it hasn't // previously been registered. const wchar_t* GetWindowClass(); // Unregisters the window class. Should only be called if there are no // instances of the window. void UnregisterWindowClass(); private: WindowClassRegistrar() = default; static WindowClassRegistrar* instance_; bool class_registered_ = false; }; WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; const wchar_t* WindowClassRegistrar::GetWindowClass() { if (!class_registered_) { WNDCLASS window_class{}; window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); window_class.lpszClassName = kWindowClassName; window_class.style = CS_HREDRAW | CS_VREDRAW; window_class.cbClsExtra = 0; window_class.cbWndExtra = 0; window_class.hInstance = GetModuleHandle(nullptr); window_class.hIcon = LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); window_class.hbrBackground = 0; window_class.lpszMenuName = nullptr; window_class.lpfnWndProc = Win32Window::WndProc; RegisterClass(&window_class); class_registered_ = true; } return kWindowClassName; } void WindowClassRegistrar::UnregisterWindowClass() { UnregisterClass(kWindowClassName, nullptr); class_registered_ = false; } Win32Window::Win32Window() { ++g_active_window_count; } Win32Window::~Win32Window() { --g_active_window_count; Destroy(); } bool Win32Window::CreateAndShow(const std::wstring& title, const Point& origin, const Size& size) { Destroy(); const wchar_t* window_class = WindowClassRegistrar::GetInstance()->GetWindowClass(); const POINT target_point = {static_cast(origin.x), static_cast(origin.y)}; HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); double scale_factor = dpi / 96.0; HWND window = CreateWindow( window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE, Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), Scale(size.width, scale_factor), Scale(size.height, scale_factor), nullptr, nullptr, GetModuleHandle(nullptr), this); if (!window) { return false; } return OnCreate(); } // static LRESULT CALLBACK Win32Window::WndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { if (message == WM_NCCREATE) { auto window_struct = reinterpret_cast(lparam); SetWindowLongPtr(window, GWLP_USERDATA, reinterpret_cast(window_struct->lpCreateParams)); auto that = static_cast(window_struct->lpCreateParams); EnableFullDpiSupportIfAvailable(window); that->window_handle_ = window; } else if (Win32Window* that = GetThisFromHandle(window)) { return that->MessageHandler(window, message, wparam, lparam); } return DefWindowProc(window, message, wparam, lparam); } LRESULT Win32Window::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { switch (message) { case WM_DESTROY: window_handle_ = nullptr; Destroy(); if (quit_on_close_) { PostQuitMessage(0); } return 0; case WM_DPICHANGED: { auto newRectSize = reinterpret_cast(lparam); LONG newWidth = newRectSize->right - newRectSize->left; LONG newHeight = newRectSize->bottom - newRectSize->top; SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, newHeight, SWP_NOZORDER | SWP_NOACTIVATE); return 0; } case WM_SIZE: { RECT rect = GetClientArea(); if (child_content_ != nullptr) { // Size and position the child window. MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, TRUE); } return 0; } case WM_ACTIVATE: if (child_content_ != nullptr) { SetFocus(child_content_); } return 0; } return DefWindowProc(window_handle_, message, wparam, lparam); } void Win32Window::Destroy() { OnDestroy(); if (window_handle_) { DestroyWindow(window_handle_); window_handle_ = nullptr; } if (g_active_window_count == 0) { WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); } } Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { return reinterpret_cast( GetWindowLongPtr(window, GWLP_USERDATA)); } void Win32Window::SetChildContent(HWND content) { child_content_ = content; SetParent(content, window_handle_); RECT frame = GetClientArea(); MoveWindow(content, frame.left, frame.top, frame.right - frame.left, frame.bottom - frame.top, true); SetFocus(child_content_); } RECT Win32Window::GetClientArea() { RECT frame; GetClientRect(window_handle_, &frame); return frame; } HWND Win32Window::GetHandle() { return window_handle_; } void Win32Window::SetQuitOnClose(bool quit_on_close) { quit_on_close_ = quit_on_close; } bool Win32Window::OnCreate() { // No-op; provided for subclasses. return true; } void Win32Window::OnDestroy() { // No-op; provided for subclasses. } ================================================ FILE: example_nav2/windows/runner/win32_window.h ================================================ #ifndef RUNNER_WIN32_WINDOW_H_ #define RUNNER_WIN32_WINDOW_H_ #include #include #include #include // A class abstraction for a high DPI-aware Win32 Window. Intended to be // inherited from by classes that wish to specialize with custom // rendering and input handling class Win32Window { public: struct Point { unsigned int x; unsigned int y; Point(unsigned int x, unsigned int y) : x(x), y(y) {} }; struct Size { unsigned int width; unsigned int height; Size(unsigned int width, unsigned int height) : width(width), height(height) {} }; Win32Window(); virtual ~Win32Window(); // Creates and shows a win32 window with |title| and position and size using // |origin| and |size|. New windows are created on the default monitor. Window // sizes are specified to the OS in physical pixels, hence to ensure a // consistent size to will treat the width height passed in to this function // as logical pixels and scale to appropriate for the default monitor. Returns // true if the window was created successfully. bool CreateAndShow(const std::wstring& title, const Point& origin, const Size& size); // Release OS resources associated with window. void Destroy(); // Inserts |content| into the window tree. void SetChildContent(HWND content); // Returns the backing Window handle to enable clients to set icon and other // window properties. Returns nullptr if the window has been destroyed. HWND GetHandle(); // If true, closing this window will quit the application. void SetQuitOnClose(bool quit_on_close); // Return a RECT representing the bounds of the current client area. RECT GetClientArea(); protected: // Processes and route salient window messages for mouse handling, // size change and DPI. Delegates handling of these to member overloads that // inheriting classes can handle. virtual LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept; // Called when CreateAndShow is called, allowing subclass window-related // setup. Subclasses should return false if setup fails. virtual bool OnCreate(); // Called when Destroy is called. virtual void OnDestroy(); private: friend class WindowClassRegistrar; // OS callback called by message pump. Handles the WM_NCCREATE message which // is passed when the non-client area is being created and enables automatic // non-client DPI scaling so that the non-client area automatically // responsponds to changes in DPI. All other messages are handled by // MessageHandler. static LRESULT CALLBACK WndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept; // Retrieves a class instance pointer for |window| static Win32Window* GetThisFromHandle(HWND const window) noexcept; bool quit_on_close_ = false; // window handle for top level window. HWND window_handle_ = nullptr; // window handle for hosted content. HWND child_content_ = nullptr; }; #endif // RUNNER_WIN32_WINDOW_H_ ================================================ FILE: lib/get.dart ================================================ /// GetX is an extra-light and powerful multi-platform framework. /// It combines high performance state management, intelligent dependency /// injection, and route management in a quick and practical way. library; export 'get_animations/index.dart'; export 'get_common/get_reset.dart'; export 'get_connect/connect.dart'; export 'get_core/get_core.dart'; export 'get_instance/get_instance.dart'; export 'get_navigation/get_navigation.dart'; export 'get_rx/get_rx.dart'; export 'get_state_manager/get_state_manager.dart'; export 'get_utils/get_utils.dart'; export 'route_manager.dart'; ================================================ FILE: lib/get_animations/animations.dart ================================================ import 'dart:math'; import 'dart:ui'; import 'package:flutter/widgets.dart'; import 'get_animated_builder.dart'; typedef OffsetBuilder = Offset Function(BuildContext, double); class FadeInAnimation extends OpacityAnimation { FadeInAnimation({ super.key, required super.duration, required super.delay, required super.child, super.onComplete, super.begin = 0, super.end = 1, super.idleValue = 0, }); } class FadeOutAnimation extends OpacityAnimation { FadeOutAnimation({ super.key, required super.duration, required super.delay, required super.child, super.onComplete, super.begin = 1, super.end = 0, super.idleValue = 1, }); } class OpacityAnimation extends GetAnimatedBuilder { OpacityAnimation({ super.key, required super.duration, required super.delay, required super.child, required super.onComplete, required double begin, required double end, required super.idleValue, }) : super( tween: Tween(begin: begin, end: end), builder: (context, value, child) { return Opacity( opacity: value, child: child!, ); }, ); } class RotateAnimation extends GetAnimatedBuilder { RotateAnimation({ super.key, required super.duration, required super.delay, required super.child, super.onComplete, required double begin, required double end, super.idleValue = 0, }) : super( builder: (context, value, child) => Transform.rotate( angle: value, child: child, ), tween: Tween(begin: begin, end: end), ); } class ScaleAnimation extends GetAnimatedBuilder { ScaleAnimation({ super.key, required super.duration, required super.delay, required super.child, super.onComplete, required double begin, required double end, super.idleValue = 0, }) : super( builder: (context, value, child) => Transform.scale( scale: value, child: child, ), tween: Tween(begin: begin, end: end), ); } // class SlideAnimation extends GetAnimatedBuilder { // SlideAnimation({ // super.key, // required super.duration, // required super.delay, // required super.child, // super.onComplete, // required Offset begin, // required Offset end, // super.idleValue = const Offset(0, 0), // }) : super( // builder: (context, value, child) => Transform.translate( // offset: value, // child: child, // ), // tween: Tween(begin: begin, end: end), // ); // } class BounceAnimation extends GetAnimatedBuilder { BounceAnimation({ super.key, required super.duration, required super.delay, required super.child, super.onComplete, super.curve = Curves.bounceOut, required double begin, required double end, super.idleValue = 0, }) : super( builder: (context, value, child) => Transform.scale( scale: 1 + value.abs(), child: child, ), tween: Tween(begin: begin, end: end), ); } class SpinAnimation extends GetAnimatedBuilder { SpinAnimation({ super.key, required super.duration, required super.delay, required super.child, super.onComplete, super.idleValue = 0, }) : super( builder: (context, value, child) => Transform.rotate( angle: value * pi / 180.0, child: child, ), tween: Tween(begin: 0, end: 360), ); } class SizeAnimation extends GetAnimatedBuilder { SizeAnimation({ super.key, required super.duration, required super.delay, required super.child, super.onComplete, super.idleValue = 0, required double begin, required double end, }) : super( builder: (context, value, child) => Transform.scale( scale: value, child: child, ), tween: Tween(begin: begin, end: end), ); } class BlurAnimation extends GetAnimatedBuilder { BlurAnimation({ super.key, required super.duration, required super.delay, required super.child, super.onComplete, required double begin, required double end, super.idleValue = 0, }) : super( builder: (context, value, child) => BackdropFilter( filter: ImageFilter.blur( sigmaX: value, sigmaY: value, ), child: child, ), tween: Tween(begin: begin, end: end), ); } class FlipAnimation extends GetAnimatedBuilder { FlipAnimation({ super.key, required super.duration, required super.delay, required super.child, super.onComplete, required double begin, required double end, super.idleValue = 0, }) : super( builder: (context, value, child) { final radians = value * pi; return Transform( transform: Matrix4.rotationY(radians), alignment: Alignment.center, child: child, ); }, tween: Tween(begin: begin, end: end), ); } class WaveAnimation extends GetAnimatedBuilder { WaveAnimation({ super.key, required super.duration, required super.delay, required super.child, super.onComplete, required double begin, required double end, super.idleValue = 0, }) : super( builder: (context, value, child) => Transform( transform: Matrix4.translationValues( 0.0, 20.0 * sin(value * pi * 2), 0.0, ), child: child, ), tween: Tween(begin: begin, end: end), ); } class WobbleAnimation extends GetAnimatedBuilder { WobbleAnimation({ super.key, required super.duration, required super.delay, required super.child, super.onComplete, required double begin, required double end, super.idleValue = 0, }) : super( builder: (context, value, child) => Transform( transform: Matrix4.identity() ..setEntry(3, 2, 0.001) ..rotateZ(sin(value * pi * 2) * 0.1), alignment: Alignment.center, child: child, ), tween: Tween(begin: begin, end: end), ); } class SlideInLeftAnimation extends SlideAnimation { SlideInLeftAnimation({ super.key, required super.duration, required super.delay, required super.child, super.onComplete, required super.begin, required super.end, super.idleValue = 0, }) : super( offsetBuild: (context, value) => Offset(value * MediaQuery.of(context).size.width, 0), ); } class SlideInRightAnimation extends SlideAnimation { SlideInRightAnimation({ super.key, required super.duration, required super.delay, required super.child, super.onComplete, required super.begin, required super.end, super.idleValue = 0, }) : super( offsetBuild: (context, value) => Offset((1 - value) * MediaQuery.of(context).size.width, 0), ); } class SlideInUpAnimation extends SlideAnimation { SlideInUpAnimation({ super.key, required super.duration, required super.delay, required super.child, super.onComplete, required super.begin, required super.end, super.idleValue = 0, }) : super( offsetBuild: (context, value) => Offset(0, value * MediaQuery.of(context).size.height), ); } class SlideInDownAnimation extends SlideAnimation { SlideInDownAnimation({ super.key, required super.duration, required super.delay, required super.child, super.onComplete, required super.begin, required super.end, super.idleValue = 0, }) : super( offsetBuild: (context, value) => Offset(0, (1 - value) * MediaQuery.of(context).size.height), ); } class SlideAnimation extends GetAnimatedBuilder { SlideAnimation({ super.key, required super.duration, required super.delay, required super.child, required double begin, required double end, required OffsetBuilder offsetBuild, super.onComplete, super.idleValue = 0, }) : super( builder: (context, value, child) => Transform.translate( offset: offsetBuild(context, value), child: child, ), tween: Tween(begin: begin, end: end), ); } // class ZoomAnimation extends GetAnimatedBuilder { // ZoomAnimation({ // super.key, // required super.duration, // required super.delay, // required super.child, // super.onComplete, // required double begin, // required double end, // super.idleValue = 0, // }) : super( // builder: (context, value, child) => Transform.scale( // scale: lerpDouble(1, end, value)!, // child: child, // ), // tween: Tween(begin: begin, end: end), // ); // } class ColorAnimation extends GetAnimatedBuilder { ColorAnimation({ super.key, required super.duration, required super.delay, required super.child, super.onComplete, required Color begin, required Color end, Color? idleColor, }) : super( builder: (context, value, child) => ColorFiltered( colorFilter: ColorFilter.mode( Color.lerp(begin, end, value!.a.toDouble())!, BlendMode.srcIn, ), child: child, ), idleValue: idleColor ?? begin, tween: ColorTween(begin: begin, end: end), ); } ================================================ FILE: lib/get_animations/extensions.dart ================================================ import 'package:flutter/material.dart'; import 'animations.dart'; import 'get_animated_builder.dart'; const _defaultDuration = Duration(seconds: 2); const _defaultDelay = Duration.zero; extension AnimationExtension on Widget { GetAnimatedBuilder? get _currentAnimation => (this is GetAnimatedBuilder) ? this as GetAnimatedBuilder : null; GetAnimatedBuilder fadeIn({ Duration duration = _defaultDuration, Duration delay = _defaultDelay, ValueSetter? onComplete, bool isSequential = false, }) { assert(isSequential || this is! FadeOutAnimation, 'Can not use fadeOut + fadeIn when isSequential is false'); return FadeInAnimation( duration: duration, delay: _getDelay(isSequential, delay), onComplete: onComplete, child: this, ); } GetAnimatedBuilder fadeOut({ Duration duration = _defaultDuration, Duration delay = _defaultDelay, ValueSetter? onComplete, bool isSequential = false, }) { assert(isSequential || this is! FadeInAnimation, 'Can not use fadeOut() + fadeIn when isSequential is false'); return FadeOutAnimation( duration: duration, delay: _getDelay(isSequential, delay), onComplete: onComplete, child: this, ); } GetAnimatedBuilder rotate({ required double begin, required double end, Duration duration = _defaultDuration, Duration delay = _defaultDelay, ValueSetter? onComplete, bool isSequential = false, }) { return RotateAnimation( duration: duration, delay: _getDelay(isSequential, delay), begin: begin, end: end, onComplete: onComplete, child: this, ); } GetAnimatedBuilder scale({ required double begin, required double end, Duration duration = _defaultDuration, Duration delay = _defaultDelay, ValueSetter? onComplete, bool isSequential = false, }) { return ScaleAnimation( duration: duration, delay: _getDelay(isSequential, delay), begin: begin, end: end, onComplete: onComplete, child: this, ); } GetAnimatedBuilder slide({ required OffsetBuilder offset, double begin = 0, double end = 1, Duration duration = _defaultDuration, Duration delay = _defaultDelay, ValueSetter? onComplete, bool isSequential = false, }) { return SlideAnimation( duration: duration, delay: _getDelay(isSequential, delay), begin: begin, end: end, onComplete: onComplete, offsetBuild: offset, child: this, ); } GetAnimatedBuilder bounce({ required double begin, required double end, Duration duration = _defaultDuration, Duration delay = _defaultDelay, ValueSetter? onComplete, bool isSequential = false, }) { return BounceAnimation( duration: duration, delay: _getDelay(isSequential, delay), begin: begin, end: end, onComplete: onComplete, child: this, ); } GetAnimatedBuilder spin({ Duration duration = _defaultDuration, Duration delay = _defaultDelay, ValueSetter? onComplete, bool isSequential = false, }) { return SpinAnimation( duration: duration, delay: _getDelay(isSequential, delay), onComplete: onComplete, child: this, ); } GetAnimatedBuilder size({ required double begin, required double end, Duration duration = _defaultDuration, Duration delay = _defaultDelay, ValueSetter? onComplete, bool isSequential = false, }) { return SizeAnimation( duration: duration, delay: _getDelay(isSequential, delay), begin: begin, end: end, onComplete: onComplete, child: this, ); } GetAnimatedBuilder blur({ double begin = 0, double end = 15, Duration duration = _defaultDuration, Duration delay = _defaultDelay, ValueSetter? onComplete, bool isSequential = false, }) { return BlurAnimation( duration: duration, delay: _getDelay(isSequential, delay), begin: begin, end: end, onComplete: onComplete, child: this, ); } GetAnimatedBuilder flip({ double begin = 0, double end = 1, Duration duration = _defaultDuration, Duration delay = _defaultDelay, ValueSetter? onComplete, bool isSequential = false, }) { return FlipAnimation( duration: duration, delay: _getDelay(isSequential, delay), begin: begin, end: end, onComplete: onComplete, child: this, ); } GetAnimatedBuilder wave({ double begin = 0, double end = 1, Duration duration = _defaultDuration, Duration delay = _defaultDelay, ValueSetter? onComplete, bool isSequential = false, }) { return WaveAnimation( duration: duration, delay: _getDelay(isSequential, delay), begin: begin, end: end, onComplete: onComplete, child: this, ); } Duration _getDelay(bool isSequential, Duration delay) { assert(!(isSequential && delay != Duration.zero), "Error: When isSequential is true, delay must be non-zero. Context: isSequential: $isSequential delay: $delay"); return isSequential ? (_currentAnimation?.totalDuration ?? Duration.zero) : delay; } } ================================================ FILE: lib/get_animations/get_animated_builder.dart ================================================ import 'package:flutter/material.dart'; import 'animations.dart'; class GetAnimatedBuilder extends StatefulWidget { final Duration duration; final Duration delay; final Widget child; final ValueSetter? onComplete; final ValueSetter? onStart; final Tween tween; final T idleValue; final ValueWidgetBuilder builder; final Curve curve; Duration get totalDuration => duration + delay; const GetAnimatedBuilder({ super.key, this.curve = Curves.linear, this.onComplete, this.onStart, required this.duration, required this.tween, required this.idleValue, required this.builder, required this.child, required this.delay, }); @override GetAnimatedBuilderState createState() => GetAnimatedBuilderState(); } class GetAnimatedBuilderState extends State> with SingleTickerProviderStateMixin { late final AnimationController _controller; late final Animation _animation; // AnimationController get controller => _controller; // Animation get animation => _animation; bool _wasStarted = false; // bool get wasStarted => _wasStarted; late T _idleValue; bool _willResetOnDispose = false; bool get willResetOnDispose => _willResetOnDispose; void _listener(AnimationStatus status) { switch (status) { case AnimationStatus.completed: widget.onComplete?.call(_controller); if (_willResetOnDispose) { _controller.reset(); } break; // case AnimationStatus.dismissed: case AnimationStatus.forward: widget.onStart?.call(_controller); break; // case AnimationStatus.reverse: default: break; } } @override void initState() { super.initState(); if (widget is OpacityAnimation) { final current = context.findRootAncestorStateOfType(); final isLast = current == null; if (widget is FadeInAnimation) { _idleValue = 1.0 as dynamic; } else { if (isLast) { _willResetOnDispose = false; } else { _willResetOnDispose = true; } _idleValue = widget.idleValue; } } else { _idleValue = widget.idleValue; } _controller = AnimationController( vsync: this, duration: widget.duration, ); _controller.addStatusListener(_listener); _animation = widget.tween.animate( CurvedAnimation( parent: _controller, curve: widget.curve, ), ); Future.delayed(widget.delay, () { if (mounted) { setState(() { _wasStarted = true; _controller.forward(); }); } }); } @override void dispose() { _controller.removeStatusListener(_listener); _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return AnimatedBuilder( animation: _animation, builder: (context, child) { final value = _wasStarted ? _animation.value : _idleValue; return widget.builder(context, value, child); }, child: widget.child, ); } } ================================================ FILE: lib/get_animations/index.dart ================================================ export './animations.dart'; export './extensions.dart'; export './get_animated_builder.dart'; ================================================ FILE: lib/get_common/get_reset.dart ================================================ import '../get.dart'; extension GetResetExt on GetInterface { void reset({bool clearRouteBindings = true}) { Get.resetInstance(clearRouteBindings: clearRouteBindings); // Get.clearRouteTree(); Get.clearTranslations(); // Get.resetRootNavigator(); } } ================================================ FILE: lib/get_connect/connect.dart ================================================ import '../get_instance/src/lifecycle.dart'; import 'http/src/certificates/certificates.dart'; import 'http/src/exceptions/exceptions.dart'; import 'http/src/http.dart'; import 'http/src/response/response.dart'; import 'sockets/sockets.dart'; export 'http/src/certificates/certificates.dart'; export 'http/src/http.dart'; export 'http/src/multipart/form_data.dart'; export 'http/src/multipart/multipart_file.dart'; export 'http/src/response/response.dart'; export 'sockets/sockets.dart'; abstract class GetConnectInterface with GetLifeCycleMixin { List? sockets; GetHttpClient get httpClient; Future> get( String url, { Map? headers, String? contentType, Map? query, Decoder? decoder, }); Future> request( String url, String method, { dynamic body, String? contentType, Map? headers, Map? query, Decoder? decoder, }); Future> post( String url, dynamic body, { String? contentType, Map? headers, Map? query, Decoder? decoder, }); Future> put( String url, dynamic body, { String? contentType, Map? headers, Map? query, Decoder? decoder, }); Future> delete( String url, { Map? headers, String? contentType, Map? query, Decoder? decoder, }); Future> patch( String url, dynamic body, { String? contentType, Map? headers, Map? query, Decoder? decoder, Progress? uploadProgress, }); Future> query( String query, { String? url, Map? variables, Map? headers, }); Future> mutation( String mutation, { String? url, Map? variables, Map? headers, }); GetSocket socket( String url, { Duration ping = const Duration(seconds: 5), }); } class GetConnect extends GetConnectInterface { GetConnect({ this.userAgent = 'getx-client', this.timeout = const Duration(seconds: 5), this.followRedirects = true, this.maxRedirects = 5, this.sendUserAgent = false, this.maxAuthRetries = 1, this.allowAutoSignedCert = false, this.withCredentials = false, }); bool allowAutoSignedCert; String userAgent; bool sendUserAgent; String? baseUrl; String defaultContentType = 'application/json; charset=utf-8'; bool followRedirects; int maxRedirects; int maxAuthRetries; Decoder? defaultDecoder; Duration timeout; List? trustedCertificates; String Function(Uri url)? findProxy; GetHttpClient? _httpClient; List? _sockets; bool withCredentials; @override List get sockets => _sockets ??= []; @override GetHttpClient get httpClient => _httpClient ??= GetHttpClient( userAgent: userAgent, sendUserAgent: sendUserAgent, timeout: timeout, followRedirects: followRedirects, maxRedirects: maxRedirects, maxAuthRetries: maxAuthRetries, allowAutoSignedCert: allowAutoSignedCert, baseUrl: baseUrl, trustedCertificates: trustedCertificates, withCredentials: withCredentials, findProxy: findProxy); @override Future> get( String url, { Map? headers, String? contentType, Map? query, Decoder? decoder, }) { _checkIfDisposed(); return httpClient.get( url, headers: headers, contentType: contentType, query: query, decoder: decoder, ); } @override Future> post( String? url, dynamic body, { String? contentType, Map? headers, Map? query, Decoder? decoder, Progress? uploadProgress, }) { _checkIfDisposed(); return httpClient.post( url, body: body, headers: headers, contentType: contentType, query: query, decoder: decoder, uploadProgress: uploadProgress, ); } @override Future> put( String url, dynamic body, { String? contentType, Map? headers, Map? query, Decoder? decoder, Progress? uploadProgress, }) { _checkIfDisposed(); return httpClient.put( url, body: body, headers: headers, contentType: contentType, query: query, decoder: decoder, uploadProgress: uploadProgress, ); } @override Future> patch( String url, dynamic body, { String? contentType, Map? headers, Map? query, Decoder? decoder, Progress? uploadProgress, }) { _checkIfDisposed(); return httpClient.patch( url, body: body, headers: headers, contentType: contentType, query: query, decoder: decoder, uploadProgress: uploadProgress, ); } @override Future> request( String url, String method, { dynamic body, String? contentType, Map? headers, Map? query, Decoder? decoder, Progress? uploadProgress, }) { _checkIfDisposed(); return httpClient.request( url, method, body: body, headers: headers, contentType: contentType, query: query, decoder: decoder, uploadProgress: uploadProgress, ); } @override Future> delete( String url, { Map? headers, String? contentType, Map? query, Decoder? decoder, }) { _checkIfDisposed(); return httpClient.delete( url, headers: headers, contentType: contentType, query: query, decoder: decoder, ); } @override GetSocket socket( String url, { Duration ping = const Duration(seconds: 5), }) { _checkIfDisposed(isHttp: false); final newSocket = GetSocket(_concatUrl(url)!, ping: ping); sockets.add(newSocket); return newSocket; } String? _concatUrl(String? url) { if (url == null) return baseUrl; return baseUrl == null ? url : baseUrl! + url; } /// query allow made GraphQL raw queries /// final connect = GetConnect(); /// connect.baseUrl = 'https://countries.trevorblades.com/'; /// final response = await connect.query( /// r""" /// { /// country(code: "BR") { /// name /// native /// currency /// languages { /// code /// name /// } /// } ///} ///""", ///); ///print(response.body); @override Future> query( String query, { String? url, Map? variables, Map? headers, }) async { try { final res = await post( url, {'query': query, 'variables': variables}, headers: headers, ); final listError = res.body['errors']; if ((listError is List) && listError.isNotEmpty) { return GraphQLResponse( graphQLErrors: listError .map((e) => GraphQLError( code: (e['extensions'] != null ? e['extensions']['code'] ?? '' : '') .toString(), message: (e['message'] ?? '').toString(), )) .toList()); } return GraphQLResponse.fromResponse(res); } on Exception catch (err) { return GraphQLResponse(graphQLErrors: [ GraphQLError( code: null, message: err.toString(), ) ]); } } @override Future> mutation( String mutation, { String? url, Map? variables, Map? headers, }) async { try { final res = await post( url, {'query': mutation, 'variables': variables}, headers: headers, ); final listError = res.body['errors']; if ((listError is List) && listError.isNotEmpty) { return GraphQLResponse( graphQLErrors: listError .map((e) => GraphQLError( code: e['extensions']['code']?.toString(), message: e['message']?.toString(), )) .toList()); } return GraphQLResponse.fromResponse(res); } on Exception catch (err) { return GraphQLResponse(graphQLErrors: [ GraphQLError( code: null, message: err.toString(), ) ]); } } bool _isDisposed = false; bool get isDisposed => _isDisposed; void _checkIfDisposed({bool isHttp = true}) { if (_isDisposed) { throw 'Can not emit events to disposed clients'; } } void dispose() { if (_sockets != null) { for (var socket in sockets) { socket.close(); } _sockets?.clear(); sockets = null; } if (_httpClient != null) { httpClient.close(); _httpClient = null; } _isDisposed = true; } } ================================================ FILE: lib/get_connect/http/src/certificates/certificates.dart ================================================ class TrustedCertificate { final List bytes; const TrustedCertificate(this.bytes); } ================================================ FILE: lib/get_connect/http/src/exceptions/exceptions.dart ================================================ class GetHttpException implements Exception { final String message; final Uri? uri; GetHttpException(this.message, [this.uri]); @override String toString() => message; } class GraphQLError { GraphQLError({this.code, this.message}); final String? message; final String? code; @override String toString() => 'GETCONNECT ERROR:\n\tcode:$code\n\tmessage:$message'; } class UnauthorizedException implements Exception { @override String toString() { return 'Operation Unauthorized'; } } class UnexpectedFormat implements Exception { final String message; UnexpectedFormat(this.message); @override String toString() { return 'Unexpected format: $message'; } } ================================================ FILE: lib/get_connect/http/src/http/html/file_decoder_html.dart ================================================ // import 'dart:html' as html; List fileToBytes(dynamic data) { if (data is List) { return data; } else { throw const FormatException( 'File is not "File" or "String" or "List"'); } } // void writeOnFile(List bytes) { // var blob = html.Blob(["data"], 'text/plain', 'native'); // var anchorElement = html.AnchorElement( // href: html.Url.createObjectUrlFromBlob(blob).toString(), // ) // ..setAttribute("download", "data.txt") // ..click(); // } ================================================ FILE: lib/get_connect/http/src/http/html/http_request_html.dart ================================================ import 'dart:async'; import 'dart:js_interop'; import 'package:web/web.dart' show XHRGetters, XMLHttpRequest; import '../../certificates/certificates.dart'; import '../../exceptions/exceptions.dart'; import '../../request/request.dart'; import '../../response/response.dart'; import '../interface/request_base.dart'; import '../utils/body_decoder.dart'; class HttpRequestImpl implements IClient { HttpRequestImpl({ bool allowAutoSignedCert = true, List? trustedCertificates, this.withCredentials = false, String Function(Uri url)? findProxy, }); final _xhrs = {}; final bool withCredentials; @override Future> send(Request request) async { if (_isClosed) { throw GetHttpException( 'HTTP request failed. Client is already closed.', request.url); } var bytes = await request.bodyBytes.toBytes(); XMLHttpRequest xhr = XMLHttpRequest(); xhr ..timeout = (timeout ?? Duration.zero).inMilliseconds ..open(request.method, '${request.url}', true); _xhrs.add(xhr); xhr ..responseType = 'arraybuffer' // Changed from 'blob' to 'arraybuffer' ..withCredentials = withCredentials; request.headers.forEach((key, value) => xhr.setRequestHeader(key, value)); var completer = Completer>(); unawaited(xhr.onLoad.first.then((_) async { final bodyBytes = (xhr.response as JSArrayBuffer).toDart.asUint8List().toStream(); if (request.responseInterceptor != null) { throw GetHttpException( 'Response interception not implemented for web yet!', request.url); } final stringBody = await bodyBytesToString(bodyBytes, xhr.responseHeaders); final contentType = xhr.responseHeaders['content-type'] ?? 'application/json'; final body = bodyDecoded(request, stringBody, contentType); final response = Response( bodyBytes: bodyBytes, statusCode: xhr.status, request: request, headers: xhr.responseHeaders, statusText: xhr.statusText, body: body, bodyString: stringBody, ); completer.complete(response); })); unawaited(xhr.onError.first.then((_) { completer.completeError( GetHttpException('XMLHttpRequest error.', request.url), StackTrace.current, ); })); xhr.send(bytes.toJS); try { return await completer.future; } finally { _xhrs.remove(xhr); } } @override void close() { _isClosed = true; for (var xhr in _xhrs) { xhr.abort(); } _xhrs.clear(); } @override Duration? timeout; bool _isClosed = false; } extension on XMLHttpRequest { Map get responseHeaders { var headers = {}; var headersString = getAllResponseHeaders(); var headersList = headersString.split('\r\n'); for (var header in headersList) { if (header.isEmpty) continue; var splitIdx = header.indexOf(': '); if (splitIdx == -1) continue; var key = header.substring(0, splitIdx).toLowerCase(); var value = header.substring(splitIdx + 2); if (headers.containsKey(key)) { headers[key] = '${headers[key]}, $value'; } else { headers[key] = value; } } return headers; } } ================================================ FILE: lib/get_connect/http/src/http/interface/request_base.dart ================================================ import '../../request/request.dart'; import '../../response/response.dart'; /// Abstract interface of [HttpRequestImpl]. abstract class IClient { /// Sends an HTTP [Request]. Future> send(Request request); /// Closes the [Request] and cleans up any resources associated with it. void close(); /// Gets and sets the timeout. /// /// For mobile, this value will be applied for both connection and request /// timeout. /// /// For web, this value will be the request timeout. Duration? timeout; } ================================================ FILE: lib/get_connect/http/src/http/io/file_decoder_io.dart ================================================ import 'dart:io'; List fileToBytes(dynamic data) { if (data is File) { return data.readAsBytesSync(); } else if (data is String) { if (File(data).existsSync()) { return File(data).readAsBytesSync(); } else { throw 'File $data not exists'; } } else if (data is List) { return data; } else { throw const FormatException( 'File is not "File" or "String" or "List"'); } } void writeOnFile(List bytes) {} ================================================ FILE: lib/get_connect/http/src/http/io/http_request_io.dart ================================================ import 'dart:async'; import 'dart:io' as io; import '../../certificates/certificates.dart'; import '../../exceptions/exceptions.dart'; import '../../request/request.dart'; import '../../response/client_response.dart'; import '../../response/response.dart'; import '../interface/request_base.dart'; import '../utils/body_decoder.dart'; class IoRedirectInfo implements RedirectInfo { final io.RedirectInfo _redirectInfo; IoRedirectInfo({required io.RedirectInfo redirectInfo}) : _redirectInfo = redirectInfo; @override int get statusCode => _redirectInfo.statusCode; @override String get method => _redirectInfo.method; @override Uri get location => _redirectInfo.location; } class IoHttpHeaders implements HttpHeaders { final io.HttpHeaders _headers; IoHttpHeaders({required io.HttpHeaders headers}) : _headers = headers; @override bool get chunkedTransferEncoding => _headers.chunkedTransferEncoding; @override int get contentLength => _headers.contentLength; @override DateTime? get date => _headers.date; @override DateTime? get expires => _headers.expires; @override String? get host => _headers.host; @override DateTime? get ifModifiedSince => _headers.ifModifiedSince; @override bool get persistentConnection => _headers.persistentConnection; @override int? get port => _headers.port; @override List? operator [](String name) { return _headers[name]; } @override void add(String name, Object value, {bool preserveHeaderCase = false}) { _headers.add(name, value, preserveHeaderCase: preserveHeaderCase); } @override void clear() { _headers.clear(); } @override void forEach(void Function(String name, List values) action) { _headers.forEach(action); } @override void noFolding(String name) { _headers.noFolding(name); } @override void remove(String name, Object value) { _headers.remove(name, value); } @override void removeAll(String name) { _headers.removeAll(name); } @override void set(String name, Object value, {bool preserveHeaderCase = false}) { _headers.set(name, value, preserveHeaderCase: preserveHeaderCase); } @override String? value(String name) { return _headers.value(name); } @override set chunkedTransferEncoding(bool chunkedTransferEncoding) { _headers.chunkedTransferEncoding = chunkedTransferEncoding; } @override set contentLength(int contentLength) { _headers.contentLength = contentLength; } @override set date(DateTime? date) { _headers.date = date; } @override set expires(DateTime? expires) { _headers.expires = expires; } @override set host(String? host) { _headers.host = host; } @override set ifModifiedSince(DateTime? ifModifiedSince) { _headers.ifModifiedSince = ifModifiedSince; } @override set persistentConnection(bool persistentConnection) { _headers.persistentConnection = persistentConnection; } @override set port(int? port) { _headers.port = port; } } class IOHttpResponse implements HttpClientResponse { IOHttpResponse({required io.HttpClientResponse response}) : _response = response; final io.HttpClientResponse _response; @override Future any(bool Function(List element) test) { return _response.any(test); } @override Stream> asBroadcastStream( {void Function(StreamSubscription> subscription)? onListen, void Function(StreamSubscription> subscription)? onCancel}) { return _response.asBroadcastStream(onListen: onListen, onCancel: onCancel); } @override Stream asyncExpand(Stream? Function(List event) convert) { return _response.asyncExpand(convert); } @override Stream asyncMap(FutureOr Function(List event) convert) { return _response.asyncMap(convert); } @override Stream cast() { return _response.cast(); } @override Future contains(Object? needle) { return _response.contains(needle); } @override int get contentLength => _response.contentLength; @override Stream> distinct( [bool Function(List previous, List next)? equals]) { return _response.distinct(equals); } @override Future drain([E? futureValue]) { return _response.drain(futureValue); } @override Future> elementAt(int index) { return _response.elementAt(index); } @override Future every(bool Function(List element) test) { return _response.every(test); } @override Stream expand(Iterable Function(List element) convert) { return _response.expand(convert); } @override Future> get first => _response.first; @override Future> firstWhere(bool Function(List element) test, {List Function()? orElse}) { return _response.firstWhere(test, orElse: orElse); } @override Future fold( S initialValue, S Function(S previous, List element) combine) { return _response.fold(initialValue, combine); } @override Future forEach(void Function(List element) action) { return _response.forEach(action); } @override Stream> handleError(Function onError, {bool Function(dynamic error)? test}) { return _response.handleError(onError, test: test); } @override HttpHeaders get headers => IoHttpHeaders(headers: _response.headers); @override bool get isBroadcast => _response.isBroadcast; @override Future get isEmpty => _response.isEmpty; @override bool get isRedirect => _response.isRedirect; @override Future join([String separator = ""]) { return _response.join(separator); } @override Future> get last => _response.last; @override Future> lastWhere(bool Function(List element) test, {List Function()? orElse}) { return _response.lastWhere(test, orElse: orElse); } @override Future get length => _response.length; @override StreamSubscription> listen(void Function(List event)? onData, {Function? onError, void Function()? onDone, bool? cancelOnError}) { return _response.listen(onData, onError: onError, onDone: onDone, cancelOnError: cancelOnError); } @override Stream map(S Function(List event) convert) { return _response.map(convert); } @override bool get persistentConnection => _response.persistentConnection; @override Future pipe(StreamConsumer> streamConsumer) { return _response.pipe(streamConsumer); } @override String get reasonPhrase => _response.reasonPhrase; @override Future redirect( [String? method, Uri? url, bool? followLoops]) async { final data = await _response.redirect(method, url, followLoops); return IOHttpResponse(response: data); } @override List get redirects => _response.redirects .map((item) => IoRedirectInfo(redirectInfo: item)) .toList(); @override Future> reduce( List Function(List previous, List element) combine) { return _response.reduce(combine); } @override Future> get single => _response.single; @override Future> singleWhere(bool Function(List element) test, {List Function()? orElse}) { return _response.singleWhere(test, orElse: orElse); } @override Stream> skip(int count) { return _response.skip(count); } @override Stream> skipWhile(bool Function(List element) test) { return _response.skipWhile(test); } @override int get statusCode => _response.statusCode; @override Stream> take(int count) { return _response.take(count); } @override Stream> takeWhile(bool Function(List element) test) { return _response.takeWhile(test); } @override Stream> timeout(Duration timeLimit, {void Function(EventSink> sink)? onTimeout}) { return _response.timeout(timeLimit, onTimeout: onTimeout); } @override Future>> toList() { return _response.toList(); } @override Future>> toSet() { return _response.toSet(); } @override Stream transform(StreamTransformer, S> streamTransformer) { return _response.transform(streamTransformer); } @override Stream> where(bool Function(List event) test) { return _response.where(test); } } /// A `dart:io` implementation of `IClient`. class HttpRequestImpl extends IClient { io.HttpClient? _httpClient; io.SecurityContext? _securityContext; HttpRequestImpl({ bool allowAutoSignedCert = true, List? trustedCertificates, bool withCredentials = false, String Function(Uri url)? findProxy, }) { _httpClient = io.HttpClient(); if (trustedCertificates != null) { _securityContext = io.SecurityContext(); for (final trustedCertificate in trustedCertificates) { _securityContext! .setTrustedCertificatesBytes(List.from(trustedCertificate.bytes)); } } _httpClient = io.HttpClient(context: _securityContext); _httpClient!.badCertificateCallback = (_, __, ___) => allowAutoSignedCert; _httpClient!.findProxy = findProxy; } @override Future> send(Request request) async { var stream = request.bodyBytes.asBroadcastStream(); io.HttpClientRequest? ioRequest; try { _httpClient!.connectionTimeout = timeout; ioRequest = (await _httpClient!.openUrl(request.method, request.url)) ..followRedirects = request.followRedirects ..persistentConnection = request.persistentConnection ..maxRedirects = request.maxRedirects ..contentLength = request.contentLength ?? -1; request.headers.forEach(ioRequest.headers.set); var response = timeout == null ? await stream.pipe(ioRequest) as io.HttpClientResponse : await stream.pipe(ioRequest).timeout(timeout!) as io.HttpClientResponse; var headers = {}; response.headers.forEach((key, values) { headers[key] = values.join(','); }); final bodyBytes = (response); final interceptionResponse = await request.responseInterceptor ?.call(request, T, IOHttpResponse(response: response)); if (interceptionResponse != null) return interceptionResponse; final stringBody = await bodyBytesToString(bodyBytes, headers); final body = bodyDecoded( request, stringBody, response.headers.contentType?.mimeType, ); return Response( headers: headers, request: request, statusCode: response.statusCode, statusText: response.reasonPhrase, bodyBytes: bodyBytes, body: body, bodyString: stringBody, ); } on TimeoutException catch (_) { ioRequest?.abort(); rethrow; } on io.HttpException catch (error) { throw GetHttpException(error.message, error.uri); } } /// Closes the HttpClient. @override void close() { if (_httpClient != null) { _httpClient!.close(force: true); _httpClient = null; } } } // extension FileExt on io.FileSystemEntity { // String get fileName { // return this?.path?.split(io.Platform.pathSeparator)?.last; // } // } ================================================ FILE: lib/get_connect/http/src/http/mock/http_request_mock.dart ================================================ import '../../request/request.dart'; import '../../response/response.dart'; import '../interface/request_base.dart'; import '../utils/body_decoder.dart'; typedef MockClientHandler = Future Function(Request request); class MockClient extends IClient { /// The handler for than transforms request on response final MockClientHandler _handler; /// Creates a [MockClient] with a handler that receives [Request]s and sends /// [Response]s. MockClient(this._handler); @override Future> send(Request request) async { var requestBody = await request.bodyBytes.toBytes(); var bodyBytes = requestBody.toStream(); var response = await _handler(request); final stringBody = await bodyBytesToString(bodyBytes, response.headers!); var mimeType = response.headers!.containsKey('content-type') ? response.headers!['content-type'] : ''; final body = bodyDecoded( request, stringBody, mimeType, ); return Response( headers: response.headers, request: request, statusCode: response.statusCode, statusText: response.statusText, bodyBytes: bodyBytes, body: body, bodyString: stringBody, ); } @override void close() {} } ================================================ FILE: lib/get_connect/http/src/http/request/http_request.dart ================================================ import '../../certificates/certificates.dart'; import '../stub/http_request_stub.dart' if (dart.library.js_interop) '../html/http_request_html.dart' if (dart.library.io) '../io/http_request_io.dart'; HttpRequestImpl createHttp({ bool allowAutoSignedCert = true, List? trustedCertificates, bool withCredentials = false, String Function(Uri url)? findProxy, }) { return HttpRequestImpl( allowAutoSignedCert: allowAutoSignedCert, trustedCertificates: trustedCertificates, withCredentials: withCredentials, findProxy: findProxy, ); } ================================================ FILE: lib/get_connect/http/src/http/stub/file_decoder_stub.dart ================================================ void writeOnFile(List bytes) {} List fileToBytes(dynamic data) { throw UnimplementedError(); } ================================================ FILE: lib/get_connect/http/src/http/stub/http_request_stub.dart ================================================ import '../../certificates/certificates.dart'; import '../../request/request.dart'; import '../../response/response.dart'; import '../interface/request_base.dart'; class HttpRequestImpl extends IClient { HttpRequestImpl({ bool allowAutoSignedCert = true, List? trustedCertificates, bool withCredentials = false, String Function(Uri url)? findProxy, }); @override void close() {} @override Future> send(Request request) { throw UnimplementedError(); } } ================================================ FILE: lib/get_connect/http/src/http/utils/body_decoder.dart ================================================ import 'dart:convert'; import '../../../../../get_core/get_core.dart'; import '../../request/request.dart'; T? bodyDecoded(Request request, String stringBody, String? mimeType) { T? body; dynamic bodyToDecode; if (mimeType != null && mimeType.contains('application/json')) { try { bodyToDecode = jsonDecode(stringBody); } on FormatException catch (_) { Get.log('Cannot decode server response to json'); bodyToDecode = stringBody; } } else { bodyToDecode = stringBody; } try { if (stringBody == '') { body = null; } else if (request.decoder == null) { body = bodyToDecode as T?; } else { body = request.decoder!(bodyToDecode); } } on Exception catch (_) { body = stringBody as T; } return body; } ================================================ FILE: lib/get_connect/http/src/http.dart ================================================ import 'dart:async'; import 'dart:convert'; import '../src/certificates/certificates.dart'; import '../src/exceptions/exceptions.dart'; import '../src/multipart/form_data.dart'; import '../src/request/request.dart'; import '../src/response/response.dart'; import '../src/status/http_status.dart'; import 'http/interface/request_base.dart'; import 'http/request/http_request.dart'; import 'interceptors/get_modifiers.dart'; import 'response/client_response.dart'; typedef Decoder = T Function(dynamic data); typedef Progress = Function(double percent); typedef ResponseInterceptor = Future?> Function( Request request, Type targetType, HttpClientResponse response); class GetHttpClient { String userAgent; String? baseUrl; String defaultContentType = 'application/json; charset=utf-8'; bool followRedirects; int maxRedirects; int maxAuthRetries; bool sendUserAgent; bool sendContentLength; Decoder? defaultDecoder; ResponseInterceptor? defaultResponseInterceptor; Duration timeout; bool errorSafety = true; final IClient _httpClient; final GetModifier _modifier; String Function(Uri url)? findProxy; GetHttpClient({ this.userAgent = 'getx-client', this.timeout = const Duration(seconds: 8), this.followRedirects = true, this.maxRedirects = 5, this.sendUserAgent = false, this.sendContentLength = true, this.maxAuthRetries = 1, bool allowAutoSignedCert = false, this.baseUrl, List? trustedCertificates, bool withCredentials = false, String Function(Uri url)? findProxy, IClient? customClient, }) : _httpClient = customClient ?? createHttp( allowAutoSignedCert: allowAutoSignedCert, trustedCertificates: trustedCertificates, withCredentials: withCredentials, findProxy: findProxy, ), _modifier = GetModifier(); void addAuthenticator(RequestModifier auth) { _modifier.authenticator = auth as RequestModifier; } void addRequestModifier(RequestModifier interceptor) { _modifier.addRequestModifier(interceptor); } void removeRequestModifier(RequestModifier interceptor) { _modifier.removeRequestModifier(interceptor); } void addResponseModifier(ResponseModifier interceptor) { _modifier.addResponseModifier(interceptor); } void removeResponseModifier(ResponseModifier interceptor) { _modifier.removeResponseModifier(interceptor); } Uri createUri(String? url, Map? query) { if (baseUrl != null) { url = baseUrl! + url!; } final uri = Uri.parse(url!); if (query != null) { return uri.replace(queryParameters: query); } return uri; } Future> _requestWithBody( String? url, String? contentType, dynamic body, String method, Map? query, Decoder? decoder, ResponseInterceptor? responseInterceptor, Progress? uploadProgress, ) async { List? bodyBytes; Stream>? bodyStream; final headers = {}; if (sendUserAgent) { headers['user-agent'] = userAgent; } if (body is FormData) { bodyBytes = await body.toBytes(); headers['content-length'] = bodyBytes.length.toString(); headers['content-type'] = 'multipart/form-data; boundary=${body.boundary}'; } else if (contentType != null && contentType.toLowerCase() == 'application/x-www-form-urlencoded' && body is Map) { var parts = []; (body as Map).forEach((key, value) { parts.add('${Uri.encodeQueryComponent(key)}=' '${Uri.encodeQueryComponent(value.toString())}'); }); var formData = parts.join('&'); bodyBytes = utf8.encode(formData); _setContentLength(headers, bodyBytes.length); headers['content-type'] = contentType; } else if (body is Map || body is List) { var jsonString = json.encode(body); bodyBytes = utf8.encode(jsonString); _setContentLength(headers, bodyBytes.length); headers['content-type'] = contentType ?? defaultContentType; } else if (body is String) { bodyBytes = utf8.encode(body); _setContentLength(headers, bodyBytes.length); headers['content-type'] = contentType ?? defaultContentType; } else if (body == null) { _setContentLength(headers, 0); headers['content-type'] = contentType ?? defaultContentType; } else { if (!errorSafety) { throw UnexpectedFormat('body cannot be ${body.runtimeType}'); } } if (bodyBytes != null) { bodyStream = _trackProgress(bodyBytes, uploadProgress); } final uri = createUri(url, query); return Request( method: method, url: uri, headers: headers, bodyBytes: bodyStream, contentLength: bodyBytes?.length ?? 0, followRedirects: followRedirects, maxRedirects: maxRedirects, decoder: decoder, responseInterceptor: responseInterceptor); } void _setContentLength(Map headers, int contentLength) { if (sendContentLength) { headers['content-length'] = '$contentLength'; } } Stream> _trackProgress( List bodyBytes, Progress? uploadProgress, ) { var total = 0; var length = bodyBytes.length; var byteStream = Stream.fromIterable(bodyBytes.map((i) => [i])).transform>( StreamTransformer.fromHandlers(handleData: (data, sink) { total += data.length; if (uploadProgress != null) { var percent = total / length * 100; uploadProgress(percent); } sink.add(data); }), ); return byteStream; } void _setSimpleHeaders( Map headers, String? contentType, ) { headers['content-type'] = contentType ?? defaultContentType; if (sendUserAgent) { headers['user-agent'] = userAgent; } } Future> _performRequest( HandlerExecute handler, { bool authenticate = false, int requestNumber = 1, Map? headers, }) async { var request = await handler(); headers?.forEach((key, value) { request.headers[key] = value; }); if (authenticate) await _modifier.authenticator!(request); final newRequest = await _modifier.modifyRequest(request); _httpClient.timeout = timeout; try { var response = await _httpClient.send(newRequest); final newResponse = await _modifier.modifyResponse(newRequest, response); if (HttpStatus.unauthorized == newResponse.statusCode && _modifier.authenticator != null && requestNumber <= maxAuthRetries) { return _performRequest( handler, authenticate: true, requestNumber: requestNumber + 1, headers: newRequest.headers, ); } else if (HttpStatus.unauthorized == newResponse.statusCode) { if (!errorSafety) { throw UnauthorizedException(); } else { return Response( request: newRequest, headers: newResponse.headers, statusCode: newResponse.statusCode, body: newResponse.body, bodyBytes: newResponse.bodyBytes, bodyString: newResponse.bodyString, statusText: newResponse.statusText, ); } } return newResponse; } on Exception catch (err) { if (!errorSafety) { throw GetHttpException(err.toString()); } else { return Response( request: newRequest, headers: null, statusCode: null, body: null, statusText: "$err", ); } } } Future> _get( String url, String? contentType, Map? query, Decoder? decoder, ResponseInterceptor? responseInterceptor, ) { final headers = {}; _setSimpleHeaders(headers, contentType); final uri = createUri(url, query); return Future.value(Request( method: 'get', url: uri, headers: headers, decoder: decoder ?? (defaultDecoder as Decoder?), responseInterceptor: _responseInterceptor(responseInterceptor), contentLength: 0, followRedirects: followRedirects, maxRedirects: maxRedirects, )); } ResponseInterceptor? _responseInterceptor( ResponseInterceptor? actual) { if (actual != null) return actual; final defaultInterceptor = defaultResponseInterceptor; return defaultInterceptor != null ? (request, targetType, response) async => await defaultInterceptor(request, targetType, response) as Response? : null; } Future> _request( String? url, String method, { String? contentType, required dynamic body, required Map? query, Decoder? decoder, ResponseInterceptor? responseInterceptor, required Progress? uploadProgress, }) { return _requestWithBody( url, contentType, body, method, query, decoder ?? (defaultDecoder as Decoder?), _responseInterceptor(responseInterceptor), uploadProgress, ); } Request _delete( String url, String? contentType, Map? query, Decoder? decoder, ResponseInterceptor? responseInterceptor, ) { final headers = {}; _setSimpleHeaders(headers, contentType); final uri = createUri(url, query); return Request( method: 'delete', url: uri, headers: headers, decoder: decoder ?? (defaultDecoder as Decoder?), responseInterceptor: _responseInterceptor(responseInterceptor), ); } Future> send(Request request) async { try { var response = await _performRequest(() => Future.value(request)); return response; } on Exception catch (e) { if (!errorSafety) { throw GetHttpException(e.toString()); } return Future.value(Response( statusText: 'Can not connect to server. Reason: $e', )); } } Future> patch( String url, { dynamic body, String? contentType, Map? headers, Map? query, Decoder? decoder, ResponseInterceptor? responseInterceptor, Progress? uploadProgress, // List files, }) async { try { var response = await _performRequest( () => _request( url, 'patch', contentType: contentType, body: body, query: query, decoder: decoder, responseInterceptor: responseInterceptor, uploadProgress: uploadProgress, ), headers: headers, ); return response; } on Exception catch (e) { if (!errorSafety) { throw GetHttpException(e.toString()); } return Future.value(Response( statusText: 'Can not connect to server. Reason: $e', )); } } Future> post( String? url, { dynamic body, String? contentType, Map? headers, Map? query, Decoder? decoder, ResponseInterceptor? responseInterceptor, Progress? uploadProgress, // List files, }) async { try { var response = await _performRequest( () => _request( url, 'post', contentType: contentType, body: body, query: query, decoder: decoder, responseInterceptor: responseInterceptor, uploadProgress: uploadProgress, ), headers: headers, ); return response; } on Exception catch (e) { if (!errorSafety) { throw GetHttpException(e.toString()); } return Future.value(Response( statusText: 'Can not connect to server. Reason: $e', )); } } Future> request( String url, String method, { dynamic body, String? contentType, Map? headers, Map? query, Decoder? decoder, ResponseInterceptor? responseInterceptor, Progress? uploadProgress, }) async { try { var response = await _performRequest( () => _request( url, method, contentType: contentType, query: query, body: body, decoder: decoder, responseInterceptor: responseInterceptor, uploadProgress: uploadProgress, ), headers: headers, ); return response; } on Exception catch (e) { if (!errorSafety) { throw GetHttpException(e.toString()); } return Future.value(Response( statusText: 'Can not connect to server. Reason: $e', )); } } Future> put( String url, { dynamic body, String? contentType, Map? headers, Map? query, Decoder? decoder, ResponseInterceptor? responseInterceptor, Progress? uploadProgress, }) async { try { var response = await _performRequest( () => _request( url, 'put', contentType: contentType, query: query, body: body, decoder: decoder, responseInterceptor: responseInterceptor, uploadProgress: uploadProgress, ), headers: headers, ); return response; } on Exception catch (e) { if (!errorSafety) { throw GetHttpException(e.toString()); } return Future.value(Response( statusText: 'Can not connect to server. Reason: $e', )); } } Future> get( String url, { Map? headers, String? contentType, Map? query, Decoder? decoder, ResponseInterceptor? responseInterceptor, }) async { try { var response = await _performRequest( () => _get(url, contentType, query, decoder, responseInterceptor), headers: headers, ); return response; } on Exception catch (e) { if (!errorSafety) { throw GetHttpException(e.toString()); } return Future.value(Response( statusText: 'Can not connect to server. Reason: $e', )); } } Future> delete(String url, {Map? headers, String? contentType, Map? query, Decoder? decoder, ResponseInterceptor? responseInterceptor}) async { try { var response = await _performRequest( () async => _delete(url, contentType, query, decoder, responseInterceptor), headers: headers, ); return response; } on Exception catch (e) { if (!errorSafety) { throw GetHttpException(e.toString()); } return Future.value(Response( statusText: 'Can not connect to server. Reason: $e', )); } } void close() { _httpClient.close(); } } ================================================ FILE: lib/get_connect/http/src/interceptors/get_modifiers.dart ================================================ import 'dart:async'; import '../request/request.dart'; import '../response/response.dart'; typedef RequestModifier = FutureOr> Function(Request request); typedef ResponseModifier = FutureOr Function( Request request, Response response); typedef HandlerExecute = Future> Function(); class GetModifier { final _requestModifiers = []; final _responseModifiers = []; RequestModifier? authenticator; void addRequestModifier(RequestModifier interceptor) { _requestModifiers.add(interceptor as RequestModifier); } void removeRequestModifier(RequestModifier interceptor) { _requestModifiers.remove(interceptor); } void addResponseModifier(ResponseModifier interceptor) { _responseModifiers.add(interceptor as ResponseModifier); } void removeResponseModifier(ResponseModifier interceptor) { _requestModifiers.remove(interceptor); } Future> modifyRequest(Request request) async { var newRequest = request; if (_requestModifiers.isNotEmpty) { for (var interceptor in _requestModifiers) { newRequest = await interceptor(newRequest) as Request; } } return newRequest; } Future> modifyResponse( Request request, Response response) async { var newResponse = response; if (_responseModifiers.isNotEmpty) { for (var interceptor in _responseModifiers) { newResponse = await interceptor(request, newResponse) as Response; } } return newResponse; } } ================================================ FILE: lib/get_connect/http/src/multipart/form_data.dart ================================================ import 'dart:async'; import 'dart:convert'; import 'dart:math'; import '../request/request.dart'; import '../utils/utils.dart'; import 'multipart_file.dart'; class FormData { FormData(Map map) : boundary = _getBoundary() { map.forEach((key, value) { if (value == null) return; if (value is MultipartFile) { files.add(MapEntry(key, value)); } else if (value is List) { files.addAll(value.map((e) => MapEntry(key, e))); } else if (value is List) { fields.addAll(value.map((e) => MapEntry(key, e.toString()))); } else { fields.add(MapEntry(key, value.toString())); } }); } static const int _maxBoundaryLength = 70; static String _getBoundary() { final newRandom = Random(); var list = List.generate(_maxBoundaryLength - GET_BOUNDARY.length, (_) => boundaryCharacters[newRandom.nextInt(boundaryCharacters.length)], growable: false); return '$GET_BOUNDARY${String.fromCharCodes(list)}'; } final String boundary; /// The form fields to send for this request. final fields = >[]; /// The files to send for this request final files = >[]; /// Returns the header string for a field. The return value is guaranteed to /// contain only ASCII characters. String _fieldHeader(String name, String value) { var header = 'content-disposition: form-data; name="${browserEncode(name)}"'; if (!isPlainAscii(value)) { header = '$header\r\n' 'content-type: text/plain; charset=utf-8\r\n' 'content-transfer-encoding: binary'; } return '$header\r\n\r\n'; } /// Returns the header string for a file. The return value is guaranteed to /// contain only ASCII characters. String _fileHeader(MapEntry file) { var header = 'content-disposition: form-data; name="${browserEncode(file.key)}"'; header = '$header; filename="${browserEncode(file.value.filename)}"'; header = '$header\r\n' 'content-type: ${file.value.contentType}'; return '$header\r\n\r\n'; } /// The length of the request body from this [FormData] int get length { var length = 0; for (final item in fields) { length += '--'.length + _maxBoundaryLength + '\r\n'.length + utf8.encode(_fieldHeader(item.key, item.value)).length + utf8.encode(item.value).length + '\r\n'.length; } for (var file in files) { length += '--'.length + _maxBoundaryLength + '\r\n'.length + utf8.encode(_fileHeader(file)).length + file.value.length! + '\r\n'.length; } return length + '--'.length + _maxBoundaryLength + '--\r\n'.length; } Future> toBytes() { return BodyBytesStream(_encode()).toBytes(); } Stream> _encode() async* { const line = [13, 10]; final separator = utf8.encode('--$boundary\r\n'); final close = utf8.encode('--$boundary--\r\n'); for (var field in fields) { yield separator; yield utf8.encode(_fieldHeader(field.key, field.value)); yield utf8.encode(field.value); yield line; } for (final file in files) { yield separator; yield utf8.encode(_fileHeader(file)); yield* file.value.stream!; yield line; } yield close; } } ================================================ FILE: lib/get_connect/http/src/multipart/multipart_file.dart ================================================ import '../http/stub/file_decoder_stub.dart' if (dart.library.js_interop) '../http/html/file_decoder_html.dart' if (dart.library.io) '../http/io/file_decoder_io.dart'; import '../request/request.dart'; class MultipartFile { MultipartFile( dynamic data, { required this.filename, this.contentType = 'application/octet-stream', }) : _bytes = fileToBytes(data) { _length = _bytes.length; _stream = _bytes.toStream(); } final List _bytes; final String contentType; /// This stream will emit the file content of File. Stream>? _stream; int? _length; Stream>? get stream => _stream; int? get length => _length; final String filename; } ================================================ FILE: lib/get_connect/http/src/request/request.dart ================================================ import 'dart:async'; import 'dart:convert'; import 'dart:typed_data'; import '../http.dart'; import '../multipart/form_data.dart'; class Request { /// Headers attach to this [Request] final Map headers; /// The [Uri] from request final Uri url; final Decoder? decoder; final ResponseInterceptor? responseInterceptor; /// The Http Method from this [Request] /// ex: `GET`,`POST`,`PUT`,`DELETE` final String method; final int? contentLength; /// The BodyBytesStream of body from this [Request] final Stream> bodyBytes; /// When true, the client will follow redirects to resolves this [Request] final bool followRedirects; /// The maximum number of redirects if [followRedirects] is true. final int maxRedirects; final bool persistentConnection; final FormData? files; const Request._({ required this.method, required this.bodyBytes, required this.url, required this.headers, required this.contentLength, required this.followRedirects, required this.maxRedirects, required this.files, required this.persistentConnection, required this.decoder, this.responseInterceptor, }); factory Request({ required Uri url, required String method, required Map headers, Stream>? bodyBytes, bool followRedirects = true, int maxRedirects = 4, int? contentLength, FormData? files, bool persistentConnection = true, Decoder? decoder, ResponseInterceptor? responseInterceptor, }) { if (followRedirects) { assert(maxRedirects > 0); } return Request._( url: url, method: method, bodyBytes: bodyBytes ??= [].toStream(), headers: Map.from(headers), followRedirects: followRedirects, maxRedirects: maxRedirects, contentLength: contentLength, files: files, persistentConnection: persistentConnection, decoder: decoder, responseInterceptor: responseInterceptor); } Request copyWith({ Uri? url, String? method, Map? headers, Stream>? bodyBytes, bool? followRedirects, int? maxRedirects, int? contentLength, FormData? files, bool? persistentConnection, Decoder? decoder, bool appendHeader = true, ResponseInterceptor? responseInterceptor, }) { // If appendHeader is set to true, we will merge origin headers with that if (appendHeader && headers != null) { headers.addAll(this.headers); } return Request._( url: url ?? this.url, method: method ?? this.method, bodyBytes: bodyBytes ?? this.bodyBytes, headers: headers == null ? this.headers : Map.from(headers), followRedirects: followRedirects ?? this.followRedirects, maxRedirects: maxRedirects ?? this.maxRedirects, contentLength: contentLength ?? this.contentLength, files: files ?? this.files, persistentConnection: persistentConnection ?? this.persistentConnection, decoder: decoder ?? this.decoder, responseInterceptor: responseInterceptor ?? this.responseInterceptor); } } extension StreamExt on List { Stream> toStream() => Stream.value(this).asBroadcastStream(); } extension BodyBytesStream on Stream> { Future toBytes() { var completer = Completer(); var sink = ByteConversionSink.withCallback( (bytes) => completer.complete( Uint8List.fromList(bytes), ), ); listen((val) => sink.add(val), onError: completer.completeError, onDone: sink.close, cancelOnError: true); return completer.future; } Future bytesToString([Encoding encoding = utf8]) => encoding.decodeStream(this); } ================================================ FILE: lib/get_connect/http/src/response/client_response.dart ================================================ /// Redirect information. abstract interface class RedirectInfo { /// Returns the status code used for the redirect. int get statusCode; /// Returns the method used for the redirect. String get method; /// Returns the location for the redirect. Uri get location; } abstract interface class HttpClientResponse implements Stream> { /// Returns the status code. /// /// The status code must be set before the body is written /// to. Setting the status code after writing to the body will throw /// a `StateError`. int get statusCode; /// Returns the reason phrase associated with the status code. /// /// The reason phrase must be set before the body is written /// to. Setting the reason phrase after writing to the body will throw /// a `StateError`. String get reasonPhrase; /// Returns the content length of the response body. Returns -1 if the size of /// the response body is not known in advance. /// /// If the content length needs to be set, it must be set before the /// body is written to. Setting the content length after writing to the body /// will throw a `StateError`. int get contentLength; // /// The compression state of the response. // /// // /// This specifies whether the response bytes were compressed when they were // /// received across the wire and whether callers will receive compressed // /// or uncompressed bytes when they listed to this response's byte stream. // @Since("2.4") // HttpClientResponseCompressionState get compressionState; /// Gets the persistent connection state returned by the server. /// /// If the persistent connection state needs to be set, it must be /// set before the body is written to. Setting the persistent connection state /// after writing to the body will throw a `StateError`. bool get persistentConnection; /// Returns whether the status code is one of the normal redirect /// codes [HttpStatus.movedPermanently], [HttpStatus.found], /// [HttpStatus.movedTemporarily], [HttpStatus.seeOther] and /// [HttpStatus.temporaryRedirect]. bool get isRedirect; /// Returns the series of redirects this connection has been through. The /// list will be empty if no redirects were followed. [redirects] will be /// updated both in the case of an automatic and a manual redirect. List get redirects; /// Redirects this connection to a new URL. The default value for /// [method] is the method for the current request. The default value /// for [url] is the value of the [HttpHeaders.locationHeader] header of /// the current response. All body data must have been read from the /// current response before calling [redirect]. /// /// All headers added to the request will be added to the redirection /// request. However, any body sent with the request will not be /// part of the redirection request. /// /// If [followLoops] is set to `true`, redirect will follow the redirect, /// even if the URL was already visited. The default value is `false`. /// /// The method will ignore [HttpClientRequest.maxRedirects] /// and will always perform the redirect. Future redirect( [String? method, Uri? url, bool? followLoops]); /// Returns the client response headers. /// /// The client response headers are immutable. HttpHeaders get headers; // /// Detach the underlying socket from the HTTP client. When the // /// socket is detached the HTTP client will no longer perform any // /// operations on it. // /// // /// This is normally used when a HTTP upgrade is negotiated and the // /// communication should continue with a different protocol. // Future detachSocket(); // /// Cookies set by the server (from the 'set-cookie' header). // List get cookies; // /// Returns the certificate of the HTTPS server providing the response. // /// Returns null if the connection is not a secure TLS or SSL connection. // X509Certificate? get certificate; // /// Gets information about the client connection. Returns `null` if the socket // /// is not available. // HttpConnectionInfo? get connectionInfo; } /// Headers for HTTP requests and responses. /// /// In some situations, headers are immutable: /// /// * [HttpRequest] and [HttpClientResponse] always have immutable headers. /// /// * [HttpResponse] and [HttpClientRequest] have immutable headers /// from the moment the body is written to. /// /// In these situations, the mutating methods throw exceptions. /// /// For all operations on HTTP headers the header name is /// case-insensitive. /// /// To set the value of a header use the `set()` method: /// /// request.headers.set(HttpHeaders.cacheControlHeader, /// 'max-age=3600, must-revalidate'); /// /// To retrieve the value of a header use the `value()` method: /// /// print(request.headers.value(HttpHeaders.userAgentHeader)); /// /// An `HttpHeaders` object holds a list of values for each name /// as the standard allows. In most cases a name holds only a single value, /// The most common mode of operation is to use `set()` for setting a value, /// and `value()` for retrieving a value. abstract interface class HttpHeaders { static const acceptHeader = "accept"; static const acceptCharsetHeader = "accept-charset"; static const acceptEncodingHeader = "accept-encoding"; static const acceptLanguageHeader = "accept-language"; static const acceptRangesHeader = "accept-ranges"; static const accessControlAllowCredentialsHeader = 'access-control-allow-credentials'; static const accessControlAllowHeadersHeader = 'access-control-allow-headers'; static const accessControlAllowMethodsHeader = 'access-control-allow-methods'; static const accessControlAllowOriginHeader = 'access-control-allow-origin'; static const accessControlExposeHeadersHeader = 'access-control-expose-headers'; static const accessControlMaxAgeHeader = 'access-control-max-age'; static const accessControlRequestHeadersHeader = 'access-control-request-headers'; static const accessControlRequestMethodHeader = 'access-control-request-method'; static const ageHeader = "age"; static const allowHeader = "allow"; static const authorizationHeader = "authorization"; static const cacheControlHeader = "cache-control"; static const connectionHeader = "connection"; static const contentEncodingHeader = "content-encoding"; static const contentLanguageHeader = "content-language"; static const contentLengthHeader = "content-length"; static const contentLocationHeader = "content-location"; static const contentMD5Header = "content-md5"; static const contentRangeHeader = "content-range"; static const contentTypeHeader = "content-type"; static const dateHeader = "date"; static const etagHeader = "etag"; static const expectHeader = "expect"; static const expiresHeader = "expires"; static const fromHeader = "from"; static const hostHeader = "host"; static const ifMatchHeader = "if-match"; static const ifModifiedSinceHeader = "if-modified-since"; static const ifNoneMatchHeader = "if-none-match"; static const ifRangeHeader = "if-range"; static const ifUnmodifiedSinceHeader = "if-unmodified-since"; static const lastModifiedHeader = "last-modified"; static const locationHeader = "location"; static const maxForwardsHeader = "max-forwards"; static const pragmaHeader = "pragma"; static const proxyAuthenticateHeader = "proxy-authenticate"; static const proxyAuthorizationHeader = "proxy-authorization"; static const rangeHeader = "range"; static const refererHeader = "referer"; static const retryAfterHeader = "retry-after"; static const serverHeader = "server"; static const teHeader = "te"; static const trailerHeader = "trailer"; static const transferEncodingHeader = "transfer-encoding"; static const upgradeHeader = "upgrade"; static const userAgentHeader = "user-agent"; static const varyHeader = "vary"; static const viaHeader = "via"; static const warningHeader = "warning"; static const wwwAuthenticateHeader = "www-authenticate"; static const contentDisposition = "content-disposition"; // Cookie headers from RFC 6265. static const cookieHeader = "cookie"; static const setCookieHeader = "set-cookie"; static const generalHeaders = [ cacheControlHeader, connectionHeader, dateHeader, pragmaHeader, trailerHeader, transferEncodingHeader, upgradeHeader, viaHeader, warningHeader ]; static const entityHeaders = [ allowHeader, contentEncodingHeader, contentLanguageHeader, contentLengthHeader, contentLocationHeader, contentMD5Header, contentRangeHeader, contentTypeHeader, expiresHeader, lastModifiedHeader ]; static const responseHeaders = [ acceptRangesHeader, ageHeader, etagHeader, locationHeader, proxyAuthenticateHeader, retryAfterHeader, serverHeader, varyHeader, wwwAuthenticateHeader, contentDisposition ]; static const requestHeaders = [ acceptHeader, acceptCharsetHeader, acceptEncodingHeader, acceptLanguageHeader, authorizationHeader, expectHeader, fromHeader, hostHeader, ifMatchHeader, ifModifiedSinceHeader, ifNoneMatchHeader, ifRangeHeader, ifUnmodifiedSinceHeader, maxForwardsHeader, proxyAuthorizationHeader, rangeHeader, refererHeader, teHeader, userAgentHeader ]; /// The date specified by the [dateHeader] header, if any. DateTime? date; /// The date and time specified by the [expiresHeader] header, if any. DateTime? expires; /// The date and time specified by the [ifModifiedSinceHeader] header, if any. DateTime? ifModifiedSince; /// The value of the [hostHeader] header, if any. String? host; /// The value of the port part of the [hostHeader] header, if any. int? port; /// The [ContentType] of the [contentTypeHeader] header, if any. // ContentType? contentType; /// The value of the [contentLengthHeader] header, if any. /// /// The value is negative if there is no content length set. int contentLength = -1; /// Whether the connection is persistent (keep-alive). late bool persistentConnection; /// Whether the connection uses chunked transfer encoding. /// /// Reflects and modifies the value of the [transferEncodingHeader] header. late bool chunkedTransferEncoding; /// The values for the header named [name]. /// /// Returns null if there is no header with the provided name, /// otherwise returns a new list containing the current values. /// Not that modifying the list does not change the header. List? operator [](String name); /// Convenience method for the value for a single valued header. /// /// The value must not have more than one value. /// /// Returns `null` if there is no header with the provided name. String? value(String name); /// Adds a header value. /// /// The header named [name] will have a string value derived from [value] /// added to its list of values. /// /// Some headers are single valued, and for these, adding a value will /// replace a previous value. If the [value] is a [DateTime], an /// HTTP date format will be applied. If the value is an [Iterable], /// each element will be added separately. For all other /// types the default [Object.toString] method will be used. /// /// Header names are converted to lower-case unless /// [preserveHeaderCase] is set to true. If two header names are /// the same when converted to lower-case, they are considered to be /// the same header, with one set of values. /// /// The current case of the a header name is that of the name used by /// the last [set] or [add] call for that header. void add(String name, Object value, {bool preserveHeaderCase = false}); /// Sets the header [name] to [value]. /// /// Removes all existing values for the header named [name] and /// then [add]s [value] to it. void set(String name, Object value, {bool preserveHeaderCase = false}); /// Removes a specific value for a header name. /// /// Some headers have system supplied values which cannot be removed. /// For all other headers and values, the [value] is converted to a string /// in the same way as for [add], then that string value is removed from the /// current values of [name]. /// If there are no remaining values for [name], the header is no longer /// considered present. void remove(String name, Object value); /// Removes all values for the specified header name. /// /// Some headers have system supplied values which cannot be removed. /// All other values for [name] are removed. /// If there are no remaining values for [name], the header is no longer /// considered present. void removeAll(String name); /// Performs the [action] on each header. /// /// The [action] function is called with each header's name and a list /// of the header's values. The casing of the name string is determined by /// the last [add] or [set] operation for that particular header, /// which defaults to lower-casing the header name unless explicitly /// set to preserve the case. void forEach(void Function(String name, List values) action); /// Disables folding for the header named [name] when sending the HTTP header. /// /// By default, multiple header values are folded into a /// single header line by separating the values with commas. /// /// The 'set-cookie' header has folding disabled by default. void noFolding(String name); /// Removes all headers. /// /// Some headers have system supplied values which cannot be removed. /// All other header values are removed, and header names with not /// remaining values are no longer considered present. void clear(); } ================================================ FILE: lib/get_connect/http/src/response/response.dart ================================================ import 'dart:collection'; import 'dart:convert'; import '../exceptions/exceptions.dart'; import '../request/request.dart'; import '../status/http_status.dart'; class GraphQLResponse extends Response { final List? graphQLErrors; GraphQLResponse({super.body, this.graphQLErrors}); GraphQLResponse.fromResponse(Response res) : graphQLErrors = null, super( request: res.request, statusCode: res.statusCode, bodyBytes: res.bodyBytes, bodyString: res.bodyString, statusText: res.statusText, headers: res.headers, body: res.body['data'] as T?); } class Response { const Response({ this.request, this.statusCode, this.bodyBytes, this.bodyString, this.statusText = '', this.headers = const {}, this.body, }); Response copyWith({ Request? request, int? statusCode, Stream>? bodyBytes, String? bodyString, String? statusText, Map? headers, T? body, }) { return Response( request: request ?? this.request, statusCode: statusCode ?? this.statusCode, bodyBytes: bodyBytes ?? this.bodyBytes, bodyString: bodyString ?? this.bodyString, statusText: statusText ?? this.statusText, headers: headers ?? this.headers, body: body ?? this.body, ); } /// The Http [Request] linked with this [Response]. final Request? request; /// The response headers. final Map? headers; /// The status code returned by the server. final int? statusCode; /// Human-readable context for [statusCode]. final String? statusText; /// [HttpStatus] from [Response]. `status.connectionError` is true /// when statusCode is null. `status.isUnauthorized` is true when /// statusCode is equal `401`. `status.isNotFound` is true when /// statusCode is equal `404`. `status.isServerError` is true when /// statusCode is between `500` and `599`. HttpStatus get status => HttpStatus(statusCode); /// `hasError` is true when statusCode is not between 200 and 299. bool get hasError => status.hasError; /// `isOk` is true when statusCode is between 200 and 299. bool get isOk => !hasError; /// `unauthorized` is true when statusCode is equal `401`. bool get unauthorized => status.isUnauthorized; /// The response body as a Stream of Bytes. final Stream>? bodyBytes; /// The response body as a Stream of Bytes. final String? bodyString; /// The decoded body of this [Response]. You can access the /// body parameters as Map /// Ex: `body['title'];` final T? body; } Future bodyBytesToString( Stream> bodyBytes, Map headers) { return bodyBytes.bytesToString(_encodingForHeaders(headers)); } /// Returns the encoding to use for a response with the given headers. /// /// Defaults to [utf8] if the headers don't specify a charset or if that /// charset is unknown. Encoding _encodingForHeaders(Map headers) => _encodingForCharset(_contentTypeForHeaders(headers).parameters!['charset']); /// Returns the [Encoding] that corresponds to [charset]. /// /// Returns [fallback] if [charset] is null or if no [Encoding] was found that /// corresponds to [charset]. Encoding _encodingForCharset(String? charset, [Encoding fallback = utf8]) { if (charset == null) return fallback; return Encoding.getByName(charset) ?? fallback; } /// Returns the MediaType object for the given headers's content-type. /// /// Defaults to `application/octet-stream`. HeaderValue _contentTypeForHeaders(Map headers) { var contentType = headers['content-type']; if (contentType != null) return HeaderValue.parse(contentType); return HeaderValue('application/octet-stream'); } class HeaderValue { String _value; Map? _parameters; Map? _unmodifiableParameters; HeaderValue([this._value = '', Map? parameters]) { if (parameters != null) { _parameters = HashMap.from(parameters); } } static HeaderValue parse(String value, {String parameterSeparator = ';', String? valueSeparator, bool preserveBackslash = false}) { var result = HeaderValue(); result._parse(value, parameterSeparator, valueSeparator, preserveBackslash); return result; } String get value => _value; void _ensureParameters() { _parameters ??= HashMap(); } Map? get parameters { _ensureParameters(); _unmodifiableParameters ??= UnmodifiableMapView(_parameters!); return _unmodifiableParameters; } @override String toString() { var stringBuffer = StringBuffer(); stringBuffer.write(_value); if (parameters != null && parameters!.isNotEmpty) { _parameters!.forEach((name, value) { stringBuffer ..write('; ') ..write(name) ..write('=') ..write(value); }); } return stringBuffer.toString(); } void _parse(String value, String parameterSeparator, String? valueSeparator, bool preserveBackslash) { var index = 0; bool done() => index == value.length; void bump() { while (!done()) { if (value[index] != ' ' && value[index] != '\t') return; index++; } } String parseValue() { var start = index; while (!done()) { if (value[index] == ' ' || value[index] == '\t' || value[index] == valueSeparator || value[index] == parameterSeparator) { break; } index++; } return value.substring(start, index); } void expect(String expected) { if (done() || value[index] != expected) { throw StateError('Failed to parse header value'); } index++; } void maybeExpect(String expected) { if (value[index] == expected) index++; } void parseParameters() { var parameters = HashMap(); _parameters = UnmodifiableMapView(parameters); String parseParameterName() { var start = index; while (!done()) { if (value[index] == ' ' || value[index] == '\t' || value[index] == '=' || value[index] == parameterSeparator || value[index] == valueSeparator) { break; } index++; } return value.substring(start, index).toLowerCase(); } String? parseParameterValue() { if (!done() && value[index] == '"') { var stringBuffer = StringBuffer(); index++; while (!done()) { if (value[index] == '\\') { if (index + 1 == value.length) { throw StateError('Failed to parse header value'); } if (preserveBackslash && value[index + 1] != '"') { stringBuffer.write(value[index]); } index++; } else if (value[index] == '"') { index++; break; } stringBuffer.write(value[index]); index++; } return stringBuffer.toString(); } else { var val = parseValue(); return val == '' ? null : val; } } while (!done()) { bump(); if (done()) return; var name = parseParameterName(); bump(); if (done()) { parameters[name] = null; return; } maybeExpect('='); bump(); if (done()) { parameters[name] = null; return; } var valueParameter = parseParameterValue(); if (name == 'charset' && valueParameter != null) { valueParameter = valueParameter.toLowerCase(); } parameters[name] = valueParameter; bump(); if (done()) return; if (value[index] == valueSeparator) return; expect(parameterSeparator); } } bump(); _value = parseValue(); bump(); if (done()) return; maybeExpect(parameterSeparator); parseParameters(); } } ================================================ FILE: lib/get_connect/http/src/status/http_status.dart ================================================ class HttpStatus { HttpStatus(this.code); final int? code; static const int continue_ = 100; static const int switchingProtocols = 101; static const int processing = 102; static const int earlyHints = 103; static const int ok = 200; static const int created = 201; static const int accepted = 202; static const int nonAuthoritativeInformation = 203; static const int noContent = 204; static const int resetContent = 205; static const int partialContent = 206; static const int multiStatus = 207; static const int alreadyReported = 208; static const int imUsed = 226; static const int multipleChoices = 300; static const int movedPermanently = 301; static const int found = 302; static const int movedTemporarily = 302; // Common alias for found. static const int seeOther = 303; static const int notModified = 304; static const int useProxy = 305; static const int switchProxy = 306; static const int temporaryRedirect = 307; static const int permanentRedirect = 308; static const int badRequest = 400; static const int unauthorized = 401; static const int paymentRequired = 402; static const int forbidden = 403; static const int notFound = 404; static const int methodNotAllowed = 405; static const int notAcceptable = 406; static const int proxyAuthenticationRequired = 407; static const int requestTimeout = 408; static const int conflict = 409; static const int gone = 410; static const int lengthRequired = 411; static const int preconditionFailed = 412; static const int requestEntityTooLarge = 413; static const int requestUriTooLong = 414; static const int unsupportedMediaType = 415; static const int requestedRangeNotSatisfiable = 416; static const int expectationFailed = 417; static const int imATeapot = 418; static const int misdirectedRequest = 421; static const int unprocessableEntity = 422; static const int locked = 423; static const int failedDependency = 424; static const int tooEarly = 425; static const int upgradeRequired = 426; static const int preconditionRequired = 428; static const int tooManyRequests = 429; static const int requestHeaderFieldsTooLarge = 431; static const int connectionClosedWithoutResponse = 444; static const int unavailableForLegalReasons = 451; static const int clientClosedRequest = 499; static const int internalServerError = 500; static const int notImplemented = 501; static const int badGateway = 502; static const int serviceUnavailable = 503; static const int gatewayTimeout = 504; static const int httpVersionNotSupported = 505; static const int variantAlsoNegotiates = 506; static const int insufficientStorage = 507; static const int loopDetected = 508; static const int notExtended = 510; static const int networkAuthenticationRequired = 511; static const int networkConnectTimeoutError = 599; bool get connectionError => code == null; bool get isUnauthorized => code == unauthorized; bool get isForbidden => code == forbidden; bool get isNotFound => code == notFound; bool get isServerError => between(internalServerError, networkConnectTimeoutError); bool between(int begin, int end) { return !connectionError && code! >= begin && code! <= end; } bool get isOk => between(200, 299); bool get hasError => !isOk; } ================================================ FILE: lib/get_connect/http/src/utils/utils.dart ================================================ // ignore_for_file: constant_identifier_names import 'dart:convert'; bool isTokenChar(int byte) { return byte > 31 && byte < 128 && !SEPARATOR_MAP[byte]; } bool isValueChar(int byte) { return (byte > 31 && byte < 128) || (byte == CharCode.SP) || (byte == CharCode.HT); } class CharCode { static const int HT = 9; static const int LF = 10; static const int CR = 13; static const int SP = 32; static const int COMMA = 44; static const int SLASH = 47; static const int ZERO = 48; static const int ONE = 49; static const int COLON = 58; static const int SEMI_COLON = 59; } const bool F = false; const bool T = true; const SEPARATOR_MAP = [ F, F, F, F, F, F, F, F, F, T, F, F, F, F, F, F, F, F, F, F, F, F, F, F, // F, F, F, F, F, F, F, F, T, F, T, F, F, F, F, F, T, T, F, F, T, F, F, T, // F, F, F, F, F, F, F, F, F, F, T, T, T, T, T, T, T, F, F, F, F, F, F, F, // F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, T, T, T, F, F, // F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, // F, F, F, T, F, T, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, // F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, // F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, // F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, // F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, // F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F ]; String validateField(String field) { for (var i = 0; i < field.length; i++) { if (!isTokenChar(field.codeUnitAt(i))) { throw FormatException( 'Invalid HTTP header field name: ${json.encode(field)}', field, i); } } return field.toLowerCase(); } // Stream> toBodyBytesStream(Stream> stream) { // return (stream); // } final _asciiOnly = RegExp(r'^[\x00-\x7F]+$'); final newlineRegExp = RegExp(r'\r\n|\r|\n'); /// Returns whether [string] is composed entirely of ASCII-compatible /// characters. bool isPlainAscii(String string) => _asciiOnly.hasMatch(string); const String GET_BOUNDARY = 'getx-http-boundary-'; /// Encode [value] like browsers String browserEncode(String value) { return value.replaceAll(newlineRegExp, '%0D%0A').replaceAll('"', '%22'); } const List boundaryCharacters = [ 43, 95, 45, 46, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122 ]; ================================================ FILE: lib/get_connect/sockets/sockets.dart ================================================ import 'src/sockets_stub.dart' if (dart.library.js_interop) 'src/sockets_html.dart' if (dart.library.io) 'src/sockets_io.dart'; class GetSocket extends BaseWebSocket { GetSocket(super.url, {super.ping, super.allowSelfSigned}); } ================================================ FILE: lib/get_connect/sockets/src/socket_notifier.dart ================================================ import 'dart:convert'; /// Signature for [SocketNotifier.addCloses]. typedef CloseSocket = void Function(Close); /// Signature for [SocketNotifier.addMessages]. typedef MessageSocket = void Function(dynamic val); /// Signature for [SocketNotifier.open]. typedef OpenSocket = void Function(); /// Wrapper class to message and reason from SocketNotifier class Close { final String? message; final int? reason; Close(this.message, this.reason); @override String toString() { return 'Closed by server [$reason => $message]!'; } } /// This class manages the transmission of messages over websockets using /// GetConnect class SocketNotifier { List? _onMessages = []; Map? _onEvents = {}; List? _onCloses = []; List? _onErrors = []; late OpenSocket open; /// subscribe to close events void addCloses(CloseSocket socket) { _onCloses!.add(socket); } /// subscribe to error events void addErrors(CloseSocket socket) { _onErrors!.add((socket)); } /// subscribe to named events void addEvents(String event, MessageSocket socket) { _onEvents![event] = socket; } /// subscribe to message events void addMessages(MessageSocket socket) { _onMessages!.add((socket)); } /// Dispose messages, events, closes and errors subscriptions void dispose() { _onMessages = null; _onEvents = null; _onCloses = null; _onErrors = null; } /// Notify all subscriptions on [addCloses] void notifyClose(Close err) { for (var item in _onCloses!) { item(err); } } /// Notify all subscriptions on [addMessages] void notifyData(dynamic data) { for (var item in _onMessages!) { item(data); } if (data is String) { _tryOn(data); } } /// Notify all subscriptions on [addErrors] void notifyError(Close err) { // rooms.removeWhere((key, value) => value.contains(_ws)); for (var item in _onErrors!) { item(err); } } void _tryOn(String message) { try { var msg = jsonDecode(message); final event = msg['type']; final data = msg['data']; if (_onEvents!.containsKey(event)) { _onEvents![event]!(data); } // ignore: avoid_catches_without_on_clauses } catch (_) { return; } } } ================================================ FILE: lib/get_connect/sockets/src/sockets_html.dart ================================================ import 'dart:async'; import 'dart:convert'; import 'dart:js_interop'; import 'package:web/web.dart' as html; import '../../../get_core/get_core.dart'; import 'socket_notifier.dart'; class BaseWebSocket { String url; html.WebSocket? socket; SocketNotifier? socketNotifier = SocketNotifier(); Duration ping; bool isDisposed = false; bool allowSelfSigned; ConnectionStatus? connectionStatus; Timer? _t; BaseWebSocket( this.url, { this.ping = const Duration(seconds: 5), this.allowSelfSigned = true, }) { url = url.startsWith('https') ? url.replaceAll('https:', 'wss:') : url.replaceAll('http:', 'ws:'); } void close([int? status, String? reason]) { // This weird code is fault of web package, they are not using null safety yet // See https://github.com/dart-lang/web/blob/main/web/lib/src/dom/websockets.dart#L60 if (status != null && reason != null) { socket?.close(status, reason); } else if (status != null) { socket?.close(status); } else { socket?.close(); } } // ignore: use_setters_to_change_properties void connect() { try { connectionStatus = ConnectionStatus.connecting; socket = html.WebSocket(url); socket!.onOpen.listen((e) { socketNotifier?.open(); _t = Timer?.periodic(ping, (t) { socket!.send(''.toJSBox); }); connectionStatus = ConnectionStatus.connected; }); socket!.onMessage.listen((event) { socketNotifier!.notifyData(event.data); }); socket!.onClose.listen((e) { _t?.cancel(); connectionStatus = ConnectionStatus.closed; socketNotifier!.notifyClose(Close(e.reason, e.code)); }); socket!.onError.listen((event) { _t?.cancel(); socketNotifier!.notifyError(Close(event.toString(), 0)); connectionStatus = ConnectionStatus.closed; }); } on Exception catch (e) { _t?.cancel(); socketNotifier!.notifyError(Close(e.toString(), 500)); connectionStatus = ConnectionStatus.closed; // close(500, e.toString()); } } void dispose() { socketNotifier!.dispose(); socketNotifier = null; isDisposed = true; } void emit(String event, dynamic data) { send(jsonEncode({'type': event, 'data': data})); } void on(String event, MessageSocket message) { socketNotifier!.addEvents(event, message); } void onClose(CloseSocket fn) { socketNotifier!.addCloses(fn); } void onError(CloseSocket fn) { socketNotifier!.addErrors(fn); } void onMessage(MessageSocket fn) { socketNotifier!.addMessages(fn); } // ignore: use_setters_to_change_properties void onOpen(OpenSocket fn) { socketNotifier!.open = fn; } void send(Object data) { if (connectionStatus == ConnectionStatus.closed) { connect(); } if (socket != null && socket!.readyState == html.WebSocket.OPEN) { socket!.send(data.toJSBox); } else { Get.log('WebSocket not connected, message $data not sent'); } } } enum ConnectionStatus { connecting, connected, closed, } ================================================ FILE: lib/get_connect/sockets/src/sockets_io.dart ================================================ import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'dart:math'; import '../../../get_core/get_core.dart'; import 'socket_notifier.dart'; class BaseWebSocket { String url; WebSocket? socket; SocketNotifier? socketNotifier = SocketNotifier(); bool isDisposed = false; Duration ping; bool allowSelfSigned; ConnectionStatus? connectionStatus; BaseWebSocket( this.url, { this.ping = const Duration(seconds: 5), this.allowSelfSigned = true, }); void close([int? status, String? reason]) { socket?.close(status, reason); } // ignore: use_setters_to_change_properties Future connect() async { if (isDisposed) { socketNotifier = SocketNotifier(); } try { connectionStatus = ConnectionStatus.connecting; socket = allowSelfSigned ? await _connectForSelfSignedCert(url) : await WebSocket.connect(url); socket!.pingInterval = ping; socketNotifier?.open(); connectionStatus = ConnectionStatus.connected; socket!.listen((data) { socketNotifier!.notifyData(data); }, onError: (err) { socketNotifier!.notifyError(Close(err.toString(), 1005)); }, onDone: () { connectionStatus = ConnectionStatus.closed; socketNotifier! .notifyClose(Close('Connection Closed', socket!.closeCode)); }, cancelOnError: true); return; } on SocketException catch (e) { connectionStatus = ConnectionStatus.closed; socketNotifier! .notifyError(Close(e.osError!.message, e.osError!.errorCode)); return; } } void dispose() { socketNotifier!.dispose(); socketNotifier = null; isDisposed = true; } void emit(String event, dynamic data) { send(jsonEncode({'type': event, 'data': data})); } void on(String event, MessageSocket message) { socketNotifier!.addEvents(event, message); } void onClose(CloseSocket fn) { socketNotifier!.addCloses(fn); } void onError(CloseSocket fn) { socketNotifier!.addErrors(fn); } void onMessage(MessageSocket fn) { socketNotifier!.addMessages(fn); } // ignore: use_setters_to_change_properties void onOpen(OpenSocket fn) { socketNotifier!.open = fn; } void send(dynamic data) async { if (connectionStatus == ConnectionStatus.closed) { await connect(); } if (socket != null) { socket!.add(data); } } Future _connectForSelfSignedCert(String url) async { try { var r = Random(); var key = base64.encode(List.generate(8, (_) => r.nextInt(255))); var client = HttpClient(context: SecurityContext()); client.badCertificateCallback = (cert, host, port) { Get.log( 'BaseWebSocket: Allow self-signed certificate => $host:$port. '); return true; }; var request = await client.getUrl(Uri.parse(url)) ..headers.add('Connection', 'Upgrade') ..headers.add('Upgrade', 'websocket') ..headers.add('Cache-Control', 'no-cache') ..headers.add('Sec-WebSocket-Version', '13') ..headers.add('Sec-WebSocket-Key', key.toLowerCase()); var response = await request.close(); // ignore: close_sinks var socket = await response.detachSocket(); var webSocket = WebSocket.fromUpgradedSocket( socket, serverSide: false, ); return webSocket; } on Exception catch (_) { rethrow; } } } enum ConnectionStatus { connecting, connected, closed, } ================================================ FILE: lib/get_connect/sockets/src/sockets_stub.dart ================================================ import './socket_notifier.dart'; class BaseWebSocket { String url; Duration ping; bool allowSelfSigned; BaseWebSocket( this.url, { this.ping = const Duration(seconds: 5), this.allowSelfSigned = true, }) { throw 'To use sockets you need dart:io or dart:html'; } Future connect() async { throw 'To use sockets you need dart:io or dart:html'; } void onOpen(OpenSocket fn) { throw 'To use sockets you need dart:io or dart:html'; } void onClose(CloseSocket fn) { throw 'To use sockets you need dart:io or dart:html'; } void onError(CloseSocket fn) { throw 'To use sockets you need dart:io or dart:html'; } void onMessage(MessageSocket fn) { throw 'To use sockets you need dart:io or dart:html'; } void on(String event, MessageSocket message) { throw 'To use sockets you need dart:io or dart:html'; } void close([int? status, String? reason]) { throw 'To use sockets you need dart:io or dart:html'; } void send(dynamic data) async { throw 'To use sockets you need dart:io or dart:html'; } void dispose() { throw 'To use sockets you need dart:io or dart:html'; } void emit(String event, dynamic data) { throw 'To use sockets you need dart:io or dart:html'; } } ================================================ FILE: lib/get_connect.dart ================================================ export 'get_connect/connect.dart'; ================================================ FILE: lib/get_core/get_core.dart ================================================ library; export 'src/flutter_engine.dart'; export 'src/get_interface.dart'; export 'src/get_main.dart'; export 'src/log.dart'; export 'src/smart_management.dart'; export 'src/typedefs.dart'; ================================================ FILE: lib/get_core/src/flutter_engine.dart ================================================ import 'package:flutter/widgets.dart'; class Engine { static WidgetsBinding get instance { return WidgetsFlutterBinding.ensureInitialized(); } } ================================================ FILE: lib/get_core/src/get_interface.dart ================================================ import 'package:flutter/foundation.dart'; import 'log.dart'; import 'smart_management.dart'; /// GetInterface allows any auxiliary package to be merged into the "Get" /// class through extensions abstract class GetInterface { SmartManagement smartManagement = SmartManagement.full; bool isLogEnable = kDebugMode; LogWriterCallback log = defaultLogWriterCallback; } ================================================ FILE: lib/get_core/src/get_main.dart ================================================ import 'get_interface.dart'; ///Use to instead of Navigator.push, off instead of Navigator.pushReplacement, ///offAll instead of Navigator.pushAndRemoveUntil. For named routes just ///add "named" after them. Example: toNamed, offNamed, and AllNamed. ///To return to the previous screen, use back(). ///No need to pass any context to Get, just put the name of the route inside ///the parentheses and the magic will occur. class _GetImpl extends GetInterface {} // ignore: non_constant_identifier_names final Get = _GetImpl(); ================================================ FILE: lib/get_core/src/log.dart ================================================ import 'dart:developer' as developer; import 'get_main.dart'; ///VoidCallback from logs typedef LogWriterCallback = void Function(String text, {bool isError}); /// default logger from GetX void defaultLogWriterCallback(String value, {bool isError = false}) { if (isError || Get.isLogEnable) developer.log(value, name: 'GETX'); } ================================================ FILE: lib/get_core/src/smart_management.dart ================================================ /// GetX by default disposes unused controllers from memory, /// Through different behaviors. /// SmartManagement.full /// [SmartManagement.full] is the default one. Dispose classes that are /// not being used and were not set to be permanent. In the majority /// of the cases you will want to keep this config untouched. /// If you new to GetX then don't change this. /// [SmartManagement.onlyBuilder] only controllers started in init: /// or loaded into a Binding with Get.lazyPut() will be disposed. If you use /// Get.put() or Get.putAsync() or any other approach, SmartManagement /// will not have permissions to exclude this dependency. With the default /// behavior, even widgets instantiated with "Get.put" will be removed, /// unlike SmartManagement.onlyBuilders. /// [SmartManagement.keepFactory]Just like SmartManagement.full, /// it will remove it's dependencies when it's not being used anymore. /// However, it will keep their factory, which means it will recreate /// the dependency if you need that instance again. enum SmartManagement { full, onlyBuilder, keepFactory, } ================================================ FILE: lib/get_core/src/typedefs.dart ================================================ typedef ValueUpdater = T Function(); ================================================ FILE: lib/get_instance/get_instance.dart ================================================ export 'src/bindings_interface.dart'; export 'src/extension_instance.dart'; export 'src/lifecycle.dart'; ================================================ FILE: lib/get_instance/src/bindings_interface.dart ================================================ // ignore: one_member_abstracts // ignore: one_member_abstracts abstract class BindingsInterface { T dependencies(); } /// [Bindings] should be extended or implemented. /// When using `GetMaterialApp`, all `GetPage`s and navigation /// methods (like Get.to()) have a `binding` property that takes an /// instance of Bindings to manage the /// dependencies() (via Get.put()) for the Route you are opening. // ignore: one_member_abstracts @Deprecated('Use Binding instead') abstract class Bindings extends BindingsInterface { @override void dependencies(); } // /// Simplifies Bindings generation from a single callback. // /// To avoid the creation of a custom Binding instance per route. // /// // /// Example: // /// ``` // /// GetPage( // /// name: '/', // /// page: () => Home(), // /// // This might cause you an error. // /// // binding: BindingsBuilder(() => Get.put(HomeController())), // /// binding: BindingsBuilder(() { Get.put(HomeController(); })), // /// // Using .lazyPut() works fine. // /// // binding: BindingsBuilder(() => Get.lazyPut(() => HomeController())), // /// ), // /// ``` // class BindingsBuilder extends Bindings { // /// Register your dependencies in the [builder] callback. // final BindingBuilderCallback builder; // /// Shortcut to register 1 Controller with Get.put(), // /// Prevents the issue of the fat arrow function with the constructor. // /// BindingsBuilder(() => Get.put(HomeController())), // /// // /// Sample: // /// ``` // /// GetPage( // /// name: '/', // /// page: () => Home(), // /// binding: BindingsBuilder.put(() => HomeController()), // /// ), // /// ``` // factory BindingsBuilder.put(InstanceBuilderCallback builder, // {String? tag, bool permanent = false}) { // return BindingsBuilder( // () => Get.put(builder(), tag: tag, permanent: permanent)); // } // /// WARNING: don't use `()=> Get.put(Controller())`, // /// if only passing 1 callback use `BindingsBuilder.put(Controller())` // /// or `BindingsBuilder(()=> Get.lazyPut(Controller()))` // BindingsBuilder(this.builder); // @override // void dependencies() { // builder(); // } // } typedef BindingBuilderCallback = void Function(); ================================================ FILE: lib/get_instance/src/extension_instance.dart ================================================ import 'dart:async'; import 'package:flutter/material.dart'; import '../../get_core/get_core.dart'; import '../../get_navigation/src/router_report.dart'; import 'lifecycle.dart'; class InstanceInfo { final bool? isPermanent; final bool? isSingleton; bool get isCreate => !isSingleton!; final bool isRegistered; final bool isPrepared; final bool? isInit; const InstanceInfo({ required this.isPermanent, required this.isSingleton, required this.isRegistered, required this.isPrepared, required this.isInit, }); @override String toString() { return 'InstanceInfo(isPermanent: $isPermanent, isSingleton: $isSingleton, isRegistered: $isRegistered, isPrepared: $isPrepared, isInit: $isInit)'; } } extension ResetInstance on GetInterface { /// Clears all registered instances (and/or tags). /// Even the persistent ones. /// This should be used at the end or tearDown of unit tests. /// /// `clearFactory` clears the callbacks registered by [lazyPut] /// `clearRouteBindings` clears Instances associated with routes. /// bool resetInstance({bool clearRouteBindings = true}) { // if (clearFactory) _factory.clear(); // deleteAll(force: true); if (clearRouteBindings) RouterReportManager.instance.clearRouteKeys(); Inst._singl.clear(); return true; } } extension Inst on GetInterface { T call() => find(); /// Holds references to every registered Instance when using /// `Get.put()` static final Map _singl = {}; /// Holds a reference to every registered callback when using /// `Get.lazyPut()` // static final Map _factory = {}; // void injector( // InjectorBuilderCallback fn, { // String? tag, // bool fenix = false, // // bool permanent = false, // }) { // lazyPut( // () => fn(this), // tag: tag, // fenix: fenix, // // permanent: permanent, // ); // } S put( S dependency, { String? tag, bool permanent = false, }) { _insert( isSingleton: true, name: tag, permanent: permanent, builder: (() => dependency)); return find(tag: tag); } /// Creates a new Instance lazily from the `builder()` callback. /// /// The first time you call `Get.find()`, the `builder()` callback will create /// the Instance and persisted as a Singleton (like you would /// use `Get.put()`). /// /// Using `Get.smartManagement` as [SmartManagement.keepFactory] has /// the same outcome as using `fenix:true` : /// The internal register of `builder()` will remain in memory to recreate /// the Instance if the Instance has been removed with `Get.delete()`. /// Therefore, future calls to `Get.find()` will return the same Instance. /// /// If you need to make use of GetxController's life-cycle /// (`onInit(), onStart(), onClose()`) [fenix] is a great choice to mix with /// `GetBuilder()` and `GetX()` widgets, and/or `GetMaterialApp` Navigation. /// /// You could use `Get.lazyPut(fenix:true)` in your app's `main()` instead /// of `Bindings()` for each `GetPage`. /// And the memory management will be similar. /// /// Subsequent calls to `Get.lazyPut()` with the same parameters /// (<[S]> and optionally [tag] will **not** override the original). void lazyPut( InstanceBuilderCallback builder, { String? tag, bool? fenix, bool permanent = false, }) { _insert( isSingleton: true, name: tag, permanent: permanent, builder: builder, fenix: fenix ?? Get.smartManagement == SmartManagement.keepFactory, ); } /// Creates a new Class Instance [S] from the builder callback[S]. /// Every time [find]<[S]>() is used, it calls the builder method to generate /// a new Instance [S]. /// It also registers each `instance.onClose()` with the current /// Route `Get.reference` to keep the lifecycle active. /// Is important to know that the instances created are only stored per Route. /// So, if you call `Get.delete()` the "instance factory" used in this /// method (`Get.spawn()`) will be removed, but NOT the instances /// already created by it. /// /// Example: /// /// ```Get.spawn(() => Repl()); /// Repl a = find(); /// Repl b = find(); /// print(a==b); (false)``` void spawn( InstanceBuilderCallback builder, { String? tag, bool permanent = true, }) { _insert( isSingleton: false, name: tag, builder: builder, permanent: permanent, ); } /// Injects the Instance [S] builder into the `_singleton` HashMap. void _insert({ bool? isSingleton, String? name, bool permanent = false, required InstanceBuilderCallback builder, bool fenix = false, }) { final key = _getKey(S, name); _InstanceBuilderFactory? dep; if (_singl.containsKey(key)) { final newDep = _singl[key]; if (newDep == null || !newDep.isDirty) { return; } else { dep = newDep as _InstanceBuilderFactory; } } _singl[key] = _InstanceBuilderFactory( isSingleton: isSingleton, builderFunc: builder, permanent: permanent, isInit: false, fenix: fenix, tag: name, lateRemove: dep, ); } /// Initializes the dependencies for a Class Instance [S] (or tag), /// If its a Controller, it starts the lifecycle process. /// Optionally associating the current Route to the lifetime of the instance, /// if `Get.smartManagement` is marked as [SmartManagement.full] or /// [SmartManagement.keepFactory] /// Only flags `isInit` if it's using `Get.create()` /// (not for Singletons access). /// Returns the instance if not initialized, required for Get.create() to /// work properly. S? _initDependencies({String? name}) { final key = _getKey(S, name); final isInit = _singl[key]!.isInit; S? i; if (!isInit) { final isSingleton = _singl[key]?.isSingleton ?? false; if (isSingleton) { _singl[key]!.isInit = true; } i = _startController(tag: name); if (isSingleton) { if (Get.smartManagement != SmartManagement.onlyBuilder) { RouterReportManager.instance .reportDependencyLinkedToRoute(_getKey(S, name)); } } } return i; } InstanceInfo getInstanceInfo({String? tag}) { final build = _getDependency(tag: tag); return InstanceInfo( isPermanent: build?.permanent, isSingleton: build?.isSingleton, isRegistered: isRegistered(tag: tag), isPrepared: !(build?.isInit ?? true), isInit: build?.isInit, ); } _InstanceBuilderFactory? _getDependency({String? tag, String? key}) { final newKey = key ?? _getKey(S, tag); if (!_singl.containsKey(newKey)) { Get.log('Instance "$newKey" is not registered.', isError: true); return null; } else { return _singl[newKey]; } } void markAsDirty({String? tag, String? key}) { final newKey = key ?? _getKey(S, tag); if (_singl.containsKey(newKey)) { final dep = _singl[newKey]; if (dep != null && !dep.permanent) { dep.isDirty = true; } } } /// Initializes the controller S _startController({String? tag}) { final key = _getKey(S, tag); final i = _singl[key]!.getDependency() as S; if (i is GetLifeCycleMixin) { i.onStart(); if (tag == null) { Get.log('Instance "$S" has been initialized'); } else { Get.log('Instance "$S" with tag "$tag" has been initialized'); } if (!_singl[key]!.isSingleton!) { RouterReportManager.instance.appendRouteByCreate(i); } } return i; } S putOrFind(InstanceBuilderCallback dep, {String? tag}) { final key = _getKey(S, tag); if (_singl.containsKey(key)) { return _singl[key]!.getDependency() as S; } else { return put(dep(), tag: tag); } } /// Finds the registered type <[S]> (or [tag]) /// In case of using Get.[create] to register a type <[S]> or [tag], /// it will create an instance each time you call [find]. /// If the registered type <[S]> (or [tag]) is a Controller, /// it will initialize it's lifecycle. S find({String? tag}) { final key = _getKey(S, tag); if (isRegistered(tag: tag)) { final dep = _singl[key]; if (dep == null) { if (tag == null) { throw 'Class "$S" is not registered'; } else { throw 'Class "$S" with tag "$tag" is not registered'; } } /// although dirty solution, the lifecycle starts inside /// `initDependencies`, so we have to return the instance from there /// to make it compatible with `Get.create()`. final i = _initDependencies(name: tag); return i ?? dep.getDependency() as S; } else { // ignore: lines_longer_than_80_chars throw '"$S" not found. You need to call "Get.put($S())" or "Get.lazyPut(()=>$S())"'; } } /// The findOrNull method will return the instance if it is registered; /// otherwise, it will return null. S? findOrNull({String? tag}) { if (isRegistered(tag: tag)) { return find(tag: tag); } return null; } /// Replace a parent instance of a class in dependency management /// with a [child] instance /// - [tag] optional, if you use a [tag] to register the Instance. void replace

(P child, {String? tag}) { final info = getInstanceInfo

(tag: tag); final permanent = (info.isPermanent ?? false); delete

(tag: tag, force: permanent); put(child, tag: tag, permanent: permanent); } /// Replaces a parent instance with a new Instance

lazily from the /// `

builder()` callback. /// - [tag] optional, if you use a [tag] to register the Instance. /// - [fenix] optional /// /// Note: if fenix is not provided it will be set to true if /// the parent instance was permanent void lazyReplace

(InstanceBuilderCallback

builder, {String? tag, bool? fenix}) { final info = getInstanceInfo

(tag: tag); final permanent = (info.isPermanent ?? false); delete

(tag: tag, force: permanent); lazyPut(builder, tag: tag, fenix: fenix ?? permanent); } /// Generates the key based on [type] (and optionally a [name]) /// to register an Instance Builder in the hashmap. String _getKey(Type type, String? name) { return name == null ? type.toString() : type.toString() + name; } /// Delete registered Class Instance [S] (or [tag]) and, closes any open /// controllers `DisposableInterface`, cleans up the memory /// /// /// Deletes the Instance<[S]>, cleaning the memory. // /// // /// - [tag] Optional "tag" used to register the Instance // /// - [key] For internal usage, is the processed key used to register // /// the Instance. **don't use** it unless you know what you are doing. /// Deletes the Instance<[S]>, cleaning the memory and closes any open /// controllers (`DisposableInterface`). /// /// - [tag] Optional "tag" used to register the Instance /// - [key] For internal usage, is the processed key used to register /// the Instance. **don't use** it unless you know what you are doing. /// - [force] Will delete an Instance even if marked as `permanent`. bool delete({String? tag, String? key, bool force = false}) { final newKey = key ?? _getKey(S, tag); if (!_singl.containsKey(newKey)) { Get.log('Instance "$newKey" already removed.', isError: true); return false; } final dep = _singl[newKey]; if (dep == null) return false; final _InstanceBuilderFactory builder; if (dep.isDirty) { builder = dep.lateRemove ?? dep; } else { builder = dep; } if (builder.permanent && !force) { Get.log( // ignore: lines_longer_than_80_chars '"$newKey" has been marked as permanent, SmartManagement is not authorized to delete it.', isError: true, ); return false; } final i = builder.dependency; if (i is GetxServiceMixin && !force) { return false; } if (i is GetLifeCycleMixin) { i.onDelete(); Get.log('"$newKey" onDelete() called'); } if (builder.fenix) { builder.dependency = null; builder.isInit = false; return true; } else { if (dep.lateRemove != null) { dep.lateRemove = null; Get.log('"$newKey" deleted from memory'); return false; } else { _singl.remove(newKey); if (_singl.containsKey(newKey)) { Get.log('Error removing object "$newKey"', isError: true); } else { Get.log('"$newKey" deleted from memory'); } return true; } } } /// Delete all registered Class Instances and, closes any open /// controllers `DisposableInterface`, cleans up the memory /// /// - [force] Will delete the Instances even if marked as `permanent`. void deleteAll({bool force = false}) { final keys = _singl.keys.toList(); for (final key in keys) { delete(key: key, force: force); } } void reloadAll({bool force = false}) { _singl.forEach((key, value) { if (value.permanent && !force) { Get.log('Instance "$key" is permanent. Skipping reload'); } else { value.dependency = null; value.isInit = false; Get.log('Instance "$key" was reloaded.'); } }); } void reload({ String? tag, String? key, bool force = false, }) { final newKey = key ?? _getKey(S, tag); final builder = _getDependency(tag: tag, key: newKey); if (builder == null) return; if (builder.permanent && !force) { Get.log( '''Instance "$newKey" is permanent. Use [force = true] to force the restart.''', isError: true, ); return; } final i = builder.dependency; if (i is GetxServiceMixin && !force) { return; } if (i is GetLifeCycleMixin) { i.onDelete(); Get.log('"$newKey" onDelete() called'); } builder.dependency = null; builder.isInit = false; Get.log('Instance "$newKey" was restarted.'); } /// Check if a Class Instance<[S]> (or [tag]) is registered in memory. /// - [tag] is optional, if you used a [tag] to register the Instance. bool isRegistered({String? tag}) => _singl.containsKey(_getKey(S, tag)); /// Checks if a lazy factory callback `Get.lazyPut()` that returns an /// Instance<[S]> is registered in memory. /// - [tag] is optional, if you used a [tag] to register the lazy Instance. bool isPrepared({String? tag}) { final newKey = _getKey(S, tag); final builder = _getDependency(tag: tag, key: newKey); if (builder == null) { return false; } if (!builder.isInit) { return true; } return false; } } typedef InstanceBuilderCallback = S Function(); typedef InstanceCreateBuilderCallback = S Function(BuildContext _); // typedef InstanceBuilderCallback = S Function(); // typedef InjectorBuilderCallback = S Function(Inst); typedef AsyncInstanceBuilderCallback = Future Function(); /// Internal class to register instances with `Get.put()`. class _InstanceBuilderFactory { /// Marks the Builder as a single instance. /// For reusing [dependency] instead of [builderFunc] bool? isSingleton; /// When fenix mode is available, when a new instance is need /// Instance manager will recreate a new instance of S bool fenix; /// Stores the actual object instance when [isSingleton]=true. S? dependency; /// Generates (and regenerates) the instance when [isSingleton]=false. /// Usually used by factory methods InstanceBuilderCallback builderFunc; /// Flag to persist the instance in memory, /// without considering `Get.smartManagement` bool permanent = false; bool isInit = false; _InstanceBuilderFactory? lateRemove; bool isDirty = false; String? tag; _InstanceBuilderFactory({ required this.isSingleton, required this.builderFunc, required this.permanent, required this.isInit, required this.fenix, required this.tag, required this.lateRemove, }); void _showInitLog() { if (tag == null) { Get.log('Instance "$S" has been created'); } else { Get.log('Instance "$S" has been created with tag "$tag"'); } } /// Gets the actual instance by it's [builderFunc] or the persisted instance. S getDependency() { if (isSingleton!) { if (dependency == null) { _showInitLog(); dependency = builderFunc(); } return dependency!; } else { return builderFunc(); } } } ================================================ FILE: lib/get_instance/src/lifecycle.dart ================================================ import 'package:flutter/foundation.dart'; import '../../get.dart'; /// The [GetLifeCycle] /// /// ```dart /// class SomeController with GetLifeCycle { /// SomeController() { /// configureLifeCycle(); /// } /// } /// ``` mixin GetLifeCycleMixin { /// Called immediately after the widget is allocated in memory. /// You might use this to initialize something for the controller. @protected @mustCallSuper void onInit() { Engine.instance.addPostFrameCallback((_) => onReady()); } /// Called 1 frame after onInit(). It is the perfect place to enter /// navigation events, like snackbar, dialogs, or a new route, or /// async request. void onReady() {} /// Called before [onDelete] method. [onClose] might be used to /// dispose resources used by the controller. Like closing events, /// or streams before the controller is destroyed. /// Or dispose objects that can potentially create some memory leaks, /// like TextEditingControllers, AnimationControllers. /// Might be useful as well to persist some data on disk. void onClose() {} bool _initialized = false; /// Checks whether the controller has already been initialized. bool get initialized => _initialized; /// Called at the exact moment the widget is allocated in memory. /// It uses an internal "callable" type, to avoid any @overrides in subclasses. /// This method should be internal and is required to define the /// lifetime cycle of the subclass. // @protected @mustCallSuper @nonVirtual void onStart() { // _checkIfAlreadyConfigured(); if (_initialized) return; onInit(); _initialized = true; } bool _isClosed = false; /// Checks whether the controller has already been closed. bool get isClosed => _isClosed; // Called when the controller is removed from memory. @mustCallSuper @nonVirtual void onDelete() { if (_isClosed) return; _isClosed = true; onClose(); } // void _checkIfAlreadyConfigured() { // if (_initialized) { // throw """You can only call configureLifeCycle once. // The proper place to insert it is in your class's constructor // that inherits GetLifeCycle."""; // } // } } /// Allow track difference between GetxServices and GetxControllers mixin GetxServiceMixin {} /// Unlike GetxController, which serves to control events on each of its pages, /// GetxService is not automatically disposed (nor can be removed with /// Get.delete()). /// It is ideal for situations where, once started, that service will /// remain in memory, such as Auth control for example. Only way to remove /// it is Get.reset(). abstract class GetxService with GetLifeCycleMixin, GetxServiceMixin {} ================================================ FILE: lib/get_navigation/get_navigation.dart ================================================ library; export 'src/bottomsheet/bottomsheet.dart'; export 'src/extension_navigation.dart'; export 'src/root/get_cupertino_app.dart'; export 'src/root/get_material_app.dart'; export 'src/root/internacionalization.dart'; export 'src/routes/custom_transition.dart'; export 'src/routes/default_route.dart'; export 'src/routes/get_route.dart'; export 'src/routes/index.dart'; export 'src/routes/observers/route_observer.dart'; export 'src/routes/route_middleware.dart'; export 'src/routes/transitions_type.dart'; export 'src/snackbar/snackbar.dart'; export 'src/snackbar/snackbar_controller.dart'; ================================================ FILE: lib/get_navigation/src/bottomsheet/bottomsheet.dart ================================================ import 'package:flutter/material.dart'; import '../../../get.dart'; import '../router_report.dart'; class GetModalBottomSheetRoute extends PopupRoute { GetModalBottomSheetRoute({ this.builder, this.theme, this.barrierLabel, this.backgroundColor, this.isPersistent, this.elevation, this.shape, this.removeTop = true, this.clipBehavior, this.modalBarrierColor, this.isDismissible = true, this.enableDrag = true, required this.isScrollControlled, super.settings, this.enterBottomSheetDuration = const Duration(milliseconds: 250), this.exitBottomSheetDuration = const Duration(milliseconds: 200), this.curve, }) { RouterReportManager.instance.reportCurrentRoute(this); } final bool? isPersistent; final WidgetBuilder? builder; final ThemeData? theme; final bool isScrollControlled; final Color? backgroundColor; final double? elevation; final ShapeBorder? shape; final Clip? clipBehavior; final Color? modalBarrierColor; final bool isDismissible; final bool enableDrag; // final String name; final Duration enterBottomSheetDuration; final Duration exitBottomSheetDuration; final Curve? curve; // remove safearea from top final bool removeTop; @override Duration get transitionDuration => const Duration(milliseconds: 700); @override bool get barrierDismissible => isDismissible; @override final String? barrierLabel; @override Color get barrierColor => modalBarrierColor ?? Colors.black54; AnimationController? _animationController; @override void dispose() { RouterReportManager.instance.reportRouteDispose(this); super.dispose(); } @override Animation createAnimation() { if (curve != null) { return CurvedAnimation(curve: curve!, parent: _animationController!.view); } return _animationController!.view; } @override AnimationController createAnimationController() { assert(_animationController == null); _animationController = BottomSheet.createAnimationController(navigator!.overlay!); _animationController!.duration = enterBottomSheetDuration; _animationController!.reverseDuration = exitBottomSheetDuration; return _animationController!; } @override Widget buildPage(BuildContext context, Animation animation, Animation secondaryAnimation) { final sheetTheme = theme?.bottomSheetTheme ?? Theme.of(context).bottomSheetTheme; // By definition, the bottom sheet is aligned to the bottom of the page // and isn't exposed to the top padding of the MediaQuery. Widget bottomSheet = MediaQuery.removePadding( context: context, removeTop: removeTop, child: Padding( padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom), child: _GetModalBottomSheet( route: this, backgroundColor: backgroundColor ?? sheetTheme.modalBackgroundColor ?? sheetTheme.backgroundColor, elevation: elevation ?? sheetTheme.modalElevation ?? sheetTheme.elevation, shape: shape, clipBehavior: clipBehavior, isScrollControlled: isScrollControlled, enableDrag: enableDrag, ), ), ); if (theme != null) bottomSheet = Theme(data: theme!, child: bottomSheet); return bottomSheet; } } class _GetModalBottomSheet extends StatefulWidget { const _GetModalBottomSheet({ super.key, this.route, this.backgroundColor, this.elevation, this.shape, this.clipBehavior, this.isScrollControlled = false, this.enableDrag = true, this.isPersistent = false, }); final bool isPersistent; final GetModalBottomSheetRoute? route; final bool isScrollControlled; final Color? backgroundColor; final double? elevation; final ShapeBorder? shape; final Clip? clipBehavior; final bool enableDrag; @override _GetModalBottomSheetState createState() => _GetModalBottomSheetState(); } class _GetModalBottomSheetState extends State<_GetModalBottomSheet> { String _getRouteLabel(MaterialLocalizations localizations) { if ((Theme.of(context).platform == TargetPlatform.android) || (Theme.of(context).platform == TargetPlatform.fuchsia)) { return localizations.dialogLabel; } else { return ''; } } @override Widget build(BuildContext context) { assert(debugCheckHasMediaQuery(context)); assert(debugCheckHasMaterialLocalizations(context)); final mediaQuery = MediaQuery.of(context); final localizations = MaterialLocalizations.of(context); final routeLabel = _getRouteLabel(localizations); return AnimatedBuilder( animation: widget.route!.animation!, builder: (context, child) { // Disable the initial animation when accessible navigation is on so // that the semantics are added to the tree at the correct time. final animationValue = mediaQuery.accessibleNavigation ? 1.0 : widget.route!.animation!.value; return Semantics( scopesRoute: true, namesRoute: true, label: routeLabel, explicitChildNodes: true, child: ClipRect( child: CustomSingleChildLayout( delegate: _GetModalBottomSheetLayout( animationValue, widget.isScrollControlled), child: widget.isPersistent == false ? BottomSheet( animationController: widget.route!._animationController, onClosing: () { if (widget.route!.isCurrent) { Navigator.pop(context); } }, builder: widget.route!.builder!, backgroundColor: widget.backgroundColor, elevation: widget.elevation, shape: widget.shape, clipBehavior: widget.clipBehavior, enableDrag: widget.enableDrag, ) : Scaffold( bottomSheet: BottomSheet( animationController: widget.route!._animationController, onClosing: () { // if (widget.route.isCurrent) { // Navigator.pop(context); // } }, builder: widget.route!.builder!, backgroundColor: widget.backgroundColor, elevation: widget.elevation, shape: widget.shape, clipBehavior: widget.clipBehavior, enableDrag: widget.enableDrag, ), )), ), ); }, ); } } class _GetPerModalBottomSheet extends StatefulWidget { const _GetPerModalBottomSheet({ super.key, this.route, this.isPersistent, this.backgroundColor, this.elevation, this.shape, this.clipBehavior, this.isScrollControlled = false, this.enableDrag = true, }); final bool? isPersistent; final GetModalBottomSheetRoute? route; final bool isScrollControlled; final Color? backgroundColor; final double? elevation; final ShapeBorder? shape; final Clip? clipBehavior; final bool enableDrag; @override // ignore: lines_longer_than_80_chars _GetPerModalBottomSheetState createState() => _GetPerModalBottomSheetState(); } // ignore: lines_longer_than_80_chars class _GetPerModalBottomSheetState extends State<_GetPerModalBottomSheet> { String _getRouteLabel(MaterialLocalizations localizations) { if ((Theme.of(context).platform == TargetPlatform.android) || (Theme.of(context).platform == TargetPlatform.fuchsia)) { return localizations.dialogLabel; } else { return ''; } } @override Widget build(BuildContext context) { assert(debugCheckHasMediaQuery(context)); assert(debugCheckHasMaterialLocalizations(context)); final mediaQuery = MediaQuery.of(context); final localizations = MaterialLocalizations.of(context); final routeLabel = _getRouteLabel(localizations); return AnimatedBuilder( animation: widget.route!.animation!, builder: (context, child) { // Disable the initial animation when accessible navigation is on so // that the semantics are added to the tree at the correct time. final animationValue = mediaQuery.accessibleNavigation ? 1.0 : widget.route!.animation!.value; return Semantics( scopesRoute: true, namesRoute: true, label: routeLabel, explicitChildNodes: true, child: ClipRect( child: CustomSingleChildLayout( delegate: _GetModalBottomSheetLayout( animationValue, widget.isScrollControlled), child: widget.isPersistent == false ? BottomSheet( animationController: widget.route!._animationController, onClosing: () { if (widget.route!.isCurrent) { Navigator.pop(context); } }, builder: widget.route!.builder!, backgroundColor: widget.backgroundColor, elevation: widget.elevation, shape: widget.shape, clipBehavior: widget.clipBehavior, enableDrag: widget.enableDrag, ) : Scaffold( bottomSheet: BottomSheet( animationController: widget.route!._animationController, onClosing: () { // if (widget.route.isCurrent) { // Navigator.pop(context); // } }, builder: widget.route!.builder!, backgroundColor: widget.backgroundColor, elevation: widget.elevation, shape: widget.shape, clipBehavior: widget.clipBehavior, enableDrag: widget.enableDrag, ), )), ), ); }, ); } } class _GetModalBottomSheetLayout extends SingleChildLayoutDelegate { _GetModalBottomSheetLayout(this.progress, this.isScrollControlled); final double progress; final bool isScrollControlled; @override BoxConstraints getConstraintsForChild(BoxConstraints constraints) { return BoxConstraints( minWidth: constraints.maxWidth, maxWidth: constraints.maxWidth, minHeight: 0.0, maxHeight: isScrollControlled ? constraints.maxHeight : constraints.maxHeight * 9.0 / 16.0, ); } @override Offset getPositionForChild(Size size, Size childSize) { return Offset(0.0, size.height - childSize.height * progress); } @override bool shouldRelayout(_GetModalBottomSheetLayout oldDelegate) { return progress != oldDelegate.progress; } } ================================================ FILE: lib/get_navigation/src/dialog/dialog_route.dart ================================================ import 'package:flutter/widgets.dart'; import '../router_report.dart'; class GetDialogRoute extends PopupRoute { GetDialogRoute({ required RoutePageBuilder pageBuilder, bool barrierDismissible = true, String? barrierLabel, Color barrierColor = const Color(0x80000000), Duration transitionDuration = const Duration(milliseconds: 200), RouteTransitionsBuilder? transitionBuilder, super.settings, }) : widget = pageBuilder, _barrierDismissible = barrierDismissible, _barrierLabel = barrierLabel, _barrierColor = barrierColor, _transitionDuration = transitionDuration, _transitionBuilder = transitionBuilder { RouterReportManager.instance.reportCurrentRoute(this); } final RoutePageBuilder widget; @override bool get barrierDismissible => _barrierDismissible; final bool _barrierDismissible; @override void dispose() { RouterReportManager.instance.reportRouteDispose(this); super.dispose(); } @override String? get barrierLabel => _barrierLabel; final String? _barrierLabel; @override Color get barrierColor => _barrierColor; final Color _barrierColor; @override Duration get transitionDuration => _transitionDuration; final Duration _transitionDuration; final RouteTransitionsBuilder? _transitionBuilder; @override Widget buildPage(BuildContext context, Animation animation, Animation secondaryAnimation) { return Semantics( scopesRoute: true, explicitChildNodes: true, child: widget(context, animation, secondaryAnimation), ); } @override Widget buildTransitions(BuildContext context, Animation animation, Animation secondaryAnimation, Widget child) { if (_transitionBuilder == null) { return FadeTransition( opacity: CurvedAnimation( parent: animation, curve: Curves.linear, ), child: child); } // Some default transition return _transitionBuilder(context, animation, secondaryAnimation, child); } } ================================================ FILE: lib/get_navigation/src/extension_navigation.dart ================================================ import 'dart:ui' as ui; import 'package:flutter/material.dart'; import 'package:get/get_navigation/src/routes/test_kit.dart'; import '../../get.dart'; import 'dialog/dialog_route.dart'; import 'root/get_root.dart'; /// It replaces the Flutter Navigator, but needs no context. /// You can to use navigator.push(YourRoute()) rather /// Navigator.push(context, YourRoute()); NavigatorState? get navigator => GetNavigationExt(Get).key.currentState; extension ExtensionBottomSheet on GetInterface { Future bottomSheet( Widget bottomsheet, { Color? backgroundColor, double? elevation, bool persistent = true, ShapeBorder? shape, Clip? clipBehavior, Color? barrierColor, bool? ignoreSafeArea, bool isScrollControlled = false, bool useRootNavigator = false, bool isDismissible = true, bool enableDrag = true, RouteSettings? settings, Duration? enterBottomSheetDuration, Duration? exitBottomSheetDuration, Curve? curve, }) { return Navigator.of(overlayContext!, rootNavigator: useRootNavigator) .push(GetModalBottomSheetRoute( builder: (_) => bottomsheet, isPersistent: persistent, // theme: Theme.of(key.currentContext, shadowThemeOnly: true), theme: Theme.of(key.currentContext!), isScrollControlled: isScrollControlled, barrierLabel: MaterialLocalizations.of(key.currentContext!) .modalBarrierDismissLabel, backgroundColor: backgroundColor ?? Colors.transparent, elevation: elevation, shape: shape, removeTop: ignoreSafeArea ?? true, clipBehavior: clipBehavior, isDismissible: isDismissible, modalBarrierColor: barrierColor, settings: settings, enableDrag: enableDrag, enterBottomSheetDuration: enterBottomSheetDuration ?? const Duration(milliseconds: 250), exitBottomSheetDuration: exitBottomSheetDuration ?? const Duration(milliseconds: 200), curve: curve, )); } } extension ExtensionDialog on GetInterface { /// Show a dialog. /// You can pass a [transitionDuration] and/or [transitionCurve], /// overriding the defaults when the dialog shows up and closes. /// When the dialog closes, uses those animations in reverse. Future dialog( Widget widget, { bool barrierDismissible = true, Color? barrierColor, bool useSafeArea = true, GlobalKey? navigatorKey, Object? arguments, Duration? transitionDuration, Curve? transitionCurve, String? name, RouteSettings? routeSettings, String? id, }) { assert(debugCheckHasMaterialLocalizations(context!)); // final theme = Theme.of(context, shadowThemeOnly: true); final theme = Theme.of(context!); return generalDialog( pageBuilder: (buildContext, animation, secondaryAnimation) { final pageChild = widget; Widget dialog = Builder(builder: (context) { return Theme(data: theme, child: pageChild); }); if (useSafeArea) { dialog = SafeArea(child: dialog); } return dialog; }, barrierDismissible: barrierDismissible, barrierLabel: MaterialLocalizations.of(context!).modalBarrierDismissLabel, barrierColor: barrierColor ?? Colors.black54, transitionDuration: transitionDuration ?? defaultDialogTransitionDuration, transitionBuilder: (context, animation, secondaryAnimation, child) { return FadeTransition( opacity: CurvedAnimation( parent: animation, curve: transitionCurve ?? defaultDialogTransitionCurve, ), child: child, ); }, navigatorKey: navigatorKey, routeSettings: routeSettings ?? RouteSettings(arguments: arguments, name: name), id: id, ); } /// Api from showGeneralDialog with no context Future generalDialog( {required RoutePageBuilder pageBuilder, bool barrierDismissible = false, String? barrierLabel, Color barrierColor = const Color(0x80000000), Duration transitionDuration = const Duration(milliseconds: 200), RouteTransitionsBuilder? transitionBuilder, GlobalKey? navigatorKey, RouteSettings? routeSettings, String? id}) { assert(!barrierDismissible || barrierLabel != null); final key = navigatorKey ?? Get.nestedKey(id)?.navigatorKey; final nav = key?.currentState ?? Navigator.of(overlayContext!, rootNavigator: true); //overlay context will always return the root navigator return nav.push( GetDialogRoute( pageBuilder: pageBuilder, barrierDismissible: barrierDismissible, barrierLabel: barrierLabel, barrierColor: barrierColor, transitionDuration: transitionDuration, transitionBuilder: transitionBuilder, settings: routeSettings, ), ); } /// Custom UI Dialog. Future defaultDialog({ String title = "Alert", EdgeInsetsGeometry? titlePadding, TextStyle? titleStyle, Widget? content, String? id, EdgeInsetsGeometry? contentPadding, VoidCallback? onConfirm, VoidCallback? onCancel, VoidCallback? onCustom, Color? cancelTextColor, Color? confirmTextColor, String? textConfirm, String? textCancel, String? textCustom, Widget? confirm, Widget? cancel, Widget? custom, Color? backgroundColor, bool barrierDismissible = true, Color? buttonColor, String middleText = "\n", TextStyle? middleTextStyle, double radius = 20.0, // ThemeData themeData, List? actions, // onWillPop Scope PopInvokedWithResultCallback? onWillPop, // the navigator used to push the dialog GlobalKey? navigatorKey, }) { var leanCancel = onCancel != null || textCancel != null; var leanConfirm = onConfirm != null || textConfirm != null; actions ??= []; if (cancel != null) { actions.add(cancel); } else { if (leanCancel) { actions.add(TextButton( style: TextButton.styleFrom( tapTargetSize: MaterialTapTargetSize.shrinkWrap, padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), shape: RoundedRectangleBorder( side: BorderSide( color: buttonColor ?? theme.colorScheme.secondary, width: 2, style: BorderStyle.solid), borderRadius: BorderRadius.circular(radius)), ), onPressed: () { if (onCancel == null) { //TODO: Close current dialog after api change closeAllDialogs(); } else { onCancel.call(); } }, child: Text( textCancel ?? "Cancel", style: TextStyle( color: cancelTextColor ?? theme.colorScheme.secondary), ), )); } } if (confirm != null) { actions.add(confirm); } else { if (leanConfirm) { actions.add(TextButton( style: TextButton.styleFrom( tapTargetSize: MaterialTapTargetSize.shrinkWrap, backgroundColor: buttonColor ?? theme.colorScheme.secondary, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(radius)), ), child: Text( textConfirm ?? "Ok", style: TextStyle( color: confirmTextColor ?? theme.colorScheme.surface), ), onPressed: () { onConfirm?.call(); })); } } Widget baseAlertDialog = Builder(builder: (context) { return AlertDialog( titlePadding: titlePadding ?? const EdgeInsets.all(8), contentPadding: contentPadding ?? const EdgeInsets.all(8), backgroundColor: backgroundColor ?? DialogTheme.of(context).backgroundColor, shape: RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(radius))), title: Text(title, textAlign: TextAlign.center, style: titleStyle), content: Column( crossAxisAlignment: CrossAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ content ?? Text(middleText, textAlign: TextAlign.center, style: middleTextStyle), const SizedBox(height: 16), ButtonTheme( minWidth: 78.0, height: 34.0, child: Wrap( alignment: WrapAlignment.center, spacing: 8, runSpacing: 8, children: actions!, ), ) ], ), // actions: actions, // ?? [cancelButton, confirmButton], buttonPadding: EdgeInsets.zero, ); }); return dialog( onWillPop != null ? PopScope( onPopInvokedWithResult: (didPop, result) => onWillPop(didPop, result), // onPopInvoked: onWillPop, child: baseAlertDialog, ) : baseAlertDialog, barrierDismissible: barrierDismissible, navigatorKey: navigatorKey, id: id, ); } } extension ExtensionSnackbar on GetInterface { SnackbarController rawSnackbar({ String? title, String? message, Widget? titleText, Widget? messageText, Widget? icon, bool instantInit = true, bool shouldIconPulse = true, double? maxWidth, EdgeInsets margin = const EdgeInsets.all(0.0), EdgeInsets padding = const EdgeInsets.all(16), double borderRadius = 0.0, Color? borderColor, double borderWidth = 1.0, Color backgroundColor = const Color(0xFF303030), Color? leftBarIndicatorColor, List? boxShadows, Gradient? backgroundGradient, Widget? mainButton, OnTap? onTap, Duration? duration = const Duration(seconds: 3), bool isDismissible = true, DismissDirection? dismissDirection, bool showProgressIndicator = false, AnimationController? progressIndicatorController, Color? progressIndicatorBackgroundColor, Animation? progressIndicatorValueColor, SnackPosition snackPosition = SnackPosition.bottom, SnackStyle snackStyle = SnackStyle.floating, Curve forwardAnimationCurve = Curves.easeOutCirc, Curve reverseAnimationCurve = Curves.easeOutCirc, Duration animationDuration = const Duration(seconds: 1), SnackbarStatusCallback? snackbarStatus, double barBlur = 0.0, double overlayBlur = 0.0, Color? overlayColor, Form? userInputForm, }) { final getSnackBar = GetSnackBar( snackbarStatus: snackbarStatus, title: title, message: message, titleText: titleText, messageText: messageText, snackPosition: snackPosition, borderRadius: borderRadius, margin: margin, duration: duration, barBlur: barBlur, backgroundColor: backgroundColor, icon: icon, shouldIconPulse: shouldIconPulse, maxWidth: maxWidth, padding: padding, borderColor: borderColor, borderWidth: borderWidth, leftBarIndicatorColor: leftBarIndicatorColor, boxShadows: boxShadows, backgroundGradient: backgroundGradient, mainButton: mainButton, onTap: onTap, isDismissible: isDismissible, dismissDirection: dismissDirection, showProgressIndicator: showProgressIndicator, progressIndicatorController: progressIndicatorController, progressIndicatorBackgroundColor: progressIndicatorBackgroundColor, progressIndicatorValueColor: progressIndicatorValueColor, snackStyle: snackStyle, forwardAnimationCurve: forwardAnimationCurve, reverseAnimationCurve: reverseAnimationCurve, animationDuration: animationDuration, overlayBlur: overlayBlur, overlayColor: overlayColor, userInputForm: userInputForm, ); final controller = SnackbarController(getSnackBar); if (instantInit) { controller.show(); } else { Engine.instance.addPostFrameCallback((_) { controller.show(); }); } return controller; } SnackbarController showSnackbar(GetSnackBar snackbar) { final controller = SnackbarController(snackbar); controller.show(); return controller; } SnackbarController snackbar( String title, String message, { Color? colorText, Duration? duration = const Duration(seconds: 3), /// with instantInit = false you can put snackbar on initState bool instantInit = true, SnackPosition? snackPosition, Widget? titleText, Widget? messageText, Widget? icon, bool? shouldIconPulse, double? maxWidth, EdgeInsets? margin, EdgeInsets? padding, double? borderRadius, Color? borderColor, double? borderWidth, Color? backgroundColor, Color? leftBarIndicatorColor, List? boxShadows, Gradient? backgroundGradient, TextButton? mainButton, OnTap? onTap, OnHover? onHover, bool? isDismissible, bool? showProgressIndicator, DismissDirection? dismissDirection, AnimationController? progressIndicatorController, Color? progressIndicatorBackgroundColor, Animation? progressIndicatorValueColor, SnackStyle? snackStyle, Curve? forwardAnimationCurve, Curve? reverseAnimationCurve, Duration? animationDuration, double? barBlur, double? overlayBlur, SnackbarStatusCallback? snackbarStatus, Color? overlayColor, Form? userInputForm, }) { final getSnackBar = GetSnackBar( snackbarStatus: snackbarStatus, titleText: titleText ?? Text( title, style: TextStyle( color: colorText ?? iconColor ?? Colors.black, fontWeight: FontWeight.w800, fontSize: 16, ), ), messageText: messageText ?? Text( message, style: TextStyle( color: colorText ?? iconColor ?? Colors.black, fontWeight: FontWeight.w300, fontSize: 14, ), ), snackPosition: snackPosition ?? SnackPosition.top, borderRadius: borderRadius ?? 15, margin: margin ?? const EdgeInsets.symmetric(horizontal: 10), duration: duration, barBlur: barBlur ?? 7.0, backgroundColor: backgroundColor ?? Colors.grey.withValues(alpha: 0.2), icon: icon, shouldIconPulse: shouldIconPulse ?? true, maxWidth: maxWidth, padding: padding ?? const EdgeInsets.all(16), borderColor: borderColor, borderWidth: borderWidth, leftBarIndicatorColor: leftBarIndicatorColor, boxShadows: boxShadows, backgroundGradient: backgroundGradient, mainButton: mainButton, onTap: onTap, onHover: onHover, isDismissible: isDismissible ?? true, dismissDirection: dismissDirection, showProgressIndicator: showProgressIndicator ?? false, progressIndicatorController: progressIndicatorController, progressIndicatorBackgroundColor: progressIndicatorBackgroundColor, progressIndicatorValueColor: progressIndicatorValueColor, snackStyle: snackStyle ?? SnackStyle.floating, forwardAnimationCurve: forwardAnimationCurve ?? Curves.easeOutCirc, reverseAnimationCurve: reverseAnimationCurve ?? Curves.easeOutCirc, animationDuration: animationDuration ?? const Duration(seconds: 1), overlayBlur: overlayBlur ?? 0.0, overlayColor: overlayColor ?? Colors.transparent, userInputForm: userInputForm); final controller = SnackbarController(getSnackBar); if (instantInit) { controller.show(); } else { //routing.isSnackbar = true; Engine.instance.addPostFrameCallback((_) { controller.show(); }); } return controller; } } extension GetNavigationExt on GetInterface { /// **Navigation.push()** shortcut.

/// /// Pushes a new `page` to the stack /// /// It has the advantage of not needing context, /// so you can call from your business logic /// /// You can set a custom [transition], and a transition [duration]. /// /// You can send any type of value to the other route in the [arguments]. /// /// Just like native routing in Flutter, you can push a route /// as a [fullscreenDialog], /// /// [id] is for when you are using nested navigation, /// as explained in documentation /// /// If you want the same behavior of ios that pops a route when the user drag, /// you can set [popGesture] to true /// /// If you're using the [BindingsInterface] api, you must define it here /// /// By default, GetX will prevent you from push a route that you already in, /// if you want to push anyway, set [preventDuplicates] to false Future? to(Widget Function() page, {bool? opaque, Transition? transition, Curve? curve, Duration? duration, String? id, String? routeName, bool fullscreenDialog = false, dynamic arguments, List bindings = const [], bool preventDuplicates = true, bool? popGesture, bool showCupertinoParallax = true, double Function(BuildContext context)? gestureWidth, bool rebuildStack = true, PreventDuplicateHandlingMode preventDuplicateHandlingMode = PreventDuplicateHandlingMode.reorderRoutes}) { return searchDelegate(id).to( page, opaque: opaque, transition: transition, curve: curve, duration: duration, id: id, routeName: routeName, fullscreenDialog: fullscreenDialog, arguments: arguments, bindings: bindings, preventDuplicates: preventDuplicates, popGesture: popGesture, showCupertinoParallax: showCupertinoParallax, gestureWidth: gestureWidth, rebuildStack: rebuildStack, preventDuplicateHandlingMode: preventDuplicateHandlingMode, ); } // GetPageBuilder _resolvePage(dynamic page, String method) { // if (page is GetPageBuilder) { // return page; // } else if (page is Widget) { // Get.log( // '''WARNING, consider using: "Get.$method(() => Page())" //instead of "Get.$method(Page())". // Using a widget function instead of a widget fully guarantees that the widget //and its controllers will be removed from memory when they are no longer used. // '''); // return () => page; // } else if (page is String) { // throw '''Unexpected String, // use toNamed() instead'''; // } else { // throw '''Unexpected format, // you can only use widgets and widget functions here'''; // } // } /// **Navigation.pushNamed()** shortcut.

/// /// Pushes a new named `page` to the stack. /// /// It has the advantage of not needing context, so you can call /// from your business logic. /// /// You can send any type of value to the other route in the [arguments]. /// /// [id] is for when you are using nested navigation, /// as explained in documentation /// /// By default, GetX will prevent you from push a route that you already in, /// if you want to push anyway, set [preventDuplicates] to false /// /// Note: Always put a slash on the route ('/page1'), to avoid unexpected errors Future? toNamed( String page, { dynamic arguments, dynamic id, bool preventDuplicates = true, Map? parameters, }) { // if (preventDuplicates && page == currentRoute) { // return null; // } if (parameters != null) { final uri = Uri(path: page, queryParameters: parameters); page = uri.toString(); } return searchDelegate(id).toNamed( page, arguments: arguments, id: id, preventDuplicates: preventDuplicates, parameters: parameters, ); } /// **Navigation.pushReplacementNamed()** shortcut.

/// /// Pop the current named `page` in the stack and push a new one in its place /// /// It has the advantage of not needing context, so you can call /// from your business logic. /// /// You can send any type of value to the other route in the [arguments]. /// /// [id] is for when you are using nested navigation, /// as explained in documentation /// /// By default, GetX will prevent you from push a route that you already in, /// if you want to push anyway, set [preventDuplicates] to false /// /// Note: Always put a slash on the route ('/page1'), to avoid unexpected errors Future? offNamed( String page, { dynamic arguments, String? id, Map? parameters, }) { // if (preventDuplicates && page == currentRoute) { // return null; // } if (parameters != null) { final uri = Uri(path: page, queryParameters: parameters); page = uri.toString(); } return searchDelegate(id).offNamed( page, arguments: arguments, id: id, // preventDuplicates: preventDuplicates, parameters: parameters, ); } /// **Navigation.popUntil()** shortcut.

/// /// Calls pop several times in the stack until [predicate] returns true /// /// [id] is for when you are using nested navigation, /// as explained in documentation /// /// [predicate] can be used like this: /// `Get.until((route) => Get.currentRoute == '/home')`so when you get to home page, /// /// or also like this: /// `Get.until((route) => !Get.isDialogOpen())`, to make sure the /// dialog is closed void until(bool Function(GetPage) predicate, {String? id}) { // if (key.currentState.mounted) // add this if appear problems on future with route navigate // when widget don't mounted return searchDelegate(id).backUntil(predicate); } /// **Navigation.pushNamedAndRemoveUntil()** shortcut.

/// /// Push the given named `page`, and then pop several pages in the stack /// until [predicate] returns true /// /// You can send any type of value to the other route in the [arguments]. /// /// [id] is for when you are using nested navigation, /// as explained in documentation /// /// [predicate] can be used like this: /// `Get.offNamedUntil(page, ModalRoute.withName('/home'))` /// to pop routes in stack until home, /// or like this: /// `Get.offNamedUntil((route) => !Get.isDialogOpen())`, /// to make sure the dialog is closed /// /// Note: Always put a slash on the route name ('/page1'), to avoid unexpected errors Future? offNamedUntil( String page, bool Function(GetPage)? predicate, { String? id, dynamic arguments, Map? parameters, }) { if (parameters != null) { final uri = Uri(path: page, queryParameters: parameters); page = uri.toString(); } return searchDelegate(id).offNamedUntil( page, predicate: predicate, id: id, arguments: arguments, parameters: parameters, ); } /// **Navigation.popAndPushNamed()** shortcut.

/// /// Pop the current named page and pushes a new `page` to the stack /// in its place /// /// You can send any type of value to the other route in the [arguments]. /// It is very similar to `offNamed()` but use a different approach /// /// The `offNamed()` pop a page, and goes to the next. The /// `offAndToNamed()` goes to the next page, and removes the previous one. /// The route transition animation is different. Future? offAndToNamed( String page, { dynamic arguments, String? id, dynamic result, Map? parameters, }) { if (parameters != null) { final uri = Uri(path: page, queryParameters: parameters); page = uri.toString(); } return searchDelegate(id).backAndtoNamed( page, arguments: arguments, result: result, ); } /// **Navigation.removeRoute()** shortcut.

/// /// Remove a specific [route] from the stack /// /// [id] is for when you are using nested navigation, /// as explained in documentation void removeRoute(String name, {String? id}) { return searchDelegate(id).removeRoute(name); } /// **Navigation.pushNamedAndRemoveUntil()** shortcut.

/// /// Push a named `page` and pop several pages in the stack /// until [predicate] returns true. [predicate] is optional /// /// It has the advantage of not needing context, so you can /// call from your business logic. /// /// You can send any type of value to the other route in the [arguments]. /// /// [predicate] can be used like this: /// `Get.until((route) => Get.currentRoute == '/home')`so when you get to home page, /// or also like /// `Get.until((route) => !Get.isDialogOpen())`, to make sure the dialog /// is closed /// /// [id] is for when you are using nested navigation, /// as explained in documentation /// /// Note: Always put a slash on the route ('/page1'), to avoid unexpected errors Future? offAllNamed( String newRouteName, { // bool Function(GetPage)? predicate, dynamic arguments, String? id, Map? parameters, }) { if (parameters != null) { final uri = Uri(path: newRouteName, queryParameters: parameters); newRouteName = uri.toString(); } return searchDelegate(id).offAllNamed( newRouteName, //predicate: predicate ?? (_) => false, arguments: arguments, id: id, parameters: parameters, ); } /// Returns true if a Snackbar, Dialog or BottomSheet is currently OPEN bool get isOverlaysOpen => (isSnackbarOpen || isDialogOpen! || isBottomSheetOpen!); /// Returns true if there is no Snackbar, Dialog or BottomSheet open bool get isOverlaysClosed => (!isSnackbarOpen && !isDialogOpen! && !isBottomSheetOpen!); /// **Navigation.popUntil()** shortcut.

/// /// Pop the current page, snackbar, dialog or bottomsheet in the stack /// /// if your set [closeOverlays] to true, Get.back() will close the /// currently open snackbar/dialog/bottomsheet AND the current page /// /// [id] is for when you are using nested navigation, /// as explained in documentation /// /// It has the advantage of not needing context, so you can call /// from your business logic. void back({ T? result, bool canPop = true, int times = 1, String? id, }) { if (times < 1) { times = 1; } if (times > 1) { var count = 0; return searchDelegate(id).backUntil((route) => count++ == times); } else { if (canPop) { if (searchDelegate(id).canBack == true) { return searchDelegate(id).back(result); } } else { return searchDelegate(id).back(result); } } } /// Pop the current page, snackbar, dialog or bottomsheet in the stack /// /// if your set [closeOverlays] to true, Get.back() will close the /// currently open snackbar/dialog/bottomsheet AND the current page /// /// [id] is for when you are using nested navigation, /// as explained in documentation /// /// It has the advantage of not needing context, so you can call /// from your business logic. void backLegacy({ T? result, bool closeOverlays = false, bool canPop = true, int times = 1, String? id, }) { if (closeOverlays) { closeAllOverlays(); } if (times < 1) { times = 1; } if (times > 1) { var count = 0; return searchDelegate(id).navigatorKey.currentState?.popUntil((route) { return count++ == times; }); } else { if (canPop) { if (searchDelegate(id).navigatorKey.currentState?.canPop() == true) { return searchDelegate(id).navigatorKey.currentState?.pop(result); } } else { return searchDelegate(id).navigatorKey.currentState?.pop(result); } } } void closeAllDialogsAndBottomSheets( String? id, ) { // It can not be divided, because dialogs and bottomsheets can not be consecutive while ((isDialogOpen! && isBottomSheetOpen!)) { closeOverlay(id: id); } } void closeAllDialogs({ String? id, }) { while ((isDialogOpen!)) { closeOverlay(id: id); } } /// Close the currently open dialog, returning a [result], if provided void closeDialog({String? id, T? result}) { // Stop if there is no dialog open if (isDialogOpen == null || !isDialogOpen!) return; closeOverlay(id: id, result: result); } void closeBottomSheet({String? id, T? result}) { // Stop if there is no bottomsheet open if (isBottomSheetOpen == null || !isBottomSheetOpen!) return; closeOverlay(id: id, result: result); } /// Close the current overlay returning the [result], if provided void closeOverlay({ String? id, T? result, }) { searchDelegate(id).navigatorKey.currentState?.pop(result); } void closeAllBottomSheets({ String? id, }) { while ((isBottomSheetOpen!)) { searchDelegate(id).navigatorKey.currentState?.pop(); } } void closeAllOverlays() { closeAllDialogsAndBottomSheets(null); closeAllSnackbars(); } /// **Navigation.popUntil()** (with predicate) shortcut .

/// /// Close as many routes as defined by [times] /// /// [id] is for when you are using nested navigation, /// as explained in documentation void close({ bool closeAll = true, bool closeSnackbar = true, bool closeDialog = true, bool closeBottomSheet = true, String? id, T? result, }) { void handleClose(bool closeCondition, Function closeAllFunction, Function closeSingleFunction, [bool? isOpenCondition]) { if (closeCondition) { if (closeAll) { closeAllFunction(); } else if (isOpenCondition == true) { closeSingleFunction(); } } } handleClose(closeSnackbar, closeAllSnackbars, closeCurrentSnackbar); handleClose(closeDialog, closeAllDialogs, closeOverlay, isDialogOpen); handleClose(closeBottomSheet, closeAllBottomSheets, closeOverlay, isBottomSheetOpen); } /// **Navigation.pushReplacement()** shortcut .

/// /// Pop the current page and pushes a new `page` to the stack /// /// It has the advantage of not needing context, /// so you can call from your business logic /// /// You can set a custom [transition], define a Tween [curve], /// and a transition [duration]. /// /// You can send any type of value to the other route in the [arguments]. /// /// Just like native routing in Flutter, you can push a route /// as a [fullscreenDialog], /// /// [id] is for when you are using nested navigation, /// as explained in documentation /// /// If you want the same behavior of ios that pops a route when the user drag, /// you can set [popGesture] to true /// /// If you're using the [BindingsInterface] api, you must define it here /// /// By default, GetX will prevent you from push a route that you already in, /// if you want to push anyway, set [preventDuplicates] to false Future? off( Widget Function() page, { bool? opaque, Transition? transition, Curve? curve, bool? popGesture, String? id, String? routeName, dynamic arguments, List bindings = const [], bool fullscreenDialog = false, bool preventDuplicates = true, Duration? duration, double Function(BuildContext context)? gestureWidth, }) { routeName ??= "/${page.runtimeType.toString()}"; routeName = _cleanRouteName(routeName); if (preventDuplicates && routeName == currentRoute) { return null; } return searchDelegate(id).off( page, opaque: opaque ?? true, transition: transition, curve: curve, popGesture: popGesture, id: id, routeName: routeName, arguments: arguments, bindings: bindings, fullscreenDialog: fullscreenDialog, preventDuplicates: preventDuplicates, duration: duration, gestureWidth: gestureWidth, ); } Future offUntil( Widget Function() page, bool Function(GetPage) predicate, [ Object? arguments, String? id, ]) { return searchDelegate(id).offUntil( page, predicate, arguments, ); } /// /// Push a `page` and pop several pages in the stack /// until [predicate] returns true. [predicate] is optional /// /// It has the advantage of not needing context, /// so you can call from your business logic /// /// You can set a custom [transition], a [curve] and a transition [duration]. /// /// You can send any type of value to the other route in the [arguments]. /// /// Just like native routing in Flutter, you can push a route /// as a [fullscreenDialog], /// /// [predicate] can be used like this: /// `Get.until((route) => Get.currentRoute == '/home')`so when you get to home page, /// or also like /// `Get.until((route) => !Get.isDialogOpen())`, to make sure the dialog /// is closed /// /// [id] is for when you are using nested navigation, /// as explained in documentation /// /// If you want the same behavior of ios that pops a route when the user drag, /// you can set [popGesture] to true /// /// If you're using the [BindingsInterface] api, you must define it here /// /// By default, GetX will prevent you from push a route that you already in, /// if you want to push anyway, set [preventDuplicates] to false Future? offAll( Widget Function() page, { bool Function(GetPage)? predicate, bool? opaque, bool? popGesture, String? id, String? routeName, dynamic arguments, List bindings = const [], bool fullscreenDialog = false, Transition? transition, Curve? curve, Duration? duration, double Function(BuildContext context)? gestureWidth, }) { routeName ??= "/${page.runtimeType.toString()}"; routeName = _cleanRouteName(routeName); return searchDelegate(id).offAll( page, predicate: predicate, opaque: opaque ?? true, popGesture: popGesture, id: id, // routeName routeName, arguments: arguments, bindings: bindings, fullscreenDialog: fullscreenDialog, transition: transition, curve: curve, duration: duration, gestureWidth: gestureWidth, ); } /// Takes a route [name] String generated by [to], [off], [offAll] /// (and similar context navigation methods), cleans the extra chars and /// accommodates the format. /// TODO: check for a more "appealing" URL naming convention. /// `() => MyHomeScreenView` becomes `/my-home-screen-view`. String _cleanRouteName(String name) { name = name.replaceAll('() => ', ''); /// uncomment for URL styling. // name = name.paramCase!; if (!name.startsWith('/')) { name = '/$name'; } return Uri.tryParse(name)?.toString() ?? name; } //TODO: Deprecated // /// change default config of Get // void config( // {bool? enableLog, // LogWriterCallback? logWriterCallback, // bool? defaultPopGesture, // bool? defaultOpaqueRoute, // Duration? defaultDurationTransition, // bool? defaultGlobalState, // Transition? defaultTransition}) { // if (enableLog != null) { // Get.isLogEnable = enableLog; // } // if (logWriterCallback != null) { // Get.log = logWriterCallback; // } // if (defaultPopGesture != null) { // _getxController.defaultPopGesture = defaultPopGesture; // } // if (defaultOpaqueRoute != null) { // _getxController.defaultOpaqueRoute = defaultOpaqueRoute; // } // if (defaultTransition != null) { // _getxController.defaultTransition = defaultTransition; // } // if (defaultDurationTransition != null) { // _getxController.defaultTransitionDuration = defaultDurationTransition; // } // } Future updateLocale(Locale l) async { Get.locale = l; await forceAppUpdate(); } /// As a rule, Flutter knows which widget to update, /// so this command is rarely needed. We can mention situations /// where you use const so that widgets are not updated with setState, /// but you want it to be forcefully updated when an event like /// language change happens. using context to make the widget dirty /// for performRebuild() is a viable solution. /// However, in situations where this is not possible, or at least, /// is not desired by the developer, the only solution for updating /// widgets that Flutter does not want to update is to use reassemble /// to forcibly rebuild all widgets. Attention: calling this function will /// reconstruct the application from the sketch, use this with caution. /// Your entire application will be rebuilt, and touch events will not /// work until the end of rendering. Future forceAppUpdate() async { await engine.performReassemble(); } void appUpdate() => rootController.update(); void changeTheme(ThemeData theme) { rootController.setTheme(theme); } void changeThemeMode(ThemeMode themeMode) { rootController.setThemeMode(themeMode); } GlobalKey? addKey(GlobalKey newKey) { return rootController.addKey(newKey); } GetDelegate? nestedKey(String? key) { return rootController.nestedKey(key); } GetDelegate searchDelegate(String? k) { GetDelegate key; if (k == null) { key = Get.rootController.rootDelegate; } else { if (!keys.containsKey(k)) { throw 'Route id ($k) not found'; } key = keys[k]!; } // if (_key.listenersLength == 0 && !testMode) { // throw """You are trying to use contextless navigation without // a GetMaterialApp or Get.key. // If you are testing your app, you can use: // [Get.testMode = true], or if you are running your app on // a physical device or emulator, you must exchange your [MaterialApp] // for a [GetMaterialApp]. // """; // } return key; } /// give name from current route String get currentRoute => routing.current; /// give name from previous route String get previousRoute => routing.previous; /// check if snackbar is open bool get isSnackbarOpen => SnackbarController.isSnackbarBeingShown; //routing.isSnackbar; void closeAllSnackbars() { SnackbarController.cancelAllSnackbars(); } Future closeCurrentSnackbar() async { await SnackbarController.closeCurrentSnackbar(); } /// check if dialog is open bool? get isDialogOpen => routing.isDialog; /// check if bottomsheet is open bool? get isBottomSheetOpen => routing.isBottomSheet; /// check a raw current route Route? get rawRoute => routing.route; /// check if default opaque route is enable bool get isOpaqueRouteDefault => defaultOpaqueRoute; /// give access to currentContext BuildContext? get context => key.currentContext; /// give access to current Overlay Context BuildContext? get overlayContext { BuildContext? overlay; key.currentState?.overlay?.context.visitChildElements((element) { overlay = element; }); return overlay; } /// give access to Theme.of(context) ThemeData get theme { var theme = ThemeData.fallback(); if (context != null) { theme = Theme.of(context!); } return theme; } /// The current null safe [WidgetsBinding] WidgetsBinding get engine { return WidgetsFlutterBinding.ensureInitialized(); } /// The window to which this binding is bound. ui.PlatformDispatcher get window => engine.platformDispatcher; Locale? get deviceLocale => window.locale; ///The number of device pixels for each logical pixel. double get pixelRatio => window.implicitView!.devicePixelRatio; Size get size => window.implicitView!.physicalSize / pixelRatio; ///The horizontal extent of this size. double get width => size.width; ///The vertical extent of this size double get height => size.height; ///The distance from the top edge to the first unpadded pixel, ///in physical pixels. double get statusBarHeight => window.implicitView!.padding.top; ///The distance from the bottom edge to the first unpadded pixel, ///in physical pixels. double get bottomBarHeight => window.implicitView!.padding.bottom; ///The system-reported text scale. double get textScaleFactor => window.textScaleFactor; /// give access to TextTheme.of(context) TextTheme get textTheme => theme.textTheme; /// give access to Mediaquery.of(context) MediaQueryData get mediaQuery => MediaQuery.of(context!); /// Check if dark mode theme is enable bool get isDarkMode => (theme.brightness == Brightness.dark); /// Check if dark mode theme is enable on platform on android Q+ bool get isPlatformDarkMode => (ui.PlatformDispatcher.instance.platformBrightness == Brightness.dark); /// give access to Theme.of(context).iconTheme.color Color? get iconColor => theme.iconTheme.color; /// give access to FocusScope.of(context) FocusNode? get focusScope => FocusManager.instance.primaryFocus; // /// give access to Immutable MediaQuery.of(context).size.height // double get height => MediaQuery.of(context).size.height; // /// give access to Immutable MediaQuery.of(context).size.width // double get width => MediaQuery.of(context).size.width; GlobalKey get key => rootController.key; Map get keys => rootController.keys; GetRootState get rootController => GetRootState.controller; ConfigData get _getxController => GetRootState.controller.config; bool? get defaultPopGesture => _getxController.defaultPopGesture; bool get defaultOpaqueRoute => _getxController.defaultOpaqueRoute; Transition? get defaultTransition => _getxController.defaultTransition; Duration get defaultTransitionDuration { return _getxController.defaultTransitionDuration; } Curve get defaultTransitionCurve => _getxController.defaultTransitionCurve; Curve get defaultDialogTransitionCurve { return _getxController.defaultDialogTransitionCurve; } Duration get defaultDialogTransitionDuration { return _getxController.defaultDialogTransitionDuration; } Routing get routing => _getxController.routing; bool get _shouldUseMock => GetTestMode.active && !GetRoot.treeInitialized; /// give current arguments dynamic get arguments { return args(); } T args() { if (_shouldUseMock) { return GetTestMode.arguments as T; } return rootController.rootDelegate.arguments(); } // set parameters(Map newParameters) { // rootController.parameters = newParameters; // } // @Deprecated('Use GetTestMode.active=true instead') set testMode(bool isTest) => GetTestMode.active = isTest; // @Deprecated('Use GetTestMode.active instead') bool get testMode => GetTestMode.active; Map get parameters { if (_shouldUseMock) { return GetTestMode.parameters; } return rootController.rootDelegate.parameters; } /// Casts the stored router delegate to a desired type TDelegate? delegate, TPage>() => _getxController.routerDelegate as TDelegate?; } extension OverlayExt on GetInterface { Future showOverlay({ required Future Function() asyncFunction, Color opacityColor = Colors.black, Widget? loadingWidget, double opacity = .5, }) async { final navigatorState = Navigator.of(Get.overlayContext!, rootNavigator: false); final overlayState = navigatorState.overlay!; final overlayEntryOpacity = OverlayEntry(builder: (context) { return Opacity( opacity: opacity, child: Container( color: opacityColor, )); }); final overlayEntryLoader = OverlayEntry(builder: (context) { return loadingWidget ?? const Center( child: SizedBox( height: 90, width: 90, child: Text('Loading...'), )); }); overlayState.insert(overlayEntryOpacity); overlayState.insert(overlayEntryLoader); T data; try { data = await asyncFunction(); } on Exception catch (_) { overlayEntryLoader.remove(); overlayEntryOpacity.remove(); rethrow; } overlayEntryLoader.remove(); overlayEntryOpacity.remove(); return data; } } ================================================ FILE: lib/get_navigation/src/root/get_cupertino_app.dart ================================================ import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import '../../../get_core/get_core.dart'; import '../../../get_instance/get_instance.dart'; import '../../../get_state_manager/get_state_manager.dart'; import '../../../get_utils/get_utils.dart'; import '../../get_navigation.dart'; import 'get_root.dart'; class GetCupertinoApp extends StatelessWidget { final GlobalKey? navigatorKey; final Widget? home; final Map? routes; final String? initialRoute; final RouteFactory? onGenerateRoute; final InitialRouteListFactory? onGenerateInitialRoutes; final RouteFactory? onUnknownRoute; final List? navigatorObservers; final TransitionBuilder? builder; final String title; final GenerateAppTitle? onGenerateTitle; final CustomTransition? customTransition; final Color? color; final Map>? translationsKeys; final Translations? translations; final TextDirection? textDirection; final Locale? locale; final Locale? fallbackLocale; final Iterable>? localizationsDelegates; final LocaleListResolutionCallback? localeListResolutionCallback; final LocaleResolutionCallback? localeResolutionCallback; final Iterable supportedLocales; final bool showPerformanceOverlay; final bool checkerboardRasterCacheImages; final bool checkerboardOffscreenLayers; final bool showSemanticsDebugger; final bool debugShowCheckedModeBanner; final Map? shortcuts; final ThemeData? highContrastTheme; final ThemeData? highContrastDarkTheme; final Map>? actions; final Function(Routing?)? routingCallback; final Transition? defaultTransition; final bool? opaqueRoute; final VoidCallback? onInit; final VoidCallback? onReady; final VoidCallback? onDispose; final bool? enableLog; final LogWriterCallback? logWriterCallback; final bool? popGesture; final SmartManagement smartManagement; final BindingsInterface? initialBinding; final Duration? transitionDuration; final bool? defaultGlobalState; final List? getPages; final GetPage? unknownRoute; final RouteInformationProvider? routeInformationProvider; final RouteInformationParser? routeInformationParser; final RouterDelegate? routerDelegate; final RouterConfig? routerConfig; final BackButtonDispatcher? backButtonDispatcher; final CupertinoThemeData? theme; final bool useInheritedMediaQuery; final List binds; final ScrollBehavior? scrollBehavior; const GetCupertinoApp({ super.key, this.theme, this.navigatorKey, this.home, Map this.routes = const {}, this.initialRoute, this.onGenerateRoute, this.onGenerateInitialRoutes, this.onUnknownRoute, List this.navigatorObservers = const [], this.builder, this.translationsKeys, this.translations, this.textDirection, this.title = '', this.onGenerateTitle, this.color, this.customTransition, this.onInit, this.onDispose, this.locale, this.binds = const [], this.scrollBehavior, this.fallbackLocale, this.localizationsDelegates, this.localeListResolutionCallback, this.localeResolutionCallback, this.supportedLocales = const [Locale('en', 'US')], this.showPerformanceOverlay = false, this.checkerboardRasterCacheImages = false, this.checkerboardOffscreenLayers = false, this.showSemanticsDebugger = false, this.debugShowCheckedModeBanner = true, this.shortcuts, this.smartManagement = SmartManagement.full, this.initialBinding, this.useInheritedMediaQuery = false, this.unknownRoute, this.routingCallback, this.defaultTransition, this.onReady, this.getPages, this.opaqueRoute, this.enableLog = kDebugMode, this.logWriterCallback, this.popGesture, this.transitionDuration, this.defaultGlobalState, this.highContrastTheme, this.highContrastDarkTheme, this.actions, }) : routeInformationProvider = null, backButtonDispatcher = null, routeInformationParser = null, routerDelegate = null, routerConfig = null; const GetCupertinoApp.router({ super.key, this.theme, this.routeInformationProvider, this.routeInformationParser, this.routerDelegate, this.routerConfig, this.backButtonDispatcher, this.builder, this.title = '', this.onGenerateTitle, this.useInheritedMediaQuery = false, this.color, this.highContrastTheme, this.highContrastDarkTheme, this.locale, this.localizationsDelegates, this.localeListResolutionCallback, this.localeResolutionCallback, this.supportedLocales = const [Locale('en', 'US')], this.showPerformanceOverlay = false, this.checkerboardRasterCacheImages = false, this.checkerboardOffscreenLayers = false, this.showSemanticsDebugger = false, this.debugShowCheckedModeBanner = true, this.shortcuts, this.binds = const [], this.scrollBehavior, this.actions, this.customTransition, this.translationsKeys, this.translations, this.textDirection, this.fallbackLocale, this.routingCallback, this.defaultTransition, this.opaqueRoute, this.onInit, this.onReady, this.onDispose, this.enableLog = kDebugMode, this.logWriterCallback, this.popGesture, this.smartManagement = SmartManagement.full, this.initialBinding, this.transitionDuration, this.defaultGlobalState, this.getPages, this.navigatorObservers, this.unknownRoute, }) : navigatorKey = null, onGenerateRoute = null, home = null, onGenerateInitialRoutes = null, onUnknownRoute = null, routes = null, initialRoute = null; @override Widget build(BuildContext context) { return GetRoot( config: ConfigData( backButtonDispatcher: backButtonDispatcher, binds: binds, customTransition: customTransition, defaultGlobalState: defaultGlobalState, defaultTransition: defaultTransition, enableLog: enableLog, fallbackLocale: fallbackLocale, getPages: getPages, home: home, initialRoute: initialRoute, locale: locale, logWriterCallback: logWriterCallback, navigatorKey: navigatorKey, navigatorObservers: navigatorObservers, onDispose: onDispose, onInit: onInit, onReady: onReady, routeInformationParser: routeInformationParser, routeInformationProvider: routeInformationProvider, routerDelegate: routerDelegate, routingCallback: routingCallback, scaffoldMessengerKey: GlobalKey(), smartManagement: smartManagement, transitionDuration: transitionDuration, translations: translations, translationsKeys: translationsKeys, unknownRoute: unknownRoute, defaultPopGesture: popGesture, ), child: Builder(builder: (context) { final controller = GetRoot.of(context); return CupertinoApp.router( routerDelegate: controller.config.routerDelegate, routeInformationParser: controller.config.routeInformationParser, backButtonDispatcher: backButtonDispatcher, routeInformationProvider: routeInformationProvider, routerConfig: routerConfig, key: controller.config.unikey, builder: (context, child) => Directionality( textDirection: textDirection ?? (rtlLanguages.contains(Get.locale?.languageCode) ? TextDirection.rtl : TextDirection.ltr), child: builder == null ? (child ?? const Material()) : builder!(context, child ?? const Material()), ), title: title, onGenerateTitle: onGenerateTitle, color: color, theme: theme, locale: Get.locale ?? locale, localizationsDelegates: localizationsDelegates, localeListResolutionCallback: localeListResolutionCallback, localeResolutionCallback: localeResolutionCallback, supportedLocales: supportedLocales, showPerformanceOverlay: showPerformanceOverlay, checkerboardRasterCacheImages: checkerboardRasterCacheImages, checkerboardOffscreenLayers: checkerboardOffscreenLayers, showSemanticsDebugger: showSemanticsDebugger, debugShowCheckedModeBanner: debugShowCheckedModeBanner, shortcuts: shortcuts, scrollBehavior: scrollBehavior, ); }), ); } } ================================================ FILE: lib/get_navigation/src/root/get_material_app.dart ================================================ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:get/instance_manager.dart'; import '../../../get_state_manager/get_state_manager.dart'; import '../../../get_utils/get_utils.dart'; import '../../get_navigation.dart'; import 'get_root.dart'; class GetMaterialApp extends StatelessWidget { final GlobalKey? navigatorKey; final GlobalKey? scaffoldMessengerKey; final Widget? home; final Map? routes; final String? initialRoute; final RouteFactory? onGenerateRoute; final InitialRouteListFactory? onGenerateInitialRoutes; final RouteFactory? onUnknownRoute; final List? navigatorObservers; final TransitionBuilder? builder; final String title; final GenerateAppTitle? onGenerateTitle; final ThemeData? theme; final ThemeData? darkTheme; final ThemeMode themeMode; final CustomTransition? customTransition; final Color? color; final Map>? translationsKeys; final Translations? translations; final TextDirection? textDirection; final Locale? locale; final Locale? fallbackLocale; final Iterable>? localizationsDelegates; final LocaleListResolutionCallback? localeListResolutionCallback; final LocaleResolutionCallback? localeResolutionCallback; final Iterable supportedLocales; final bool showPerformanceOverlay; final bool checkerboardRasterCacheImages; final bool checkerboardOffscreenLayers; final bool showSemanticsDebugger; final bool debugShowCheckedModeBanner; final Map? shortcuts; final ScrollBehavior? scrollBehavior; final ThemeData? highContrastTheme; final ThemeData? highContrastDarkTheme; final Map>? actions; final bool debugShowMaterialGrid; final ValueChanged? routingCallback; final Transition? defaultTransition; final bool? opaqueRoute; final VoidCallback? onInit; final VoidCallback? onReady; final VoidCallback? onDispose; final bool? enableLog; final LogWriterCallback? logWriterCallback; final bool? popGesture; final SmartManagement smartManagement; final List binds; final Duration? transitionDuration; final bool? defaultGlobalState; final List? getPages; final GetPage? unknownRoute; final RouteInformationProvider? routeInformationProvider; final RouteInformationParser? routeInformationParser; final RouterDelegate? routerDelegate; final RouterConfig? routerConfig; final BackButtonDispatcher? backButtonDispatcher; final bool useInheritedMediaQuery; const GetMaterialApp({ super.key, this.navigatorKey, this.scaffoldMessengerKey, this.home, Map this.routes = const {}, this.initialRoute, this.onGenerateRoute, this.onGenerateInitialRoutes, this.onUnknownRoute, this.useInheritedMediaQuery = false, List this.navigatorObservers = const [], this.builder, this.textDirection, this.title = '', this.onGenerateTitle, this.color, this.theme, this.darkTheme, this.themeMode = ThemeMode.system, this.locale, this.fallbackLocale, this.localizationsDelegates, this.localeListResolutionCallback, this.localeResolutionCallback, this.supportedLocales = const [Locale('en', 'US')], this.debugShowMaterialGrid = false, this.showPerformanceOverlay = false, this.checkerboardRasterCacheImages = false, this.checkerboardOffscreenLayers = false, this.showSemanticsDebugger = false, this.debugShowCheckedModeBanner = true, this.shortcuts, this.scrollBehavior, this.customTransition, this.translationsKeys, this.translations, this.onInit, this.onReady, this.onDispose, this.routingCallback, this.defaultTransition, this.getPages, this.opaqueRoute, this.enableLog = kDebugMode, this.logWriterCallback, this.popGesture, this.transitionDuration, this.defaultGlobalState, this.smartManagement = SmartManagement.full, this.binds = const [], this.unknownRoute, this.highContrastTheme, this.highContrastDarkTheme, this.actions, }) : routeInformationProvider = null, backButtonDispatcher = null, routeInformationParser = null, routerDelegate = null, routerConfig = null; const GetMaterialApp.router({ super.key, this.routeInformationProvider, this.scaffoldMessengerKey, this.routeInformationParser, this.routerDelegate, this.routerConfig, this.backButtonDispatcher, this.builder, this.title = '', this.onGenerateTitle, this.color, this.theme, this.darkTheme, this.useInheritedMediaQuery = false, this.highContrastTheme, this.highContrastDarkTheme, this.themeMode = ThemeMode.system, this.locale, this.localizationsDelegates, this.localeListResolutionCallback, this.localeResolutionCallback, this.supportedLocales = const [Locale('en', 'US')], this.debugShowMaterialGrid = false, this.showPerformanceOverlay = false, this.checkerboardRasterCacheImages = false, this.checkerboardOffscreenLayers = false, this.showSemanticsDebugger = false, this.debugShowCheckedModeBanner = true, this.shortcuts, this.scrollBehavior, this.actions, this.customTransition, this.translationsKeys, this.translations, this.textDirection, this.fallbackLocale, this.routingCallback, this.defaultTransition, this.opaqueRoute, this.onInit, this.onReady, this.onDispose, this.enableLog = kDebugMode, this.logWriterCallback, this.popGesture, this.smartManagement = SmartManagement.full, this.binds = const [], this.transitionDuration, this.defaultGlobalState, this.getPages, this.navigatorObservers, this.unknownRoute, }) : navigatorKey = null, onGenerateRoute = null, home = null, onGenerateInitialRoutes = null, onUnknownRoute = null, routes = null, initialRoute = null; @override Widget build(BuildContext context) { return GetRoot( config: ConfigData( backButtonDispatcher: backButtonDispatcher, binds: binds, customTransition: customTransition, defaultGlobalState: defaultGlobalState, defaultTransition: defaultTransition, enableLog: enableLog, fallbackLocale: fallbackLocale, getPages: getPages, home: home, initialRoute: initialRoute, locale: locale, logWriterCallback: logWriterCallback, navigatorKey: navigatorKey, navigatorObservers: navigatorObservers, onDispose: onDispose, onInit: onInit, onReady: onReady, routeInformationParser: routeInformationParser, routeInformationProvider: routeInformationProvider, routerDelegate: routerDelegate, routingCallback: routingCallback, scaffoldMessengerKey: scaffoldMessengerKey, smartManagement: smartManagement, transitionDuration: transitionDuration, translations: translations, translationsKeys: translationsKeys, unknownRoute: unknownRoute, theme: theme, darkTheme: darkTheme, themeMode: themeMode, defaultPopGesture: popGesture, ), // binds: [ // Bind.lazyPut( // () => GetMaterialController( // ), // onClose: () { // Get.clearTranslations(); // RouterReportManager.dispose(); // Get.resetInstance(clearRouteBindings: true); // }, // ), // ...binds, // ], child: Builder(builder: (context) { final controller = GetRoot.of(context); return MaterialApp.router( routerDelegate: controller.config.routerDelegate, routeInformationParser: controller.config.routeInformationParser, backButtonDispatcher: backButtonDispatcher, routeInformationProvider: routeInformationProvider, routerConfig: routerConfig, key: controller.config.unikey, builder: (context, child) => Directionality( textDirection: textDirection ?? (rtlLanguages.contains(Get.locale?.languageCode) ? TextDirection.rtl : TextDirection.ltr), child: builder == null ? (child ?? const Material()) : builder!(context, child ?? const Material()), ), title: title, onGenerateTitle: onGenerateTitle, color: color, theme: controller.config.theme ?? ThemeData.fallback(), darkTheme: controller.config.darkTheme ?? controller.config.theme ?? ThemeData.fallback(), themeMode: controller.config.themeMode, locale: Get.locale ?? locale, scaffoldMessengerKey: controller.config.scaffoldMessengerKey, localizationsDelegates: localizationsDelegates, localeListResolutionCallback: localeListResolutionCallback, localeResolutionCallback: localeResolutionCallback, supportedLocales: supportedLocales, debugShowMaterialGrid: debugShowMaterialGrid, showPerformanceOverlay: showPerformanceOverlay, checkerboardRasterCacheImages: checkerboardRasterCacheImages, checkerboardOffscreenLayers: checkerboardOffscreenLayers, showSemanticsDebugger: showSemanticsDebugger, debugShowCheckedModeBanner: debugShowCheckedModeBanner, shortcuts: shortcuts, scrollBehavior: scrollBehavior, ); }), ); } } ================================================ FILE: lib/get_navigation/src/root/get_root.dart ================================================ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:get/get_navigation/src/routes/test_kit.dart'; import '../../../get.dart'; import '../router_report.dart'; class ConfigData { final ValueChanged? routingCallback; final Transition? defaultTransition; final VoidCallback? onInit; final VoidCallback? onReady; final VoidCallback? onDispose; final bool? enableLog; final LogWriterCallback? logWriterCallback; final SmartManagement smartManagement; final List binds; final Duration? transitionDuration; final bool? defaultGlobalState; final List? getPages; final GetPage? unknownRoute; final RouteInformationProvider? routeInformationProvider; final RouteInformationParser? routeInformationParser; final RouterDelegate? routerDelegate; final BackButtonDispatcher? backButtonDispatcher; final List? navigatorObservers; final GlobalKey? navigatorKey; final GlobalKey? scaffoldMessengerKey; final Map>? translationsKeys; final Translations? translations; final Locale? locale; final Locale? fallbackLocale; final String? initialRoute; final CustomTransition? customTransition; final Widget? home; final bool testMode; final Key? unikey; final ThemeData? theme; final ThemeData? darkTheme; final ThemeMode? themeMode; final bool? defaultPopGesture; final bool defaultOpaqueRoute; final Duration defaultTransitionDuration; final Curve defaultTransitionCurve; final Curve defaultDialogTransitionCurve; final Duration defaultDialogTransitionDuration; final Routing routing; final Map parameters; final SnackBarQueue snackBarQueue = SnackBarQueue(); ConfigData({ required this.routingCallback, required this.defaultTransition, required this.onInit, required this.onReady, required this.onDispose, required this.enableLog, required this.logWriterCallback, required this.smartManagement, required this.binds, required this.transitionDuration, required this.defaultGlobalState, required this.getPages, required this.unknownRoute, required this.routeInformationProvider, required this.routeInformationParser, required this.routerDelegate, required this.backButtonDispatcher, required this.navigatorObservers, required this.navigatorKey, required this.scaffoldMessengerKey, required this.translationsKeys, required this.translations, required this.locale, required this.fallbackLocale, required this.initialRoute, required this.customTransition, required this.home, this.theme, this.darkTheme, this.themeMode, this.unikey, this.testMode = false, this.defaultOpaqueRoute = true, this.defaultTransitionDuration = const Duration(milliseconds: 300), this.defaultTransitionCurve = Curves.easeOutQuad, this.defaultDialogTransitionCurve = Curves.easeOutQuad, this.defaultDialogTransitionDuration = const Duration(milliseconds: 300), this.parameters = const {}, required this.defaultPopGesture, Routing? routing, }) : routing = routing ?? Routing(); ConfigData copyWith({ ValueChanged? routingCallback, Transition? defaultTransition, VoidCallback? onInit, VoidCallback? onReady, VoidCallback? onDispose, bool? enableLog, LogWriterCallback? logWriterCallback, SmartManagement? smartManagement, List? binds, Duration? transitionDuration, bool? defaultGlobalState, List? getPages, GetPage? unknownRoute, RouteInformationProvider? routeInformationProvider, RouteInformationParser? routeInformationParser, RouterDelegate? routerDelegate, BackButtonDispatcher? backButtonDispatcher, List? navigatorObservers, GlobalKey? navigatorKey, GlobalKey? scaffoldMessengerKey, Map>? translationsKeys, Translations? translations, Locale? locale, Locale? fallbackLocale, String? initialRoute, CustomTransition? customTransition, Widget? home, bool? testMode, Key? unikey, ThemeData? theme, ThemeData? darkTheme, ThemeMode? themeMode, bool? defaultPopGesture, bool? defaultOpaqueRoute, Duration? defaultTransitionDuration, Curve? defaultTransitionCurve, Curve? defaultDialogTransitionCurve, Duration? defaultDialogTransitionDuration, Routing? routing, Map? parameters, }) { return ConfigData( routingCallback: routingCallback ?? this.routingCallback, defaultTransition: defaultTransition ?? this.defaultTransition, onInit: onInit ?? this.onInit, onReady: onReady ?? this.onReady, onDispose: onDispose ?? this.onDispose, enableLog: enableLog ?? this.enableLog, logWriterCallback: logWriterCallback ?? this.logWriterCallback, smartManagement: smartManagement ?? this.smartManagement, binds: binds ?? this.binds, transitionDuration: transitionDuration ?? this.transitionDuration, defaultGlobalState: defaultGlobalState ?? this.defaultGlobalState, getPages: getPages ?? this.getPages, unknownRoute: unknownRoute ?? this.unknownRoute, routeInformationProvider: routeInformationProvider ?? this.routeInformationProvider, routeInformationParser: routeInformationParser ?? this.routeInformationParser, routerDelegate: routerDelegate ?? this.routerDelegate, backButtonDispatcher: backButtonDispatcher ?? this.backButtonDispatcher, navigatorObservers: navigatorObservers ?? this.navigatorObservers, navigatorKey: navigatorKey ?? this.navigatorKey, scaffoldMessengerKey: scaffoldMessengerKey ?? this.scaffoldMessengerKey, translationsKeys: translationsKeys ?? this.translationsKeys, translations: translations ?? this.translations, locale: locale ?? this.locale, fallbackLocale: fallbackLocale ?? this.fallbackLocale, initialRoute: initialRoute ?? this.initialRoute, customTransition: customTransition ?? this.customTransition, home: home ?? this.home, testMode: testMode ?? this.testMode, unikey: unikey ?? this.unikey, theme: theme ?? this.theme, darkTheme: darkTheme ?? this.darkTheme, themeMode: themeMode ?? this.themeMode, defaultPopGesture: defaultPopGesture ?? this.defaultPopGesture, defaultOpaqueRoute: defaultOpaqueRoute ?? this.defaultOpaqueRoute, defaultTransitionDuration: defaultTransitionDuration ?? this.defaultTransitionDuration, defaultTransitionCurve: defaultTransitionCurve ?? this.defaultTransitionCurve, defaultDialogTransitionCurve: defaultDialogTransitionCurve ?? this.defaultDialogTransitionCurve, defaultDialogTransitionDuration: defaultDialogTransitionDuration ?? this.defaultDialogTransitionDuration, routing: routing ?? this.routing, parameters: parameters ?? this.parameters, ); } @override bool operator ==(Object other) { if (identical(this, other)) return true; return other is ConfigData && other.routingCallback == routingCallback && other.defaultTransition == defaultTransition && other.onInit == onInit && other.onReady == onReady && other.onDispose == onDispose && other.enableLog == enableLog && other.logWriterCallback == logWriterCallback && other.smartManagement == smartManagement && listEquals(other.binds, binds) && other.transitionDuration == transitionDuration && other.defaultGlobalState == defaultGlobalState && listEquals(other.getPages, getPages) && other.unknownRoute == unknownRoute && other.routeInformationProvider == routeInformationProvider && other.routeInformationParser == routeInformationParser && other.routerDelegate == routerDelegate && other.backButtonDispatcher == backButtonDispatcher && listEquals(other.navigatorObservers, navigatorObservers) && other.navigatorKey == navigatorKey && other.scaffoldMessengerKey == scaffoldMessengerKey && mapEquals(other.translationsKeys, translationsKeys) && other.translations == translations && other.locale == locale && other.fallbackLocale == fallbackLocale && other.initialRoute == initialRoute && other.customTransition == customTransition && other.home == home && other.testMode == testMode && other.unikey == unikey && other.theme == theme && other.darkTheme == darkTheme && other.themeMode == themeMode && other.defaultPopGesture == defaultPopGesture && other.defaultOpaqueRoute == defaultOpaqueRoute && other.defaultTransitionDuration == defaultTransitionDuration && other.defaultTransitionCurve == defaultTransitionCurve && other.defaultDialogTransitionCurve == defaultDialogTransitionCurve && other.defaultDialogTransitionDuration == defaultDialogTransitionDuration && other.routing == routing && mapEquals(other.parameters, parameters); } @override int get hashCode { return routingCallback.hashCode ^ defaultTransition.hashCode ^ onInit.hashCode ^ onReady.hashCode ^ onDispose.hashCode ^ enableLog.hashCode ^ logWriterCallback.hashCode ^ smartManagement.hashCode ^ binds.hashCode ^ transitionDuration.hashCode ^ defaultGlobalState.hashCode ^ getPages.hashCode ^ unknownRoute.hashCode ^ routeInformationProvider.hashCode ^ routeInformationParser.hashCode ^ routerDelegate.hashCode ^ backButtonDispatcher.hashCode ^ navigatorObservers.hashCode ^ navigatorKey.hashCode ^ scaffoldMessengerKey.hashCode ^ translationsKeys.hashCode ^ translations.hashCode ^ locale.hashCode ^ fallbackLocale.hashCode ^ initialRoute.hashCode ^ customTransition.hashCode ^ home.hashCode ^ testMode.hashCode ^ unikey.hashCode ^ theme.hashCode ^ darkTheme.hashCode ^ themeMode.hashCode ^ defaultPopGesture.hashCode ^ defaultOpaqueRoute.hashCode ^ defaultTransitionDuration.hashCode ^ defaultTransitionCurve.hashCode ^ defaultDialogTransitionCurve.hashCode ^ defaultDialogTransitionDuration.hashCode ^ routing.hashCode ^ parameters.hashCode; } } class GetRoot extends StatefulWidget { const GetRoot({ super.key, required this.config, required this.child, }); final ConfigData config; final Widget child; @override State createState() => GetRootState(); static bool get treeInitialized => GetRootState._controller != null; static GetRootState of(BuildContext context) { // Handles the case where the input context is a navigator element. GetRootState? root; if (context is StatefulElement && context.state is GetRootState) { root = context.state as GetRootState; } root = context.findRootAncestorStateOfType() ?? root; assert(() { if (root == null) { throw FlutterError( 'GetRoot operation requested with a context that does not include a GetRoot.\n' 'The context used must be that of a ' 'widget that is a descendant of a GetRoot widget.', ); } return true; }()); return root!; } } class GetRootState extends State with WidgetsBindingObserver { static GetRootState? _controller; static GetRootState get controller { if (_controller == null) { throw Exception('GetRoot is not part of the three'); } else { return _controller!; } } late ConfigData config; @override void initState() { config = widget.config; GetRootState._controller = this; Engine.instance.addObserver(this); onInit(); super.initState(); } // @override // void didUpdateWidget(covariant GetRoot oldWidget) { // if (oldWidget.config != widget.config) { // config = widget.config; // } // super.didUpdateWidget(oldWidget); // } void onClose() { config.onDispose?.call(); Get.clearTranslations(); config.snackBarQueue.disposeControllers(); RouterReportManager.instance.clearRouteKeys(); RouterReportManager.dispose(); Get.resetInstance(clearRouteBindings: true); _controller = null; Engine.instance.removeObserver(this); } @override void dispose() { onClose(); super.dispose(); } void onInit() { if (config.getPages == null && config.home == null) { throw 'You need add pages or home'; } if (config.routerDelegate == null) { final newDelegate = GetDelegate.createDelegate( pages: config.getPages ?? [ GetPage( name: cleanRouteName("/${config.home.runtimeType}"), page: () => config.home!, ), ], notFoundRoute: config.unknownRoute, navigatorKey: config.navigatorKey, navigatorObservers: (config.navigatorObservers == null ? [ GetObserver(config.routingCallback, Get.routing) ] : [ GetObserver(config.routingCallback, config.routing), ...config.navigatorObservers! ]), ); config = config.copyWith(routerDelegate: newDelegate); } if (config.routeInformationParser == null) { final newRouteInformationParser = GetInformationParser.createInformationParser( initialRoute: config.initialRoute ?? config.getPages?.first.name ?? cleanRouteName("/${config.home.runtimeType}"), ); config = config.copyWith(routeInformationParser: newRouteInformationParser); } if (config.locale != null) Get.locale = config.locale; if (config.fallbackLocale != null) { Get.fallbackLocale = config.fallbackLocale; } if (config.translations != null) { Get.addTranslations(config.translations!.keys); } else if (config.translationsKeys != null) { Get.addTranslations(config.translationsKeys!); } Get.smartManagement = config.smartManagement; config.onInit?.call(); Get.isLogEnable = config.enableLog ?? kDebugMode; Get.log = config.logWriterCallback ?? defaultLogWriterCallback; if (config.defaultTransition == null) { config = config.copyWith(defaultTransition: getThemeTransition()); } // defaultOpaqueRoute = config.opaqueRoute ?? true; // defaultPopGesture = config.popGesture ?? GetPlatform.isIOS; // defaultTransitionDuration = // config.transitionDuration ?? Duration(milliseconds: 300); Future(() => onReady()); } set parameters(Map newParameters) { // rootController.parameters = newParameters; config = config.copyWith(parameters: newParameters); } set testMode(bool isTest) { config = config.copyWith(testMode: isTest); GetTestMode.active = isTest; } void onReady() { config.onReady?.call(); } Transition? getThemeTransition() { final platform = context.theme.platform; final matchingTransition = Get.theme.pageTransitionsTheme.builders[platform]; switch (matchingTransition) { case CupertinoPageTransitionsBuilder(): return Transition.cupertino; case ZoomPageTransitionsBuilder(): return Transition.zoom; case FadeUpwardsPageTransitionsBuilder(): return Transition.fade; case OpenUpwardsPageTransitionsBuilder(): return Transition.native; default: return null; } } @override void didChangeLocales(List? locales) { Get.asap(() { final locale = Get.deviceLocale; if (locale != null) { Get.updateLocale(locale); } }); } void setTheme(ThemeData value) { if (config.darkTheme == null) { config = config.copyWith(theme: value); } else { if (value.brightness == Brightness.light) { config = config.copyWith(theme: value); } else { config = config.copyWith(darkTheme: value); } } update(); } void setThemeMode(ThemeMode value) { config = config.copyWith(themeMode: value); update(); } void restartApp() { config = config.copyWith(unikey: UniqueKey()); update(); } void update() { context.visitAncestorElements((element) { element.markNeedsBuild(); return false; }); } GlobalKey get key => rootDelegate.navigatorKey; GetDelegate get rootDelegate => config.routerDelegate as GetDelegate; RouteInformationParser get informationParser => config.routeInformationParser!; GlobalKey? addKey(GlobalKey newKey) { rootDelegate.navigatorKey = newKey; return key; } Map keys = {}; GetDelegate? nestedKey(String? key) { if (key == null) { return rootDelegate; } keys.putIfAbsent( key, () => GetDelegate( showHashOnUrl: true, //debugLabel: 'Getx nested key: ${key.toString()}', pages: RouteDecoder.fromRoute(key).currentChildren ?? [], ), ); return keys[key]; } @override Widget build(BuildContext context) { return widget.child; } String cleanRouteName(String name) { name = name.replaceAll('() => ', ''); /// uncomment for URL styling. // name = name.paramCase!; if (!name.startsWith('/')) { name = '/$name'; } return Uri.tryParse(name)?.toString() ?? name; } } ================================================ FILE: lib/get_navigation/src/root/internacionalization.dart ================================================ const List rtlLanguages = [ 'ar', // Arabic 'fa', // Farsi 'he', // Hebrew 'ps', // Pashto 'ur', ]; abstract class Translations { Map> get keys; } ================================================ FILE: lib/get_navigation/src/router_report.dart ================================================ import 'dart:collection'; import '../../get.dart'; class RouterReportManager { /// Holds a reference to `Get.reference` when the Instance was /// created to manage the memory. final Map> _routesKey = {}; /// Stores the onClose() references of instances created with `Get.create()` /// using the `Get.reference`. /// Experimental feature to keep the lifecycle and memory management with /// non-singleton instances. final Map> _routesByCreate = {}; static RouterReportManager? _instance; RouterReportManager._(); static RouterReportManager get instance => _instance ??= RouterReportManager._(); static void dispose() { _instance = null; } void printInstanceStack() { Get.log(_routesKey.toString()); } T? _current; // ignore: use_setters_to_change_properties void reportCurrentRoute(T newRoute) { _current = newRoute; } /// Links a Class instance [S] (or [tag]) to the current route. /// Requires usage of `GetMaterialApp`. void reportDependencyLinkedToRoute(String dependencyKey) { if (_current == null) return; if (_routesKey.containsKey(_current)) { _routesKey[_current!]!.add(dependencyKey); } else { _routesKey[_current] = [dependencyKey]; } } void clearRouteKeys() { _routesKey.clear(); _routesByCreate.clear(); } void appendRouteByCreate(GetLifeCycleMixin i) { _routesByCreate[_current] ??= HashSet(); // _routesByCreate[Get.reference]!.add(i.onDelete as Function); _routesByCreate[_current]!.add(i.onDelete); } void reportRouteDispose(T disposed) { if (Get.smartManagement != SmartManagement.onlyBuilder) { // Engine.instance.addPostFrameCallback((_) { // Future.microtask(() { _removeDependencyByRoute(disposed); // }); } } void reportRouteWillDispose(T disposed) { final keysToRemove = []; _routesKey[disposed]?.forEach(keysToRemove.add); /// Removes `Get.create()` instances registered in `routeName`. if (_routesByCreate.containsKey(disposed)) { for (final onClose in _routesByCreate[disposed]!) { // assure the [DisposableInterface] instance holding a reference // to onClose() wasn't disposed. onClose(); } _routesByCreate[disposed]!.clear(); _routesByCreate.remove(disposed); } for (final element in keysToRemove) { Get.markAsDirty(key: element); //_routesKey.remove(element); } keysToRemove.clear(); } /// Clears from memory registered Instances associated with [routeName] when /// using `Get.smartManagement` as [SmartManagement.full] or /// [SmartManagement.keepFactory] /// Meant for internal usage of `GetPageRoute` and `GetDialogRoute` void _removeDependencyByRoute(T routeName) { final keysToRemove = []; _routesKey[routeName]?.forEach(keysToRemove.add); /// Removes `Get.create()` instances registered in `routeName`. if (_routesByCreate.containsKey(routeName)) { for (final onClose in _routesByCreate[routeName]!) { // assure the [DisposableInterface] instance holding a reference // to onClose() wasn't disposed. onClose(); } _routesByCreate[routeName]!.clear(); _routesByCreate.remove(routeName); } for (final element in keysToRemove) { final value = Get.delete(key: element); if (value) { _routesKey[routeName]?.remove(element); } } _routesKey.remove(routeName); keysToRemove.clear(); } } ================================================ FILE: lib/get_navigation/src/routes/circular_reveal_clipper.dart ================================================ import 'dart:math' show sqrt, max; import 'dart:ui' show lerpDouble; import 'package:flutter/material.dart'; class CircularRevealClipper extends CustomClipper { final double fraction; final Alignment? centerAlignment; final Offset? centerOffset; final double? minRadius; final double? maxRadius; CircularRevealClipper({ required this.fraction, this.centerAlignment, this.centerOffset, this.minRadius, this.maxRadius, }); @override Path getClip(Size size) { final center = centerAlignment?.alongSize(size) ?? centerOffset ?? Offset(size.width / 2, size.height / 2); final minRadius = this.minRadius ?? 0; final maxRadius = this.maxRadius ?? calcMaxRadius(size, center); return Path() ..addOval( Rect.fromCircle( center: center, radius: lerpDouble(minRadius, maxRadius, fraction)!, ), ); } @override bool shouldReclip(CustomClipper oldClipper) => true; static double calcMaxRadius(Size size, Offset center) { final w = max(center.dx, size.width - center.dx); final h = max(center.dy, size.height - center.dy); return sqrt(w * w + h * h); } } ================================================ FILE: lib/get_navigation/src/routes/custom_transition.dart ================================================ import 'package:flutter/widgets.dart'; // ignore: one_member_abstracts abstract class CustomTransition { Widget buildTransition( BuildContext context, Curve? curve, Alignment? alignment, Animation animation, Animation secondaryAnimation, Widget child, ); } ================================================ FILE: lib/get_navigation/src/routes/default_route.dart ================================================ import 'package:flutter/cupertino.dart'; import '../../../get.dart'; import '../router_report.dart'; @optionalTypeArgs mixin RouteReportMixin on State { @override void initState() { super.initState(); RouterReportManager.instance.reportCurrentRoute(this); } @override void dispose() { super.dispose(); RouterReportManager.instance.reportRouteDispose(this); } } mixin PageRouteReportMixin on Route { @override void install() { super.install(); RouterReportManager.instance.reportCurrentRoute(this); } @override void dispose() { super.dispose(); RouterReportManager.instance.reportRouteDispose(this); } } class GetPageRoute extends PageRoute with GetPageRouteTransitionMixin, PageRouteReportMixin { /// Creates a page route for use in an iOS designed app. /// /// The [builder], [maintainState], and [fullscreenDialog] arguments must not /// be null. GetPageRoute({ super.settings, this.transitionDuration = const Duration(milliseconds: 300), this.reverseTransitionDuration = const Duration(milliseconds: 300), this.opaque = true, this.parameter, this.gestureWidth, this.curve, this.alignment, this.transition, this.popGesture, this.customTransition, this.barrierDismissible = false, this.barrierColor, BindingsInterface? binding, List bindings = const [], this.binds, this.routeName, this.page, this.title, this.showCupertinoParallax = true, this.barrierLabel, this.maintainState = true, super.fullscreenDialog, this.middlewares, }) : bindings = (binding == null) ? bindings : [...bindings, binding], _middlewareRunner = MiddlewareRunner(middlewares); @override final Duration transitionDuration; @override final Duration reverseTransitionDuration; final GetPageBuilder? page; final String? routeName; //final String reference; final CustomTransition? customTransition; final List bindings; final Map? parameter; final List? binds; @override final bool showCupertinoParallax; @override final bool opaque; final bool? popGesture; @override final bool barrierDismissible; final Transition? transition; final Curve? curve; final Alignment? alignment; final List? middlewares; @override final Color? barrierColor; @override final String? barrierLabel; @override final bool maintainState; final MiddlewareRunner _middlewareRunner; @override void dispose() { super.dispose(); _middlewareRunner.runOnPageDispose(); _child = null; } Widget? _child; Widget _getChild() { if (_child != null) return _child!; final localBinds = [if (binds != null) ...binds!]; final bindingsToBind = _middlewareRunner .runOnBindingsStart(bindings.isNotEmpty ? bindings : localBinds); final pageToBuild = _middlewareRunner.runOnPageBuildStart(page)!; if (bindingsToBind != null && bindingsToBind.isNotEmpty) { if (bindingsToBind is List) { for (final item in bindingsToBind) { final dep = item.dependencies(); if (dep is List) { _child = Binds( binds: dep, child: _middlewareRunner.runOnPageBuilt(pageToBuild()), ); } } } else if (bindingsToBind is List) { _child = Binds( binds: bindingsToBind, child: _middlewareRunner.runOnPageBuilt(pageToBuild()), ); } } return _child ??= _middlewareRunner.runOnPageBuilt(pageToBuild()); } @override Widget buildContent(BuildContext context) { return _getChild(); } @override final String? title; @override String get debugLabel => '${super.debugLabel}(${settings.name})'; @override final double Function(BuildContext context)? gestureWidth; } ================================================ FILE: lib/get_navigation/src/routes/default_transitions.dart ================================================ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'circular_reveal_clipper.dart'; class LeftToRightFadeTransition { Widget buildTransitions( BuildContext context, Curve? curve, Alignment? alignment, Animation animation, Animation secondaryAnimation, Widget child) { return SlideTransition( position: Tween( begin: const Offset(-1.0, 0.0), end: Offset.zero, ).animate(animation), child: FadeTransition( opacity: animation, child: SlideTransition( position: Tween( begin: Offset.zero, end: const Offset(1.0, 0.0), ).animate(secondaryAnimation), child: child), ), ); } } class RightToLeftFadeTransition { Widget buildTransitions( BuildContext context, Curve? curve, Alignment? alignment, Animation animation, Animation secondaryAnimation, Widget child) { return SlideTransition( position: Tween( begin: const Offset(1.0, 0.0), end: Offset.zero, ).animate(animation), child: FadeTransition( opacity: animation, child: SlideTransition( position: Tween( begin: Offset.zero, end: const Offset(-1.0, 0.0), ).animate(secondaryAnimation), child: child), ), ); } } class NoTransition { Widget buildTransitions( BuildContext context, Curve curve, Alignment alignment, Animation animation, Animation secondaryAnimation, Widget child) { return child; } } class FadeInTransition { Widget buildTransitions( BuildContext context, Curve? curve, Alignment? alignment, Animation animation, Animation secondaryAnimation, Widget child) { return FadeTransition(opacity: animation, child: child); } } class SlideDownTransition { Widget buildTransitions( BuildContext context, Curve? curve, Alignment? alignment, Animation animation, Animation secondaryAnimation, Widget child) { return SlideTransition( position: Tween( begin: const Offset(0.0, 1.0), end: Offset.zero, ).animate(animation), child: child, ); } } class SlideLeftTransition { Widget buildTransitions( BuildContext context, Curve? curve, Alignment? alignment, Animation animation, Animation secondaryAnimation, Widget child) { return SlideTransition( position: Tween( begin: const Offset(-1.0, 0.0), end: Offset.zero, ).animate(animation), child: child, ); } } class SlideRightTransition { Widget buildTransitions( BuildContext context, Curve? curve, Alignment? alignment, Animation animation, Animation secondaryAnimation, Widget child) { return SlideTransition( position: Tween( begin: const Offset(1.0, 0.0), end: Offset.zero, ).animate(animation), child: child, ); } } class SlideTopTransition { Widget buildTransitions( BuildContext context, Curve? curve, Alignment? alignment, Animation animation, Animation secondaryAnimation, Widget child) { return SlideTransition( position: Tween( begin: const Offset(0.0, -1.0), end: Offset.zero, ).animate(animation), child: child, ); } } class ZoomInTransition { Widget buildTransitions( BuildContext context, Curve? curve, Alignment? alignment, Animation animation, Animation secondaryAnimation, Widget child) { return ScaleTransition( scale: animation, child: child, ); } } class SizeTransitions { Widget buildTransitions( BuildContext context, Curve curve, Alignment? alignment, Animation animation, Animation secondaryAnimation, Widget child) { return Align( alignment: Alignment.center, child: SizeTransition( sizeFactor: CurvedAnimation( parent: animation, curve: curve, ), child: child, ), ); } } class CircularRevealTransition { Widget buildTransitions( BuildContext context, Curve? curve, Alignment? alignment, Animation animation, Animation secondaryAnimation, Widget child) { return ClipPath( clipper: CircularRevealClipper( fraction: animation.value, centerAlignment: Alignment.center, centerOffset: Offset.zero, minRadius: 0, maxRadius: 800, ), child: child, ); } } ================================================ FILE: lib/get_navigation/src/routes/get_information_parser.dart ================================================ import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import '../../../get.dart'; class GetInformationParser extends RouteInformationParser { factory GetInformationParser.createInformationParser( {String initialRoute = '/'}) { return GetInformationParser(initialRoute: initialRoute); } final String initialRoute; GetInformationParser({ required this.initialRoute, }) { Get.log('GetInformationParser is created !'); } @override SynchronousFuture parseRouteInformation( RouteInformation routeInformation, ) { final uri = routeInformation.uri; var location = uri.toString(); if (location == '/') { //check if there is a corresponding page //if not, relocate to initialRoute if (!(Get.rootController.rootDelegate) .registeredRoutes .any((element) => element.name == '/')) { location = initialRoute; } } else if (location.isEmpty) { location = initialRoute; } Get.log('GetInformationParser: route location: $location'); return SynchronousFuture(RouteDecoder.fromRoute(location)); } @override RouteInformation restoreRouteInformation(RouteDecoder configuration) { return RouteInformation( uri: Uri.tryParse(configuration.pageSettings?.name ?? ''), state: null, ); } } ================================================ FILE: lib/get_navigation/src/routes/get_navigation_interface.dart ================================================ import 'package:flutter/widgets.dart'; import '../../../get_instance/src/bindings_interface.dart'; import '../routes/get_route.dart'; import '../routes/transitions_type.dart'; /// Enables the user to customize the intended pop behavior /// /// Goes to either the previous _activePages entry or the previous page entry /// /// e.g. if the user navigates to these pages /// 1) /home /// 2) /home/products/1234 /// /// when popping on [History] mode, it will emulate a browser back button. /// /// so the new _activePages stack will be: /// 1) /home /// /// when popping on [Page] mode, it will only remove the last part of the route /// so the new _activePages stack will be: /// 1) /home /// 2) /home/products /// /// another pop will change the _activePages stack to: /// 1) /home enum PopMode { history, page, } /// Enables the user to customize the behavior when pushing multiple routes that /// shouldn't be duplicates enum PreventDuplicateHandlingMode { /// Removes the _activePages entries until it reaches the old route popUntilOriginalRoute, /// Simply don't push the new route doNothing, /// Recommended - Moves the old route entry to the front /// /// With this mode, you guarantee there will be only one /// route entry for each location reorderRoutes, recreate, } mixin IGetNavigation { Future to( Widget Function() page, { bool? opaque, Transition? transition, Curve? curve, Duration? duration, String? id, String? routeName, bool fullscreenDialog = false, dynamic arguments, List bindings = const [], bool preventDuplicates = true, bool? popGesture, bool showCupertinoParallax = true, double Function(BuildContext context)? gestureWidth, }); Future popModeUntil( String fullRoute, { PopMode popMode = PopMode.history, }); Future off( Widget Function() page, { bool? opaque, Transition? transition, Curve? curve, Duration? duration, String? id, String? routeName, bool fullscreenDialog = false, dynamic arguments, List bindings = const [], bool preventDuplicates = true, bool? popGesture, bool showCupertinoParallax = true, double Function(BuildContext context)? gestureWidth, }); Future? offAll( Widget Function() page, { bool Function(GetPage route)? predicate, bool opaque = true, bool? popGesture, String? id, String? routeName, dynamic arguments, List bindings = const [], bool fullscreenDialog = false, Transition? transition, Curve? curve, Duration? duration, bool showCupertinoParallax = true, double Function(BuildContext context)? gestureWidth, }); Future toNamed( String page, { dynamic arguments, String? id, bool preventDuplicates = true, Map? parameters, }); Future offNamed( String page, { dynamic arguments, String? id, Map? parameters, }); Future? offAllNamed( String newRouteName, { // bool Function(GetPage route)? predicate, dynamic arguments, String? id, Map? parameters, }); Future? offNamedUntil( String page, { bool Function(GetPage route)? predicate, dynamic arguments, String? id, Map? parameters, }); Future toNamedAndOffUntil( String page, bool Function(GetPage) predicate, [ Object? data, ]); Future offUntil( Widget Function() page, bool Function(GetPage) predicate, [ Object? arguments, ]); void removeRoute(String name); void back([T? result]); Future backAndtoNamed(String page, {T? result, Object? arguments}); void backUntil(bool Function(GetPage) predicate); void goToUnknownPage([bool clearPages = true]); } ================================================ FILE: lib/get_navigation/src/routes/get_navigator.dart ================================================ import 'package:flutter/widgets.dart'; class GetNavigator extends Navigator { GetNavigator({ super.key, bool Function(Route, dynamic)? onPopPage, required super.pages, List? observers, super.reportsRouteUpdateToEngine, TransitionDelegate? transitionDelegate, super.initialRoute, super.restorationScopeId, }) : super( // ignore: deprecated_member_use onPopPage: onPopPage ?? (route, result) { final didPop = route.didPop(result); if (!didPop) { return false; } return true; }, observers: [ // GetObserver(null, Get.routing), HeroController(), ...?observers, ], transitionDelegate: transitionDelegate ?? const DefaultTransitionDelegate(), ); } ================================================ FILE: lib/get_navigation/src/routes/get_route.dart ================================================ // ignore_for_file: overridden_fields import 'dart:async'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import '../../../get_instance/src/bindings_interface.dart'; import '../../../get_state_manager/src/simple/get_state.dart'; import '../../get_navigation.dart'; class GetPage extends Page { final GetPageBuilder page; final bool? popGesture; final Map? parameters; final String? title; final Transition? transition; final Curve curve; final bool? participatesInRootNavigator; final Alignment? alignment; final bool maintainState; final bool opaque; final double Function(BuildContext context)? gestureWidth; final BindingsInterface? binding; final List bindings; final List binds; final CustomTransition? customTransition; final Duration? transitionDuration; final Duration? reverseTransitionDuration; final bool fullscreenDialog; final bool preventDuplicates; final Completer? completer; // @override // final LocalKey? key; // @override // RouteSettings get settings => this; @override final Object? arguments; @override final String name; final bool inheritParentPath; final List children; final List middlewares; final PathDecoded path; final GetPage? unknownRoute; final bool showCupertinoParallax; final PreventDuplicateHandlingMode preventDuplicateHandlingMode; static void _defaultPopInvokedHandler(bool didPop, Object? result) {} GetPage({ required this.name, required this.page, this.title, this.participatesInRootNavigator, this.gestureWidth, // RouteSettings settings, this.maintainState = true, this.curve = Curves.linear, this.alignment, this.parameters, this.opaque = true, this.transitionDuration, this.reverseTransitionDuration, this.popGesture, this.binding, this.bindings = const [], this.binds = const [], this.transition, this.customTransition, this.fullscreenDialog = false, this.children = const [], this.middlewares = const [], this.unknownRoute, this.arguments, this.showCupertinoParallax = true, this.preventDuplicates = true, this.preventDuplicateHandlingMode = PreventDuplicateHandlingMode.reorderRoutes, this.completer, this.inheritParentPath = true, LocalKey? key, super.canPop, super.onPopInvoked = _defaultPopInvokedHandler, super.restorationId, }) : path = _nameToRegex(name), assert(name.startsWith('/'), 'It is necessary to start route name [$name] with a slash: /$name'), super( key: key ?? ValueKey(name), name: name, // arguments: Get.arguments, ); // settings = RouteSettings(name: name, arguments: Get.arguments); GetPage copyWith({ LocalKey? key, String? name, GetPageBuilder? page, bool? popGesture, Map? parameters, String? title, Transition? transition, Curve? curve, Alignment? alignment, bool? maintainState, bool? opaque, List? bindings, BindingsInterface? binding, List? binds, CustomTransition? customTransition, Duration? transitionDuration, Duration? reverseTransitionDuration, bool? fullscreenDialog, RouteSettings? settings, List>? children, GetPage? unknownRoute, List? middlewares, bool? preventDuplicates, final double Function(BuildContext context)? gestureWidth, bool? participatesInRootNavigator, Object? arguments, bool? showCupertinoParallax, Completer? completer, bool? inheritParentPath, bool? canPop, PopInvokedWithResultCallback? onPopInvoked, String? restorationId, }) { return GetPage( key: key ?? this.key, participatesInRootNavigator: participatesInRootNavigator ?? this.participatesInRootNavigator, preventDuplicates: preventDuplicates ?? this.preventDuplicates, name: name ?? this.name, page: page ?? this.page, popGesture: popGesture ?? this.popGesture, parameters: parameters ?? this.parameters, title: title ?? this.title, transition: transition ?? this.transition, curve: curve ?? this.curve, alignment: alignment ?? this.alignment, maintainState: maintainState ?? this.maintainState, opaque: opaque ?? this.opaque, bindings: bindings ?? this.bindings, binds: binds ?? this.binds, binding: binding ?? this.binding, customTransition: customTransition ?? this.customTransition, transitionDuration: transitionDuration ?? this.transitionDuration, reverseTransitionDuration: reverseTransitionDuration ?? this.reverseTransitionDuration, fullscreenDialog: fullscreenDialog ?? this.fullscreenDialog, children: children ?? this.children, unknownRoute: unknownRoute ?? this.unknownRoute, middlewares: middlewares ?? this.middlewares, gestureWidth: gestureWidth ?? this.gestureWidth, arguments: arguments ?? this.arguments, showCupertinoParallax: showCupertinoParallax ?? this.showCupertinoParallax, completer: completer ?? this.completer, inheritParentPath: inheritParentPath ?? this.inheritParentPath, canPop: canPop ?? this.canPop, onPopInvoked: onPopInvoked ?? this.onPopInvoked, restorationId: restorationId ?? restorationId, ); } @override Route createRoute(BuildContext context) { // return GetPageRoute(settings: this, page: page); final page = PageRedirect( route: this, settings: this, unknownRoute: unknownRoute, ).getPageToRoute(this, unknownRoute, context); return page; } static PathDecoded _nameToRegex(String path) { var keys = []; String recursiveReplace(Match pattern) { var buffer = StringBuffer('(?:'); if (pattern[1] != null) buffer.write('.'); buffer.write('([\\w%+-._~!\$&\'()*,;=:@]+))'); if (pattern[3] != null) buffer.write('?'); keys.add(pattern[2]); return "$buffer"; } var stringPath = '$path/?' .replaceAllMapped(RegExp(r'(\.)?:(\w+)(\?)?'), recursiveReplace) .replaceAll('//', '/'); return PathDecoded(RegExp('^$stringPath\$'), keys); } @override bool operator ==(Object other) { if (identical(this, other)) return true; return other is GetPage && other.key == key; } @override String toString() => '${objectRuntimeType(this, 'Page')}("$name", $key, $arguments)'; @override int get hashCode { return key.hashCode; } } @immutable class PathDecoded { final RegExp regex; final List keys; const PathDecoded(this.regex, this.keys); @override int get hashCode => regex.hashCode; @override bool operator ==(Object other) { if (identical(this, other)) return true; return other is PathDecoded && other.regex == regex; // && listEquals(other.keys, keys); } } ================================================ FILE: lib/get_navigation/src/routes/get_router_delegate.dart ================================================ import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import '../../../get_instance/src/bindings_interface.dart'; import '../../../get_utils/src/platform/platform.dart'; import '../../../route_manager.dart'; class GetDelegate extends RouterDelegate with ChangeNotifier, PopNavigatorRouterDelegateMixin, IGetNavigation { factory GetDelegate.createDelegate({ GetPage? notFoundRoute, List pages = const [], List? navigatorObservers, TransitionDelegate? transitionDelegate, PopMode backButtonPopMode = PopMode.history, PreventDuplicateHandlingMode preventDuplicateHandlingMode = PreventDuplicateHandlingMode.reorderRoutes, GlobalKey? navigatorKey, }) { return GetDelegate( notFoundRoute: notFoundRoute, navigatorObservers: navigatorObservers, transitionDelegate: transitionDelegate, backButtonPopMode: backButtonPopMode, preventDuplicateHandlingMode: preventDuplicateHandlingMode, pages: pages, navigatorKey: navigatorKey, ); } final List _activePages = []; final PopMode backButtonPopMode; final PreventDuplicateHandlingMode preventDuplicateHandlingMode; final GetPage notFoundRoute; final List? navigatorObservers; final TransitionDelegate? transitionDelegate; final Iterable Function(RouteDecoder currentNavStack)? pickPagesForRootNavigator; List get activePages => _activePages; final _routeTree = ParseRouteTree(routes: []); List get registeredRoutes => _routeTree.routes; void addPages(List getPages) { _routeTree.addRoutes(getPages); } void clearRouteTree() { _routeTree.routes.clear(); } void addPage(GetPage getPage) { _routeTree.addRoute(getPage); } void removePage(GetPage getPage) { _routeTree.removeRoute(getPage); } RouteDecoder matchRoute(String name, {PageSettings? arguments}) { return _routeTree.matchRoute(name, arguments: arguments); } // GlobalKey get navigatorKey => Get.key; @override GlobalKey navigatorKey; final String? restorationScopeId; GetDelegate({ GetPage? notFoundRoute, this.navigatorObservers, this.transitionDelegate, this.backButtonPopMode = PopMode.history, this.preventDuplicateHandlingMode = PreventDuplicateHandlingMode.reorderRoutes, this.pickPagesForRootNavigator, this.restorationScopeId, bool showHashOnUrl = false, GlobalKey? navigatorKey, required List pages, }) : navigatorKey = navigatorKey ?? GlobalKey(), notFoundRoute = notFoundRoute ??= GetPage( name: '/404', page: () => const Scaffold( body: Center(child: Text('Route not found')), ), ) { if (!showHashOnUrl && GetPlatform.isWeb) setUrlStrategy(); addPages(pages); addPage(notFoundRoute); Get.log('GetDelegate is created !'); } Future runMiddleware(RouteDecoder config) async { final middlewares = config.currentTreeBranch.last.middlewares; if (middlewares.isEmpty) { return config; } var iterator = config; for (var item in middlewares) { var redirectRes = await item.redirectDelegate(iterator); if (redirectRes == null) { config.route?.completer?.complete(); return null; } if (config != redirectRes) { config.route?.completer?.complete(); Get.log('Redirect to ${redirectRes.pageSettings?.name}'); } iterator = redirectRes; // Stop the iteration over the middleware if we changed page // and that redirectRes is not the same as the current config. if (config != redirectRes) { break; } } // If the target is not the same as the source, we need // to run the middlewares for the new route. if (iterator != config) { return await runMiddleware(iterator); } return iterator; } Future _unsafeHistoryAdd(RouteDecoder config) async { final res = await runMiddleware(config); if (res == null) return; _activePages.add(res); } // Future _unsafeHistoryRemove(RouteDecoder config, T result) async { // var index = _activePages.indexOf(config); // if (index >= 0) return _unsafeHistoryRemoveAt(index, result); // return null; // } Future _unsafeHistoryRemoveAt(int index, T result) async { if (index == _activePages.length - 1 && _activePages.length > 1) { //removing WILL update the current route final toCheck = _activePages[_activePages.length - 2]; final resMiddleware = await runMiddleware(toCheck); if (resMiddleware == null) return null; _activePages[_activePages.length - 2] = resMiddleware; } final completer = _activePages.removeAt(index).route?.completer; if (completer?.isCompleted == false) completer!.complete(result); return completer?.future as T?; } T arguments() { return currentConfiguration?.pageSettings?.arguments as T; } Map get parameters { return currentConfiguration?.pageSettings?.params ?? {}; } PageSettings? get pageSettings { return currentConfiguration?.pageSettings; } Future _pushHistory(RouteDecoder config) async { if (config.route!.preventDuplicates) { final originalEntryIndex = _activePages.indexWhere( (element) => element.pageSettings?.name == config.pageSettings?.name); if (originalEntryIndex >= 0) { switch (preventDuplicateHandlingMode) { case PreventDuplicateHandlingMode.popUntilOriginalRoute: popModeUntil(config.pageSettings!.name, popMode: PopMode.page); break; case PreventDuplicateHandlingMode.reorderRoutes: await _unsafeHistoryRemoveAt(originalEntryIndex, null); await _unsafeHistoryAdd(config); break; case PreventDuplicateHandlingMode.doNothing: default: break; } return; } } await _unsafeHistoryAdd(config); } Future _popHistory(T result) async { if (!_canPopHistory()) return null; return await _doPopHistory(result); } Future _doPopHistory(T result) async { return _unsafeHistoryRemoveAt(_activePages.length - 1, result); } Future _popPage(T result) async { if (!_canPopPage()) return null; return await _doPopPage(result); } // returns the popped page Future _doPopPage(T result) async { final currentBranch = currentConfiguration?.currentTreeBranch; if (currentBranch != null && currentBranch.length > 1) { //remove last part only final remaining = currentBranch.take(currentBranch.length - 1); final prevHistoryEntry = _activePages.length > 1 ? _activePages[_activePages.length - 2] : null; //check if current route is the same as the previous route if (prevHistoryEntry != null) { //if so, pop the entire _activePages entry final newLocation = remaining.last.name; final prevLocation = prevHistoryEntry.pageSettings?.name; if (newLocation == prevLocation) { //pop the entire _activePages entry return await _popHistory(result); } } //create a new route with the remaining tree branch final res = await _popHistory(result); await _pushHistory( RouteDecoder( remaining.toList(), null, //TOOD: persist state?? ), ); return res; } else { //remove entire entry return await _popHistory(result); } } Future _pop(PopMode mode, T result) async { switch (mode) { case PopMode.history: return await _popHistory(result); case PopMode.page: return await _popPage(result); } } Future popHistory(T result) async { return await _popHistory(result); } bool _canPopHistory() { return _activePages.length > 1; } Future canPopHistory() { return SynchronousFuture(_canPopHistory()); } bool _canPopPage() { final currentTreeBranch = currentConfiguration?.currentTreeBranch; if (currentTreeBranch == null) return false; return currentTreeBranch.length > 1 ? true : _canPopHistory(); } Future canPopPage() { return SynchronousFuture(_canPopPage()); } bool _canPop(mode) { switch (mode) { case PopMode.history: return _canPopHistory(); case PopMode.page: default: return _canPopPage(); } } /// gets the visual pages from the current _activePages entry /// /// visual pages must have [GetPage.participatesInRootNavigator] set to true Iterable getVisualPages(RouteDecoder? currentHistory) { final res = currentHistory!.currentTreeBranch .where((r) => r.participatesInRootNavigator != null); if (res.isEmpty) { //default behavior, all routes participate in root navigator return _activePages.map((e) => e.route!); } else { //user specified at least one participatesInRootNavigator return res .where((element) => element.participatesInRootNavigator == true); } } @override Widget build(BuildContext context) { final currentHistory = currentConfiguration; final pages = currentHistory == null ? [] : pickPagesForRootNavigator?.call(currentHistory).toList() ?? getVisualPages(currentHistory).toList(); if (pages.isEmpty) { return ColoredBox( color: Theme.of(context).scaffoldBackgroundColor, ); } return GetNavigator( key: navigatorKey, onPopPage: _onPopVisualRoute, pages: pages, observers: navigatorObservers, transitionDelegate: transitionDelegate ?? const DefaultTransitionDelegate(), ); } @override Future goToUnknownPage([bool clearPages = false]) async { if (clearPages) _activePages.clear(); final pageSettings = _buildPageSettings(notFoundRoute.name); final routeDecoder = _getRouteDecoder(pageSettings); _push(routeDecoder!); } @protected void _popWithResult([T? result]) { final completer = _activePages.removeLast().route?.completer; if (completer?.isCompleted == false) completer!.complete(result); } @override Future toNamed( String page, { dynamic arguments, dynamic id, bool preventDuplicates = true, Map? parameters, }) async { final args = _buildPageSettings(page, arguments); final route = _getRouteDecoder(args); if (route != null) { return _push(route); } else { goToUnknownPage(); } return null; } @override Future to( Widget Function() page, { bool? opaque, Transition? transition, Curve? curve, Duration? duration, String? id, String? routeName, bool fullscreenDialog = false, dynamic arguments, List bindings = const [], bool preventDuplicates = true, bool? popGesture, bool showCupertinoParallax = true, double Function(BuildContext context)? gestureWidth, bool rebuildStack = true, PreventDuplicateHandlingMode preventDuplicateHandlingMode = PreventDuplicateHandlingMode.reorderRoutes, }) async { routeName ??= _cleanRouteName("/${page.runtimeType}"); // if (preventDuplicateHandlingMode == //PreventDuplicateHandlingMode.Recreate) { // routeName = routeName + page.hashCode.toString(); // } final getPage = GetPage( name: routeName, opaque: opaque ?? true, page: page, gestureWidth: gestureWidth, showCupertinoParallax: showCupertinoParallax, popGesture: popGesture ?? Get.defaultPopGesture, transition: transition ?? Get.defaultTransition, curve: curve ?? Get.defaultTransitionCurve, fullscreenDialog: fullscreenDialog, bindings: bindings, transitionDuration: duration ?? Get.defaultTransitionDuration, preventDuplicateHandlingMode: preventDuplicateHandlingMode, ); _routeTree.addRoute(getPage); final args = _buildPageSettings(routeName, arguments); final route = _getRouteDecoder(args); final result = await _push( route!, rebuildStack: rebuildStack, ); _routeTree.removeRoute(getPage); return result; } @override Future off( Widget Function() page, { bool? opaque, Transition? transition, Curve? curve, Duration? duration, String? id, String? routeName, bool fullscreenDialog = false, dynamic arguments, List bindings = const [], bool preventDuplicates = true, bool? popGesture, bool showCupertinoParallax = true, double Function(BuildContext context)? gestureWidth, }) async { routeName ??= _cleanRouteName("/${page.runtimeType}"); final route = GetPage( name: routeName, opaque: opaque ?? true, page: page, gestureWidth: gestureWidth, showCupertinoParallax: showCupertinoParallax, popGesture: popGesture ?? Get.defaultPopGesture, transition: transition ?? Get.defaultTransition, curve: curve ?? Get.defaultTransitionCurve, fullscreenDialog: fullscreenDialog, bindings: bindings, transitionDuration: duration ?? Get.defaultTransitionDuration, ); final args = _buildPageSettings(routeName, arguments); return _replace(args, route); } @override Future? offAll( Widget Function() page, { bool Function(GetPage route)? predicate, bool opaque = true, bool? popGesture, String? id, String? routeName, dynamic arguments, List bindings = const [], bool fullscreenDialog = false, Transition? transition, Curve? curve, Duration? duration, bool showCupertinoParallax = true, double Function(BuildContext context)? gestureWidth, }) async { routeName ??= _cleanRouteName("/${page.runtimeType}"); final route = GetPage( name: routeName, opaque: opaque, page: page, gestureWidth: gestureWidth, showCupertinoParallax: showCupertinoParallax, popGesture: popGesture ?? Get.defaultPopGesture, transition: transition ?? Get.defaultTransition, curve: curve ?? Get.defaultTransitionCurve, fullscreenDialog: fullscreenDialog, bindings: bindings, transitionDuration: duration ?? Get.defaultTransitionDuration, ); final args = _buildPageSettings(routeName, arguments); final newPredicate = predicate ?? (route) => false; while (_activePages.length > 1 && !newPredicate(_activePages.last.route!)) { _popWithResult(); } return _replace(args, route); } @override Future? offAllNamed( String newRouteName, { // bool Function(GetPage route)? predicate, dynamic arguments, String? id, Map? parameters, }) async { final args = _buildPageSettings(newRouteName, arguments); final route = _getRouteDecoder(args); if (route == null) return null; while (_activePages.length > 1) { _activePages.removeLast(); } return _replaceNamed(route); } @override Future? offNamedUntil( String page, { bool Function(GetPage route)? predicate, dynamic arguments, String? id, Map? parameters, }) async { final args = _buildPageSettings(page, arguments); final route = _getRouteDecoder(args); if (route == null) return null; final newPredicate = predicate ?? (route) => false; while (_activePages.length > 1 && !newPredicate(_activePages.last.route!)) { _activePages.removeLast(); } return _push(route); } @override Future offNamed( String page, { dynamic arguments, String? id, Map? parameters, }) async { final args = _buildPageSettings(page, arguments); final route = _getRouteDecoder(args); if (route == null) return null; _popWithResult(); return _push(route); } @override Future toNamedAndOffUntil( String page, bool Function(GetPage) predicate, [ Object? data, ]) async { final arguments = _buildPageSettings(page, data); final route = _getRouteDecoder(arguments); if (route == null) return null; while (_activePages.isNotEmpty && !predicate(_activePages.last.route!)) { _popWithResult(); } return _push(route); } @override Future offUntil( Widget Function() page, bool Function(GetPage) predicate, [ Object? arguments, ]) async { while (_activePages.isNotEmpty && !predicate(_activePages.last.route!)) { _popWithResult(); } return to(page, arguments: arguments); } @override void removeRoute(String name) { _activePages.remove(RouteDecoder.fromRoute(name)); } bool get canBack { return _activePages.length > 1; } void _checkIfCanBack() { assert(() { if (!canBack) { final last = _activePages.last; final name = last.route?.name; throw 'The page $name cannot be popped'; } return true; }()); } @override Future backAndtoNamed(String page, {T? result, Object? arguments}) async { final args = _buildPageSettings(page, arguments); final route = _getRouteDecoder(args); if (route == null) return null; _popWithResult(result); return _push(route); } /// Removes routes according to [PopMode] /// until it reaches the specific [fullRoute], /// DOES NOT remove the [fullRoute] @override Future popModeUntil( String fullRoute, { PopMode popMode = PopMode.history, }) async { // remove history or page entries until you meet route var iterator = currentConfiguration; while (_canPop(popMode) && iterator != null) { //the next line causes wasm compile error if included in the while loop //https://github.com/flutter/flutter/issues/140110 if (iterator.pageSettings?.name == fullRoute) { break; } await _pop(popMode, null); // replace iterator iterator = currentConfiguration; } notifyListeners(); } @override void backUntil(bool Function(GetPage) predicate) { while (_activePages.length > 1 && !predicate(_activePages.last.route!)) { _popWithResult(); } notifyListeners(); } Future _replace(PageSettings arguments, GetPage page) async { final index = _activePages.length > 1 ? _activePages.length - 1 : 0; _routeTree.addRoute(page); final activePage = _getRouteDecoder(arguments); // final activePage = _configureRouterDecoder(route!, arguments); _activePages[index] = activePage!; notifyListeners(); final result = await activePage.route?.completer?.future as Future?; _routeTree.removeRoute(page); return result; } Future _replaceNamed(RouteDecoder activePage) async { final index = _activePages.length > 1 ? _activePages.length - 1 : 0; // final activePage = _configureRouterDecoder(page, arguments); _activePages[index] = activePage; notifyListeners(); final result = await activePage.route?.completer?.future as Future?; return result; } /// Takes a route [name] String generated by [to], [off], [offAll] /// (and similar context navigation methods), cleans the extra chars and /// accommodates the format. /// TODO: check for a more "appealing" URL naming convention. /// `() => MyHomeScreenView` becomes `/my-home-screen-view`. String _cleanRouteName(String name) { name = name.replaceAll('() => ', ''); /// uncomment for URL styling. // name = name.paramCase!; if (!name.startsWith('/')) { name = '/$name'; } return Uri.tryParse(name)?.toString() ?? name; } PageSettings _buildPageSettings(String page, [Object? data]) { var uri = Uri.parse(page); return PageSettings(uri, data); } @protected RouteDecoder? _getRouteDecoder(PageSettings arguments) { var page = arguments.uri.path; final parameters = arguments.params; if (parameters.isNotEmpty) { final uri = Uri(path: page, queryParameters: parameters); page = uri.toString(); } final decoder = _routeTree.matchRoute(page, arguments: arguments); final route = decoder.route; if (route == null) return null; return _configureRouterDecoder(decoder, arguments); } @protected RouteDecoder _configureRouterDecoder( RouteDecoder decoder, PageSettings arguments) { final parameters = arguments.params.isEmpty ? arguments.query : arguments.params; arguments.params.addAll(arguments.query); if (decoder.parameters.isEmpty) { decoder.parameters.addAll(parameters); } decoder.route = decoder.route?.copyWith( completer: _activePages.isEmpty ? null : Completer(), arguments: arguments, parameters: parameters, key: ValueKey(arguments.name), ); return decoder; } Future _push(RouteDecoder decoder, {bool rebuildStack = true}) async { var res = await runMiddleware(decoder); if (res == null) return null; // final res = mid ?? decoder; // if (res == null) res = decoder; final preventDuplicateHandlingMode = res.route?.preventDuplicateHandlingMode ?? PreventDuplicateHandlingMode.reorderRoutes; final onStackPage = _activePages .firstWhereOrNull((element) => element.route?.key == res.route?.key); /// There are no duplicate routes in the stack if (onStackPage == null) { _activePages.add(res); } else { /// There are duplicate routes, reorder switch (preventDuplicateHandlingMode) { case PreventDuplicateHandlingMode.doNothing: break; case PreventDuplicateHandlingMode.reorderRoutes: _activePages.remove(onStackPage); _activePages.add(res); break; case PreventDuplicateHandlingMode.popUntilOriginalRoute: while (_activePages.last == onStackPage) { _popWithResult(); } break; case PreventDuplicateHandlingMode.recreate: _activePages.remove(onStackPage); _activePages.add(res); } } if (rebuildStack) { notifyListeners(); } return decoder.route?.completer?.future as Future?; } @override Future setNewRoutePath(RouteDecoder configuration) async { final page = configuration.route; if (page == null) { goToUnknownPage(); return; } else { _push(configuration); } } @override RouteDecoder? get currentConfiguration { if (_activePages.isEmpty) return null; final route = _activePages.last; return route; } Future handlePopupRoutes({ Object? result, }) async { Route? currentRoute; navigatorKey.currentState!.popUntil((route) { currentRoute = route; return true; }); if (currentRoute is PopupRoute) { return await navigatorKey.currentState!.maybePop(result); } return false; } @override Future popRoute({ Object? result, PopMode? popMode, }) async { //Returning false will cause the entire app to be popped. final wasPopup = await handlePopupRoutes(result: result); if (wasPopup) return true; if (_canPop(popMode ?? backButtonPopMode)) { await _pop(popMode ?? backButtonPopMode, result); notifyListeners(); return true; } return super.popRoute(); } @override void back([T? result]) { _checkIfCanBack(); _popWithResult(result); notifyListeners(); } bool _onPopVisualRoute(Route route, dynamic result) { final didPop = route.didPop(result); if (!didPop) { return false; } _popWithResult(result); // final settings = route.settings; // if (settings is GetPage) { // final config = _activePages.cast().firstWhere( // (element) => element?.route == settings, // orElse: () => null, // ); // if (config != null) { // _removeHistoryEntry(config, result); // } // } notifyListeners(); //return !route.navigator!.userGestureInProgress; return true; } } ================================================ FILE: lib/get_navigation/src/routes/get_transition_mixin.dart ================================================ import 'dart:math'; import 'dart:ui'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import '../../../get.dart'; import '../root/get_root.dart'; const double _kBackGestureWidth = 20.0; const double _kMinFlingVelocity = 1; // Screen widths per second. // An eyeballed value for the maximum time it takes for a page to animate // forward if the user releases a page mid swipe. const int _kMaxMidSwipePageForwardAnimationTime = 800; // Milliseconds. // The maximum time for a page to get reset to it's original position if the // user releases a page mid swipe. const int _kMaxPageBackAnimationTime = 300; // Milliseconds. class GetBackGestureDetector extends StatefulWidget { const GetBackGestureDetector({ super.key, required this.limitedSwipe, required this.gestureWidth, required this.initialOffset, required this.popGestureEnable, required this.onStartPopGesture, required this.child, }); final bool limitedSwipe; final double gestureWidth; final double initialOffset; final Widget child; final ValueGetter popGestureEnable; final ValueGetter> onStartPopGesture; @override GetBackGestureDetectorState createState() => GetBackGestureDetectorState(); } class GetBackGestureDetectorState extends State> { GetBackGestureController? _backGestureController; void _handleDragStart(DragStartDetails details) { assert(mounted); assert(_backGestureController == null); _backGestureController = widget.onStartPopGesture(); } void _handleDragUpdate(DragUpdateDetails details) { assert(mounted); assert(_backGestureController != null); _backGestureController!.dragUpdate( _convertToLogical(details.primaryDelta! / context.size!.width), ); } void _handleDragEnd(DragEndDetails details) { assert(mounted); assert(_backGestureController != null); _backGestureController!.dragEnd(_convertToLogical( details.velocity.pixelsPerSecond.dx / context.size!.width, )); _backGestureController = null; } void _handleDragCancel() { assert(mounted); // This can be called even if start is not called, paired with the "down" // event that we don't consider here. _backGestureController?.dragEnd(0); _backGestureController = null; } double _convertToLogical(double value) { switch (Directionality.of(context)) { case TextDirection.rtl: return -value; case TextDirection.ltr: return value; } } @override Widget build(BuildContext context) { assert(debugCheckHasDirectionality(context)); final gestureDetector = RawGestureDetector( behavior: HitTestBehavior.translucent, gestures: { _DirectionalityDragGestureRecognizer: GestureRecognizerFactoryWithHandlers< _DirectionalityDragGestureRecognizer>( () { final directionality = Directionality.of(context); return _DirectionalityDragGestureRecognizer( debugOwner: this, isRTL: directionality == TextDirection.rtl, isLTR: directionality == TextDirection.ltr, hasbackGestureController: () => _backGestureController != null, popGestureEnable: widget.popGestureEnable, ); }, (directionalityDragGesture) => directionalityDragGesture ..onStart = _handleDragStart ..onUpdate = _handleDragUpdate ..onEnd = _handleDragEnd ..onCancel = _handleDragCancel, ) }, ); return Stack( fit: StackFit.passthrough, children: [ widget.child, if (widget.limitedSwipe) PositionedDirectional( start: widget.initialOffset, width: _dragAreaWidth(context), top: 0, bottom: 0, child: gestureDetector, ) else Positioned.fill(child: gestureDetector), ], ); } double _dragAreaWidth(BuildContext context) { // For devices with notches, the drag area needs to be larger on the side // that has the notch. final dragAreaWidth = Directionality.of(context) == TextDirection.ltr ? context.mediaQuery.padding.left : context.mediaQuery.padding.right; return max(dragAreaWidth, widget.gestureWidth); } } class GetBackGestureController { GetBackGestureController({ required this.navigator, required this.controller, }) { navigator.didStartUserGesture(); } final AnimationController controller; final NavigatorState navigator; /// The drag gesture has changed by [delta]. The total range of the /// drag should be 0.0 to 1.0. void dragUpdate(double delta) { controller.value -= delta; } /// The drag gesture has ended with a horizontal motion of [velocity] as a /// fraction of screen width per second. void dragEnd(double velocity) { // Fling in the appropriate direction. // AnimationController.fling is guaranteed to // take at least one frame. // // This curve has been determined through rigorously eyeballing native iOS // animations. const Curve animationCurve = Curves.fastLinearToSlowEaseIn; final bool animateForward; // If the user releases the page before mid screen with sufficient velocity, // or after mid screen, we should animate the page out. Otherwise, the page // should be animated back in. if (velocity.abs() >= _kMinFlingVelocity) { animateForward = velocity <= 0; } else { animateForward = controller.value > 0.5; } if (animateForward) { // The closer the panel is to dismissing, the shorter the animation is. // We want to cap the animation time, but we want to use a linear curve // to determine it. final droppedPageForwardAnimationTime = min( lerpDouble(_kMaxMidSwipePageForwardAnimationTime, 0, controller.value)! .floor(), _kMaxPageBackAnimationTime, ); controller.animateTo(1.0, duration: Duration(milliseconds: droppedPageForwardAnimationTime), curve: animationCurve); } else { // This route is destined to pop at this point. Reuse navigator's pop. Get.back(); // The popping may have finished inline if already at the // target destination. if (controller.isAnimating) { // Otherwise, use a custom popping animation duration and curve. final droppedPageBackAnimationTime = lerpDouble( 0, _kMaxMidSwipePageForwardAnimationTime, controller.value)! .floor(); controller.animateBack(0.0, duration: Duration(milliseconds: droppedPageBackAnimationTime), curve: animationCurve); } } if (controller.isAnimating) { // Keep the userGestureInProgress in true state so we don't change the // curve of the page transition mid-flight since CupertinoPageTransition // depends on userGestureInProgress. late AnimationStatusListener animationStatusCallback; animationStatusCallback = (status) { navigator.didStopUserGesture(); controller.removeStatusListener(animationStatusCallback); }; controller.addStatusListener(animationStatusCallback); } else { navigator.didStopUserGesture(); } } } mixin GetPageRouteTransitionMixin on PageRoute { ValueNotifier? _previousTitle; @override Color? get barrierColor => null; @override String? get barrierLabel => null; double Function(BuildContext context)? get gestureWidth; /// True if an iOS-style back swipe pop gesture is currently /// underway for this route. /// /// See also: /// /// * [isPopGestureInProgress], which returns true if a Cupertino pop gesture /// is currently underway for specific route. /// * [popGestureEnabled], which returns true if a user-triggered pop gesture /// would be allowed. //bool get popGestureInProgress => isPopGestureInProgress(this); /// The title string of the previous [CupertinoPageRoute]. /// /// The [ValueListenable]'s value is readable after the route is installed /// onto a [Navigator]. The [ValueListenable] will also notify its listeners /// if the value changes (such as by replacing the previous route). /// /// The [ValueListenable] itself will be null before the route is installed. /// Its content value will be null if the previous route has no title or /// is not a [CupertinoPageRoute]. /// /// See also: /// /// * [ValueListenableBuilder], which can be used to listen and rebuild /// widgets based on a ValueListenable. ValueListenable get previousTitle { assert( _previousTitle != null, ''' Cannot read the previousTitle for a route that has not yet been installed''', ); return _previousTitle!; } bool get showCupertinoParallax; /// {@template flutter.cupertino.CupertinoRouteTransitionMixin.title} /// A title string for this route. /// /// Used to auto-populate [CupertinoNavigationBar] and /// [CupertinoSliverNavigationBar]'s `middle`/`largeTitle` widgets when /// one is not manually supplied. /// {@endtemplate} String? get title; @override // A relatively rigorous eyeball estimation. Duration get transitionDuration; @override Duration get reverseTransitionDuration; /// Builds the primary contents of the route. @protected Widget buildContent(BuildContext context); @override Widget buildPage(BuildContext context, Animation animation, Animation secondaryAnimation) { final child = buildContent(context); final Widget result = Semantics( scopesRoute: true, explicitChildNodes: true, child: child, ); return result; } @override Widget buildTransitions(BuildContext context, Animation animation, Animation secondaryAnimation, Widget child) { return buildPageTransitions( this, context, animation, secondaryAnimation, child); } @override bool canTransitionTo(TransitionRoute nextRoute) { // Don't perform outgoing animation if the next route is a // fullscreen dialog. return (nextRoute is GetPageRouteTransitionMixin && !nextRoute.fullscreenDialog && nextRoute.showCupertinoParallax) || (nextRoute is CupertinoRouteTransitionMixin && !nextRoute.fullscreenDialog) || (nextRoute is CupertinoSheetRoute && !nextRoute.fullscreenDialog); } @override void didChangePrevious(Route? previousRoute) { final previousTitleString = previousRoute is CupertinoRouteTransitionMixin ? previousRoute.title : null; if (_previousTitle == null) { _previousTitle = ValueNotifier(previousTitleString); } else { _previousTitle!.value = previousTitleString; } super.didChangePrevious(previousRoute); } static bool canSwipe(GetPageRoute route) => route.popGesture ?? Get.defaultPopGesture ?? GetPlatform.isIOS; /// Returns a [CupertinoFullscreenDialogTransition] if [route] is a full /// screen dialog, otherwise a [CupertinoPageTransition] is returned. /// /// Used by [CupertinoPageRoute.buildTransitions]. /// /// This method can be applied to any [PageRoute], not just /// [CupertinoPageRoute]. It's typically used to provide a Cupertino style /// horizontal transition for material widgets when the target platform /// is [TargetPlatform.iOS]. /// /// See also: /// /// * [CupertinoPageTransitionsBuilder], which uses this method to define a /// [PageTransitionsBuilder] for the [PageTransitionsTheme]. static Widget buildPageTransitions( PageRoute rawRoute, BuildContext context, Animation animation, Animation secondaryAnimation, Widget child, { bool limitedSwipe = false, double initialOffset = 0, }) { // Check if the route has an animation that's currently participating // in a back swipe gesture. // // In the middle of a back gesture drag, let the transition be linear to // match finger motions. final route = rawRoute as GetPageRoute; final linearTransition = route.popGestureInProgress; final finalCurve = route.curve ?? Get.defaultTransitionCurve; final hasCurve = route.curve != null; if (route.fullscreenDialog && route.transition == null) { return CupertinoFullscreenDialogTransition( primaryRouteAnimation: hasCurve ? CurvedAnimation(parent: animation, curve: finalCurve) : animation, secondaryRouteAnimation: secondaryAnimation, linearTransition: linearTransition, child: child, ); } else { if (route.customTransition != null) { return route.customTransition!.buildTransition( context, finalCurve, route.alignment, animation, secondaryAnimation, GetBackGestureDetector( popGestureEnable: () => _isPopGestureEnabled(route, canSwipe(route), context), onStartPopGesture: () { assert(_isPopGestureEnabled(route, canSwipe(route), context)); return _startPopGesture(route); }, limitedSwipe: limitedSwipe, gestureWidth: route.gestureWidth?.call(context) ?? _kBackGestureWidth, initialOffset: initialOffset, child: child, ), ); } /// Apply the curve by default... final iosAnimation = animation; animation = CurvedAnimation(parent: animation, curve: finalCurve); switch (route.transition ?? Get.defaultTransition) { case Transition.leftToRight: return SlideLeftTransition().buildTransitions( context, route.curve, route.alignment, animation, secondaryAnimation, GetBackGestureDetector( popGestureEnable: () => _isPopGestureEnabled(route, canSwipe(route), context), onStartPopGesture: () { assert(_isPopGestureEnabled(route, canSwipe(route), context)); return _startPopGesture(route); }, limitedSwipe: limitedSwipe, gestureWidth: route.gestureWidth?.call(context) ?? _kBackGestureWidth, initialOffset: initialOffset, child: child, )); case Transition.downToUp: return SlideDownTransition().buildTransitions( context, route.curve, route.alignment, animation, secondaryAnimation, GetBackGestureDetector( popGestureEnable: () => _isPopGestureEnabled(route, canSwipe(route), context), onStartPopGesture: () { assert(_isPopGestureEnabled(route, canSwipe(route), context)); return _startPopGesture(route); }, limitedSwipe: limitedSwipe, gestureWidth: route.gestureWidth?.call(context) ?? _kBackGestureWidth, initialOffset: initialOffset, child: child, )); case Transition.upToDown: return SlideTopTransition().buildTransitions( context, route.curve, route.alignment, animation, secondaryAnimation, GetBackGestureDetector( popGestureEnable: () => _isPopGestureEnabled(route, canSwipe(route), context), onStartPopGesture: () { assert(_isPopGestureEnabled(route, canSwipe(route), context)); return _startPopGesture(route); }, limitedSwipe: limitedSwipe, gestureWidth: route.gestureWidth?.call(context) ?? _kBackGestureWidth, initialOffset: initialOffset, child: child, )); case Transition.noTransition: return GetBackGestureDetector( popGestureEnable: () => _isPopGestureEnabled(route, canSwipe(route), context), onStartPopGesture: () { assert(_isPopGestureEnabled(route, canSwipe(route), context)); return _startPopGesture(route); }, limitedSwipe: limitedSwipe, gestureWidth: route.gestureWidth?.call(context) ?? _kBackGestureWidth, initialOffset: initialOffset, child: child, ); case Transition.rightToLeft: return SlideRightTransition().buildTransitions( context, route.curve, route.alignment, animation, secondaryAnimation, GetBackGestureDetector( popGestureEnable: () => _isPopGestureEnabled(route, canSwipe(route), context), onStartPopGesture: () { assert(_isPopGestureEnabled(route, canSwipe(route), context)); return _startPopGesture(route); }, limitedSwipe: limitedSwipe, gestureWidth: route.gestureWidth?.call(context) ?? _kBackGestureWidth, initialOffset: initialOffset, child: child, )); case Transition.zoom: return ZoomInTransition().buildTransitions( context, route.curve, route.alignment, animation, secondaryAnimation, GetBackGestureDetector( popGestureEnable: () => _isPopGestureEnabled(route, canSwipe(route), context), onStartPopGesture: () { assert(_isPopGestureEnabled(route, canSwipe(route), context)); return _startPopGesture(route); }, limitedSwipe: limitedSwipe, gestureWidth: route.gestureWidth?.call(context) ?? _kBackGestureWidth, initialOffset: initialOffset, child: child, )); case Transition.fadeIn: return FadeInTransition().buildTransitions( context, route.curve, route.alignment, animation, secondaryAnimation, GetBackGestureDetector( popGestureEnable: () => _isPopGestureEnabled(route, canSwipe(route), context), onStartPopGesture: () { assert(_isPopGestureEnabled(route, canSwipe(route), context)); return _startPopGesture(route); }, limitedSwipe: limitedSwipe, gestureWidth: route.gestureWidth?.call(context) ?? _kBackGestureWidth, initialOffset: initialOffset, child: child, )); case Transition.rightToLeftWithFade: return RightToLeftFadeTransition().buildTransitions( context, route.curve, route.alignment, animation, secondaryAnimation, GetBackGestureDetector( popGestureEnable: () => _isPopGestureEnabled(route, canSwipe(route), context), onStartPopGesture: () { assert(_isPopGestureEnabled(route, canSwipe(route), context)); return _startPopGesture(route); }, limitedSwipe: limitedSwipe, gestureWidth: route.gestureWidth?.call(context) ?? _kBackGestureWidth, initialOffset: initialOffset, child: child, )); case Transition.leftToRightWithFade: return LeftToRightFadeTransition().buildTransitions( context, route.curve, route.alignment, animation, secondaryAnimation, GetBackGestureDetector( popGestureEnable: () => _isPopGestureEnabled(route, canSwipe(route), context), onStartPopGesture: () { assert(_isPopGestureEnabled(route, canSwipe(route), context)); return _startPopGesture(route); }, limitedSwipe: limitedSwipe, gestureWidth: route.gestureWidth?.call(context) ?? _kBackGestureWidth, initialOffset: initialOffset, child: child, )); case Transition.cupertino: return CupertinoPageTransition( primaryRouteAnimation: animation, secondaryRouteAnimation: secondaryAnimation, linearTransition: linearTransition, child: GetBackGestureDetector( popGestureEnable: () => _isPopGestureEnabled(route, canSwipe(route), context), onStartPopGesture: () { assert(_isPopGestureEnabled(route, canSwipe(route), context)); return _startPopGesture(route); }, limitedSwipe: limitedSwipe, gestureWidth: route.gestureWidth?.call(context) ?? _kBackGestureWidth, initialOffset: initialOffset, child: child, )); case Transition.size: return SizeTransitions().buildTransitions( context, route.curve!, route.alignment, animation, secondaryAnimation, GetBackGestureDetector( popGestureEnable: () => _isPopGestureEnabled(route, canSwipe(route), context), onStartPopGesture: () { assert(_isPopGestureEnabled(route, canSwipe(route), context)); return _startPopGesture(route); }, limitedSwipe: limitedSwipe, gestureWidth: route.gestureWidth?.call(context) ?? _kBackGestureWidth, initialOffset: initialOffset, child: child, )); case Transition.fade: return const FadeUpwardsPageTransitionsBuilder().buildTransitions( route, context, animation, secondaryAnimation, GetBackGestureDetector( popGestureEnable: () => _isPopGestureEnabled(route, canSwipe(route), context), onStartPopGesture: () { assert(_isPopGestureEnabled(route, canSwipe(route), context)); return _startPopGesture(route); }, limitedSwipe: limitedSwipe, gestureWidth: route.gestureWidth?.call(context) ?? _kBackGestureWidth, initialOffset: initialOffset, child: child, )); case Transition.topLevel: return const ZoomPageTransitionsBuilder().buildTransitions( route, context, animation, secondaryAnimation, GetBackGestureDetector( popGestureEnable: () => _isPopGestureEnabled(route, canSwipe(route), context), onStartPopGesture: () { assert(_isPopGestureEnabled(route, canSwipe(route), context)); return _startPopGesture(route); }, limitedSwipe: limitedSwipe, gestureWidth: route.gestureWidth?.call(context) ?? _kBackGestureWidth, initialOffset: initialOffset, child: child, )); case Transition.native: return const PageTransitionsTheme().buildTransitions( route, context, iosAnimation, secondaryAnimation, GetBackGestureDetector( popGestureEnable: () => _isPopGestureEnabled(route, canSwipe(route), context), onStartPopGesture: () { assert(_isPopGestureEnabled(route, canSwipe(route), context)); return _startPopGesture(route); }, limitedSwipe: limitedSwipe, gestureWidth: route.gestureWidth?.call(context) ?? _kBackGestureWidth, initialOffset: initialOffset, child: child, )); case Transition.circularReveal: return CircularRevealTransition().buildTransitions( context, route.curve, route.alignment, animation, secondaryAnimation, GetBackGestureDetector( popGestureEnable: () => _isPopGestureEnabled(route, canSwipe(route), context), onStartPopGesture: () { assert(_isPopGestureEnabled(route, canSwipe(route), context)); return _startPopGesture(route); }, limitedSwipe: limitedSwipe, gestureWidth: route.gestureWidth?.call(context) ?? _kBackGestureWidth, initialOffset: initialOffset, child: child, )); default: final customTransition = GetRoot.of(context).config.customTransition; if (customTransition != null) { return customTransition.buildTransition(context, route.curve, route.alignment, animation, secondaryAnimation, child); } PageTransitionsTheme pageTransitionsTheme = Theme.of(context).pageTransitionsTheme; return pageTransitionsTheme.buildTransitions( route, context, iosAnimation, secondaryAnimation, GetBackGestureDetector( popGestureEnable: () => _isPopGestureEnabled(route, canSwipe(route), context), onStartPopGesture: () { assert( _isPopGestureEnabled(route, canSwipe(route), context), ); return _startPopGesture(route); }, limitedSwipe: limitedSwipe, gestureWidth: route.gestureWidth?.call(context) ?? _kBackGestureWidth, initialOffset: initialOffset, child: child, )); } } } // Called by GetBackGestureDetector when a pop ("back") drag start // gesture is detected. The returned controller handles all of the subsequent // drag events. /// True if an iOS-style back swipe pop gesture is currently /// underway for [route]. /// /// This just check the route's [NavigatorState.userGestureInProgress]. /// /// See also: /// /// * [popGestureEnabled], which returns true if a user-triggered pop gesture /// would be allowed. static bool isPopGestureInProgress(BuildContext context) { final route = ModalRoute.of(context)!; return route.navigator!.userGestureInProgress; } static bool _isPopGestureEnabled( PageRoute route, bool canSwipe, BuildContext context) { // If there's nothing to go back to, then obviously we don't support // the back gesture. if (route.isFirst) return false; // If the route wouldn't actually pop if we popped it, then the gesture // would be really confusing (or would skip internal routes), // so disallow it. if (route.willHandlePopInternally) return false; // support [PopScope] if (route.popDisposition == RoutePopDisposition.doNotPop) return false; // Fullscreen dialogs aren't dismissible by back swipe. if (route.fullscreenDialog) return false; // If we're in an animation already, we cannot be manually swiped. if (route.animation!.status != AnimationStatus.completed) return false; // If we're being popped into, we also cannot be swiped until the pop above // it completes. This translates to our secondary animation being // dismissed. if (route.secondaryAnimation!.status != AnimationStatus.dismissed) { return false; } // If we're in a gesture already, we cannot start another. if (GetPageRouteTransitionMixin.isPopGestureInProgress(context)) { return false; } // Don't perfome swipe if canSwipe be false if (!canSwipe) return false; // Looks like a back gesture would be welcome! return true; } static GetBackGestureController _startPopGesture( PageRoute route, ) { return GetBackGestureController( navigator: route.navigator!, controller: route.controller!, // protected access ); } } class _DirectionalityDragGestureRecognizer extends HorizontalDragGestureRecognizer { final ValueGetter popGestureEnable; final ValueGetter hasbackGestureController; final bool isRTL; final bool isLTR; _DirectionalityDragGestureRecognizer({ required this.isRTL, required this.isLTR, required this.popGestureEnable, required this.hasbackGestureController, super.debugOwner, }); @override void handleEvent(PointerEvent event) { final dx = event.delta.dx; if (hasbackGestureController() || popGestureEnable() && (isRTL && dx < 0 || isLTR && dx > 0 || dx == 0)) { super.handleEvent(event); } else { stopTrackingPointer(event.pointer); } } } ================================================ FILE: lib/get_navigation/src/routes/index.dart ================================================ export 'circular_reveal_clipper.dart'; export 'custom_transition.dart'; export 'default_route.dart'; export 'default_transitions.dart'; export 'get_information_parser.dart'; export 'get_navigation_interface.dart'; export 'get_navigator.dart'; export 'get_route.dart'; export 'get_router_delegate.dart'; export 'get_transition_mixin.dart'; export 'modules.dart'; export 'observers/route_observer.dart'; export 'page_settings.dart'; export 'parse_route.dart'; export 'route_middleware.dart'; export 'route_report.dart'; export 'router_outlet.dart'; export 'transitions_type.dart'; export 'url_strategy/url_strategy.dart'; ================================================ FILE: lib/get_navigation/src/routes/modules.dart ================================================ import 'package:flutter/material.dart'; import '../../../instance_manager.dart'; import '../router_report.dart'; class Dependencies { void lazyPut(InstanceBuilderCallback builder, {String? tag, bool fenix = false}) { Get.lazyPut(builder, tag: tag, fenix: fenix); } S call() { return find(); } void spawn(InstanceBuilderCallback builder, {String? tag, bool permanent = true}) => Get.spawn(builder, tag: tag, permanent: permanent); S find({String? tag}) => Get.find(tag: tag); S put(S dependency, {String? tag, bool permanent = false, InstanceBuilderCallback? builder}) => Get.put(dependency, tag: tag, permanent: permanent); Future delete({String? tag, bool force = false}) async => Get.delete(tag: tag, force: force); Future deleteAll({bool force = false}) async => Get.deleteAll(force: force); void reloadAll({bool force = false}) => Get.reloadAll(force: force); void reload({String? tag, String? key, bool force = false}) => Get.reload(tag: tag, key: key, force: force); bool isRegistered({String? tag}) => Get.isRegistered(tag: tag); bool isPrepared({String? tag}) => Get.isPrepared(tag: tag); void replace

(P child, {String? tag}) { final info = Get.getInstanceInfo

(tag: tag); final permanent = (info.isPermanent ?? false); delete

(tag: tag, force: permanent); put(child, tag: tag, permanent: permanent); } void lazyReplace

(InstanceBuilderCallback

builder, {String? tag, bool? fenix}) { final info = Get.getInstanceInfo

(tag: tag); final permanent = (info.isPermanent ?? false); delete

(tag: tag, force: permanent); lazyPut(builder, tag: tag, fenix: fenix ?? permanent); } } abstract class Module extends StatefulWidget { const Module({super.key}); Widget view(BuildContext context); void dependencies(Dependencies i); @override ModuleState createState() => ModuleState(); } class ModuleState extends State { @override void initState() { RouterReportManager.instance.reportCurrentRoute(this); widget.dependencies(Dependencies()); super.initState(); } @override void dispose() { RouterReportManager.instance.reportRouteDispose(this); super.dispose(); } @override Widget build(BuildContext context) { return widget.view(context); } } ================================================ FILE: lib/get_navigation/src/routes/new_path_route.dart ================================================ import 'dart:async'; import 'package:flutter/widgets.dart'; import 'get_route.dart'; class RouteMatcher { final RouteNode _root = RouteNode('/', '/'); RouteNode addRoute(String path) { final segments = _parsePath(path); var currentNode = _root; for (final segment in segments) { final existingChild = currentNode.findChild(segment); if (existingChild != null) { currentNode = existingChild; } else { final newChild = RouteNode(segment, path); currentNode.addChild(newChild); currentNode = newChild; } } return currentNode; } void removeRoute(String path) { final segments = _parsePath(path); var currentNode = _root; RouteNode? nodeToDelete; // Traverse the tree to find the node to delete for (final segment in segments) { final child = currentNode.findChild(segment); if (child == null) { return; // Node not found, nothing to delete } if (child.nodeSegments.length == segments.length) { nodeToDelete = child; break; } currentNode = child; } if (nodeToDelete == null) { return; // Node not found, nothing to delete } final parent = nodeToDelete.parent!; parent.nodeSegments.remove(nodeToDelete); } RouteNode? _findChild(RouteNode currentNode, String segment) { return currentNode.nodeSegments .firstWhereOrNull((node) => node.matches(segment)); } MatchResult? matchRoute(String path) { final uri = Uri.parse(path); final segments = _parsePath(uri.path); var currentNode = _root; final parameters = {}; final urlParameters = uri.queryParameters; for (final segment in segments) { if (segment.isEmpty) continue; final child = _findChild(currentNode, segment); if (child == null) { return null; } else { if (child.path.startsWith(':')) { parameters[child.path.substring(1)] = segment; } if (child.nodeSegments.length == segments.length) { return null; } currentNode = child; } } return MatchResult( currentNode, parameters, path, urlParameters: urlParameters, ); } List _parsePath(String path) { return path.split('/').where((segment) => segment.isNotEmpty).toList(); } } class RouteTreeResult { final GetPage? route; final MatchResult matchResult; RouteTreeResult({ required this.route, required this.matchResult, }); @override String toString() { return 'RouteTreeResult(route: $route, matchResult: $matchResult)'; } RouteTreeResult configure(String page, Object? arguments) { return copyWith( route: route?.copyWith( key: ValueKey(page), settings: RouteSettings(name: page, arguments: arguments), completer: Completer(), arguments: arguments, )); } RouteTreeResult copyWith({ GetPage? route, MatchResult? matchResult, }) { return RouteTreeResult( route: route ?? this.route, matchResult: matchResult ?? this.matchResult, ); } } class RouteTree { static final instance = RouteTree(); final Map tree = {}; final RouteMatcher matcher = RouteMatcher(); void addRoute(GetPage route) { matcher.addRoute(route.name); tree[route.name] = route; handleChild(route); } void addRoutes(List routes) { for (var route in routes) { addRoute(route); } } void handleChild(GetPage route) { final children = route.children; for (var child in children) { final middlewares = List.of(route.middlewares); final bindings = List.of(route.bindings); middlewares.addAll(child.middlewares); bindings.addAll(child.bindings); child = child.copyWith(middlewares: middlewares, bindings: bindings); if (child.inheritParentPath) { child = child.copyWith( name: ('${route.path}/${child.path}').replaceAll(r'//', '/')); } addRoute(child); } } void removeRoute(GetPage route) { matcher.removeRoute(route.name); tree.remove(route.name); } void removeRoutes(List routes) { for (var route in routes) { removeRoute(route); } } RouteTreeResult? matchRoute(String path) { final matchResult = matcher.matchRoute(path); if (matchResult != null) { final route = tree[matchResult.node.originalPath]; return RouteTreeResult( route: route, matchResult: matchResult, ); } return null; } } /// A class representing the result of a route matching operation. class MatchResult { /// The route found that matches the result final RouteNode node; /// The current path of match, eg: adding 'user/:id' the match result for 'user/123' will be: 'user/123' final String currentPath; /// Route parameters eg: adding 'user/:id' the match result for 'user/123' will be: {id: 123} final Map parameters; /// Route url parameters eg: adding 'user' the match result for 'user?foo=bar' will be: {foo: bar} final Map urlParameters; MatchResult(this.node, this.parameters, this.currentPath, {this.urlParameters = const {}}); @override String toString() => 'MatchResult(node: $node, currentPath: $currentPath, parameters: $parameters, urlParameters: $urlParameters)'; } // A class representing a node in a routing tree. class RouteNode { String path; String originalPath; RouteNode? parent; List nodeSegments = []; RouteNode(this.path, this.originalPath, {this.parent}); bool get isRoot => parent == null; String get fullPath { if (isRoot) { return '/'; } else { final parentPath = parent?.fullPath == '/' ? '' : parent?.fullPath; return '$parentPath/$path'; } } bool get hasChildren => nodeSegments.isNotEmpty; void addChild(RouteNode child) { nodeSegments.add(child); child.parent = this; } RouteNode? findChild(String name) { return nodeSegments.firstWhereOrNull((node) => node.path == name); } bool matches(String name) { return name == path || path == '*' || path.startsWith(':'); } @override String toString() => 'RouteNode(name: $path, nodeSegments: $nodeSegments, fullPath: $fullPath )'; } extension Foo on Iterable { T? firstWhereOrNull(bool Function(T element) test) { for (var element in this) { if (test(element)) return element; } return null; } } ================================================ FILE: lib/get_navigation/src/routes/observers/route_observer.dart ================================================ import 'package:flutter/widgets.dart'; import '../../../../get_core/get_core.dart'; import '../../../../instance_manager.dart'; import '../../../get_navigation.dart'; import '../../dialog/dialog_route.dart'; import '../../router_report.dart'; /// Extracts the name of a route based on it's instance type /// or null if not possible. String? _extractRouteName(Route? route) { if (route?.settings.name != null) { return route!.settings.name; } if (route is GetPageRoute) { return route.routeName; } if (route is GetDialogRoute) { return 'DIALOG ${route.hashCode}'; } if (route is GetModalBottomSheetRoute) { return 'BOTTOMSHEET ${route.hashCode}'; } return null; } class GetObserver extends NavigatorObserver { final Function(Routing?)? routing; final Routing? _routeSend; GetObserver([this.routing, this._routeSend]); @override void didPop(Route route, Route? previousRoute) { super.didPop(route, previousRoute); final currentRoute = _RouteData.ofRoute(route); final newRoute = _RouteData.ofRoute(previousRoute); if (currentRoute.isBottomSheet || currentRoute.isDialog) { Get.log("CLOSE ${currentRoute.name}"); } else if (currentRoute.isGetPageRoute) { Get.log("CLOSE TO ROUTE ${currentRoute.name}"); } if (previousRoute != null) { RouterReportManager.instance.reportCurrentRoute(previousRoute); } // Here we use a 'inverse didPush set', meaning that we use // previous route instead of 'route' because this is // a 'inverse push' _routeSend?.update((value) { // Only PageRoute is allowed to change current value if (previousRoute is PageRoute) { value.current = _extractRouteName(previousRoute) ?? ''; value.previous = newRoute.name ?? ''; } else if (value.previous.isNotEmpty) { value.current = value.previous; } value.args = previousRoute?.settings.arguments; value.route = previousRoute; value.isBack = true; value.removed = ''; value.isBottomSheet = newRoute.isBottomSheet; value.isDialog = newRoute.isDialog; }); routing?.call(_routeSend); } @override void didPush(Route route, Route? previousRoute) { super.didPush(route, previousRoute); final newRoute = _RouteData.ofRoute(route); if (newRoute.isBottomSheet || newRoute.isDialog) { Get.log("OPEN ${newRoute.name}"); } else if (newRoute.isGetPageRoute) { Get.log("GOING TO ROUTE ${newRoute.name}"); } RouterReportManager.instance.reportCurrentRoute(route); _routeSend?.update((value) { if (route is PageRoute) { value.current = newRoute.name ?? ''; } final previousRouteName = _extractRouteName(previousRoute); if (previousRouteName != null) { value.previous = previousRouteName; } value.args = route.settings.arguments; value.route = route; value.isBack = false; value.removed = ''; value.isBottomSheet = newRoute.isBottomSheet ? true : value.isBottomSheet ?? false; value.isDialog = newRoute.isDialog ? true : value.isDialog ?? false; }); if (routing != null) { routing!(_routeSend); } } @override void didRemove(Route route, Route? previousRoute) { super.didRemove(route, previousRoute); final routeName = _extractRouteName(route); final currentRoute = _RouteData.ofRoute(route); final previousRouteName = _extractRouteName(previousRoute); Get.log("REMOVING ROUTE $routeName"); Get.log("PREVIOUS ROUTE $previousRouteName"); _routeSend?.update((value) { value.route = previousRoute; value.isBack = false; value.removed = routeName ?? ''; value.previous = previousRouteName ?? ''; value.isBottomSheet = currentRoute.isBottomSheet ? false : value.isBottomSheet; value.isDialog = currentRoute.isDialog ? false : value.isDialog; }); if (route is GetPageRoute) { RouterReportManager.instance.reportRouteWillDispose(route); } routing?.call(_routeSend); } @override void didReplace({Route? newRoute, Route? oldRoute}) { super.didReplace(newRoute: newRoute, oldRoute: oldRoute); final newName = _extractRouteName(newRoute); final oldName = _extractRouteName(oldRoute); final currentRoute = _RouteData.ofRoute(oldRoute); Get.log("REPLACE ROUTE $oldName"); Get.log("NEW ROUTE $newName"); if (newRoute != null) { RouterReportManager.instance.reportCurrentRoute(newRoute); } _routeSend?.update((value) { // Only PageRoute is allowed to change current value if (newRoute is PageRoute) { value.current = newName ?? ''; } value.args = newRoute?.settings.arguments; value.route = newRoute; value.isBack = false; value.removed = ''; value.previous = oldName ?? ''; value.isBottomSheet = currentRoute.isBottomSheet ? false : value.isBottomSheet; value.isDialog = currentRoute.isDialog ? false : value.isDialog; }); if (oldRoute is GetPageRoute) { RouterReportManager.instance.reportRouteWillDispose(oldRoute); } routing?.call(_routeSend); } } //TODO: Use copyWith, and remove mutate variables class Routing { String current; String previous; dynamic args; String removed; Route? route; bool? isBack; bool? isBottomSheet; bool? isDialog; Routing({ this.current = '', this.previous = '', this.args, this.removed = '', this.route, this.isBack, this.isBottomSheet, this.isDialog, }); void update(void Function(Routing value) fn) { fn(this); } } /// This is basically a util for rules about 'what a route is' class _RouteData { final bool isGetPageRoute; final bool isBottomSheet; final bool isDialog; final String? name; const _RouteData({ required this.name, required this.isGetPageRoute, required this.isBottomSheet, required this.isDialog, }); factory _RouteData.ofRoute(Route? route) { return _RouteData( name: _extractRouteName(route), isGetPageRoute: route is GetPageRoute, isDialog: route is GetDialogRoute, isBottomSheet: route is GetModalBottomSheetRoute, ); } } ================================================ FILE: lib/get_navigation/src/routes/page_settings.dart ================================================ import 'package:flutter/widgets.dart'; import '../../../route_manager.dart'; extension PageArgExt on BuildContext { RouteSettings? get settings { return ModalRoute.of(this)!.settings; } PageSettings? get pageSettings { final args = ModalRoute.of(this)?.settings.arguments; if (args is PageSettings) { return args; } return null; } dynamic get arguments { final args = settings?.arguments; if (args is PageSettings) { return args.arguments; } else { return args; } } Map get params { final args = settings?.arguments; if (args is PageSettings) { return args.params; } else { return {}; } } Router get router { return Router.of(this); } String get location { final parser = router.routeInformationParser; final config = delegate.currentConfiguration; return parser?.restoreRouteInformation(config)?.uri.toString() ?? '/'; } GetDelegate get delegate { return router.routerDelegate as GetDelegate; } } class PageSettings extends RouteSettings { PageSettings( this.uri, [ Object? arguments, ]) : super(arguments: arguments); @override String get name => '$uri'; final Uri uri; final params = {}; String get path => uri.path; List get paths => uri.pathSegments; Map get query => uri.queryParameters; Map> get queries => uri.queryParametersAll; @override String toString() => name; PageSettings copy({ Uri? uri, Object? arguments, }) { return PageSettings( uri ?? this.uri, arguments ?? this.arguments, ); } @override bool operator ==(Object other) { if (identical(this, other)) return true; return other is PageSettings && other.uri == uri && other.arguments == arguments; } @override int get hashCode => uri.hashCode ^ arguments.hashCode; } ================================================ FILE: lib/get_navigation/src/routes/parse_route.dart ================================================ import 'package:flutter/foundation.dart'; import '../../../get.dart'; @immutable class RouteDecoder { const RouteDecoder( this.currentTreeBranch, this.pageSettings, ); final List currentTreeBranch; final PageSettings? pageSettings; factory RouteDecoder.fromRoute(String location) { var uri = Uri.parse(location); final args = PageSettings(uri); final decoder = (Get.rootController.rootDelegate).matchRoute(location, arguments: args); decoder.route = decoder.route?.copyWith( completer: null, arguments: args, parameters: args.params, ); return decoder; } GetPage? get route => currentTreeBranch.isEmpty ? null : currentTreeBranch.last; GetPage routeOrUnknown(GetPage onUnknow) => currentTreeBranch.isEmpty ? onUnknow : currentTreeBranch.last; set route(GetPage? getPage) { if (getPage == null) return; if (currentTreeBranch.isEmpty) { currentTreeBranch.add(getPage); } else { currentTreeBranch[currentTreeBranch.length - 1] = getPage; } } List? get currentChildren => route?.children; Map get parameters => pageSettings?.params ?? {}; dynamic get args { return pageSettings?.arguments; } T? arguments() { final args = pageSettings?.arguments; if (args is T) { return pageSettings?.arguments as T; } else { return null; } } // void replaceArguments(Object? arguments) { // final newRoute = route; // if (newRoute != null) { // final index = currentTreeBranch.indexOf(newRoute); // currentTreeBranch[index] = newRoute.copyWith(arguments: arguments); // } // } @override bool operator ==(Object other) { if (identical(this, other)) return true; return other is RouteDecoder && listEquals(other.currentTreeBranch, currentTreeBranch) && other.pageSettings == pageSettings; } @override int get hashCode => currentTreeBranch.hashCode ^ pageSettings.hashCode; @override String toString() => 'RouteDecoder(currentTreeBranch: $currentTreeBranch, pageSettings: $pageSettings)'; } class ParseRouteTree { ParseRouteTree({ required this.routes, }); final List routes; RouteDecoder matchRoute(String name, {PageSettings? arguments}) { final uri = Uri.parse(name); final split = uri.path.split('/').where((element) => element.isNotEmpty); var curPath = '/'; final cumulativePaths = [ '/', ]; for (var item in split) { if (curPath.endsWith('/')) { curPath += item; } else { curPath += '/$item'; } cumulativePaths.add(curPath); } final treeBranch = cumulativePaths .map((e) => MapEntry(e, _findRoute(e))) .where((element) => element.value != null) ///Prevent page be disposed .map((e) => MapEntry(e.key, e.value!.copyWith(key: ValueKey(e.key)))) .toList(); final params = Map.from(uri.queryParameters); if (treeBranch.isNotEmpty) { //route is found, do further parsing to get nested query params final lastRoute = treeBranch.last; final parsedParams = _parseParams(name, lastRoute.value.path); if (parsedParams.isNotEmpty) { params.addAll(parsedParams); } //copy parameters to all pages. final mappedTreeBranch = treeBranch .map( (e) => e.value.copyWith( parameters: { if (e.value.parameters != null) ...e.value.parameters!, ...params, }, name: e.key, ), ) .toList(); arguments?.params.clear(); arguments?.params.addAll(params); return RouteDecoder( mappedTreeBranch, arguments, ); } arguments?.params.clear(); arguments?.params.addAll(params); //route not found return RouteDecoder( treeBranch.map((e) => e.value).toList(), arguments, ); } void addRoutes(List> getPages) { for (final route in getPages) { addRoute(route); } } void removeRoutes(List> getPages) { for (final route in getPages) { removeRoute(route); } } void removeRoute(GetPage route) { routes.remove(route); for (var page in _flattenPage(route)) { removeRoute(page); } } void addRoute(GetPage route) { routes.add(route); // Add Page children. for (var page in _flattenPage(route)) { addRoute(page); } } List _flattenPage(GetPage route) { final result = []; if (route.children.isEmpty) { return result; } final parentPath = route.name; for (var page in route.children) { // Add Parent middlewares to children final parentMiddlewares = [ if (page.middlewares.isNotEmpty) ...page.middlewares, if (route.middlewares.isNotEmpty) ...route.middlewares ]; final parentBindings = [ if (page.binding != null) page.binding!, if (page.bindings.isNotEmpty) ...page.bindings, if (route.bindings.isNotEmpty) ...route.bindings ]; final parentBinds = [ if (page.binds.isNotEmpty) ...page.binds, if (route.binds.isNotEmpty) ...route.binds ]; result.add( _addChild( page, parentPath, parentMiddlewares, parentBindings, parentBinds, ), ); final children = _flattenPage(page); for (var child in children) { result.add(_addChild( child, parentPath, [ ...parentMiddlewares, if (child.middlewares.isNotEmpty) ...child.middlewares, ], [ ...parentBindings, if (child.binding != null) child.binding!, if (child.bindings.isNotEmpty) ...child.bindings, ], [ ...parentBinds, if (child.binds.isNotEmpty) ...child.binds, ], )); } } return result; } /// Change the Path for a [GetPage] GetPage _addChild( GetPage origin, String parentPath, List middlewares, List bindings, List binds, ) { return origin.copyWith( middlewares: middlewares, name: origin.inheritParentPath ? (parentPath + origin.name).replaceAll(r'//', '/') : origin.name, bindings: bindings, binds: binds, // key: ); } GetPage? _findRoute(String name) { final value = routes.firstWhereOrNull( (route) => route.path.regex.hasMatch(name), ); return value; } Map _parseParams(String path, PathDecoded routePath) { final params = {}; var idx = path.indexOf('?'); final uri = Uri.tryParse(path); if (uri == null) return params; if (idx > -1) { params.addAll(uri.queryParameters); } var paramsMatch = routePath.regex.firstMatch(uri.path); if (paramsMatch == null) { return params; } for (var i = 0; i < routePath.keys.length; i++) { var param = Uri.decodeQueryComponent(paramsMatch[i + 1]!); params[routePath.keys[i]!] = param; } return params; } } extension FirstWhereOrNullExt on List { /// The first element satisfying [test], or `null` if there are none. T? firstWhereOrNull(bool Function(T element) test) { for (var element in this) { if (test(element)) return element; } return null; } } ================================================ FILE: lib/get_navigation/src/routes/route_middleware.dart ================================================ import 'dart:async'; import 'package:flutter/cupertino.dart'; import '../../../get.dart'; /// The Page Middlewares. /// The Functions will be called in this order /// (( [redirect] -> [onPageCalled] -> [onBindingsStart] -> /// [onPageBuildStart] -> [onPageBuilt] -> [onPageDispose] )) abstract class GetMiddleware { GetMiddleware({this.priority = 0}); /// The Order of the Middlewares to run. /// /// {@tool snippet} /// This Middewares will be called in this order. /// ```dart /// final middlewares = [ /// GetMiddleware(priority: 2), /// GetMiddleware(priority: 5), /// GetMiddleware(priority: 4), /// GetMiddleware(priority: -8), /// ]; /// ``` /// -8 => 2 => 4 => 5 /// {@end-tool} final int priority; /// This function will be called when the page of /// the called route is being searched for. /// It take RouteSettings as a result an redirect to the new settings or /// give it null and there will be no redirecting. /// {@tool snippet} /// ```dart /// GetPage redirect(String route) { /// final authService = Get.find(); /// return authService.authed.value ? null : RouteSettings(name: '/login'); /// } /// ``` /// {@end-tool} RouteSettings? redirect(String? route) => null; /// Similar to [redirect], /// This function will be called when the router delegate changes the /// current route. /// /// The default implmentation is to navigate to /// the input route, with no redirection. /// /// if this returns null, the navigation is stopped, /// and no new routes are pushed. /// {@tool snippet} /// ```dart /// GetNavConfig? redirect(GetNavConfig route) { /// final authService = Get.find(); /// return authService.authed.value ? null : RouteSettings(name: '/login'); /// } /// ``` /// {@end-tool} FutureOr redirectDelegate(RouteDecoder route) => (route); /// This function will be called when this Page is called /// you can use it to change something about the page or give it new page /// {@tool snippet} /// ```dart /// GetPage onPageCalled(GetPage page) { /// final authService = Get.find(); /// return page.copyWith(title: 'Welcome ${authService.UserName}'); /// } /// ``` /// {@end-tool} GetPage? onPageCalled(GetPage? page) => page; /// This function will be called right before the [BindingsInterface] are initialize. /// Here you can change [BindingsInterface] for this page /// {@tool snippet} /// ```dart /// List onBindingsStart(List bindings) { /// final authService = Get.find(); /// if (authService.isAdmin) { /// bindings.add(AdminBinding()); /// } /// return bindings; /// } /// ``` /// {@end-tool} List? onBindingsStart(List? bindings) => bindings; /// This function will be called right after the [BindingsInterface] are initialize. GetPageBuilder? onPageBuildStart(GetPageBuilder? page) => page; /// This function will be called right after the /// GetPage.page function is called and will give you the result /// of the function. and take the widget that will be showed. Widget onPageBuilt(Widget page) => page; void onPageDispose() {} } class MiddlewareRunner { MiddlewareRunner(List? middlewares) : _middlewares = middlewares != null ? (List.of(middlewares)..sort(_compareMiddleware)) : const []; final List _middlewares; static int _compareMiddleware(GetMiddleware a, GetMiddleware b) => a.priority.compareTo(b.priority); GetPage? runOnPageCalled(GetPage? page) { for (final middleware in _middlewares) { page = middleware.onPageCalled(page); } return page; } RouteSettings? runRedirect(String? route) { for (final middleware in _middlewares) { final redirectTo = middleware.redirect(route); if (redirectTo != null) { return redirectTo; } } return null; } List? runOnBindingsStart(List? bindings) { for (final middleware in _middlewares) { bindings = middleware.onBindingsStart(bindings); } return bindings; } GetPageBuilder? runOnPageBuildStart(GetPageBuilder? page) { for (final middleware in _middlewares) { page = middleware.onPageBuildStart(page); } return page; } Widget runOnPageBuilt(Widget page) { for (final middleware in _middlewares) { page = middleware.onPageBuilt(page); } return page; } void runOnPageDispose() { for (final middleware in _middlewares) { middleware.onPageDispose(); } } } class PageRedirect { GetPage? route; GetPage? unknownRoute; RouteSettings? settings; bool isUnknown; PageRedirect({ this.route, this.unknownRoute, this.isUnknown = false, this.settings, }); // redirect all pages that needes redirecting GetPageRoute getPageToRoute( GetPage rou, GetPage? unk, BuildContext context) { while (needRecheck(context)) {} final r = (isUnknown ? unk : rou)!; return GetPageRoute( page: r.page, parameter: r.parameters, alignment: r.alignment, title: r.title, maintainState: r.maintainState, routeName: r.name, settings: r, curve: r.curve, showCupertinoParallax: r.showCupertinoParallax, gestureWidth: r.gestureWidth, opaque: r.opaque, customTransition: r.customTransition, bindings: r.bindings, binding: r.binding, binds: r.binds, transitionDuration: r.transitionDuration ?? Get.defaultTransitionDuration, reverseTransitionDuration: r.reverseTransitionDuration ?? Get.defaultTransitionDuration, // performIncomeAnimation: _r.performIncomeAnimation, // performOutGoingAnimation: _r.performOutGoingAnimation, transition: r.transition, popGesture: r.popGesture, fullscreenDialog: r.fullscreenDialog, middlewares: r.middlewares, ); } /// check if redirect is needed bool needRecheck(BuildContext context) { if (settings == null && route != null) { settings = route; } final match = context.delegate.matchRoute(settings!.name!); // No Match found if (match.route == null) { isUnknown = true; return false; } // No middlewares found return match. if (match.route!.middlewares.isEmpty) { return false; } final runner = MiddlewareRunner(match.route!.middlewares); route = runner.runOnPageCalled(match.route); addPageParameter(route!); final newSettings = runner.runRedirect(settings!.name); if (newSettings == null) { return false; } settings = newSettings; return true; } void addPageParameter(GetPage route) { if (route.parameters == null) return; final parameters = Map.from(Get.parameters); parameters.addEntries(route.parameters!.entries); // Get.parameters = parameters; } } ================================================ FILE: lib/get_navigation/src/routes/route_report.dart ================================================ import 'package:flutter/material.dart'; import '../router_report.dart'; import 'default_route.dart'; class RouteReport extends StatefulWidget { const RouteReport({super.key, required this.builder}); final WidgetBuilder builder; @override RouteReportState createState() => RouteReportState(); } class RouteReportState extends State with RouteReportMixin { @override void initState() { RouterReportManager.instance.reportCurrentRoute(this); super.initState(); } @override void dispose() { RouterReportManager.instance.reportRouteDispose(this); super.dispose(); } @override Widget build(BuildContext context) { return widget.builder(context); } } ================================================ FILE: lib/get_navigation/src/routes/router_outlet.dart ================================================ import 'package:flutter/material.dart'; import '../../../get.dart'; class RouterOutlet, T extends Object> extends StatefulWidget { final TDelegate routerDelegate; final Widget Function(BuildContext context) builder; RouterOutlet.builder({ super.key, TDelegate? delegate, required this.builder, }) : routerDelegate = delegate ?? Get.delegate()!; RouterOutlet({ Key? key, TDelegate? delegate, required Iterable Function(T currentNavStack) pickPages, required Widget Function( BuildContext context, TDelegate, Iterable? page, ) pageBuilder, }) : this.builder( builder: (context) { final currentConfig = context.delegate.currentConfiguration as T?; final rDelegate = context.delegate as TDelegate; var picked = currentConfig == null ? null : pickPages(currentConfig); if (picked?.isEmpty ?? true) { picked = null; } return pageBuilder(context, rDelegate, picked); }, delegate: delegate, key: key); @override RouterOutletState createState() => RouterOutletState(); } class RouterOutletState, T extends Object> extends State> { RouterDelegate? delegate; late ChildBackButtonDispatcher _backButtonDispatcher; void _listener() { setState(() {}); } VoidCallback? disposer; @override void didChangeDependencies() { super.didChangeDependencies(); disposer?.call(); final router = Router.of(context); delegate ??= router.routerDelegate; delegate?.addListener(_listener); disposer = () => delegate?.removeListener(_listener); _backButtonDispatcher = router.backButtonDispatcher!.createChildBackButtonDispatcher(); } @override void dispose() { super.dispose(); disposer?.call(); } @override Widget build(BuildContext context) { _backButtonDispatcher.takePriority(); return widget.builder(context); } } class GetRouterOutlet extends RouterOutlet { GetRouterOutlet({ Key? key, String? anchorRoute, required String initialRoute, Iterable Function(Iterable afterAnchor)? filterPages, GetDelegate? delegate, String? restorationScopeId, }) : this.pickPages( restorationScopeId: restorationScopeId, pickPages: (config) { Iterable> ret; if (anchorRoute == null) { // jump the ancestor path final length = Uri.parse(initialRoute).pathSegments.length; return config.currentTreeBranch .skip(length) .take(length) .toList(); } ret = config.currentTreeBranch.pickAfterRoute(anchorRoute); if (filterPages != null) { ret = filterPages(ret); } return ret; }, key: key, emptyPage: (delegate) => delegate.matchRoute(initialRoute).route ?? delegate.notFoundRoute, navigatorKey: Get.nestedKey(anchorRoute)?.navigatorKey, delegate: delegate, ); GetRouterOutlet.pickPages({ super.key, Widget Function(GetDelegate delegate)? emptyWidget, GetPage Function(GetDelegate delegate)? emptyPage, required super.pickPages, bool Function(Route, dynamic)? onPopPage, String? restorationScopeId, GlobalKey? navigatorKey, GetDelegate? delegate, }) : super( pageBuilder: (context, rDelegate, pages) { final pageRes = [ ...?pages, if (pages == null || pages.isEmpty) emptyPage?.call(rDelegate), ].whereType(); if (pageRes.isNotEmpty) { return InheritedNavigator( navigatorKey: navigatorKey ?? Get.rootController.rootDelegate.navigatorKey, child: GetNavigator( restorationScopeId: restorationScopeId, onPopPage: onPopPage ?? (route, result) { final didPop = route.didPop(result); if (!didPop) { return false; } return true; }, pages: pageRes.toList(), key: navigatorKey, ), ); } return (emptyWidget?.call(rDelegate) ?? const SizedBox.shrink()); }, delegate: delegate ?? Get.rootController.rootDelegate, ); GetRouterOutlet.builder({ super.key, required super.builder, String? route, GetDelegate? routerDelegate, }) : super.builder( delegate: routerDelegate ?? (route != null ? Get.nestedKey(route) : Get.rootController.rootDelegate), ); } class InheritedNavigator extends InheritedWidget { const InheritedNavigator({ super.key, required super.child, required this.navigatorKey, }); final GlobalKey navigatorKey; static InheritedNavigator? of(BuildContext context) { return context.dependOnInheritedWidgetOfExactType(); } @override bool updateShouldNotify(InheritedNavigator oldWidget) { return true; } } extension NavKeyExt on BuildContext { GlobalKey? get parentNavigatorKey { return InheritedNavigator.of(this)?.navigatorKey; } } extension PagesListExt on List { /// Returns the route and all following routes after the given route. Iterable pickFromRoute(String route) { return skipWhile((value) => value.name != route); } /// Returns the routes after the given route. Iterable pickAfterRoute(String route) { // If the provided route is root, we take the first route after root. if (route == '/') { return pickFromRoute(route).skip(1).take(1); } // Otherwise, we skip the route and take all routes after it. return pickFromRoute(route).skip(1); } } typedef NavigatorItemBuilderBuilder = Widget Function( BuildContext context, List routes, int index); class IndexedRouteBuilder extends StatelessWidget { const IndexedRouteBuilder({ super.key, required this.builder, required this.routes, }); final List routes; final NavigatorItemBuilderBuilder builder; // Method to get the current index based on the route int _getCurrentIndex(String currentLocation) { for (int i = 0; i < routes.length; i++) { if (currentLocation.startsWith(routes[i])) { return i; } } return 0; // default index } @override Widget build(BuildContext context) { final location = context.location; final index = _getCurrentIndex(location); return builder(context, routes, index); } } mixin RouterListenerMixin on State { RouterDelegate? delegate; void _listener() { setState(() {}); } VoidCallback? disposer; @override void didChangeDependencies() { super.didChangeDependencies(); disposer?.call(); final router = Router.of(context); delegate ??= router.routerDelegate as GetDelegate; delegate?.addListener(_listener); disposer = () => delegate?.removeListener(_listener); } @override void dispose() { super.dispose(); disposer?.call(); } } class RouterListenerInherited extends InheritedWidget { const RouterListenerInherited({ super.key, required super.child, }); static RouterListenerInherited? of(BuildContext context) { return context .dependOnInheritedWidgetOfExactType(); } @override bool updateShouldNotify(covariant InheritedWidget oldWidget) { return true; } } class RouterListener extends StatefulWidget { const RouterListener({ super.key, required this.builder, }); final WidgetBuilder builder; @override State createState() => RouteListenerState(); } class RouteListenerState extends State with RouterListenerMixin { @override Widget build(BuildContext context) { return RouterListenerInherited(child: Builder(builder: widget.builder)); } } class BackButtonCallback extends StatefulWidget { const BackButtonCallback({super.key, required this.builder}); final WidgetBuilder builder; @override State createState() => RouterListenerState(); } class RouterListenerState extends State with RouterListenerMixin { late ChildBackButtonDispatcher backButtonDispatcher; @override void didChangeDependencies() { super.didChangeDependencies(); final router = Router.of(context); backButtonDispatcher = router.backButtonDispatcher!.createChildBackButtonDispatcher(); } @override Widget build(BuildContext context) { backButtonDispatcher.takePriority(); return widget.builder(context); } } ================================================ FILE: lib/get_navigation/src/routes/test_kit.dart ================================================ class GetTestMode { static bool active = false; static Object? _arguments; static void setTestArguments(Object? arguments) { _arguments = arguments; } static Object? get arguments => _arguments; static Map _parameters = {}; static void setTestParameters(Map parameters) { _parameters = parameters; } static Map get parameters => _parameters; } ================================================ FILE: lib/get_navigation/src/routes/transitions_type.dart ================================================ import 'package:flutter/widgets.dart'; import 'default_route.dart'; enum Transition { fade, fadeIn, rightToLeft, leftToRight, upToDown, downToUp, rightToLeftWithFade, leftToRightWithFade, zoom, topLevel, noTransition, cupertino, cupertinoDialog, size, circularReveal, native, } typedef GetPageBuilder = Widget Function(); typedef GetRouteAwarePageBuilder = Widget Function([GetPageRoute? route]); ================================================ FILE: lib/get_navigation/src/routes/url_strategy/impl/io_url.dart ================================================ void removeHash() {} ================================================ FILE: lib/get_navigation/src/routes/url_strategy/impl/stub_url.dart ================================================ void removeHash() { throw UnimplementedError(); } ================================================ FILE: lib/get_navigation/src/routes/url_strategy/impl/web_url.dart ================================================ import 'package:flutter_web_plugins/flutter_web_plugins.dart'; void removeHash() { setUrlStrategy(PathUrlStrategy()); } ================================================ FILE: lib/get_navigation/src/routes/url_strategy/url_strategy.dart ================================================ import 'impl/stub_url.dart' if (dart.library.js_interop) 'impl/web_url.dart' if (dart.library.io) 'impl/io_url.dart'; void setUrlStrategy() { removeHash(); } void removeLastHistory(String? url) { removeLastHistory(url); } ================================================ FILE: lib/get_navigation/src/snackbar/snackbar.dart ================================================ import 'dart:async'; import 'dart:ui'; import 'package:flutter/material.dart'; import '../../../get_core/get_core.dart'; import '../../get_navigation.dart'; typedef OnTap = void Function(GetSnackBar snack); typedef OnHover = void Function( GetSnackBar snack, SnackHoverState snackHoverState); typedef SnackbarStatusCallback = void Function(SnackbarStatus? status); class GetSnackBar extends StatefulWidget { /// A callback for you to listen to the different Snack status final SnackbarStatusCallback? snackbarStatus; /// The title displayed to the user final String? title; /// Defines how the snack bar area, including margin, will behave during hit testing. /// /// If this property is null and [margin] is not null, then [HitTestBehavior.deferToChild] is used by default. /// /// Please refer to [HitTestBehavior] for a detailed explanation of every behavior. final HitTestBehavior? hitTestBehavior; /// The direction in which the SnackBar can be dismissed. /// /// Default is [DismissDirection.down] when /// [snackPosition] == [SnackPosition.BOTTOM] and [DismissDirection.up] /// when [snackPosition] == [SnackPosition.TOP] final DismissDirection? dismissDirection; /// The message displayed to the user. final String? message; /// Replaces [title]. Although this accepts a [Widget], it is meant /// to receive [Text] or [RichText] final Widget? titleText; /// Replaces [message]. Although this accepts a [Widget], it is meant /// to receive [Text] or [RichText] final Widget? messageText; /// Will be ignored if [backgroundGradient] is not null final Color backgroundColor; /// If not null, shows a left vertical colored bar on notification. /// It is not possible to use it with a [Form] and I do not recommend /// using it with [LinearProgressIndicator] final Color? leftBarIndicatorColor; /// [boxShadows] The shadows generated by Snack. Leave it null /// if you don't want a shadow. /// You can use more than one if you feel the need. /// Check (this example)[https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/material/shadows.dart] final List? boxShadows; /// Give to GetSnackbar a gradient background. /// It Makes [backgroundColor] be ignored. final Gradient? backgroundGradient; /// You can use any widget here, but I recommend [Icon] or [Image] as /// indication of what kind /// of message you are displaying. Other widgets may break the layout final Widget? icon; /// An option to animate the icon (if present). Defaults to true. final bool shouldIconPulse; /// (optional) An action that the user can take based on the snack bar. /// /// For example, the snack bar might let the user undo the operation that /// prompted the snackbar. final Widget? mainButton; /// A callback that registers the user's click anywhere. /// An alternative to [mainButton] final OnTap? onTap; /// A callback that registers the user's hover anywhere over the Snackbar. final OnHover? onHover; /// How long until Snack will hide itself (be dismissed). /// To make it indefinite, leave it null. final Duration? duration; /// True if you want to show a [LinearProgressIndicator]. final bool showProgressIndicator; /// An optional [AnimationController] when you want to control the /// progress of your [LinearProgressIndicator]. final AnimationController? progressIndicatorController; /// A [LinearProgressIndicator] configuration parameter. final Color? progressIndicatorBackgroundColor; /// A [LinearProgressIndicator] configuration parameter. final Animation? progressIndicatorValueColor; /// Determines if the user can swipe or click the overlay /// (if [overlayBlur] > 0) to dismiss. /// It is recommended that you set [duration] != null if this is false. /// If the user swipes to dismiss or clicks the overlay, no value /// will be returned. final bool isDismissible; /// Used to limit Snack width (usually on large screens) final double? maxWidth; /// Adds a custom margin to Snack final EdgeInsets margin; /// Adds a custom padding to Snack /// The default follows material design guide line final EdgeInsets padding; /// Adds a radius to all corners of Snack. Best combined with [margin]. /// I do not recommend using it with [showProgressIndicator] /// or [leftBarIndicatorColor]. final double borderRadius; /// Adds a border to every side of Snack /// I do not recommend using it with [showProgressIndicator] /// or [leftBarIndicatorColor]. final Color? borderColor; /// Changes the width of the border if [borderColor] is specified final double? borderWidth; /// Snack can be based on [SnackPosition.TOP] or on [SnackPosition.BOTTOM] /// of your screen. /// [SnackPosition.BOTTOM] is the default. final SnackPosition snackPosition; /// Snack can be floating or be grounded to the edge of the screen. /// If grounded, I do not recommend using [margin] or [borderRadius]. /// [SnackStyle.FLOATING] is the default /// If grounded, I do not recommend using a [backgroundColor] with /// transparency or [barBlur] final SnackStyle snackStyle; /// The [Curve] animation used when show() is called. /// [Curves.easeOut] is default final Curve forwardAnimationCurve; /// The [Curve] animation used when dismiss() is called. /// [Curves.fastOutSlowIn] is default final Curve reverseAnimationCurve; /// Use it to speed up or slow down the animation duration final Duration animationDuration; /// Default is 0.0. If different than 0.0, blurs only Snack's background. /// To take effect, make sure your [backgroundColor] has some opacity. /// The greater the value, the greater the blur. final double barBlur; /// Default is 0.0. If different than 0.0, creates a blurred /// overlay that prevents the user from interacting with the screen. /// The greater the value, the greater the blur. final double overlayBlur; /// Default is [Colors.transparent]. Only takes effect if [overlayBlur] > 0.0. /// Make sure you use a color with transparency here e.g. /// Colors.grey[600].withValues(alpha:0.2). final Color? overlayColor; /// A [TextFormField] in case you want a simple user input. /// Every other widget is ignored if this is not null. final Form? userInputForm; const GetSnackBar({ super.key, this.title, this.message, this.titleText, this.messageText, this.icon, this.shouldIconPulse = true, this.maxWidth, this.margin = const EdgeInsets.all(0.0), this.padding = const EdgeInsets.all(16), this.borderRadius = 0.0, this.borderColor, this.borderWidth = 1.0, this.backgroundColor = const Color(0xFF303030), this.leftBarIndicatorColor, this.boxShadows, this.backgroundGradient, this.mainButton, this.onTap, this.onHover, this.duration, this.isDismissible = true, this.dismissDirection, this.showProgressIndicator = false, this.progressIndicatorController, this.progressIndicatorBackgroundColor, this.progressIndicatorValueColor, this.snackPosition = SnackPosition.bottom, this.snackStyle = SnackStyle.floating, this.forwardAnimationCurve = Curves.easeOutCirc, this.reverseAnimationCurve = Curves.easeOutCirc, this.animationDuration = const Duration(seconds: 1), this.barBlur = 0.0, this.overlayBlur = 0.0, this.overlayColor = Colors.transparent, this.userInputForm, this.snackbarStatus, this.hitTestBehavior, }); @override State createState() => GetSnackBarState(); /// Show the snack. It's call [SnackbarStatus.OPENING] state /// followed by [SnackbarStatus.OPEN] SnackbarController show() { return Get.showSnackbar(this); } } class GetSnackBarState extends State with TickerProviderStateMixin { AnimationController? _fadeController; late Animation _fadeAnimation; final Widget _emptyWidget = const SizedBox(width: 0.0, height: 0.0); final double _initialOpacity = 1.0; final double _finalOpacity = 0.4; final Duration _pulseAnimationDuration = const Duration(seconds: 1); late bool _isTitlePresent; late double _messageTopMargin; FocusScopeNode? _focusNode; late FocusAttachment _focusAttachment; final Completer _boxHeightCompleter = Completer(); late CurvedAnimation _progressAnimation; final _backgroundBoxKey = GlobalKey(); double get buttonPadding { if (widget.padding.right - 12 < 0) { return 4; } else { return widget.padding.right - 12; } } RowStyle get _rowStyle { if (widget.mainButton != null && widget.icon == null) { return RowStyle.action; } else if (widget.mainButton == null && widget.icon != null) { return RowStyle.icon; } else if (widget.mainButton != null && widget.icon != null) { return RowStyle.all; } else { return RowStyle.none; } } @override Widget build(BuildContext context) { return Align( heightFactor: 1.0, child: Material( color: widget.snackStyle == SnackStyle.floating ? Colors.transparent : widget.backgroundColor, child: SafeArea( minimum: widget.snackPosition == SnackPosition.bottom ? EdgeInsets.only( bottom: MediaQuery.of(context).viewInsets.bottom) : EdgeInsets.only(top: MediaQuery.of(context).padding.top), bottom: widget.snackPosition == SnackPosition.bottom, top: widget.snackPosition == SnackPosition.top, left: false, right: false, child: Stack( children: [ FutureBuilder( future: _boxHeightCompleter.future, builder: (context, snapshot) { if (snapshot.hasData) { if (widget.barBlur == 0) { return _emptyWidget; } return ClipRRect( borderRadius: BorderRadius.circular(widget.borderRadius), child: BackdropFilter( filter: ImageFilter.blur( sigmaX: widget.barBlur, sigmaY: widget.barBlur), child: Container( height: snapshot.data!.height, width: snapshot.data!.width, decoration: BoxDecoration( color: Colors.transparent, borderRadius: BorderRadius.circular(widget.borderRadius), ), ), ), ); } else { return _emptyWidget; } }, ), if (widget.userInputForm != null) _containerWithForm() else _containerWithoutForm() ], ), ), ), ); } @override void dispose() { _fadeController?.dispose(); widget.progressIndicatorController?.removeListener(_updateProgress); widget.progressIndicatorController?.dispose(); _focusAttachment.detach(); _focusNode!.dispose(); super.dispose(); } @override void initState() { super.initState(); assert( widget.userInputForm != null || ((widget.message != null && widget.message!.isNotEmpty) || widget.messageText != null), ''' You need to either use message[String], or messageText[Widget] or define a userInputForm[Form] in GetSnackbar'''); _isTitlePresent = (widget.title != null || widget.titleText != null); _messageTopMargin = _isTitlePresent ? 6.0 : widget.padding.top; _configureLeftBarFuture(); _configureProgressIndicatorAnimation(); if (widget.icon != null && widget.shouldIconPulse) { _configurePulseAnimation(); _fadeController?.forward(); } _focusNode = FocusScopeNode(); _focusAttachment = _focusNode!.attach(context); } Widget _buildLeftBarIndicator() { if (widget.leftBarIndicatorColor != null) { return FutureBuilder( future: _boxHeightCompleter.future, builder: (buildContext, snapshot) { if (snapshot.hasData) { return Container( color: widget.leftBarIndicatorColor, width: 5.0, height: snapshot.data!.height, ); } else { return _emptyWidget; } }, ); } else { return _emptyWidget; } } void _configureLeftBarFuture() { Engine.instance.addPostFrameCallback( (_) { final keyContext = _backgroundBoxKey.currentContext; if (keyContext != null) { final box = keyContext.findRenderObject() as RenderBox; _boxHeightCompleter.complete(box.size); } }, ); } void _configureProgressIndicatorAnimation() { if (widget.showProgressIndicator && widget.progressIndicatorController != null) { widget.progressIndicatorController!.addListener(_updateProgress); _progressAnimation = CurvedAnimation( curve: Curves.linear, parent: widget.progressIndicatorController!); } } void _configurePulseAnimation() { _fadeController = AnimationController(vsync: this, duration: _pulseAnimationDuration); _fadeAnimation = Tween(begin: _initialOpacity, end: _finalOpacity).animate( CurvedAnimation( parent: _fadeController!, curve: Curves.linear, ), ); _fadeController!.addStatusListener((status) { if (status == AnimationStatus.completed) { _fadeController!.reverse(); } if (status == AnimationStatus.dismissed) { _fadeController!.forward(); } }); _fadeController!.forward(); } Widget _containerWithForm() { return Container( key: _backgroundBoxKey, constraints: widget.maxWidth != null ? BoxConstraints(maxWidth: widget.maxWidth!) : null, decoration: BoxDecoration( color: widget.backgroundColor, gradient: widget.backgroundGradient, boxShadow: widget.boxShadows, borderRadius: BorderRadius.circular(widget.borderRadius), border: widget.borderColor != null ? Border.all( color: widget.borderColor!, width: widget.borderWidth!, ) : null, ), child: Padding( padding: const EdgeInsets.only( left: 8.0, right: 8.0, bottom: 8.0, top: 16.0), child: FocusScope( node: _focusNode, autofocus: true, child: widget.userInputForm!, ), ), ); } Widget _containerWithoutForm() { final iconPadding = widget.padding.left > 16.0 ? widget.padding.left : 0.0; final left = _rowStyle == RowStyle.icon || _rowStyle == RowStyle.all ? 4.0 : widget.padding.left; final right = _rowStyle == RowStyle.action || _rowStyle == RowStyle.all ? 8.0 : widget.padding.right; return Container( key: _backgroundBoxKey, constraints: widget.maxWidth != null ? BoxConstraints(maxWidth: widget.maxWidth!) : null, decoration: BoxDecoration( color: widget.backgroundColor, gradient: widget.backgroundGradient, boxShadow: widget.boxShadows, borderRadius: BorderRadius.circular(widget.borderRadius), border: widget.borderColor != null ? Border.all(color: widget.borderColor!, width: widget.borderWidth!) : null, ), child: Column( mainAxisSize: MainAxisSize.min, children: [ widget.showProgressIndicator ? LinearProgressIndicator( value: widget.progressIndicatorController != null ? _progressAnimation.value : null, backgroundColor: widget.progressIndicatorBackgroundColor, valueColor: widget.progressIndicatorValueColor, ) : _emptyWidget, Row( mainAxisSize: MainAxisSize.max, children: [ _buildLeftBarIndicator(), if (_rowStyle == RowStyle.icon || _rowStyle == RowStyle.all) ConstrainedBox( constraints: BoxConstraints.tightFor(width: 42.0 + iconPadding), child: _getIcon(), ), Expanded( flex: 1, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisSize: MainAxisSize.min, children: [ if (_isTitlePresent) Padding( padding: EdgeInsets.only( top: widget.padding.top, left: left, right: right, ), child: widget.titleText ?? Text( widget.title ?? "", style: const TextStyle( fontSize: 16.0, color: Colors.white, fontWeight: FontWeight.bold, ), ), ) else _emptyWidget, Padding( padding: EdgeInsets.only( top: _messageTopMargin, left: left, right: right, bottom: widget.padding.bottom, ), child: widget.messageText ?? Text( widget.message ?? "", style: const TextStyle( fontSize: 14.0, color: Colors.white), ), ), ], ), ), if (_rowStyle == RowStyle.action || _rowStyle == RowStyle.all) Padding( padding: EdgeInsets.only(right: buttonPadding), child: widget.mainButton, ), ], ), ], ), ); } Widget? _getIcon() { if (widget.icon != null && widget.icon is Icon && widget.shouldIconPulse) { return FadeTransition( opacity: _fadeAnimation, child: widget.icon, ); } else if (widget.icon != null) { return widget.icon; } else { return _emptyWidget; } } void _updateProgress() => setState(() {}); } enum RowStyle { icon, action, all, none, } /// Indicates Status of snackbar /// [SnackbarStatus.OPEN] Snack is fully open, [SnackbarStatus.CLOSED] Snackbar /// has closed, /// [SnackbarStatus.OPENING] Starts with the opening animation and ends /// with the full /// snackbar display, [SnackbarStatus.CLOSING] Starts with the closing animation /// and ends /// with the full snackbar dispose enum SnackbarStatus { open, closed, opening, closing } /// Indicates if snack is going to start at the [TOP] or at the [BOTTOM] enum SnackPosition { top, bottom } /// Indicates if snack will be attached to the edge of the screen or not enum SnackStyle { floating, grounded } /// Indicates if the mouse entered or exited enum SnackHoverState { entered, exited } ================================================ FILE: lib/get_navigation/src/snackbar/snackbar_controller.dart ================================================ import 'dart:async'; import 'dart:math'; import 'dart:ui'; import 'package:flutter/material.dart'; import '../../../get.dart'; import '../root/get_root.dart'; class SnackbarController { final key = GlobalKey(); static bool get isSnackbarBeingShown => GetRootState.controller.config.snackBarQueue.isJobInProgress; late Animation _filterBlurAnimation; late Animation _filterColorAnimation; final GetSnackBar snackbar; final _transitionCompleter = Completer(); late SnackbarStatusCallback? _snackbarStatus; late final Alignment? _initialAlignment; late final Alignment? _endAlignment; bool _wasDismissedBySwipe = false; bool _onTappedDismiss = false; Timer? _timer; /// The animation that drives the route's transition and the previous route's /// forward transition. late final Animation _animation; /// The animation controller that the route uses to drive the transitions. /// /// The animation itself is exposed by the [animation] property. late final AnimationController _controller; SnackbarStatus? _currentStatus; final _overlayEntries = []; OverlayState? _overlayState; SnackbarController(this.snackbar); Future get future => _transitionCompleter.future; /// Close the snackbar with animation Future close({bool withAnimations = true}) async { if (!withAnimations) { _removeOverlay(); return; } _removeEntry(); await future; } /// Adds GetSnackbar to a view queue. /// Only one GetSnackbar will be displayed at a time, and this method returns /// a future to when the snackbar disappears. Future show() { return GetRootState.controller.config.snackBarQueue.addJob(this); } void _cancelTimer() { if (_timer != null && _timer!.isActive) { _timer!.cancel(); } } // ignore: avoid_returning_this void _configureAlignment(SnackPosition snackPosition) { switch (snackbar.snackPosition) { case SnackPosition.top: { _initialAlignment = const Alignment(-1.0, -2.0); _endAlignment = const Alignment(-1.0, -1.0); break; } case SnackPosition.bottom: { _initialAlignment = const Alignment(-1.0, 2.0); _endAlignment = const Alignment(-1.0, 1.0); break; } } } bool _isTesting = false; void _configureOverlay() { _isTesting = Get.overlayContext == null; _overlayState = _isTesting ? OverlayState() : Get.key.currentState?.overlay; _overlayEntries.clear(); _overlayEntries.addAll(_createOverlayEntries(_getBodyWidget())); if (!_isTesting) { _overlayState!.insertAll(_overlayEntries); } _configureSnackBarDisplay(); } void _configureSnackBarDisplay() { assert(!_transitionCompleter.isCompleted, 'Cannot configure a snackbar after disposing it.'); _controller = _createAnimationController(); _configureAlignment(snackbar.snackPosition); _snackbarStatus = snackbar.snackbarStatus; _filterBlurAnimation = _createBlurFilterAnimation(); _filterColorAnimation = _createColorOverlayColor(); _animation = _createAnimation(); _animation.addStatusListener(_handleStatusChanged); _configureTimer(); _controller.forward(); } void _configureTimer() { if (snackbar.duration != null) { if (_timer != null && _timer!.isActive) { _timer!.cancel(); } _timer = Timer(snackbar.duration!, _removeEntry); } else { if (_timer != null) { _timer!.cancel(); } } } /// Called to create the animation that exposes the current progress of /// the transition controlled by the animation controller created by /// `createAnimationController()`. Animation _createAnimation() { assert(!_transitionCompleter.isCompleted, 'Cannot create a animation from a disposed snackbar'); return AlignmentTween(begin: _initialAlignment, end: _endAlignment).animate( CurvedAnimation( parent: _controller, curve: snackbar.forwardAnimationCurve, reverseCurve: snackbar.reverseAnimationCurve, ), ); } /// Called to create the animation controller that will drive the transitions /// to this route from the previous one, and back to the previous route /// from this one. AnimationController _createAnimationController() { assert(!_transitionCompleter.isCompleted, 'Cannot create a animationController from a disposed snackbar'); assert(snackbar.animationDuration >= Duration.zero); return AnimationController( duration: snackbar.animationDuration, debugLabel: '$runtimeType', vsync: _overlayState!, ); } Animation _createBlurFilterAnimation() { return Tween(begin: 0.0, end: snackbar.overlayBlur).animate( CurvedAnimation( parent: _controller, curve: const Interval( 0.0, 0.35, curve: Curves.easeInOutCirc, ), ), ); } Animation _createColorOverlayColor() { return ColorTween( begin: const Color(0x00000000), end: snackbar.overlayColor) .animate( CurvedAnimation( parent: _controller, curve: const Interval( 0.0, 0.35, curve: Curves.easeInOutCirc, ), ), ); } Iterable _createOverlayEntries(Widget child) { return [ if (snackbar.overlayBlur > 0.0) ...[ OverlayEntry( builder: (context) => GestureDetector( onTap: () { if (snackbar.isDismissible && !_onTappedDismiss) { _onTappedDismiss = true; close(); } }, child: AnimatedBuilder( animation: _filterBlurAnimation, builder: (context, child) { return BackdropFilter( filter: ImageFilter.blur( sigmaX: max(0.001, _filterBlurAnimation.value), sigmaY: max(0.001, _filterBlurAnimation.value), ), child: Container( constraints: const BoxConstraints.expand(), color: _filterColorAnimation.value, ), ); }, ), ), maintainState: false, opaque: false, ), ], OverlayEntry( builder: (context) => Semantics( focused: false, container: true, explicitChildNodes: true, child: AlignTransition( alignment: _animation, child: snackbar.isDismissible ? _getDismissibleSnack(child) : _getSnackbarContainer(child), ), ), maintainState: false, opaque: false, ), ]; } Widget _getBodyWidget() { return Builder(builder: (_) { return MouseRegion( onEnter: (_) => snackbar.onHover?.call(snackbar, SnackHoverState.entered), onExit: (_) => snackbar.onHover?.call(snackbar, SnackHoverState.exited), child: GestureDetector( behavior: snackbar.hitTestBehavior ?? HitTestBehavior.deferToChild, onTap: snackbar.onTap != null ? () => snackbar.onTap?.call(snackbar) : null, child: snackbar, ), ); }); } DismissDirection _getDefaultDismissDirection() { if (snackbar.snackPosition == SnackPosition.top) { return DismissDirection.up; } return DismissDirection.down; } Widget _getDismissibleSnack(Widget child) { return Dismissible( behavior: snackbar.hitTestBehavior ?? HitTestBehavior.opaque, direction: snackbar.dismissDirection ?? _getDefaultDismissDirection(), resizeDuration: null, confirmDismiss: (_) { if (_currentStatus == SnackbarStatus.opening || _currentStatus == SnackbarStatus.closing) { return Future.value(false); } return Future.value(true); }, key: const Key('dismissible'), onDismissed: (_) { _wasDismissedBySwipe = true; _removeEntry(); }, child: _getSnackbarContainer(child), ); } Widget _getSnackbarContainer(Widget child) { return Container( margin: snackbar.margin, child: child, ); } void _handleStatusChanged(AnimationStatus status) { switch (status) { case AnimationStatus.completed: _currentStatus = SnackbarStatus.open; _snackbarStatus?.call(_currentStatus); if (_overlayEntries.isNotEmpty) _overlayEntries.first.opaque = false; break; case AnimationStatus.forward: _currentStatus = SnackbarStatus.opening; _snackbarStatus?.call(_currentStatus); break; case AnimationStatus.reverse: _currentStatus = SnackbarStatus.closing; _snackbarStatus?.call(_currentStatus); if (_overlayEntries.isNotEmpty) _overlayEntries.first.opaque = false; break; case AnimationStatus.dismissed: assert(!_overlayEntries.first.opaque); _currentStatus = SnackbarStatus.closed; _snackbarStatus?.call(_currentStatus); _removeOverlay(); break; } } void _removeEntry() { assert( !_transitionCompleter.isCompleted, 'Cannot remove entry from a disposed snackbar', ); _cancelTimer(); if (_wasDismissedBySwipe) { Timer(const Duration(milliseconds: 200), _controller.reset); _wasDismissedBySwipe = false; } else { _controller.reverse(); } } void _removeOverlay() { if (!_isTesting) { for (var element in _overlayEntries) { element.remove(); } } assert(!_transitionCompleter.isCompleted, 'Cannot remove overlay from a disposed snackbar'); _controller.dispose(); _overlayEntries.clear(); _transitionCompleter.complete(); } Future _show() { _configureOverlay(); return future; } static Future cancelAllSnackbars() async { await GetRootState.controller.config.snackBarQueue.cancelAllJobs(); } static Future closeCurrentSnackbar() async { await GetRootState.controller.config.snackBarQueue.closeCurrentJob(); } } class SnackBarQueue { final _queue = GetQueue(); final _snackbarList = []; SnackbarController? get _currentSnackbar { if (_snackbarList.isEmpty) return null; return _snackbarList.first; } bool get isJobInProgress => _snackbarList.isNotEmpty; Future addJob(SnackbarController job) async { _snackbarList.add(job); final data = await _queue.add(job._show); _snackbarList.remove(job); return data; } Future cancelAllJobs() async { await _currentSnackbar?.close(); _queue.cancelAllJobs(); _snackbarList.clear(); } void disposeControllers() { if (_currentSnackbar != null) { _currentSnackbar?._removeOverlay(); _currentSnackbar?._controller.dispose(); _snackbarList.remove(_currentSnackbar); } _queue.cancelAllJobs(); for (var element in _snackbarList) { element._controller.dispose(); } _snackbarList.clear(); } Future closeCurrentJob() async { if (_currentSnackbar == null) return; await _currentSnackbar!.close(); } } ================================================ FILE: lib/get_rx/get_rx.dart ================================================ library; export 'src/rx_stream/rx_stream.dart'; export 'src/rx_types/rx_types.dart'; export 'src/rx_workers/rx_workers.dart'; ================================================ FILE: lib/get_rx/src/rx_stream/mini_stream.dart ================================================ part of 'rx_stream.dart'; class Node { T? data; Node? next; Node? prev; Node({this.data, this.next, this.prev}); } class MiniSubscription { const MiniSubscription( this.data, this.onError, this.onDone, this.cancelOnError, this.listener); final OnData data; final Function? onError; final Callback? onDone; final bool cancelOnError; Future cancel() async => listener.removeListener(this); final FastList listener; } class MiniStream { FastList listenable = FastList(); late T _value; T get value => _value; set value(T val) { add(val); } void add(T event) { _value = event; listenable._notifyData(event); } void addError(Object error, [StackTrace? stackTrace]) { listenable._notifyError(error, stackTrace); } int get length => listenable.length; bool get hasListeners => listenable.isNotEmpty; bool get isClosed => _isClosed; MiniSubscription listen(void Function(T event) onData, {Function? onError, void Function()? onDone, bool cancelOnError = false}) { final subs = MiniSubscription( onData, onError, onDone, cancelOnError, listenable, ); listenable.addListener(subs); return subs; } bool _isClosed = false; void close() { if (_isClosed) { throw 'You can not close a closed Stream'; } listenable._notifyDone(); listenable.clear(); _isClosed = true; } } class FastList { Node>? _head; Node>? _tail; int _length = 0; void _notifyData(T data) { var currentNode = _head; while (currentNode != null) { currentNode.data?.data(data); currentNode = currentNode.next; } } void _notifyDone() { var currentNode = _head; while (currentNode != null) { currentNode.data?.onDone?.call(); currentNode = currentNode.next; } } void _notifyError(Object error, [StackTrace? stackTrace]) { var currentNode = _head; while (currentNode != null) { currentNode.data?.onError?.call(error, stackTrace); currentNode = currentNode.next; } } bool get isEmpty => _length == 0; bool get isNotEmpty => _length > 0; int get length => _length; MiniSubscription? elementAt(int position) { if (isEmpty || position < 0 || position >= _length) return null; var node = _head; var current = 0; while (current != position) { node = node!.next; current++; } return node!.data; } void addListener(MiniSubscription data) { var newNode = Node(data: data); if (isEmpty) { _head = _tail = newNode; } else { _tail!.next = newNode; newNode.prev = _tail; _tail = newNode; } _length++; } bool contains(T element) { var currentNode = _head; while (currentNode != null) { if (currentNode.data == element) return true; currentNode = currentNode.next; } return false; } void removeListener(MiniSubscription element) { var currentNode = _head; while (currentNode != null) { if (currentNode.data == element) { _removeNode(currentNode); break; } currentNode = currentNode.next; } } void clear() { _head = _tail = null; _length = 0; } void _removeNode(Node> node) { if (node.prev == null) { _head = node.next; } else { node.prev!.next = node.next; } if (node.next == null) { _tail = node.prev; } else { node.next!.prev = node.prev; } _length--; } } ================================================ FILE: lib/get_rx/src/rx_stream/rx_stream.dart ================================================ library; import 'dart:async'; import '../rx_typedefs/rx_typedefs.dart'; //part 'get_stream.dart'; part 'mini_stream.dart'; ================================================ FILE: lib/get_rx/src/rx_typedefs/rx_typedefs.dart ================================================ typedef Condition = bool Function(); typedef OnData = void Function(T data); typedef Callback = void Function(); ================================================ FILE: lib/get_rx/src/rx_types/rx_core/rx_impl.dart ================================================ part of '../rx_types.dart'; /// global object that registers against `GetX` and `Obx`, and allows the /// reactivity /// of those `Widgets` and Rx values. mixin RxObjectMixin on GetListenable { //late T _value; /// Makes a direct update of [value] adding it to the Stream /// useful when you make use of Rx for custom Types to refresh your UI. /// /// Sample: /// ``` /// class Person { /// String name, last; /// int age; /// Person({this.name, this.last, this.age}); /// @override /// String toString() => '$name $last, $age years old'; /// } /// /// final person = Person(name: 'John', last: 'Doe', age: 18).obs; /// person.value.name = 'Roi'; /// person.refresh(); /// print( person ); /// ``` // void refresh() { // subject.add(value); // } /// updates the value to `null` and adds it to the Stream. /// Even with null-safety coming, is still an important feature to support, as /// `call()` doesn't accept `null` values. For instance, /// `InputDecoration.errorText` has to be null to not show the "error state". /// /// Sample: /// ``` /// final inputError = ''.obs..nil(); /// print('${inputError.runtimeType}: $inputError'); // outputs > RxString: null /// ``` // void nil() { // subject.add(_value = null); // } /// Makes this Rx looks like a function so you can update a new /// value using `rx(someOtherValue)`. Practical to assign the Rx directly /// to some Widget that has a signature ::onChange( value ) /// /// Example: /// ``` /// final myText = 'GetX rocks!'.obs; /// /// // in your Constructor, just to check it works :P /// ever( myText, print ) ; /// /// // in your build(BuildContext) { /// TextField( /// onChanged: myText, /// ), ///``` @override T call([T? v]) { if (v != null) { value = v; } return value; } bool firstRebuild = true; bool sentToStream = false; /// Same as `toString()` but using a getter. String get string => value.toString(); @override String toString() => value.toString(); /// Returns the json representation of `value`. dynamic toJson() => value; /// This equality override works for _RxImpl instances and the internal /// values. @override // ignore: avoid_equals_and_hash_code_on_mutable_classes bool operator ==(Object o) { // Todo, find a common implementation for the hashCode of different Types. if (o is T) return value == o; if (o is RxObjectMixin) return value == o.value; return false; } @override // ignore: avoid_equals_and_hash_code_on_mutable_classes int get hashCode => value.hashCode; /// Updates the [value] and adds it to the stream, updating the observer /// Widget, only if it's different from the previous value. @override set value(T val) { if (isDisposed) return; sentToStream = false; if (value == val && !firstRebuild) return; firstRebuild = false; sentToStream = true; super.value = val; } /// Returns a [StreamSubscription] similar to [listen], but with the /// added benefit that it primes the stream with the current [value], rather /// than waiting for the next [value]. This should not be called in [onInit] /// or anywhere else during the build process. StreamSubscription listenAndPump(void Function(T event) onData, {Function? onError, void Function()? onDone, bool? cancelOnError}) { final subscription = listen( onData, onError: onError, onDone: onDone, cancelOnError: cancelOnError, ); subject.add(value); return subscription; } /// Binds an existing `Stream` to this `Rx` to keep the values in sync. /// You can bind multiple sources to update the value. /// Closing the subscription will happen automatically when the observer /// Widget (`GetX` or `Obx`) gets unmounted from the Widget tree. void bindStream(Stream stream) { // final listSubscriptions = // _subscriptions[subject] ??= []; final sub = stream.listen((va) => value = va); reportAdd(sub.cancel); } } /// Base Rx class that manages all the stream logic for any Type. abstract class _RxImpl extends GetListenable with RxObjectMixin { _RxImpl(super.initial); void addError(Object error, [StackTrace? stackTrace]) { subject.addError(error, stackTrace); } Stream map(R Function(T? data) mapper) => stream.map(mapper); /// Uses a callback to update [value] internally, similar to [refresh], /// but provides the current value as the argument. /// Makes sense for custom Rx types (like Models). /// /// Sample: /// ``` /// class Person { /// String name, last; /// int age; /// Person({this.name, this.last, this.age}); /// @override /// String toString() => '$name $last, $age years old'; /// } /// /// final person = Person(name: 'John', last: 'Doe', age: 18).obs; /// person.update((person) { /// person.name = 'Roi'; /// }); /// print( person ); /// ``` void update(T Function(T? val) fn) { value = fn(value); // subject.add(value); } /// Following certain practices on Rx data, we might want to react to certain /// listeners when a value has been provided, even if the value is the same. /// At the moment, we ignore part of the process if we `.call(value)` with /// the same value since it holds the value and there's no real /// need triggering the entire process for the same value inside, but /// there are other situations where we might be interested in /// triggering this. /// /// For example, supposed we have a `int seconds = 2` and we want to animate /// from invisible to visible a widget in two seconds: /// `RxEvent.call(seconds);` /// then after a click happens, you want to call a `RxEvent.call(seconds)`. /// By doing `call(seconds)`, if the value being held is the same, /// the listeners won't trigger, hence we need this new `trigger` function. /// This will refresh the listener of an AnimatedWidget and will keep /// the value if the Rx is kept in memory. /// Sample: /// ``` /// Rx secondsRx = RxInt(); /// secondsRx.listen((value) => print("$value seconds set")); /// /// secondsRx.call(2); // This won't trigger any listener, since the value is the same /// secondsRx.trigger(2); // This will trigger the listener independently from the value. /// ``` /// void trigger(T v) { var firstRebuild = this.firstRebuild; value = v; // If it's not the first rebuild, the listeners have been called already // So we won't call them again. if (!firstRebuild && !sentToStream) { subject.add(v); } } } class RxBool extends Rx { RxBool(super.initial); @override String toString() { return value ? "true" : "false"; } } class RxnBool extends Rx { RxnBool([super.initial]); @override String toString() { return "$value"; } } extension RxBoolExt on Rx { bool get isTrue => value; bool get isFalse => !isTrue; bool operator &(bool other) => other && value; bool operator |(bool other) => other || value; bool operator ^(bool other) => !other == value; /// Toggles the bool [value] between false and true. /// A shortcut for `flag.value = !flag.value;` void toggle() { call(!value); // return this; } } extension RxnBoolExt on Rx { bool? get isTrue => value; bool? get isFalse { if (value != null) return !isTrue!; return null; } bool? operator &(bool other) { if (value != null) { return other && value!; } return null; } bool? operator |(bool other) { if (value != null) { return other || value!; } return null; } bool? operator ^(bool other) => !other == value; /// Toggles the bool [value] between false and true. /// A shortcut for `flag.value = !flag.value;` void toggle() { if (value != null) { call(!value!); // return this; } } } /// Foundation class used for custom `Types` outside the common native Dart /// types. /// For example, any custom "Model" class, like User().obs will use `Rx` as /// wrapper. class Rx extends _RxImpl { Rx(super.initial); @override dynamic toJson() { try { return (value as dynamic)?.toJson(); } on Exception catch (_) { throw '$T has not method [toJson]'; } } } class Rxn extends Rx { Rxn([super.initial]); @override dynamic toJson() { try { return (value as dynamic)?.toJson(); } on Exception catch (_) { throw '$T has not method [toJson]'; } } } extension StringExtension on String { /// Returns a `RxString` with [this] `String` as initial value. RxString get obs => RxString(this); } extension IntExtension on int { /// Returns a `RxInt` with [this] `int` as initial value. RxInt get obs => RxInt(this); } extension DoubleExtension on double { /// Returns a `RxDouble` with [this] `double` as initial value. RxDouble get obs => RxDouble(this); } extension BoolExtension on bool { /// Returns a `RxBool` with [this] `bool` as initial value. RxBool get obs => RxBool(this); } extension RxT on T { /// Returns a `Rx` instance with [this] `T` as initial value. Rx get obs => Rx(this); } /// This method will replace the old `.obs` method. /// It's a breaking change, but it is essential to avoid conflicts with /// the new dart 3 features. T will be inferred by contextual type inference /// rather than the extension type. extension RxTnew on Object { /// Returns a `Rx` instance with [this] `T` as initial value. Rx obs() => Rx(this as T); } ================================================ FILE: lib/get_rx/src/rx_types/rx_core/rx_interface.dart ================================================ part of '../rx_types.dart'; /// This class is the foundation for all reactive (Rx) classes that makes Get /// so powerful. /// This interface is the contract that `_RxImpl` uses in all it's /// subclass. abstract class RxInterface implements ValueListenable { /// Close the Rx Variable void close(); /// Calls `callback` with current value, when the value changes. StreamSubscription listen(void Function(T event) onData, {Function? onError, void Function()? onDone, bool? cancelOnError}); } class ObxError { const ObxError(); @override String toString() { return """ [Get] the improper use of a GetX has been detected. You should only use GetX or Obx for the specific widget that will be updated. If you are seeing this error, you probably did not insert any observable variables into GetX/Obx or insert them outside the scope that GetX considers suitable for an update (example: GetX => HeavyWidget => variableObservable). If you need to update a parent widget and a child widget, wrap each one in an Obx/GetX. """; } } ================================================ FILE: lib/get_rx/src/rx_types/rx_core/rx_num.dart ================================================ part of '../rx_types.dart'; extension RxNumExt on Rx { /// Multiplication operator. num operator *(num other) => value * other; /// Euclidean modulo operator. /// /// Returns the remainder of the Euclidean division. The Euclidean division of /// two integers `a` and `b` yields two integers `q` and `r` such that /// `a == b * q + r` and `0 <= r < b.abs()`. /// /// The Euclidean division is only defined for integers, but can be easily /// extended to work with doubles. In that case `r` may have a non-integer /// value, but it still verifies `0 <= r < |b|`. /// /// The sign of the returned value `r` is always positive. /// /// See [remainder] for the remainder of the truncating division. num operator %(num other) => value % other; /// Division operator. double operator /(num other) => value / other; /// Truncating division operator. /// /// If either operand is a [double] then the result of the truncating division /// `a ~/ b` is equivalent to `(a / b).truncate().toInt()`. /// /// If both operands are [int]s then `a ~/ b` performs the truncating /// integer division. int operator ~/(num other) => value ~/ other; /// Negate operator. num operator -() => -value; /// Returns the remainder of the truncating division of `this` by [other]. /// /// The result `r` of this operation satisfies: /// `this == (this ~/ other) * other + r`. /// As a consequence the remainder `r` has the same sign as the divider /// `this`. num remainder(num other) => value.remainder(other); /// Relational less than operator. bool operator <(num other) => value < other; /// Relational less than or equal operator. bool operator <=(num other) => value <= other; /// Relational greater than operator. bool operator >(num other) => value > other; /// Relational greater than or equal operator. bool operator >=(num other) => value >= other; /// True if the number is the double Not-a-Number value; otherwise, false. bool get isNaN => value.isNaN; /// True if the number is negative; otherwise, false. /// /// Negative numbers are those less than zero, and the double `-0.0`. bool get isNegative => value.isNegative; /// True if the number is positive infinity or negative infinity; otherwise, /// false. bool get isInfinite => value.isInfinite; /// True if the number is finite; otherwise, false. /// /// The only non-finite numbers are NaN, positive infinity, and /// negative infinity. bool get isFinite => value.isFinite; /// Returns the absolute value of this [num]. num abs() => value.abs(); /// Returns minus one, zero or plus one depending on the sign and /// numerical value of the number. /// /// Returns minus one if the number is less than zero, /// plus one if the number is greater than zero, /// and zero if the number is equal to zero. /// /// Returns NaN if the number is the double NaN value. /// /// Returns a number of the same type as this number. /// For doubles, `-0.0.sign == -0.0`. /// The result satisfies: /// /// n == n.sign * n.abs() /// /// for all numbers `n` (except NaN, because NaN isn't `==` to itself). num get sign => value.sign; /// Returns the integer closest to `this`. /// /// Rounds away from zero when there is no closest integer: /// `(3.5).round() == 4` and `(-3.5).round() == -4`. /// /// If `this` is not finite (`NaN` or infinity), throws an [UnsupportedError]. int round() => value.round(); /// Returns the greatest integer no greater than `this`. /// /// If `this` is not finite (`NaN` or infinity), throws an [UnsupportedError]. int floor() => value.floor(); /// Returns the least integer no smaller than `this`. /// /// If `this` is not finite (`NaN` or infinity), throws an [UnsupportedError]. int ceil() => value.ceil(); /// Returns the integer obtained by discarding any fractional /// digits from `this`. /// /// If `this` is not finite (`NaN` or infinity), throws an [UnsupportedError]. int truncate() => value.truncate(); /// Returns the double integer value closest to `this`. /// /// Rounds away from zero when there is no closest integer: /// `(3.5).roundToDouble() == 4` and `(-3.5).roundToDouble() == -4`. /// /// If this is already an integer valued double, including `-0.0`, or it is a /// non-finite double value, the value is returned unmodified. /// /// For the purpose of rounding, `-0.0` is considered to be below `0.0`, /// and `-0.0` is therefore considered closer to negative numbers than `0.0`. /// This means that for a value, `d` in the range `-0.5 < d < 0.0`, /// the result is `-0.0`. /// /// The result is always a double. /// If this is a numerically large integer, the result may be an infinite /// double. double roundToDouble() => value.roundToDouble(); /// Returns the greatest double integer value no greater than `this`. /// /// If this is already an integer valued double, including `-0.0`, or it is a /// non-finite double value, the value is returned unmodified. /// /// For the purpose of rounding, `-0.0` is considered to be below `0.0`. /// A number `d` in the range `0.0 < d < 1.0` will return `0.0`. /// /// The result is always a double. /// If this is a numerically large integer, the result may be an infinite /// double. double floorToDouble() => value.floorToDouble(); /// Returns the least double integer value no smaller than `this`. /// /// If this is already an integer valued double, including `-0.0`, or it is a /// non-finite double value, the value is returned unmodified. /// /// For the purpose of rounding, `-0.0` is considered to be below `0.0`. /// A number `d` in the range `-1.0 < d < 0.0` will return `-0.0`. /// /// The result is always a double. /// If this is a numerically large integer, the result may be an infinite /// double. double ceilToDouble() => value.ceilToDouble(); /// Returns the double integer value obtained by discarding any fractional /// digits from the double value of `this`. /// /// If this is already an integer valued double, including `-0.0`, or it is a /// non-finite double value, the value is returned unmodified. /// /// For the purpose of rounding, `-0.0` is considered to be below `0.0`. /// A number `d` in the range `-1.0 < d < 0.0` will return `-0.0`, and /// in the range `0.0 < d < 1.0` it will return 0.0. /// /// The result is always a double. /// If this is a numerically large integer, the result may be an infinite /// double. double truncateToDouble() => value.truncateToDouble(); /// Returns this [num] clamped to be in the range [lowerLimit]-[upperLimit]. /// /// The comparison is done using [compareTo] and therefore takes `-0.0` into /// account. This also implies that [double.nan] is treated as the maximal /// double value. /// /// The arguments [lowerLimit] and [upperLimit] must form a valid range where /// `lowerLimit.compareTo(upperLimit) <= 0`. num clamp(num lowerLimit, num upperLimit) => value.clamp(lowerLimit, upperLimit); /// Truncates this [num] to an integer and returns the result as an [int]. */ int toInt() => value.toInt(); /// Return this [num] as a [double]. /// /// If the number is not representable as a [double], an /// approximation is returned. For numerically large integers, the /// approximation may be infinite. double toDouble() => value.toDouble(); /// Returns a decimal-point string-representation of `this`. /// /// Converts `this` to a [double] before computing the string representation. /// /// If the absolute value of `this` is greater or equal to `10^21` then this /// methods returns an exponential representation computed by /// `this.toStringAsExponential()`. Otherwise the result /// is the closest string representation with exactly [fractionDigits] digits /// after the decimal point. If [fractionDigits] equals 0 then the decimal /// point is omitted. /// /// The parameter [fractionDigits] must be an integer satisfying: /// `0 <= fractionDigits <= 20`. /// /// Examples: /// /// 1.toStringAsFixed(3); // 1.000 /// (4321.12345678).toStringAsFixed(3); // 4321.123 /// (4321.12345678).toStringAsFixed(5); // 4321.12346 /// 123456789012345.toStringAsFixed(3); // 123456789012345.000 /// 10000000000000000.toStringAsFixed(4); // 10000000000000000.0000 /// 5.25.toStringAsFixed(0); // 5 String toStringAsFixed(int fractionDigits) => value.toStringAsFixed(fractionDigits); /// Returns an exponential string-representation of `this`. /// /// Converts `this` to a [double] before computing the string representation. /// /// If [fractionDigits] is given then it must be an integer satisfying: /// `0 <= fractionDigits <= 20`. In this case the string contains exactly /// [fractionDigits] after the decimal point. Otherwise, without the /// parameter, the returned string uses the shortest number of digits that /// accurately represent [this]. /// /// If [fractionDigits] equals 0 then the decimal point is omitted. /// Examples: /// /// 1.toStringAsExponential(); // 1e+0 /// 1.toStringAsExponential(3); // 1.000e+0 /// 123456.toStringAsExponential(); // 1.23456e+5 /// 123456.toStringAsExponential(3); // 1.235e+5 /// 123.toStringAsExponential(0); // 1e+2 String toStringAsExponential([int? fractionDigits]) => value.toStringAsExponential(fractionDigits); /// Converts `this` to a double and returns a string representation with /// exactly [precision] significant digits. /// /// The parameter [precision] must be an integer satisfying: /// `1 <= precision <= 21`. /// /// Examples: /// /// 1.toStringAsPrecision(2); // 1.0 /// 1e15.toStringAsPrecision(3); // 1.00e+15 /// 1234567.toStringAsPrecision(3); // 1.23e+6 /// 1234567.toStringAsPrecision(9); // 1234567.00 /// 12345678901234567890.toStringAsPrecision(20); // 12345678901234567168 /// 12345678901234567890.toStringAsPrecision(14); // 1.2345678901235e+19 /// 0.00000012345.toStringAsPrecision(15); // 1.23450000000000e-7 /// 0.0000012345.toStringAsPrecision(15); // 0.00000123450000000000 String toStringAsPrecision(int precision) => value.toStringAsPrecision(precision); } extension RxnNumExt on Rx { /// Multiplication operator. num? operator *(num other) { if (value != null) { return value! * other; } return null; } /// Euclidean modulo operator. /// /// Returns the remainder of the Euclidean division. The Euclidean division of /// two integers `a` and `b` yields two integers `q` and `r` such that /// `a == b * q + r` and `0 <= r < b.abs()`. /// /// The Euclidean division is only defined for integers, but can be easily /// extended to work with doubles. In that case `r` may have a non-integer /// value, but it still verifies `0 <= r < |b|`. /// /// The sign of the returned value `r` is always positive. /// /// See [remainder] for the remainder of the truncating division. num? operator %(num other) { if (value != null) { return value! % other; } return null; } /// Division operator. double? operator /(num other) { if (value != null) { return value! / other; } return null; } /// Truncating division operator. /// /// If either operand is a [double] then the result of the truncating division /// `a ~/ b` is equivalent to `(a / b).truncate().toInt()`. /// /// If both operands are [int]s then `a ~/ b` performs the truncating /// integer division. int? operator ~/(num other) { if (value != null) { return value! ~/ other; } return null; } /// Negate operator. num? operator -() { if (value != null) { return -value!; } return null; } /// Returns the remainder of the truncating division of `this` by [other]. /// /// The result `r` of this operation satisfies: /// `this == (this ~/ other) * other + r`. /// As a consequence the remainder `r` has the same sign as the divider /// `this`. num? remainder(num other) => value?.remainder(other); /// Relational less than operator. bool? operator <(num other) { if (value != null) { return value! < other; } return null; } /// Relational less than or equal operator. bool? operator <=(num other) { if (value != null) { return value! <= other; } return null; } /// Relational greater than operator. bool? operator >(num other) { if (value != null) { return value! > other; } return null; } /// Relational greater than or equal operator. bool? operator >=(num other) { if (value != null) { return value! >= other; } return null; } /// True if the number is the double Not-a-Number value; otherwise, false. bool? get isNaN => value?.isNaN; /// True if the number is negative; otherwise, false. /// /// Negative numbers are those less than zero, and the double `-0.0`. bool? get isNegative => value?.isNegative; /// True if the number is positive infinity or negative infinity; otherwise, /// false. bool? get isInfinite => value?.isInfinite; /// True if the number is finite; otherwise, false. /// /// The only non-finite numbers are NaN, positive infinity, and /// negative infinity. bool? get isFinite => value?.isFinite; /// Returns the absolute value of this [num]. num? abs() => value?.abs(); /// Returns minus one, zero or plus one depending on the sign and /// numerical value of the number. /// /// Returns minus one if the number is less than zero, /// plus one if the number is greater than zero, /// and zero if the number is equal to zero. /// /// Returns NaN if the number is the double NaN value. /// /// Returns a number of the same type as this number. /// For doubles, `-0.0.sign == -0.0`. /// The result satisfies: /// /// n == n.sign * n.abs() /// /// for all numbers `n` (except NaN, because NaN isn't `==` to itself). num? get sign => value?.sign; /// Returns the integer closest to `this`. /// /// Rounds away from zero when there is no closest integer: /// `(3.5).round() == 4` and `(-3.5).round() == -4`. /// /// If `this` is not finite (`NaN` or infinity), throws an [UnsupportedError]. int? round() => value?.round(); /// Returns the greatest integer no greater than `this`. /// /// If `this` is not finite (`NaN` or infinity), throws an [UnsupportedError]. int? floor() => value?.floor(); /// Returns the least integer no smaller than `this`. /// /// If `this` is not finite (`NaN` or infinity), throws an [UnsupportedError]. int? ceil() => value?.ceil(); /// Returns the integer obtained by discarding any fractional /// digits from `this`. /// /// If `this` is not finite (`NaN` or infinity), throws an [UnsupportedError]. int? truncate() => value?.truncate(); /// Returns the double integer value closest to `this`. /// /// Rounds away from zero when there is no closest integer: /// `(3.5).roundToDouble() == 4` and `(-3.5).roundToDouble() == -4`. /// /// If this is already an integer valued double, including `-0.0`, or it is a /// non-finite double value, the value is returned unmodified. /// /// For the purpose of rounding, `-0.0` is considered to be below `0.0`, /// and `-0.0` is therefore considered closer to negative numbers than `0.0`. /// This means that for a value, `d` in the range `-0.5 < d < 0.0`, /// the result is `-0.0`. /// /// The result is always a double. /// If this is a numerically large integer, the result may be an infinite /// double. double? roundToDouble() => value?.roundToDouble(); /// Returns the greatest double integer value no greater than `this`. /// /// If this is already an integer valued double, including `-0.0`, or it is a /// non-finite double value, the value is returned unmodified. /// /// For the purpose of rounding, `-0.0` is considered to be below `0.0`. /// A number `d` in the range `0.0 < d < 1.0` will return `0.0`. /// /// The result is always a double. /// If this is a numerically large integer, the result may be an infinite /// double. double? floorToDouble() => value?.floorToDouble(); /// Returns the least double integer value no smaller than `this`. /// /// If this is already an integer valued double, including `-0.0`, or it is a /// non-finite double value, the value is returned unmodified. /// /// For the purpose of rounding, `-0.0` is considered to be below `0.0`. /// A number `d` in the range `-1.0 < d < 0.0` will return `-0.0`. /// /// The result is always a double. /// If this is a numerically large integer, the result may be an infinite /// double. double? ceilToDouble() => value?.ceilToDouble(); /// Returns the double integer value obtained by discarding any fractional /// digits from the double value of `this`. /// /// If this is already an integer valued double, including `-0.0`, or it is a /// non-finite double value, the value is returned unmodified. /// /// For the purpose of rounding, `-0.0` is considered to be below `0.0`. /// A number `d` in the range `-1.0 < d < 0.0` will return `-0.0`, and /// in the range `0.0 < d < 1.0` it will return 0.0. /// /// The result is always a double. /// If this is a numerically large integer, the result may be an infinite /// double. double? truncateToDouble() => value?.truncateToDouble(); /// Returns this [num] clamped to be in the range [lowerLimit]-[upperLimit]. /// /// The comparison is done using [compareTo] and therefore takes `-0.0` into /// account. This also implies that [double.nan] is treated as the maximal /// double value. /// /// The arguments [lowerLimit] and [upperLimit] must form a valid range where /// `lowerLimit.compareTo(upperLimit) <= 0`. num? clamp(num lowerLimit, num upperLimit) => value?.clamp(lowerLimit, upperLimit); /// Truncates this [num] to an integer and returns the result as an [int]. */ int? toInt() => value?.toInt(); /// Return this [num] as a [double]. /// /// If the number is not representable as a [double], an /// approximation is returned. For numerically large integers, the /// approximation may be infinite. double? toDouble() => value?.toDouble(); /// Returns a decimal-point string-representation of `this`. /// /// Converts `this` to a [double] before computing the string representation. /// /// If the absolute value of `this` is greater or equal to `10^21` then this /// methods returns an exponential representation computed by /// `this.toStringAsExponential()`. Otherwise the result /// is the closest string representation with exactly [fractionDigits] digits /// after the decimal point. If [fractionDigits] equals 0 then the decimal /// point is omitted. /// /// The parameter [fractionDigits] must be an integer satisfying: /// `0 <= fractionDigits <= 20`. /// /// Examples: /// /// 1.toStringAsFixed(3); // 1.000 /// (4321.12345678).toStringAsFixed(3); // 4321.123 /// (4321.12345678).toStringAsFixed(5); // 4321.12346 /// 123456789012345.toStringAsFixed(3); // 123456789012345.000 /// 10000000000000000.toStringAsFixed(4); // 10000000000000000.0000 /// 5.25.toStringAsFixed(0); // 5 String? toStringAsFixed(int fractionDigits) => value?.toStringAsFixed(fractionDigits); /// Returns an exponential string-representation of `this`. /// /// Converts `this` to a [double] before computing the string representation. /// /// If [fractionDigits] is given then it must be an integer satisfying: /// `0 <= fractionDigits <= 20`. In this case the string contains exactly /// [fractionDigits] after the decimal point. Otherwise, without the /// parameter, the returned string uses the shortest number of digits that /// accurately represent [this]. /// /// If [fractionDigits] equals 0 then the decimal point is omitted. /// Examples: /// /// 1.toStringAsExponential(); // 1e+0 /// 1.toStringAsExponential(3); // 1.000e+0 /// 123456.toStringAsExponential(); // 1.23456e+5 /// 123456.toStringAsExponential(3); // 1.235e+5 /// 123.toStringAsExponential(0); // 1e+2 String? toStringAsExponential([int? fractionDigits]) => value?.toStringAsExponential(fractionDigits); /// Converts `this` to a double and returns a string representation with /// exactly [precision] significant digits. /// /// The parameter [precision] must be an integer satisfying: /// `1 <= precision <= 21`. /// /// Examples: /// /// 1.toStringAsPrecision(2); // 1.0 /// 1e15.toStringAsPrecision(3); // 1.00e+15 /// 1234567.toStringAsPrecision(3); // 1.23e+6 /// 1234567.toStringAsPrecision(9); // 1234567.00 /// 12345678901234567890.toStringAsPrecision(20); // 12345678901234567168 /// 12345678901234567890.toStringAsPrecision(14); // 1.2345678901235e+19 /// 0.00000012345.toStringAsPrecision(15); // 1.23450000000000e-7 /// 0.0000012345.toStringAsPrecision(15); // 0.00000123450000000000 String? toStringAsPrecision(int precision) => value?.toStringAsPrecision(precision); } class RxNum extends Rx { RxNum(super.initial); num operator +(num other) { value += other; return value; } /// Subtraction operator. num operator -(num other) { value -= other; return value; } } class RxnNum extends Rx { RxnNum([super.initial]); num? operator +(num other) { if (value != null) { value = value! + other; return value; } return null; } /// Subtraction operator. num? operator -(num other) { if (value != null) { value = value! - other; return value; } return null; } } extension RxDoubleExt on Rx { /// Addition operator. Rx operator +(num other) { value = value + other; return this; } /// Subtraction operator. Rx operator -(num other) { value = value - other; return this; } /// Multiplication operator. double operator *(num other) => value * other; double operator %(num other) => value % other; /// Division operator. double operator /(num other) => value / other; /// Truncating division operator. /// /// The result of the truncating division `a ~/ b` is equivalent to /// `(a / b).truncate()`. int operator ~/(num other) => value ~/ other; /// Negate operator. */ double operator -() => -value; /// Returns the absolute value of this [double]. double abs() => value.abs(); /// Returns the sign of the double's numerical value. /// /// Returns -1.0 if the value is less than zero, /// +1.0 if the value is greater than zero, /// and the value itself if it is -0.0, 0.0 or NaN. double get sign => value.sign; /// Returns the integer closest to `this`. /// /// Rounds away from zero when there is no closest integer: /// `(3.5).round() == 4` and `(-3.5).round() == -4`. /// /// If `this` is not finite (`NaN` or infinity), throws an [UnsupportedError]. int round() => value.round(); /// Returns the greatest integer no greater than `this`. /// /// If `this` is not finite (`NaN` or infinity), throws an [UnsupportedError]. int floor() => value.floor(); /// Returns the least integer no smaller than `this`. /// /// If `this` is not finite (`NaN` or infinity), throws an [UnsupportedError]. int ceil() => value.ceil(); /// Returns the integer obtained by discarding any fractional /// digits from `this`. /// /// If `this` is not finite (`NaN` or infinity), throws an [UnsupportedError]. int truncate() => value.truncate(); /// Returns the integer double value closest to `this`. /// /// Rounds away from zero when there is no closest integer: /// `(3.5).roundToDouble() == 4` and `(-3.5).roundToDouble() == -4`. /// /// If this is already an integer valued double, including `-0.0`, or it is /// not a finite value, the value is returned unmodified. /// /// For the purpose of rounding, `-0.0` is considered to be below `0.0`, /// and `-0.0` is therefore considered closer to negative numbers than `0.0`. /// This means that for a value, `d` in the range `-0.5 < d < 0.0`, /// the result is `-0.0`. double roundToDouble() => value.roundToDouble(); /// Returns the greatest integer double value no greater than `this`. /// /// If this is already an integer valued double, including `-0.0`, or it is /// not a finite value, the value is returned unmodified. /// /// For the purpose of rounding, `-0.0` is considered to be below `0.0`. /// A number `d` in the range `0.0 < d < 1.0` will return `0.0`. double floorToDouble() => value.floorToDouble(); /// Returns the least integer double value no smaller than `this`. /// /// If this is already an integer valued double, including `-0.0`, or it is /// not a finite value, the value is returned unmodified. /// /// For the purpose of rounding, `-0.0` is considered to be below `0.0`. /// A number `d` in the range `-1.0 < d < 0.0` will return `-0.0`. double ceilToDouble() => value.ceilToDouble(); /// Returns the integer double value obtained by discarding any fractional /// digits from `this`. /// /// If this is already an integer valued double, including `-0.0`, or it is /// not a finite value, the value is returned unmodified. /// /// For the purpose of rounding, `-0.0` is considered to be below `0.0`. /// A number `d` in the range `-1.0 < d < 0.0` will return `-0.0`, and /// in the range `0.0 < d < 1.0` it will return 0.0. double truncateToDouble() => value.truncateToDouble(); } extension RxnDoubleExt on Rx { /// Addition operator. Rx? operator +(num other) { if (value != null) { value = value! + other; return this; } return null; } /// Subtraction operator. Rx? operator -(num other) { if (value != null) { value = value! + other; return this; } return null; } /// Multiplication operator. double? operator *(num other) { if (value != null) { return value! * other; } return null; } double? operator %(num other) { if (value != null) { return value! % other; } return null; } /// Division operator. double? operator /(num other) { if (value != null) { return value! / other; } return null; } /// Truncating division operator. /// /// The result of the truncating division `a ~/ b` is equivalent to /// `(a / b).truncate()`. int? operator ~/(num other) { if (value != null) { return value! ~/ other; } return null; } /// Negate operator. */ double? operator -() { if (value != null) { return -value!; } return null; } /// Returns the absolute value of this [double]. double? abs() { return value?.abs(); } /// Returns the sign of the double's numerical value. /// /// Returns -1.0 if the value is less than zero, /// +1.0 if the value is greater than zero, /// and the value itself if it is -0.0, 0.0 or NaN. double? get sign => value?.sign; /// Returns the integer closest to `this`. /// /// Rounds away from zero when there is no closest integer: /// `(3.5).round() == 4` and `(-3.5).round() == -4`. /// /// If `this` is not finite (`NaN` or infinity), throws an [UnsupportedError]. int? round() => value?.round(); /// Returns the greatest integer no greater than `this`. /// /// If `this` is not finite (`NaN` or infinity), throws an [UnsupportedError]. int? floor() => value?.floor(); /// Returns the least integer no smaller than `this`. /// /// If `this` is not finite (`NaN` or infinity), throws an [UnsupportedError]. int? ceil() => value?.ceil(); /// Returns the integer obtained by discarding any fractional /// digits from `this`. /// /// If `this` is not finite (`NaN` or infinity), throws an [UnsupportedError]. int? truncate() => value?.truncate(); /// Returns the integer double value closest to `this`. /// /// Rounds away from zero when there is no closest integer: /// `(3.5).roundToDouble() == 4` and `(-3.5).roundToDouble() == -4`. /// /// If this is already an integer valued double, including `-0.0`, or it is /// not a finite value, the value is returned unmodified. /// /// For the purpose of rounding, `-0.0` is considered to be below `0.0`, /// and `-0.0` is therefore considered closer to negative numbers than `0.0`. /// This means that for a value, `d` in the range `-0.5 < d < 0.0`, /// the result is `-0.0`. double? roundToDouble() => value?.roundToDouble(); /// Returns the greatest integer double value no greater than `this`. /// /// If this is already an integer valued double, including `-0.0`, or it is /// not a finite value, the value is returned unmodified. /// /// For the purpose of rounding, `-0.0` is considered to be below `0.0`. /// A number `d` in the range `0.0 < d < 1.0` will return `0.0`. double? floorToDouble() => value?.floorToDouble(); /// Returns the least integer double value no smaller than `this`. /// /// If this is already an integer valued double, including `-0.0`, or it is /// not a finite value, the value is returned unmodified. /// /// For the purpose of rounding, `-0.0` is considered to be below `0.0`. /// A number `d` in the range `-1.0 < d < 0.0` will return `-0.0`. double? ceilToDouble() => value?.ceilToDouble(); /// Returns the integer double value obtained by discarding any fractional /// digits from `this`. /// /// If this is already an integer valued double, including `-0.0`, or it is /// not a finite value, the value is returned unmodified. /// /// For the purpose of rounding, `-0.0` is considered to be below `0.0`. /// A number `d` in the range `-1.0 < d < 0.0` will return `-0.0`, and /// in the range `0.0 < d < 1.0` it will return 0.0. double? truncateToDouble() => value?.truncateToDouble(); } class RxDouble extends Rx { RxDouble(super.initial); } class RxnDouble extends Rx { RxnDouble([super.initial]); } class RxInt extends Rx { RxInt(super.initial); /// Addition operator. RxInt operator +(int other) { value = value + other; return this; } /// Subtraction operator. RxInt operator -(int other) { value = value - other; return this; } } class RxnInt extends Rx { RxnInt([super.initial]); /// Addition operator. RxnInt operator +(int other) { if (value != null) { value = value! + other; } return this; } /// Subtraction operator. RxnInt operator -(int other) { if (value != null) { value = value! - other; } return this; } } extension RxIntExt on Rx { /// Bit-wise and operator. /// /// Treating both `this` and [other] as sufficiently large two's component /// integers, the result is a number with only the bits set that are set in /// both `this` and [other] /// /// If both operands are negative, the result is negative, otherwise /// the result is non-negative. int operator &(int other) => value & other; /// Bit-wise or operator. /// /// Treating both `this` and [other] as sufficiently large two's component /// integers, the result is a number with the bits set that are set in either /// of `this` and [other] /// /// If both operands are non-negative, the result is non-negative, /// otherwise the result is negative. int operator |(int other) => value | other; /// Bit-wise exclusive-or operator. /// /// Treating both `this` and [other] as sufficiently large two's component /// integers, the result is a number with the bits set that are set in one, /// but not both, of `this` and [other] /// /// If the operands have the same sign, the result is non-negative, /// otherwise the result is negative. int operator ^(int other) => value ^ other; /// The bit-wise negate operator. /// /// Treating `this` as a sufficiently large two's component integer, /// the result is a number with the opposite bits set. /// /// This maps any integer `x` to `-x - 1`. int operator ~() => ~value; /// Shift the bits of this integer to the left by [shiftAmount]. /// /// Shifting to the left makes the number larger, effectively multiplying /// the number by `pow(2, shiftIndex)`. /// /// There is no limit on the size of the result. It may be relevant to /// limit intermediate values by using the "and" operator with a suitable /// mask. /// /// It is an error if [shiftAmount] is negative. int operator <<(int shiftAmount) => value << shiftAmount; /// Shift the bits of this integer to the right by [shiftAmount]. /// /// Shifting to the right makes the number smaller and drops the least /// significant bits, effectively doing an integer division by ///`pow(2, shiftIndex)`. /// /// It is an error if [shiftAmount] is negative. int operator >>(int shiftAmount) => value >> shiftAmount; /// Returns this integer to the power of [exponent] modulo [modulus]. /// /// The [exponent] must be non-negative and [modulus] must be /// positive. int modPow(int exponent, int modulus) => value.modPow(exponent, modulus); /// Returns the modular multiplicative inverse of this integer /// modulo [modulus]. /// /// The [modulus] must be positive. /// /// It is an error if no modular inverse exists. int modInverse(int modulus) => value.modInverse(modulus); /// Returns the greatest common divisor of this integer and [other]. /// /// If either number is non-zero, the result is the numerically greatest /// integer dividing both `this` and `other`. /// /// The greatest common divisor is independent of the order, /// so `x.gcd(y)` is always the same as `y.gcd(x)`. /// /// For any integer `x`, `x.gcd(x)` is `x.abs()`. /// /// If both `this` and `other` is zero, the result is also zero. int gcd(int other) => value.gcd(other); /// Returns true if and only if this integer is even. bool get isEven => value.isEven; /// Returns true if and only if this integer is odd. bool get isOdd => value.isOdd; /// Returns the minimum number of bits required to store this integer. /// /// The number of bits excludes the sign bit, which gives the natural length /// for non-negative (unsigned) values. Negative values are complemented to /// return the bit position of the first bit that differs from the sign bit. /// /// To find the number of bits needed to store the value as a signed value, /// add one, i.e. use `x.bitLength + 1`. /// ``` /// x.bitLength == (-x-1).bitLength /// /// 3.bitLength == 2; // 00000011 /// 2.bitLength == 2; // 00000010 /// 1.bitLength == 1; // 00000001 /// 0.bitLength == 0; // 00000000 /// (-1).bitLength == 0; // 11111111 /// (-2).bitLength == 1; // 11111110 /// (-3).bitLength == 2; // 11111101 /// (-4).bitLength == 2; // 11111100 /// ``` int get bitLength => value.bitLength; /// Returns the least significant [width] bits of this integer as a /// non-negative number (i.e. unsigned representation). The returned value /// has zeros in all bit positions higher than [width]. /// ``` /// (-1).toUnsigned(5) == 31 // 11111111 -> 00011111 /// ``` /// This operation can be used to simulate arithmetic from low level /// languages. /// For example, to increment an 8 bit quantity: /// ``` /// q = (q + 1).toUnsigned(8); /// ``` /// `q` will count from `0` up to `255` and then wrap around to `0`. /// /// If the input fits in [width] bits without truncation, the result is the /// same as the input. The minimum width needed to avoid truncation of `x` is /// given by `x.bitLength`, i.e. /// ``` /// x == x.toUnsigned(x.bitLength); /// ``` int toUnsigned(int width) => value.toUnsigned(width); /// Returns the least significant [width] bits of this integer, extending the /// highest retained bit to the sign. This is the same as truncating the /// value to fit in [width] bits using an signed 2-s complement /// representation. /// The returned value has the same bit value in all positions higher than /// [width]. /// /// ``` /// V--sign bit-V /// 16.toSigned(5) == -16 // 00010000 -> 11110000 /// 239.toSigned(5) == 15 // 11101111 -> 00001111 /// ^ ^ /// ``` /// This operation can be used to simulate arithmetic from low level /// languages. /// For example, to increment an 8 bit signed quantity: /// ``` /// q = (q + 1).toSigned(8); /// ``` /// `q` will count from `0` up to `127`, wrap to `-128` and count back up to /// `127`. /// /// If the input value fits in [width] bits without truncation, the result is /// the same as the input. The minimum width needed to avoid truncation /// of `x` is `x.bitLength + 1`, i.e. /// ``` /// x == x.toSigned(x.bitLength + 1); /// ``` int toSigned(int width) => value.toSigned(width); /// Return the negative value of this integer. /// /// The result of negating an integer always has the opposite sign, except /// for zero, which is its own negation. int operator -() => -value; /// Returns the absolute value of this integer. /// /// For any integer `x`, the result is the same as `x < 0 ? -x : x`. int abs() => value.abs(); /// Returns the sign of this integer. /// /// Returns 0 for zero, -1 for values less than zero and /// +1 for values greater than zero. int get sign => value.sign; /// Returns `this`. int round() => value.round(); /// Returns `this`. int floor() => value.floor(); /// Returns `this`. int ceil() => value.ceil(); /// Returns `this`. int truncate() => value.truncate(); /// Returns `this.toDouble()`. double roundToDouble() => value.roundToDouble(); /// Returns `this.toDouble()`. double floorToDouble() => value.floorToDouble(); /// Returns `this.toDouble()`. double ceilToDouble() => value.ceilToDouble(); /// Returns `this.toDouble()`. double truncateToDouble() => value.truncateToDouble(); } extension RxnIntExt on Rx { /// Bit-wise and operator. /// /// Treating both `this` and [other] as sufficiently large two's component /// integers, the result is a number with only the bits set that are set in /// both `this` and [other] /// /// If both operands are negative, the result is negative, otherwise /// the result is non-negative. int? operator &(int other) { if (value != null) { return value! & other; } return null; } /// Bit-wise or operator. /// /// Treating both `this` and [other] as sufficiently large two's component /// integers, the result is a number with the bits set that are set in either /// of `this` and [other] /// /// If both operands are non-negative, the result is non-negative, /// otherwise the result is negative. int? operator |(int other) { if (value != null) { return value! | other; } return null; } /// Bit-wise exclusive-or operator. /// /// Treating both `this` and [other] as sufficiently large two's component /// integers, the result is a number with the bits set that are set in one, /// but not both, of `this` and [other] /// /// If the operands have the same sign, the result is non-negative, /// otherwise the result is negative. int? operator ^(int other) { if (value != null) { return value! ^ other; } return null; } /// The bit-wise negate operator. /// /// Treating `this` as a sufficiently large two's component integer, /// the result is a number with the opposite bits set. /// /// This maps any integer `x` to `-x - 1`. int? operator ~() { if (value != null) { return ~value!; } return null; } /// Shift the bits of this integer to the left by [shiftAmount]. /// /// Shifting to the left makes the number larger, effectively multiplying /// the number by `pow(2, shiftIndex)`. /// /// There is no limit on the size of the result. It may be relevant to /// limit intermediate values by using the "and" operator with a suitable /// mask. /// /// It is an error if [shiftAmount] is negative. int? operator <<(int shiftAmount) { if (value != null) { return value! << shiftAmount; } return null; } /// Shift the bits of this integer to the right by [shiftAmount]. /// /// Shifting to the right makes the number smaller and drops the least /// significant bits, effectively doing an integer division by ///`pow(2, shiftIndex)`. /// /// It is an error if [shiftAmount] is negative. int? operator >>(int shiftAmount) { if (value != null) { return value! >> shiftAmount; } return null; } /// Returns this integer to the power of [exponent] modulo [modulus]. /// /// The [exponent] must be non-negative and [modulus] must be /// positive. int? modPow(int exponent, int modulus) => value?.modPow(exponent, modulus); /// Returns the modular multiplicative inverse of this integer /// modulo [modulus]. /// /// The [modulus] must be positive. /// /// It is an error if no modular inverse exists. int? modInverse(int modulus) => value?.modInverse(modulus); /// Returns the greatest common divisor of this integer and [other]. /// /// If either number is non-zero, the result is the numerically greatest /// integer dividing both `this` and `other`. /// /// The greatest common divisor is independent of the order, /// so `x.gcd(y)` is always the same as `y.gcd(x)`. /// /// For any integer `x`, `x.gcd(x)` is `x.abs()`. /// /// If both `this` and `other` is zero, the result is also zero. int? gcd(int other) => value?.gcd(other); /// Returns true if and only if this integer is even. bool? get isEven => value?.isEven; /// Returns true if and only if this integer is odd. bool? get isOdd => value?.isOdd; /// Returns the minimum number of bits required to store this integer. /// /// The number of bits excludes the sign bit, which gives the natural length /// for non-negative (unsigned) values. Negative values are complemented to /// return the bit position of the first bit that differs from the sign bit. /// /// To find the number of bits needed to store the value as a signed value, /// add one, i.e. use `x.bitLength + 1`. /// ``` /// x.bitLength == (-x-1).bitLength /// /// 3.bitLength == 2; // 00000011 /// 2.bitLength == 2; // 00000010 /// 1.bitLength == 1; // 00000001 /// 0.bitLength == 0; // 00000000 /// (-1).bitLength == 0; // 11111111 /// (-2).bitLength == 1; // 11111110 /// (-3).bitLength == 2; // 11111101 /// (-4).bitLength == 2; // 11111100 /// ``` int? get bitLength => value?.bitLength; /// Returns the least significant [width] bits of this integer as a /// non-negative number (i.e. unsigned representation). The returned value /// has zeros in all bit positions higher than [width]. /// ``` /// (-1).toUnsigned(5) == 31 // 11111111 -> 00011111 /// ``` /// This operation can be used to simulate arithmetic from low level /// languages. /// For example, to increment an 8 bit quantity: /// ``` /// q = (q + 1).toUnsigned(8); /// ``` /// `q` will count from `0` up to `255` and then wrap around to `0`. /// /// If the input fits in [width] bits without truncation, the result is the /// same as the input. The minimum width needed to avoid truncation of `x` is /// given by `x.bitLength`, i.e. /// ``` /// x == x.toUnsigned(x.bitLength); /// ``` int? toUnsigned(int width) => value?.toUnsigned(width); /// Returns the least significant [width] bits of this integer, extending the /// highest retained bit to the sign. This is the same as truncating the /// value to fit in [width] bits using an signed 2-s complement /// representation. /// The returned value has the same bit value in all positions higher than /// [width]. /// /// ``` /// V--sign bit-V /// 16.toSigned(5) == -16 // 00010000 -> 11110000 /// 239.toSigned(5) == 15 // 11101111 -> 00001111 /// ^ ^ /// ``` /// This operation can be used to simulate arithmetic from low level /// languages. /// For example, to increment an 8 bit signed quantity: /// ``` /// q = (q + 1).toSigned(8); /// ``` /// `q` will count from `0` up to `127`, wrap to `-128` and count back up to /// `127`. /// /// If the input value fits in [width] bits without truncation, the result is /// the same as the input. The minimum width needed to avoid truncation /// of `x` is `x.bitLength + 1`, i.e. /// ``` /// x == x.toSigned(x.bitLength + 1); /// ``` int? toSigned(int width) => value?.toSigned(width); /// Return the negative value of this integer. /// /// The result of negating an integer always has the opposite sign, except /// for zero, which is its own negation. int? operator -() { if (value != null) { return -value!; } return null; } /// Returns the absolute value of this integer. /// /// For any integer `x`, the result is the same as `x < 0 ? -x : x`. int? abs() => value?.abs(); /// Returns the sign of this integer. /// /// Returns 0 for zero, -1 for values less than zero and /// +1 for values greater than zero. int? get sign => value?.sign; /// Returns `this`. int? round() => value?.round(); /// Returns `this`. int? floor() => value?.floor(); /// Returns `this`. int? ceil() => value?.ceil(); /// Returns `this`. int? truncate() => value?.truncate(); /// Returns `this.toDouble()`. double? roundToDouble() => value?.roundToDouble(); /// Returns `this.toDouble()`. double? floorToDouble() => value?.floorToDouble(); /// Returns `this.toDouble()`. double? ceilToDouble() => value?.ceilToDouble(); /// Returns `this.toDouble()`. double? truncateToDouble() => value?.truncateToDouble(); } ================================================ FILE: lib/get_rx/src/rx_types/rx_core/rx_string.dart ================================================ part of '../rx_types.dart'; extension RxStringExt on Rx { String operator +(String val) => value + val; int compareTo(String other) { return value.compareTo(other); } /// Returns true if this string ends with [other]. For example: /// /// 'Dart'.endsWith('t'); // true bool endsWith(String other) { return value.endsWith(other); } /// Returns true if this string starts with a match of [pattern]. bool startsWith(Pattern pattern, [int index = 0]) { return value.startsWith(pattern, index); } /// Returns the position of the first match of [pattern] in this string int indexOf(Pattern pattern, [int start = 0]) { return value.indexOf(pattern, start); } /// Returns the starting position of the last match [pattern] in this string, /// searching backward starting at [start], inclusive: int lastIndexOf(Pattern pattern, [int? start]) { return value.lastIndexOf(pattern, start); } /// Returns true if this string is empty. bool get isEmpty => value.isEmpty; /// Returns true if this string is not empty. bool get isNotEmpty => !isEmpty; /// Returns the substring of this string that extends from [startIndex], /// inclusive, to [endIndex], exclusive String substring(int startIndex, [int? endIndex]) { return value.substring(startIndex, endIndex); } /// Returns the string without any leading and trailing whitespace. String trim() { return value.trim(); } /// Returns the string without any leading whitespace. /// /// As [trim], but only removes leading whitespace. String trimLeft() { return value.trimLeft(); } /// Returns the string without any trailing whitespace. /// /// As [trim], but only removes trailing whitespace. String trimRight() { return value.trimRight(); } /// Pads this string on the left if it is shorter than [width]. /// /// Return a new string that prepends [padding] onto this string /// one time for each position the length is less than [width]. String padLeft(int width, [String padding = ' ']) { return value.padLeft(width, padding); } /// Pads this string on the right if it is shorter than [width]. /// Return a new string that appends [padding] after this string /// one time for each position the length is less than [width]. String padRight(int width, [String padding = ' ']) { return value.padRight(width, padding); } /// Returns true if this string contains a match of [other]: bool contains(Pattern other, [int startIndex = 0]) { return value.contains(other, startIndex); } /// Replaces all substrings that match [from] with [replace]. String replaceAll(Pattern from, String replace) { return value.replaceAll(from, replace); } /// Splits the string at matches of [pattern] and returns a list /// of substrings. List split(Pattern pattern) { return value.split(pattern); } /// Returns an unmodifiable list of the UTF-16 code units of this string. List get codeUnits => value.codeUnits; /// Returns an [Iterable] of Unicode code-points of this string. /// /// If the string contains surrogate pairs, they are combined and returned /// as one integer by this iterator. Unmatched surrogate halves are treated /// like valid 16-bit code-units. Runes get runes => value.runes; /// Converts all characters in this string to lower case. /// If the string is already in all lower case, this method returns `this`. String toLowerCase() { return value.toLowerCase(); } /// Converts all characters in this string to upper case. /// If the string is already in all upper case, this method returns `this`. String toUpperCase() { return value.toUpperCase(); } Iterable allMatches(String string, [int start = 0]) { return value.allMatches(string, start); } Match? matchAsPrefix(String string, [int start = 0]) { return value.matchAsPrefix(string, start); } } extension RxnStringExt on Rx { String operator +(String val) => (value ?? '') + val; int? compareTo(String other) { return value?.compareTo(other); } /// Returns true if this string ends with [other]. For example: /// /// 'Dart'.endsWith('t'); // true bool? endsWith(String other) { return value?.endsWith(other); } /// Returns true if this string starts with a match of [pattern]. bool? startsWith(Pattern pattern, [int index = 0]) { return value?.startsWith(pattern, index); } /// Returns the position of the first match of [pattern] in this string int? indexOf(Pattern pattern, [int start = 0]) { return value?.indexOf(pattern, start); } /// Returns the starting position of the last match [pattern] in this string, /// searching backward starting at [start], inclusive: int? lastIndexOf(Pattern pattern, [int? start]) { return value?.lastIndexOf(pattern, start); } /// Returns true if this string is empty. bool? get isEmpty => value?.isEmpty; /// Returns true if this string is not empty. bool? get isNotEmpty => value?.isNotEmpty; /// Returns the substring of this string that extends from [startIndex], /// inclusive, to [endIndex], exclusive String? substring(int startIndex, [int? endIndex]) { return value?.substring(startIndex, endIndex); } /// Returns the string without any leading and trailing whitespace. String? trim() { return value?.trim(); } /// Returns the string without any leading whitespace. /// /// As [trim], but only removes leading whitespace. String? trimLeft() { return value?.trimLeft(); } /// Returns the string without any trailing whitespace. /// /// As [trim], but only removes trailing whitespace. String? trimRight() { return value?.trimRight(); } /// Pads this string on the left if it is shorter than [width]. /// /// Return a new string that prepends [padding] onto this string /// one time for each position the length is less than [width]. String? padLeft(int width, [String padding = ' ']) { return value?.padLeft(width, padding); } /// Pads this string on the right if it is shorter than [width]. /// Return a new string that appends [padding] after this string /// one time for each position the length is less than [width]. String? padRight(int width, [String padding = ' ']) { return value?.padRight(width, padding); } /// Returns true if this string contains a match of [other]: bool? contains(Pattern other, [int startIndex = 0]) { return value?.contains(other, startIndex); } /// Replaces all substrings that match [from] with [replace]. String? replaceAll(Pattern from, String replace) { return value?.replaceAll(from, replace); } /// Splits the string at matches of [pattern] and returns a list /// of substrings. List? split(Pattern pattern) { return value?.split(pattern); } /// Returns an unmodifiable list of the UTF-16 code units of this string. List? get codeUnits => value?.codeUnits; /// Returns an [Iterable] of Unicode code-points of this string. /// /// If the string contains surrogate pairs, they are combined and returned /// as one integer by this iterator. Unmatched surrogate halves are treated /// like valid 16-bit code-units. Runes? get runes => value?.runes; /// Converts all characters in this string to lower case. /// If the string is already in all lower case, this method returns `this`. String? toLowerCase() { return value?.toLowerCase(); } /// Converts all characters in this string to upper case. /// If the string is already in all upper case, this method returns `this`. String? toUpperCase() { return value?.toUpperCase(); } Iterable? allMatches(String string, [int start = 0]) { return value?.allMatches(string, start); } Match? matchAsPrefix(String string, [int start = 0]) { return value?.matchAsPrefix(string, start); } } /// Rx class for `String` Type. class RxString extends Rx implements Comparable, Pattern { RxString(super.initial); @override Iterable allMatches(String string, [int start = 0]) { return value.allMatches(string, start); } @override Match? matchAsPrefix(String string, [int start = 0]) { return value.matchAsPrefix(string, start); } @override int compareTo(String other) { return value.compareTo(other); } } /// Rx class for `String` Type. class RxnString extends Rx implements Comparable, Pattern { RxnString([super.initial]); @override Iterable allMatches(String string, [int start = 0]) { return value!.allMatches(string, start); } @override Match? matchAsPrefix(String string, [int start = 0]) { return value!.matchAsPrefix(string, start); } @override int compareTo(String other) { return value!.compareTo(other); } } ================================================ FILE: lib/get_rx/src/rx_types/rx_iterables/rx_list.dart ================================================ part of '../rx_types.dart'; /// Create a list similar to `List` class RxList extends GetListenable> with ListMixin, RxObjectMixin> { RxList([super.initial = const []]); factory RxList.filled(int length, E fill, {bool growable = false}) { return RxList(List.filled(length, fill, growable: growable)); } factory RxList.empty({bool growable = false}) { return RxList(List.empty(growable: growable)); } /// Creates a list containing all [elements]. factory RxList.from(Iterable elements, {bool growable = true}) { return RxList(List.from(elements, growable: growable)); } /// Creates a list from [elements]. factory RxList.of(Iterable elements, {bool growable = true}) { return RxList(List.of(elements, growable: growable)); } /// Generates a list of values. factory RxList.generate(int length, E Function(int index) generator, {bool growable = true}) { return RxList(List.generate(length, generator, growable: growable)); } /// Creates an unmodifiable list containing all [elements]. factory RxList.unmodifiable(Iterable elements) { return RxList(List.unmodifiable(elements)); } @override Iterator get iterator => value.iterator; @override void operator []=(int index, E val) { value[index] = val; refresh(); } /// Special override to push() element(s) in a reactive way /// inside the List, @override RxList operator +(Iterable val) { addAll(val); // refresh(); return this; } @override E operator [](int index) { return value[index]; } @override void add(E element) { value.add(element); refresh(); } @override void addAll(Iterable iterable) { value.addAll(iterable); refresh(); } @override bool remove(Object? element) { final removed = value.remove(element); refresh(); return removed; } @override void removeWhere(bool Function(E element) test) { value.removeWhere(test); refresh(); } @override void retainWhere(bool Function(E element) test) { value.retainWhere(test); refresh(); } @override int get length => value.length; // @override // @protected // List get value { // RxInterface.proxy?.addListener(subject); // return subject.value; // } @override set length(int newLength) { value.length = newLength; refresh(); } @override void insertAll(int index, Iterable iterable) { value.insertAll(index, iterable); refresh(); } @override Iterable get reversed => value.reversed; // @override // set value(List val) { // value = val; // refresh(); // } @override Iterable where(bool Function(E) test) { return value.where(test); } @override Iterable whereType() { return value.whereType(); } @override void sort([int Function(E a, E b)? compare]) { value.sort(compare); refresh(); } } extension ListExtension on List { RxList get obs => RxList(this); /// Add [item] to [List] only if [item] is not null. void addNonNull(E item) { if (item != null) add(item); } /// Add [item] to [List] only if [condition] is true. void addIf(dynamic condition, E item) { if (condition is Condition) condition = condition(); if (condition is bool && condition) add(item); } /// Adds [Iterable] to [List] only if [condition] is true. void addAllIf(dynamic condition, Iterable items) { if (condition is Condition) condition = condition(); if (condition is bool && condition) addAll(items); } /// Replaces all existing items of this list with [item] void assign(E item) { // if (this is RxList) { // (this as RxList)._value; // } if (this is RxList) { (this as RxList).value.clear(); } add(item); } /// Replaces all existing items of this list with [items] void assignAll(Iterable items) { if (this is RxList) { (this as RxList).value.clear(); } //clear(); addAll(items); } } ================================================ FILE: lib/get_rx/src/rx_types/rx_iterables/rx_map.dart ================================================ part of '../rx_types.dart'; class RxMap extends GetListenable> with MapMixin, RxObjectMixin> { RxMap([super.initial = const {}]); factory RxMap.from(Map other) { return RxMap(Map.from(other)); } /// Creates a [LinkedHashMap] with the same keys and values as [other]. factory RxMap.of(Map other) { return RxMap(Map.of(other)); } ///Creates an unmodifiable hash based map containing the entries of [other]. factory RxMap.unmodifiable(Map other) { return RxMap(Map.unmodifiable(other)); } /// Creates an identity map with the default implementation, [LinkedHashMap]. factory RxMap.identity() { return RxMap(Map.identity()); } @override V? operator [](Object? key) { return value[key as K]; } @override void operator []=(K key, V value) { this.value[key] = value; refresh(); } @override void clear() { value.clear(); refresh(); } @override Iterable get keys => value.keys; @override V? remove(Object? key) { final val = value.remove(key); refresh(); return val; } // @override // @protected // Map get value { // return subject.value; // // RxInterface.proxy?.addListener(subject); // // return _value; // } } extension MapExtension on Map { RxMap get obs { return RxMap(this); } void addIf(dynamic condition, K key, V value) { if (condition is Condition) condition = condition(); if (condition is bool && condition) { this[key] = value; } } void addAllIf(dynamic condition, Map values) { if (condition is Condition) condition = condition(); if (condition is bool && condition) addAll(values); } void assign(K key, V val) { if (this is RxMap) { final map = (this as RxMap); // map._value; map.value.clear(); this[key] = val; } else { clear(); this[key] = val; } } void assignAll(Map val) { if (val is RxMap && this is RxMap) { if ((val as RxMap).value == (this as RxMap).value) return; } if (this is RxMap) { final map = (this as RxMap); if (map.value == val) return; map.value = val; // ignore: invalid_use_of_protected_member map.refresh(); } else { if (this == val) return; clear(); addAll(val); } } } ================================================ FILE: lib/get_rx/src/rx_types/rx_iterables/rx_set.dart ================================================ part of '../rx_types.dart'; class RxSet extends GetListenable> with SetMixin, RxObjectMixin> { RxSet([super.initial = const {}]); /// Special override to push() element(s) in a reactive way /// inside the List, RxSet operator +(Set val) { addAll(val); //refresh(); return this; } void update(void Function(Iterable? value) fn) { fn(value); refresh(); } // @override // @protected // Set get value { // return subject.value; // // RxInterface.proxy?.addListener(subject); // // return _value; // } // @override // @protected // set value(Set val) { // if (value == val) return; // value = val; // refresh(); // } @override bool add(E value) { final hasAdded = this.value.add(value); if (hasAdded) { refresh(); } return hasAdded; } @override bool contains(Object? element) { return value.contains(element); } @override Iterator get iterator => value.iterator; @override int get length => value.length; @override E? lookup(Object? element) { return value.lookup(element); } @override bool remove(Object? value) { var hasRemoved = this.value.remove(value); if (hasRemoved) { refresh(); } return hasRemoved; } @override Set toSet() { return value.toSet(); } @override void addAll(Iterable elements) { value.addAll(elements); refresh(); } @override void clear() { value.clear(); refresh(); } @override void removeAll(Iterable elements) { value.removeAll(elements); refresh(); } @override void retainAll(Iterable elements) { value.retainAll(elements); refresh(); } @override void retainWhere(bool Function(E) test) { value.retainWhere(test); refresh(); } } extension SetExtension on Set { RxSet get obs { return RxSet({})..addAll(this); } // /// Add [item] to [List] only if [item] is not null. // void addNonNull(E item) { // if (item != null) add(item); // } // /// Add [Iterable] to [List] only if [Iterable] is not null. // void addAllNonNull(Iterable item) { // if (item != null) addAll(item); // } /// Add [item] to [List] only if [condition] is true. void addIf(dynamic condition, E item) { if (condition is Condition) condition = condition(); if (condition is bool && condition) add(item); } /// Adds [Iterable] to [List] only if [condition] is true. void addAllIf(dynamic condition, Iterable items) { if (condition is Condition) condition = condition(); if (condition is bool && condition) addAll(items); } /// Replaces all existing items of this list with [item] void assign(E item) { // if (this is RxSet) { // (this as RxSet)._value; // } clear(); add(item); } /// Replaces all existing items of this list with [items] void assignAll(Iterable items) { // if (this is RxSet) { // (this as RxSet)._value; // } clear(); addAll(items); } } ================================================ FILE: lib/get_rx/src/rx_types/rx_types.dart ================================================ library; import 'dart:async'; import 'dart:collection'; import 'package:flutter/foundation.dart'; import '../../../get_state_manager/src/rx_flutter/rx_notifier.dart'; import '../rx_typedefs/rx_typedefs.dart'; part 'rx_core/rx_impl.dart'; part 'rx_core/rx_interface.dart'; part 'rx_core/rx_num.dart'; part 'rx_core/rx_string.dart'; part 'rx_iterables/rx_list.dart'; part 'rx_iterables/rx_map.dart'; part 'rx_iterables/rx_set.dart'; ================================================ FILE: lib/get_rx/src/rx_workers/rx_workers.dart ================================================ import 'dart:async'; import '../../../get_core/get_core.dart'; import '../../../get_state_manager/src/rx_flutter/rx_notifier.dart'; import '../rx_types/rx_types.dart'; import 'utils/debouncer.dart'; bool _conditional(dynamic condition) { if (condition == null) return true; if (condition is bool) return condition; if (condition is bool Function()) return condition(); return true; } typedef WorkerCallback = Function(T callback); class Workers { Workers(this.workers); final List workers; void dispose() { for (final worker in workers) { if (!worker._disposed) { worker.dispose(); } } } } /// /// Called every time [listener] changes. As long as the [condition] /// returns true. /// /// Sample: /// Every time increment() is called, ever() will process the [condition] /// (can be a [bool] expression or a `bool Function()`), and only call /// the callback when [condition] is true. /// In our case, only when count is bigger to 5. In order to "dispose" /// this Worker /// that will run forever, we made a `worker` variable. So, when the count value /// reaches 10, the worker gets disposed, and releases any memory resources. /// /// ``` /// // imagine some counter widget... /// /// class _CountController extends GetxController { /// final count = 0.obs; /// Worker worker; /// /// void onInit() { /// worker = ever(count, (value) { /// print('counter changed to: $value'); /// if (value == 10) worker.dispose(); /// }, condition: () => count > 5); /// } /// /// void increment() => count + 1; /// } /// ``` Worker ever( GetListenable listener, WorkerCallback callback, { dynamic condition = true, Function? onError, void Function()? onDone, bool? cancelOnError, }) { StreamSubscription sub = listener.listen( (event) { if (_conditional(condition)) callback(event); }, onError: onError, onDone: onDone, cancelOnError: cancelOnError, ); return Worker(sub.cancel, '[ever]'); } /// Similar to [ever], but takes a list of [listeners], the condition /// for the [callback] is common to all [listeners], /// and the [callback] is executed to each one of them. The [Worker] is /// common to all, so `worker.dispose()` will cancel all streams. Worker everAll( List listeners, WorkerCallback callback, { dynamic condition = true, Function? onError, void Function()? onDone, bool? cancelOnError, }) { final evers = []; for (var i in listeners) { final sub = i.listen( (event) { if (_conditional(condition)) callback(event); }, onError: onError, onDone: onDone, cancelOnError: cancelOnError, ); evers.add(sub); } Future cancel() async { for (var i in evers) { i.cancel(); } } return Worker(cancel, '[everAll]'); } /// `once()` will execute only 1 time when [condition] is met and cancel /// the subscription to the [listener] stream right after that. /// [condition] defines when [callback] is called, and /// can be a [bool] or a `bool Function()`. /// /// Sample: /// ``` /// class _CountController extends GetxController { /// final count = 0.obs; /// Worker worker; /// /// @override /// Future onInit() async { /// worker = once(count, (value) { /// print("counter reached $value before 3 seconds."); /// }, condition: () => count() > 2); /// 3.delay(worker.dispose); /// } /// void increment() => count + 1; /// } ///``` Worker once( GetListenable listener, WorkerCallback callback, { dynamic condition = true, Function? onError, void Function()? onDone, bool? cancelOnError, }) { late Worker ref; StreamSubscription? sub; sub = listener.listen( (event) { if (!_conditional(condition)) return; ref._disposed = true; ref._log('called'); sub?.cancel(); callback(event); }, onError: onError, onDone: onDone, cancelOnError: cancelOnError, ); ref = Worker(sub.cancel, '[once]'); return ref; } /// Ignore all changes in [listener] during [time] (1 sec by default) or until /// [condition] is met (can be a [bool] expression or a `bool Function()`), /// It brings the 1st "value" since the period of time, so /// if you click a counter button 3 times in 1 sec, it will show you "1" /// (after 1 sec of the first press) /// click counter 3 times in 1 sec, it will show you "4" (after 1 sec) /// click counter 2 times in 1 sec, it will show you "7" (after 1 sec). /// /// Sample: /// // wait 1 sec each time an event starts, only if counter is lower than 20. /// worker = interval( /// count, /// (value) => print(value), /// time: 1.seconds, /// condition: () => count < 20, /// ); /// ``` Worker interval( GetListenable listener, WorkerCallback callback, { Duration time = const Duration(seconds: 1), dynamic condition = true, Function? onError, void Function()? onDone, bool? cancelOnError, }) { var debounceActive = false; StreamSubscription sub = listener.listen( (event) async { if (debounceActive || !_conditional(condition)) return; debounceActive = true; await Future.delayed(time); debounceActive = false; callback(event); }, onError: onError, onDone: onDone, cancelOnError: cancelOnError, ); return Worker(sub.cancel, '[interval]'); } /// [debounce] is similar to [interval], but sends the last value. /// Useful for Anti DDos, every time the user stops typing for 1 second, /// for instance. /// When [listener] emits the last "value", when [time] hits, /// it calls [callback] with the last "value" emitted. /// /// Sample: /// /// ``` /// worker = debounce( /// count, /// (value) { /// print(value); /// if( value > 20 ) worker.dispose(); /// }, /// time: 1.seconds, /// ); /// } /// ``` Worker debounce( GetListenable listener, WorkerCallback callback, { Duration? time, Function? onError, void Function()? onDone, bool? cancelOnError, }) { final newDebouncer = Debouncer(delay: time ?? const Duration(milliseconds: 800)); StreamSubscription sub = listener.listen( (event) { newDebouncer(() { callback(event); }); }, onError: onError, onDone: onDone, cancelOnError: cancelOnError, ); return Worker(sub.cancel, '[debounce]'); } class Worker { Worker(this.worker, this.type); /// subscription.cancel() callback final Future Function() worker; /// type of worker (debounce, interval, ever).. final String type; bool _disposed = false; bool get disposed => _disposed; //final bool _verbose = true; void _log(String msg) { // if (!_verbose) return; Get.log('$runtimeType $type $msg'); } void dispose() { if (_disposed) { _log('already disposed'); return; } _disposed = true; worker(); _log('disposed'); } void call() => dispose(); } ================================================ FILE: lib/get_rx/src/rx_workers/utils/debouncer.dart ================================================ import 'dart:async'; /// This "function" class is the implementation of `debouncer()` Worker. /// It calls the function passed after specified [delay] parameter. /// Example: /// ``` /// final delayed = Debouncer( delay: Duration( seconds: 1 )) ; /// print( 'the next function will be called after 1 sec' ); /// delayed( () => print( 'called after 1 sec' )); /// ``` class Debouncer { final Duration delay; Timer? _timer; Debouncer({required this.delay}); void call(void Function() action) { _timer?.cancel(); _timer = Timer(delay, action); } /// Notifies if the delayed call is active. bool get isRunning => _timer?.isActive ?? false; /// Cancel the current delayed call. void cancel() => _timer?.cancel(); } ================================================ FILE: lib/get_state_manager/get_state_manager.dart ================================================ library; export 'src/rx_flutter/rx_getx_widget.dart'; export 'src/rx_flutter/rx_notifier.dart'; export 'src/rx_flutter/rx_obx_widget.dart'; export 'src/rx_flutter/rx_ticket_provider_mixin.dart'; export 'src/simple/get_controllers.dart'; export 'src/simple/get_responsive.dart'; export 'src/simple/get_state.dart'; export 'src/simple/get_view.dart'; export 'src/simple/simple_builder.dart'; ================================================ FILE: lib/get_state_manager/src/rx_flutter/rx_getx_widget.dart ================================================ import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import '../../../get_core/get_core.dart'; import '../../../get_instance/src/extension_instance.dart'; import '../../../get_instance/src/lifecycle.dart'; import '../simple/list_notifier.dart'; typedef GetXControllerBuilder = Widget Function( T controller); class GetX extends StatefulWidget { final GetXControllerBuilder builder; final bool global; final bool autoRemove; final bool assignId; final void Function(GetXState state)? initState, dispose, didChangeDependencies; final void Function(GetX oldWidget, GetXState state)? didUpdateWidget; final T? init; final String? tag; const GetX({ super.key, this.tag, required this.builder, this.global = true, this.autoRemove = true, this.initState, this.assignId = false, // this.stream, this.dispose, this.didChangeDependencies, this.didUpdateWidget, this.init, // this.streamController }); @override StatefulElement createElement() => StatefulElement(this); @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties ..add( DiagnosticsProperty('controller', init), ) ..add(DiagnosticsProperty('tag', tag)) ..add( ObjectFlagProperty>.has('builder', builder)); } @override GetXState createState() => GetXState(); } class GetXState extends State> { T? controller; bool? _isCreator = false; @override void initState() { // var isPrepared = Get.isPrepared(tag: widget.tag); final isRegistered = Get.isRegistered(tag: widget.tag); if (widget.global) { if (isRegistered) { _isCreator = Get.isPrepared(tag: widget.tag); controller = Get.find(tag: widget.tag); } else { controller = widget.init; _isCreator = true; Get.put(controller!, tag: widget.tag); } } else { controller = widget.init; _isCreator = true; controller?.onStart(); } widget.initState?.call(this); if (widget.global && Get.smartManagement == SmartManagement.onlyBuilder) { controller?.onStart(); } super.initState(); } @override void didChangeDependencies() { super.didChangeDependencies(); if (widget.didChangeDependencies != null) { widget.didChangeDependencies!(this); } } @override void didUpdateWidget(GetX oldWidget) { super.didUpdateWidget(oldWidget as GetX); widget.didUpdateWidget?.call(oldWidget, this); } @override void dispose() { if (widget.dispose != null) widget.dispose!(this); if (_isCreator! || widget.assignId) { if (widget.autoRemove && Get.isRegistered(tag: widget.tag)) { Get.delete(tag: widget.tag); } } for (final disposer in disposers) { disposer(); } disposers.clear(); controller = null; _isCreator = null; super.dispose(); } void _update() { if (mounted) { setState(() {}); } } final disposers = []; @override Widget build(BuildContext context) => Notifier.instance.append( NotifyData(disposers: disposers, updater: _update), () => widget.builder(controller!)); @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.add(DiagnosticsProperty('controller', controller)); } } ================================================ FILE: lib/get_state_manager/src/rx_flutter/rx_notifier.dart ================================================ import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:get/utils.dart'; import '../../../get_rx/src/rx_types/rx_types.dart'; import '../../../instance_manager.dart'; import '../../get_state_manager.dart'; import '../simple/list_notifier.dart'; extension _Empty on Object { bool _isEmpty() { final val = this; // if (val == null) return true; var result = false; if (val is Iterable) { result = val.isEmpty; } else if (val is String) { result = val.trim().isEmpty; } else if (val is Map) { result = val.isEmpty; } return result; } } mixin StateMixin on ListNotifier { T? _value; GetStatus? _status; void _fillInitialStatus() { _status = (_value == null || _value!._isEmpty()) ? GetStatus.loading() : GetStatus.success(_value as T); } GetStatus get status { reportRead(); return _status ??= _status = GetStatus.loading(); } T get state => value; set status(GetStatus newStatus) { if (newStatus == status) return; _status = newStatus; if (newStatus is SuccessStatus) { _value = newStatus.data; } refresh(); } @protected T get value { reportRead(); return _value as T; } @protected set value(T newValue) { if (_value == newValue) return; _value = newValue; refresh(); } @protected void change(GetStatus status) { if (status != this.status) { this.status = status; } } void setSuccess(T data) { change(GetStatus.success(data)); } void setError(Object error) { change(GetStatus.error(error)); } void setLoading() { change(GetStatus.loading()); } void setEmpty() { change(GetStatus.empty()); } void futurize(Future Function() body, {T? initialData, String? errorMessage, bool useEmpty = true}) { final compute = body; _value ??= initialData; status = GetStatus.loading(); compute().then((newValue) { if ((newValue == null || newValue._isEmpty()) && useEmpty) { status = GetStatus.empty(); } else { status = GetStatus.success(newValue); } refresh(); }, onError: (err) { status = GetStatus.error( err is Exception ? err : Exception(errorMessage ?? err.toString())); refresh(); }); } } typedef FuturizeCallback = Future Function(VoidCallback fn); typedef VoidCallback = void Function(); class GetListenable extends ListNotifierSingle implements RxInterface { GetListenable(T val) : _value = val; StreamController? _controller; StreamController get subject { if (_controller == null) { _controller = StreamController.broadcast(onCancel: addListener(_streamListener)); _controller?.add(_value); ///TODO: report to controller dispose } return _controller!; } void _streamListener() { _controller?.add(_value); } @override @mustCallSuper void close() { removeListener(_streamListener); _controller?.close(); dispose(); } Stream get stream { return subject.stream; } T _value; @override T get value { reportRead(); return _value; } void _notify() { refresh(); } set value(T newValue) { if (_value == newValue) return; _value = newValue; _notify(); } T? call([T? v]) { if (v != null) { value = v; } return value; } @override StreamSubscription listen( void Function(T)? onData, { Function? onError, void Function()? onDone, bool? cancelOnError, }) => stream.listen( onData, onError: onError, onDone: onDone, cancelOnError: cancelOnError ?? false, ); @override String toString() => value.toString(); } class Value extends ListNotifier with StateMixin implements ValueListenable { Value(T val) { _value = val; _fillInitialStatus(); } @override T get value { reportRead(); return _value as T; } @override set value(T newValue) { if (_value == newValue) return; _value = newValue; refresh(); } T? call([T? v]) { if (v != null) { value = v; } return value; } void update(T Function(T? value) fn) { value = fn(value); // refresh(); } @override String toString() => value.toString(); dynamic toJson() => (value as dynamic)?.toJson(); } /// GetNotifier has a native status and state implementation, with the /// Get Lifecycle abstract class GetNotifier extends Value with GetLifeCycleMixin { GetNotifier(super.initial); } extension StateExt on StateMixin { Widget obx( NotifierBuilder widget, { Widget Function(String? error)? onError, Widget? onLoading, Widget? onEmpty, WidgetBuilder? onCustom, }) { return Observer(builder: (context) { if (status.isLoading) { return onLoading ?? const Center(child: CircularProgressIndicator()); } else if (status.isError) { return onError != null ? onError(status.errorMessage) : Center(child: Text('A error occurred: ${status.errorMessage}')); } else if (status.isEmpty) { return onEmpty ?? const SizedBox.shrink(); // Also can be widget(null); but is risky } else if (status.isSuccess) { return widget(value); } else if (status.isCustom) { return onCustom?.call(context) ?? const SizedBox.shrink(); // Also can be widget(null); but is risky } return widget(value); }); } } typedef NotifierBuilder = Widget Function(T state); abstract class GetStatus with Equality { const GetStatus(); factory GetStatus.loading() => LoadingStatus(); factory GetStatus.error(Object message) => ErrorStatus(message); factory GetStatus.empty() => EmptyStatus(); factory GetStatus.success(T data) => SuccessStatus(data); factory GetStatus.custom() => CustomStatus(); } class CustomStatus extends GetStatus { @override List get props => []; } class LoadingStatus extends GetStatus { @override List get props => []; } class SuccessStatus extends GetStatus { final T data; const SuccessStatus(this.data); @override List get props => [data]; } class ErrorStatus extends GetStatus { final S? error; const ErrorStatus([this.error]); @override List get props => [error]; } class EmptyStatus extends GetStatus { @override List get props => []; } extension StatusDataExt on GetStatus { bool get isLoading => this is LoadingStatus; bool get isSuccess => this is SuccessStatus; bool get isError => this is ErrorStatus; bool get isEmpty => this is EmptyStatus; bool get isCustom => !isLoading && !isSuccess && !isError && !isEmpty; dynamic get error { if (this is ErrorStatus) { return (this as ErrorStatus).error; } return null; } String get errorMessage { final isError = this is ErrorStatus; if (isError) { final err = this as ErrorStatus; if (err.error != null) { if (err.error is String) { return err.error as String; } return err.error.toString(); } } return ''; } T? get data { if (this is SuccessStatus) { final success = this as SuccessStatus; return success.data; } return null; } } ================================================ FILE: lib/get_state_manager/src/rx_flutter/rx_obx_widget.dart ================================================ import 'package:flutter/widgets.dart'; import '../../../get_rx/src/rx_types/rx_types.dart'; import '../simple/simple_builder.dart'; typedef WidgetCallback = Widget Function(); /// The [ObxWidget] is the base for all GetX reactive widgets /// /// See also: /// - [Obx] /// - [ObxValue] abstract class ObxWidget extends ObxStatelessWidget { const ObxWidget({super.key}); } /// The simplest reactive widget in GetX. /// /// Just pass your Rx variable in the root scope of the callback to have it /// automatically registered for changes. /// /// final _name = "GetX".obs; /// Obx(() => Text( _name.value )),... ; class Obx extends ObxWidget { final WidgetCallback builder; const Obx(this.builder, {super.key}); @override Widget build(BuildContext context) { return builder(); } } /// Similar to Obx, but manages a local state. /// Pass the initial data in constructor. /// Useful for simple local states, like toggles, visibility, themes, /// button states, etc. /// Sample: /// ObxValue((data) => Switch( /// value: data.value, /// onChanged: (flag) => data.value = flag, /// ), /// false.obs, /// ), class ObxValue extends ObxWidget { final Widget Function(T) builder; final T data; const ObxValue(this.builder, this.data, {super.key}); @override Widget build(BuildContext context) => builder(data); } ================================================ FILE: lib/get_state_manager/src/rx_flutter/rx_ticket_provider_mixin.dart ================================================ // ignore_for_file: lines_longer_than_80_chars import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import '../../../get_instance/src/lifecycle.dart'; import '../../get_state_manager.dart'; /// Used like `SingleTickerProviderMixin` but only with Get Controllers. /// Simplifies AnimationController creation inside GetxController. /// /// Example: ///``` ///class SplashController extends GetxController with /// GetSingleTickerProviderStateMixin { /// AnimationController controller; /// /// @override /// void onInit() { /// final duration = const Duration(seconds: 2); /// controller = /// AnimationController.unbounded(duration: duration, vsync: this); /// controller.repeat(); /// controller.addListener(() => /// print("Animation Controller value: ${controller.value}")); /// } /// ... /// ``` mixin GetSingleTickerProviderStateMixin on GetxController implements TickerProvider { Ticker? _ticker; @override Ticker createTicker(TickerCallback onTick) { assert(() { if (_ticker == null) return true; throw FlutterError.fromParts([ ErrorSummary( '$runtimeType is a GetSingleTickerProviderStateMixin but multiple tickers were created.'), ErrorDescription( 'A GetSingleTickerProviderStateMixin can only be used as a TickerProvider once.'), ErrorHint( 'If a State is used for multiple AnimationController objects, or if it is passed to other ' 'objects and those objects might use it more than one time in total, then instead of ' 'mixing in a GetSingleTickerProviderStateMixin, use a regular GetTickerProviderStateMixin.', ), ]); }()); _ticker = Ticker(onTick, debugLabel: kDebugMode ? 'created by $this' : null); // We assume that this is called from initState, build, or some sort of // event handler, and that thus TickerMode.of(context) would return true. We // can't actually check that here because if we're in initState then we're // not allowed to do inheritance checks yet. return _ticker!; } void didChangeDependencies(BuildContext context) { if (_ticker != null) _ticker!.muted = !TickerMode.of(context); } @override void onClose() { assert(() { if (_ticker == null || !_ticker!.isActive) return true; throw FlutterError.fromParts([ ErrorSummary('$this was disposed with an active Ticker.'), ErrorDescription( '$runtimeType created a Ticker via its GetSingleTickerProviderStateMixin, but at the time ' 'dispose() was called on the mixin, that Ticker was still active. The Ticker must ' 'be disposed before calling super.dispose().', ), ErrorHint( 'Tickers used by AnimationControllers ' 'should be disposed by calling dispose() on the AnimationController itself. ' 'Otherwise, the ticker will leak.', ), _ticker!.describeForError('The offending ticker was'), ]); }()); super.onClose(); } } /// Used like `TickerProviderMixin` but only with Get Controllers. /// Simplifies multiple AnimationController creation inside GetxController. /// /// Example: ///``` ///class SplashController extends GetxController with /// GetTickerProviderStateMixin { /// AnimationController first_controller; /// AnimationController second_controller; /// /// @override /// void onInit() { /// final duration = const Duration(seconds: 2); /// first_controller = /// AnimationController.unbounded(duration: duration, vsync: this); /// second_controller = /// AnimationController.unbounded(duration: duration, vsync: this); /// first_controller.repeat(); /// first_controller.addListener(() => /// print("Animation Controller value: ${first_controller.value}")); /// second_controller.addListener(() => /// print("Animation Controller value: ${second_controller.value}")); /// } /// ... /// ``` mixin GetTickerProviderStateMixin on GetxController implements TickerProvider { Set? _tickers; @override Ticker createTicker(TickerCallback onTick) { _tickers ??= <_WidgetTicker>{}; final result = _WidgetTicker(onTick, this, debugLabel: kDebugMode ? 'created by ${describeIdentity(this)}' : null); _tickers!.add(result); return result; } void _removeTicker(_WidgetTicker ticker) { assert(_tickers != null); assert(_tickers!.contains(ticker)); _tickers!.remove(ticker); } void didChangeDependencies(BuildContext context) { final muted = !TickerMode.of(context); if (_tickers != null) { for (final ticker in _tickers!) { ticker.muted = muted; } } } @override void onClose() { assert(() { if (_tickers != null) { for (final ticker in _tickers!) { if (ticker.isActive) { throw FlutterError.fromParts([ ErrorSummary('$this was disposed with an active Ticker.'), ErrorDescription( '$runtimeType created a Ticker via its GetTickerProviderStateMixin, but at the time ' 'dispose() was called on the mixin, that Ticker was still active. All Tickers must ' 'be disposed before calling super.dispose().', ), ErrorHint( 'Tickers used by AnimationControllers ' 'should be disposed by calling dispose() on the AnimationController itself. ' 'Otherwise, the ticker will leak.', ), ticker.describeForError('The offending ticker was'), ]); } } } return true; }()); super.onClose(); } } class _WidgetTicker extends Ticker { _WidgetTicker(super.onTick, this._creator, {super.debugLabel}); final GetTickerProviderStateMixin _creator; @override void dispose() { _creator._removeTicker(this); super.dispose(); } } @Deprecated('use GetSingleTickerProviderStateMixin') /// Used like `SingleTickerProviderMixin` but only with Get Controllers. /// Simplifies AnimationController creation inside GetxController. /// /// Example: ///``` ///class SplashController extends GetxController with /// SingleGetTickerProviderMixin { /// AnimationController _ac; /// /// @override /// void onInit() { /// final dur = const Duration(seconds: 2); /// _ac = AnimationController.unbounded(duration: dur, vsync: this); /// _ac.repeat(); /// _ac.addListener(() => print("Animation Controller value: ${_ac.value}")); /// } /// ... /// ``` mixin SingleGetTickerProviderMixin on GetLifeCycleMixin implements TickerProvider { @override Ticker createTicker(TickerCallback onTick) => Ticker(onTick); } ================================================ FILE: lib/get_state_manager/src/simple/get_controllers.dart ================================================ // ignore: prefer_mixin import 'package:flutter/widgets.dart'; import '../../../instance_manager.dart'; import '../rx_flutter/rx_notifier.dart'; import 'list_notifier.dart'; /// A base controller class that provides state management functionality. /// /// Extend this class to create a controller that can be used with GetX's /// state management system. This class provides methods to update the UI /// when the controller's state changes. /// /// Example: /// ```dart /// class CounterController extends GetxController { /// var count = 0; /// /// void increment() { /// count++; /// update(); // Triggers UI update /// } /// } /// ``` // ignore: prefer_mixin abstract class GetxController extends ListNotifier with GetLifeCycleMixin { /// Notifies listeners to update the UI. /// /// When called without parameters, it will update all widgets that depend on /// this controller. You can also specify specific widget IDs to update only /// those widgets. /// /// Parameters: /// - [ids]: Optional list of widget IDs to update. If null, updates all widgets. /// - [condition]: If false, the update will be skipped. /// void update([List? ids, bool condition = true]) { if (!condition) { return; } if (ids == null) { refresh(); } else { for (final id in ids) { refreshGroup(id); } } } } /// A mixin that provides scroll-based data fetching capabilities. /// /// This mixin can be used with controllers that need to load more data /// when the user scrolls to the top or bottom of a scrollable view. /// /// Example: /// ```dart /// class MyController extends GetxController with ScrollMixin { /// @override /// Future onEndScroll() async { /// // Load more data when scrolled to bottom /// } /// /// @override /// Future onTopScroll() async { /// // Load previous data when scrolled to top /// } /// } /// ``` mixin ScrollMixin on GetLifeCycleMixin { /// The scroll controller used to detect scroll position final ScrollController scroll = ScrollController(); @override void onInit() { super.onInit(); scroll.addListener(_listener); } /// Flag to prevent multiple simultaneous bottom fetches bool _canFetchBottom = true; /// Flag to prevent multiple simultaneous top fetches bool _canFetchTop = true; void _listener() { if (scroll.position.atEdge) { _checkIfCanLoadMore(); } } Future _checkIfCanLoadMore() async { if (scroll.position.pixels == 0) { if (!_canFetchTop) return; _canFetchTop = false; await onTopScroll(); _canFetchTop = true; } else { if (!_canFetchBottom) return; _canFetchBottom = false; await onEndScroll(); _canFetchBottom = true; } } /// this method is called when the scroll is at the bottom Future onEndScroll(); /// this method is called when the scroll is at the top Future onTopScroll(); @override void onClose() { scroll.removeListener(_listener); scroll.dispose(); super.onClose(); } } /// A base controller class for reactive state management using Rx variables. /// /// This class is a lightweight alternative to [GetxController] when you only /// need reactive variables without the need for manual UI updates. /// /// Example: /// ```dart /// class UserController extends RxController { /// final name = 'John'.obs; /// final age = 30.obs; /// } /// ``` abstract class RxController with GetLifeCycleMixin {} /// A controller that manages state for asynchronous operations. /// /// This controller provides a standard way to handle loading, error, and success /// states for async operations. It's particularly useful for API calls and /// other asynchronous operations. /// /// Type parameters: /// - [T]: The type of data this controller will manage /// /// Example: /// ```dart /// class UserController extends StateController { /// Future fetchUser() async { /// change(null, status: RxStatus.loading()); /// try { /// final user = await userRepository.getUser(); /// change(user, status: RxStatus.success()); /// } catch (e) { /// change(state, status: RxStatus.error(e.toString())); /// } /// } /// } /// ``` abstract class StateController extends GetxController with StateMixin {} /// A controller that combines full lifecycle management with state management. /// /// This controller is ideal for complex scenarios where you need both: /// 1. Full app lifecycle awareness /// 2. State management with loading/error states /// /// Type parameters: /// - [T]: The type of data this controller will manage /// /// Example: /// ```dart /// class HomeController extends SuperController { /// @override /// void onResumed() { /// // App came to foreground /// fetchData(); /// } /// /// Future fetchData() async { /// change(state, status: RxStatus.loading()); /// try { /// final data = await repository.getData(); /// change(HomeState(data: data), status: RxStatus.success()); /// } catch (e) { /// change(state, status: RxStatus.error(e.toString())); /// } /// } /// } /// ``` abstract class SuperController extends FullLifeCycleController with FullLifeCycleMixin, StateMixin {} /// A controller that can observe the full app lifecycle. /// /// This controller extends [GetxController] and implements [WidgetsBindingObserver] /// to provide full lifecycle awareness. It can respond to: /// - App lifecycle changes (resumed, paused, etc.) /// - Memory pressure events /// - Accessibility changes /// /// Note: Don't forget to call `super.onClose()` in your controller's /// [onClose] method to properly clean up the observer. /// /// Example: /// ```dart /// class MyController extends FullLifeCycleController { /// @override /// void onResumed() { /// // App came to foreground /// } /// /// @override /// void onPaused() { /// // App went to background /// } /// /// @override /// void onClose() { /// // Clean up resources /// super.onClose(); /// } /// } /// ``` abstract class FullLifeCycleController extends GetxController with // ignore: prefer_mixin WidgetsBindingObserver {} /// A mixin that provides full lifecycle callbacks for the controller. /// /// This mixin handles app lifecycle events, memory pressure, and accessibility changes. /// Override the provided methods to respond to these events. /// /// Example: /// ```dart /// class MyController extends FullLifeCycleController with FullLifeCycleMixin { /// @override /// void onResumed() { /// // App came to foreground /// } /// /// @override /// void onMemoryPressure() { /// // Clean up resources when system is low on memory /// } /// } /// ``` mixin FullLifeCycleMixin on FullLifeCycleController { @mustCallSuper @override void onInit() { super.onInit(); Engine.instance.addObserver(this); } @mustCallSuper @override void onClose() { Engine.instance.removeObserver(this); super.onClose(); } @mustCallSuper @override void didChangeAppLifecycleState(AppLifecycleState state) { switch (state) { case AppLifecycleState.resumed: onResumed(); break; case AppLifecycleState.inactive: onInactive(); break; case AppLifecycleState.paused: onPaused(); break; case AppLifecycleState.detached: onDetached(); break; case AppLifecycleState.hidden: onHidden(); break; } } @override void didHaveMemoryPressure() { super.didHaveMemoryPressure(); onMemoryPressure(); } @override void didChangeAccessibilityFeatures() { super.didChangeAccessibilityFeatures(); onAccessibilityChanged(); } /// Called when the system reports that the app is visible and interactive. /// This is called when the app returns to the foreground. void onResumed() {} /// Called when the app is not currently visible to the user, not responding to /// user input, and running in the background. void onPaused() {} /// Called when the app is in an inactive state and is not receiving user input. /// For example, when a phone call is received or when the app is in a /// multi-window mode. void onInactive() {} /// Called before the app is destroyed. /// This is the final callback the app will receive before it is terminated. void onDetached() {} /// Called when the app is hidden (e.g., when the device is locked). void onHidden() {} /// Called when the system is running low on memory. /// Override this method to release caches or other resources that aren't /// critical for the app to function. void onMemoryPressure() {} /// Called when the system changes the set of currently active accessibility /// features. void onAccessibilityChanged() {} } ================================================ FILE: lib/get_state_manager/src/simple/get_responsive.dart ================================================ import 'package:flutter/widgets.dart'; import '../../../get.dart'; mixin GetResponsiveMixin on Widget { ResponsiveScreen get screen; bool get alwaysUseBuilder; @protected Widget build(BuildContext context) { screen.context = context; Widget? widget; if (alwaysUseBuilder) { widget = builder(); if (widget != null) return widget; } if (screen.isDesktop) { widget = desktop() ?? widget; if (widget != null) return widget; } if (screen.isTablet) { widget = tablet() ?? desktop(); if (widget != null) return widget; } if (screen.isPhone) { widget = phone() ?? tablet() ?? desktop(); if (widget != null) return widget; } return watch() ?? phone() ?? tablet() ?? desktop() ?? builder()!; } Widget? builder() => null; Widget? desktop() => null; Widget? phone() => null; Widget? tablet() => null; Widget? watch() => null; } /// Extend this widget to build responsive view. /// this widget contains the `screen` property that have all /// information about the screen size and type. /// You have two options to build it. /// 1- with `builder` method you return the widget to build. /// 2- with methods `desktop`, `tablet`,`phone`, `watch`. the specific /// method will be built when the screen type matches the method /// when the screen is [ScreenType.Tablet] the `tablet` method /// will be exuded and so on. /// Note if you use this method please set the /// property `alwaysUseBuilder` to false /// With `settings` property you can set the width limit for the screen types. class GetResponsiveView extends GetView with GetResponsiveMixin { @override final bool alwaysUseBuilder; @override final ResponsiveScreen screen; GetResponsiveView({ this.alwaysUseBuilder = false, ResponsiveScreenSettings settings = const ResponsiveScreenSettings(), super.key, }) : screen = ResponsiveScreen(settings); } class GetResponsiveWidget extends GetWidget with GetResponsiveMixin { @override final bool alwaysUseBuilder; @override final ResponsiveScreen screen; GetResponsiveWidget({ this.alwaysUseBuilder = false, ResponsiveScreenSettings settings = const ResponsiveScreenSettings(), super.key, }) : screen = ResponsiveScreen(settings); } class ResponsiveScreenSettings { /// When the width is greater als this value /// the display will be set as [ScreenType.Desktop] final double desktopChangePoint; /// When the width is greater als this value /// the display will be set as [ScreenType.Tablet] /// or when width greater als [watchChangePoint] and smaller als this value /// the display will be [ScreenType.Phone] final double tabletChangePoint; /// When the width is smaller als this value /// the display will be set as [ScreenType.Watch] /// or when width greater als this value and smaller als [tabletChangePoint] /// the display will be [ScreenType.Phone] final double watchChangePoint; const ResponsiveScreenSettings( {this.desktopChangePoint = 1200, this.tabletChangePoint = 600, this.watchChangePoint = 300}); } class ResponsiveScreen { late BuildContext context; final ResponsiveScreenSettings settings; late bool _isPlatformDesktop; ResponsiveScreen(this.settings) { _isPlatformDesktop = GetPlatform.isDesktop; } double get height => context.height; double get width => context.width; /// Is [screenType] [ScreenType.Desktop] bool get isDesktop => (screenType == ScreenType.desktop); /// Is [screenType] [ScreenType.Tablet] bool get isTablet => (screenType == ScreenType.tablet); /// Is [screenType] [ScreenType.Phone] bool get isPhone => (screenType == ScreenType.phone); /// Is [screenType] [ScreenType.Watch] bool get isWatch => (screenType == ScreenType.watch); double get _getDeviceWidth { if (_isPlatformDesktop) { return width; } return context.mediaQueryShortestSide; } ScreenType get screenType { final deviceWidth = _getDeviceWidth; if (deviceWidth >= settings.desktopChangePoint) return ScreenType.desktop; if (deviceWidth >= settings.tabletChangePoint) return ScreenType.tablet; if (deviceWidth < settings.watchChangePoint) return ScreenType.watch; return ScreenType.phone; } /// Return widget according to screen type /// if the [screenType] is [ScreenType.Desktop] and /// `desktop` object is null the `tablet` object will be returned /// and if `tablet` object is null the `mobile` object will be returned /// and if `mobile` object is null the `watch` object will be returned /// also when it is null. T? responsiveValue({ T? mobile, T? tablet, T? desktop, T? watch, }) { if (isDesktop && desktop != null) return desktop; if (isTablet && tablet != null) return tablet; if (isPhone && mobile != null) return mobile; return watch; } } enum ScreenType { watch, phone, tablet, desktop, } ================================================ FILE: lib/get_state_manager/src/simple/get_state.dart ================================================ // ignore_for_file: overridden_fields import 'dart:async'; import 'package:flutter/material.dart'; import '../../../instance_manager.dart'; import '../../get_state_manager.dart'; import 'list_notifier.dart'; typedef InitBuilder = T Function(); typedef GetControllerBuilder = Widget Function( T controller); extension WatchExt on BuildContext { T listen() { return Bind.of(this, rebuild: true); } } extension ReadExt on BuildContext { T get() { return Bind.of(this); } } // extension FilterExt on BuildContext { // T filter(Object Function(T value)? filter) { // return Bind.of(this, filter: filter, rebuild: true); // } // } class GetBuilder extends StatelessWidget { final GetControllerBuilder builder; final bool global; final Object? id; final String? tag; final bool autoRemove; final bool assignId; final Object Function(T value)? filter; final void Function(BindElement state)? initState, dispose, didChangeDependencies; final void Function(Binder oldWidget, BindElement state)? didUpdateWidget; final T? init; const GetBuilder({ super.key, this.init, this.global = true, required this.builder, this.autoRemove = true, this.assignId = false, this.initState, this.filter, this.tag, this.dispose, this.id, this.didChangeDependencies, this.didUpdateWidget, }); @override Widget build(BuildContext context) { return Binder( init: init == null ? null : () => init!, global: global, autoRemove: autoRemove, assignId: assignId, initState: initState, filter: filter, tag: tag, dispose: dispose, id: id, lazy: false, didChangeDependencies: didChangeDependencies, didUpdateWidget: didUpdateWidget, child: Builder(builder: (context) { final controller = Bind.of(context, rebuild: true); return builder(controller); }), ); // return widget.builder(controller!); } } abstract class Bind extends StatelessWidget { const Bind({ super.key, required this.child, this.init, this.global = true, this.autoRemove = true, this.assignId = false, this.initState, this.filter, this.tag, this.dispose, this.id, this.didChangeDependencies, this.didUpdateWidget, }); final InitBuilder? init; final bool global; final Object? id; final String? tag; final bool autoRemove; final bool assignId; final Object Function(T value)? filter; final void Function(BindElement state)? initState, dispose, didChangeDependencies; final void Function(Binder oldWidget, BindElement state)? didUpdateWidget; final Widget? child; static Bind put( S dependency, { String? tag, bool permanent = false, }) { Get.put(dependency, tag: tag, permanent: permanent); return _FactoryBind( autoRemove: permanent, assignId: true, tag: tag, ); } static bool fenixMode = false; static Bind lazyPut( InstanceBuilderCallback builder, { String? tag, bool? fenix, // VoidCallback? onInit, VoidCallback? onClose, }) { Get.lazyPut(builder, tag: tag, fenix: fenix ?? fenixMode); return _FactoryBind( tag: tag, // initState: (_) { // onInit?.call(); // }, dispose: (_) { onClose?.call(); }, ); } static Bind create(InstanceCreateBuilderCallback builder, {String? tag, bool permanent = true}) { return _FactoryBind( create: builder, tag: tag, global: false, ); } static Bind spawn(InstanceBuilderCallback builder, {String? tag, bool permanent = true}) { Get.spawn(builder, tag: tag, permanent: permanent); return _FactoryBind( tag: tag, global: false, autoRemove: permanent, ); } static S find({String? tag}) => Get.find(tag: tag); static Future delete({String? tag, bool force = false}) async => Get.delete(tag: tag, force: force); static Future deleteAll({bool force = false}) async => Get.deleteAll(force: force); static void reloadAll({bool force = false}) => Get.reloadAll(force: force); static void reload({String? tag, String? key, bool force = false}) => Get.reload(tag: tag, key: key, force: force); static bool isRegistered({String? tag}) => Get.isRegistered(tag: tag); static bool isPrepared({String? tag}) => Get.isPrepared(tag: tag); static void replace

(P child, {String? tag}) { final info = Get.getInstanceInfo

(tag: tag); final permanent = (info.isPermanent ?? false); delete

(tag: tag, force: permanent); Get.put(child, tag: tag, permanent: permanent); } static void lazyReplace

(InstanceBuilderCallback

builder, {String? tag, bool? fenix}) { final info = Get.getInstanceInfo

(tag: tag); final permanent = (info.isPermanent ?? false); delete

(tag: tag, force: permanent); Get.lazyPut(builder, tag: tag, fenix: fenix ?? permanent); } factory Bind.builder({ Widget? child, InitBuilder? init, InstanceCreateBuilderCallback? create, bool global = true, bool autoRemove = true, bool assignId = false, Object Function(T value)? filter, String? tag, Object? id, void Function(BindElement state)? initState, void Function(BindElement state)? dispose, void Function(BindElement state)? didChangeDependencies, void Function(Binder oldWidget, BindElement state)? didUpdateWidget, }) => _FactoryBind( // key: key, init: init, create: create, global: global, autoRemove: autoRemove, assignId: assignId, initState: initState, filter: filter, tag: tag, dispose: dispose, id: id, didChangeDependencies: didChangeDependencies, didUpdateWidget: didUpdateWidget, child: child, ); static T of( BuildContext context, { bool rebuild = false, // Object Function(T value)? filter, }) { final inheritedElement = context.getElementForInheritedWidgetOfExactType>() as BindElement?; if (inheritedElement == null) { throw BindError(controller: '$T', tag: null); } if (rebuild) { // var newFilter = filter?.call(inheritedElement.controller!); // if (newFilter != null) { // context.dependOnInheritedElement(inheritedElement, aspect: newFilter); // } else { context.dependOnInheritedElement(inheritedElement); // } } final controller = inheritedElement.controller; return controller; } @factory Bind _copyWithChild(Widget child); } class _FactoryBind extends Bind { @override final InitBuilder? init; final InstanceCreateBuilderCallback? create; @override final bool global; @override final Object? id; @override final String? tag; @override final bool autoRemove; @override final bool assignId; @override final Object Function(T value)? filter; @override final void Function(BindElement state)? initState, dispose, didChangeDependencies; @override final void Function(Binder oldWidget, BindElement state)? didUpdateWidget; @override final Widget? child; const _FactoryBind({ super.key, this.child, this.init, this.create, this.global = true, this.autoRemove = true, this.assignId = false, this.initState, this.filter, this.tag, this.dispose, this.id, this.didChangeDependencies, this.didUpdateWidget, }) : super(child: child); @override Bind _copyWithChild(Widget child) { return Bind.builder( init: init, create: create, global: global, autoRemove: autoRemove, assignId: assignId, initState: initState, filter: filter, tag: tag, dispose: dispose, id: id, didChangeDependencies: didChangeDependencies, didUpdateWidget: didUpdateWidget, child: child, ); } @override Widget build(BuildContext context) { return Binder( create: create, global: global, autoRemove: autoRemove, assignId: assignId, initState: initState, filter: filter, tag: tag, dispose: dispose, id: id, didChangeDependencies: didChangeDependencies, didUpdateWidget: didUpdateWidget, child: child!, ); } } class Binds extends StatelessWidget { final List> binds; final Widget child; Binds({ super.key, required this.binds, required this.child, }) : assert(binds.isNotEmpty); @override Widget build(BuildContext context) => binds.reversed.fold(child, (widget, e) => e._copyWithChild(widget)); } class Binder extends InheritedWidget { /// Create an inherited widget that updates its dependents when [controller] /// sends notifications. /// /// The [child] argument is required const Binder({ super.key, required super.child, this.init, this.global = true, this.autoRemove = true, this.assignId = false, this.lazy = true, this.initState, this.filter, this.tag, this.dispose, this.id, this.didChangeDependencies, this.didUpdateWidget, this.create, }); final InitBuilder? init; final InstanceCreateBuilderCallback? create; final bool global; final Object? id; final String? tag; final bool lazy; final bool autoRemove; final bool assignId; final Object Function(T value)? filter; final void Function(BindElement state)? initState, dispose, didChangeDependencies; final void Function(Binder oldWidget, BindElement state)? didUpdateWidget; @override bool updateShouldNotify(Binder oldWidget) { return oldWidget.id != id || oldWidget.global != global || oldWidget.autoRemove != autoRemove || oldWidget.assignId != assignId; } @override InheritedElement createElement() => BindElement(this); } /// The BindElement is responsible for injecting dependencies into the widget /// tree so that they can be observed class BindElement extends InheritedElement { BindElement(Binder super.widget) { initState(); } final disposers = []; InitBuilder? _controllerBuilder; T? _controller; T get controller { if (_controller == null) { _controller = _controllerBuilder?.call(); _subscribeToController(); if (_controller == null) { throw BindError(controller: T, tag: widget.tag); } return _controller!; } else { return _controller!; } } bool? _isCreator = false; bool? _needStart = false; bool _wasStarted = false; VoidCallback? _remove; Object? _filter; void initState() { widget.initState?.call(this); var isRegistered = Get.isRegistered(tag: widget.tag); if (widget.global) { if (isRegistered) { if (Get.isPrepared(tag: widget.tag)) { _isCreator = true; } else { _isCreator = false; } _controllerBuilder = () => Get.find(tag: widget.tag); } else { _controllerBuilder = () => (widget.create?.call(this) ?? widget.init?.call()); _isCreator = true; if (widget.lazy) { Get.lazyPut(_controllerBuilder!, tag: widget.tag); } else { Get.put(_controllerBuilder!(), tag: widget.tag); } } } else { if (widget.create != null) { _controllerBuilder = () => widget.create!.call(this); Get.spawn(_controllerBuilder!, tag: widget.tag, permanent: false); } else { _controllerBuilder = widget.init; } _controllerBuilder = (widget.create != null ? () => widget.create!.call(this) : null) ?? widget.init; _isCreator = true; _needStart = true; } } /// Register to listen Controller's events. /// It gets a reference to the remove() callback, to delete the /// setState "link" from the Controller. void _subscribeToController() { if (widget.filter != null) { _filter = widget.filter!(_controller as T); } final filter = _filter != null ? _filterUpdate : getUpdate; final localController = _controller; if (_needStart == true && localController is GetLifeCycleMixin) { localController.onStart(); _needStart = false; _wasStarted = true; } if (localController is GetxController) { _remove?.call(); _remove = (widget.id == null) ? localController.addListener(filter) : localController.addListenerId(widget.id, filter); } else if (localController is Listenable) { _remove?.call(); localController.addListener(filter); _remove = () => localController.removeListener(filter); } else if (localController is StreamController) { _remove?.call(); final stream = localController.stream.listen((_) => filter()); _remove = () => stream.cancel(); } } void _filterUpdate() { var newFilter = widget.filter!(_controller as T); if (newFilter != _filter) { _filter = newFilter; getUpdate(); } } void dispose() { widget.dispose?.call(this); if (_isCreator! || widget.assignId) { if (widget.autoRemove && Get.isRegistered(tag: widget.tag)) { Get.delete(tag: widget.tag); } } for (final disposer in disposers) { disposer(); } disposers.clear(); _remove?.call(); _controller = null; _isCreator = null; _remove = null; _filter = null; _needStart = null; _controllerBuilder = null; _controller = null; } @override Binder get widget => super.widget as Binder; var _dirty = false; @override void update(Binder newWidget) { final oldNotifier = widget.id; final newNotifier = newWidget.id; if (oldNotifier != newNotifier && _wasStarted) { _subscribeToController(); } widget.didUpdateWidget?.call(widget, this); super.update(newWidget); } @override void didChangeDependencies() { super.didChangeDependencies(); widget.didChangeDependencies?.call(this); } @override Widget build() { if (_dirty) { notifyClients(widget); } // return Notifier.instance.notifyAppend( // NotifyData( // disposers: disposers, updater: getUpdate, throwException: false), return super.build(); //); } void getUpdate() { _dirty = true; markNeedsBuild(); } @override void notifyClients(Binder oldWidget) { super.notifyClients(oldWidget); _dirty = false; } @override void unmount() { dispose(); super.unmount(); } } class BindError extends Error { /// The type of the class the user tried to retrieve final T controller; final String? tag; /// Creates a [BindError] BindError({required this.controller, required this.tag}); @override String toString() { if (controller == 'dynamic') { return '''Error: please specify type [] when calling context.listen() or context.find() method.'''; } return '''Error: No Bind<$controller> ancestor found. To fix this, please add a Bind<$controller> widget ancestor to the current context. '''; } } /// [Binding] should be extended. /// When using `GetMaterialApp`, all `GetPage`s and navigation /// methods (like Get.to()) have a `binding` property that takes an /// instance of Bindings to manage the /// dependencies() (via Get.put()) for the Route you are opening. // ignore: one_member_abstracts abstract class Binding extends BindingsInterface> {} ================================================ FILE: lib/get_state_manager/src/simple/get_view.dart ================================================ import 'package:flutter/widgets.dart'; import '../../../instance_manager.dart'; import '../../../utils.dart'; import 'get_state.dart'; import 'get_widget_cache.dart'; /// GetView is a great way of quickly access your Controller /// without having to call `Get.find()` yourself. /// /// Sample: /// ``` /// class AwesomeController extends GetxController { /// final String title = 'My Awesome View'; /// } /// /// class AwesomeView extends GetView { /// /// if you need you can pass the tag for /// /// Get.find(tag:"myTag"); /// @override /// final String tag = "myTag"; /// /// AwesomeView({Key key}):super(key:key); /// /// @override /// Widget build(BuildContext context) { /// return Container( /// padding: EdgeInsets.all(20), /// child: Text( controller.title ), /// ); /// } /// } ///`` abstract class GetView extends StatelessWidget { const GetView({super.key}); final String? tag = null; T get controller => Get.find(tag: tag)!; @override Widget build(BuildContext context); } /// GetWidget is a great way of quickly access your individual Controller /// without having to call `Get.find()` yourself. /// Get save you controller on cache, so, you can to use Get.create() safely /// GetWidget is perfect to multiples instance of a same controller. Each /// GetWidget will have your own controller, and will be call events as `onInit` /// and `onClose` when the controller get in/get out on memory. abstract class GetWidget extends GetWidgetCache { const GetWidget({super.key}); @protected final String? tag = null; S get controller => GetWidget._cache[this] as S; // static final _cache = {}; static final _cache = Expando(); @protected Widget build(BuildContext context); @override WidgetCache createWidgetCache() => _GetCache(); } class _GetCache extends WidgetCache> { S? _controller; bool _isCreator = false; InstanceInfo? info; @override void onInit() { info = Get.getInstanceInfo(tag: widget!.tag); _isCreator = info!.isPrepared && info!.isCreate; if (info!.isRegistered) { _controller = Get.find(tag: widget!.tag); } GetWidget._cache[widget!] = _controller; super.onInit(); } @override void onClose() { if (_isCreator) { Get.asap(() { widget!.controller.onDelete(); Get.log('"${widget!.controller.runtimeType}" onClose() called'); Get.log('"${widget!.controller.runtimeType}" deleted from memory'); // GetWidget._cache[widget!] = null; }); } info = null; super.onClose(); } @override Widget build(BuildContext context) { return Binder( init: () => _controller, child: widget!.build(context), ); } } ================================================ FILE: lib/get_state_manager/src/simple/get_widget_cache.dart ================================================ import 'package:flutter/widgets.dart'; abstract class GetWidgetCache extends Widget { const GetWidgetCache({super.key}); @override GetWidgetCacheElement createElement() => GetWidgetCacheElement(this); @protected @factory WidgetCache createWidgetCache(); } class GetWidgetCacheElement extends ComponentElement { GetWidgetCacheElement(GetWidgetCache widget) : cache = widget.createWidgetCache(), super(widget) { cache._element = this; cache._widget = widget; } @override void mount(Element? parent, dynamic newSlot) { cache.onInit(); super.mount(parent, newSlot); } @override Widget build() => cache.build(this); final WidgetCache cache; @override void activate() { super.activate(); markNeedsBuild(); } @override void unmount() { super.unmount(); cache.onClose(); cache._element = null; } } @optionalTypeArgs abstract class WidgetCache { T? get widget => _widget; T? _widget; BuildContext? get context => _element; GetWidgetCacheElement? _element; @protected @mustCallSuper void onInit() {} @protected @mustCallSuper void onClose() {} @protected Widget build(BuildContext context); } ================================================ FILE: lib/get_state_manager/src/simple/list_notifier.dart ================================================ import 'dart:collection'; import 'package:flutter/foundation.dart'; // This callback remove the listener on addListener function typedef Disposer = void Function(); // replacing StateSetter, return if the Widget is mounted for extra validation. // if it brings overhead the extra call, typedef GetStateUpdate = void Function(); class ListNotifier extends Listenable with ListNotifierSingleMixin, ListNotifierGroupMixin {} /// A Notifier with single listeners class ListNotifierSingle = ListNotifier with ListNotifierSingleMixin; /// A notifier with group of listeners identified by id class ListNotifierGroup = ListNotifier with ListNotifierGroupMixin; /// This mixin add to Listenable the addListener, removerListener and /// containsListener implementation mixin ListNotifierSingleMixin on Listenable { List? _updaters = []; // final int _version = 0; // final int _microtaskVersion = 0; @override Disposer addListener(GetStateUpdate listener) { assert(_debugAssertNotDisposed()); _updaters!.add(listener); return () => _updaters!.remove(listener); } bool containsListener(GetStateUpdate listener) { return _updaters?.contains(listener) ?? false; } @override void removeListener(VoidCallback listener) { assert(_debugAssertNotDisposed()); _updaters!.remove(listener); } @protected void refresh() { assert(_debugAssertNotDisposed()); _notifyUpdate(); } @protected void reportRead() { Notifier.instance.read(this); } @protected void reportAdd(VoidCallback disposer) { Notifier.instance.add(disposer); } void _notifyUpdate() { // if (_microtaskVersion == _version) { // _microtaskVersion++; // scheduleMicrotask(() { // _version++; // _microtaskVersion = _version; final list = _updaters?.toList() ?? []; for (var element in list) { element(); } // }); // } } bool get isDisposed => _updaters == null; bool _debugAssertNotDisposed() { assert(() { if (isDisposed) { throw FlutterError('''A $runtimeType was used after being disposed.\n 'Once you have called dispose() on a $runtimeType, it can no longer be used.'''); } return true; }()); return true; } int get listenersLength { assert(_debugAssertNotDisposed()); return _updaters!.length; } @mustCallSuper void dispose() { assert(_debugAssertNotDisposed()); _updaters = null; } } mixin ListNotifierGroupMixin on Listenable { HashMap? _updatersGroupIds = HashMap(); void _notifyGroupUpdate(Object id) { if (_updatersGroupIds!.containsKey(id)) { _updatersGroupIds![id]!._notifyUpdate(); } } @protected void notifyGroupChildrens(Object id) { assert(_debugAssertNotDisposed()); Notifier.instance.read(_updatersGroupIds![id]!); } bool containsId(Object id) { return _updatersGroupIds?.containsKey(id) ?? false; } @protected void refreshGroup(Object id) { assert(_debugAssertNotDisposed()); _notifyGroupUpdate(id); } bool _debugAssertNotDisposed() { assert(() { if (_updatersGroupIds == null) { throw FlutterError('''A $runtimeType was used after being disposed.\n 'Once you have called dispose() on a $runtimeType, it can no longer be used.'''); } return true; }()); return true; } void removeListenerId(Object id, VoidCallback listener) { assert(_debugAssertNotDisposed()); if (_updatersGroupIds!.containsKey(id)) { _updatersGroupIds![id]!.removeListener(listener); } } @mustCallSuper void dispose() { assert(_debugAssertNotDisposed()); _updatersGroupIds?.forEach((key, value) => value.dispose()); _updatersGroupIds = null; } Disposer addListenerId(Object? key, GetStateUpdate listener) { _updatersGroupIds![key] ??= ListNotifierSingle(); return _updatersGroupIds![key]!.addListener(listener); } /// To dispose an [id] from future updates(), this ids are registered /// by `GetBuilder()` or similar, so is a way to unlink the state change with /// the Widget from the Controller. void disposeId(Object id) { _updatersGroupIds?[id]?.dispose(); _updatersGroupIds!.remove(id); } } class Notifier { Notifier._(); static Notifier? _instance; static Notifier get instance => _instance ??= Notifier._(); NotifyData? _notifyData; void add(VoidCallback listener) { _notifyData?.disposers.add(listener); } void read(ListNotifierSingleMixin updaters) { final listener = _notifyData?.updater; if (listener != null && !updaters.containsListener(listener)) { updaters.addListener(listener); add(() => updaters.removeListener(listener)); } } T append(NotifyData data, T Function() builder) { _notifyData = data; final result = builder(); if (data.disposers.isEmpty && data.throwException) { throw const ObxError(); } _notifyData = null; return result; } } class NotifyData { const NotifyData( {required this.updater, required this.disposers, this.throwException = true}); final GetStateUpdate updater; final List disposers; final bool throwException; } class ObxError { const ObxError(); @override String toString() { return """ [Get] the improper use of a GetX has been detected. You should only use GetX or Obx for the specific widget that will be updated. If you are seeing this error, you probably did not insert any observable variables into GetX/Obx or insert them outside the scope that GetX considers suitable for an update (example: GetX => HeavyWidget => variableObservable). If you need to update a parent widget and a child widget, wrap each one in an Obx/GetX. """; } } ================================================ FILE: lib/get_state_manager/src/simple/mixin_builder.dart ================================================ import 'package:flutter/material.dart'; import '../rx_flutter/rx_obx_widget.dart'; import 'get_controllers.dart'; import 'get_state.dart'; class MixinBuilder extends StatelessWidget { @required final Widget Function(T) builder; final bool global; final String? id; final bool autoRemove; final void Function(BindElement state)? initState, dispose, didChangeDependencies; final void Function(Binder oldWidget, BindElement state)? didUpdateWidget; final T? init; const MixinBuilder({ super.key, this.init, this.global = true, required this.builder, this.autoRemove = true, this.initState, this.dispose, this.id, this.didChangeDependencies, this.didUpdateWidget, }); @override Widget build(BuildContext context) { return GetBuilder( init: init, global: global, autoRemove: autoRemove, initState: initState, dispose: dispose, id: id, didChangeDependencies: didChangeDependencies, didUpdateWidget: didUpdateWidget, builder: (controller) => Obx(() => builder.call(controller))); } } ================================================ FILE: lib/get_state_manager/src/simple/simple_builder.dart ================================================ import 'dart:async'; import 'package:flutter/widgets.dart'; import 'list_notifier.dart'; typedef ValueBuilderUpdateCallback = void Function(T snapshot); typedef ValueBuilderBuilder = Widget Function( T snapshot, ValueBuilderUpdateCallback updater); /// Manages a local state like ObxValue, but uses a callback instead of /// a Rx value. /// /// Example: /// ``` /// ValueBuilder( /// initialValue: false, /// builder: (value, update) => Switch( /// value: value, /// onChanged: (flag) { /// update( flag ); /// },), /// onUpdate: (value) => print("Value updated: $value"), /// ), /// ``` class ValueBuilder extends StatefulWidget { final T initialValue; final ValueBuilderBuilder builder; final void Function()? onDispose; final void Function(T)? onUpdate; const ValueBuilder({ super.key, required this.initialValue, this.onDispose, this.onUpdate, required this.builder, }); @override ValueBuilderState createState() => ValueBuilderState(); } class ValueBuilderState extends State> { late T value; @override void initState() { value = widget.initialValue; super.initState(); } @override Widget build(BuildContext context) => widget.builder(value, updater); void updater(T newValue) { if (widget.onUpdate != null) { widget.onUpdate!(newValue); } setState(() { value = newValue; }); } @override void dispose() { super.dispose(); widget.onDispose?.call(); if (value is ChangeNotifier) { (value as ChangeNotifier?)?.dispose(); } else if (value is StreamController) { (value as StreamController?)?.close(); } } } class ObxElement = StatelessElement with StatelessObserverComponent; // It's a experimental feature class Observer extends ObxStatelessWidget { final WidgetBuilder builder; const Observer({super.key, required this.builder}); @override Widget build(BuildContext context) => builder(context); } /// A StatelessWidget than can listen reactive changes. abstract class ObxStatelessWidget extends StatelessWidget { /// Initializes [key] for subclasses. const ObxStatelessWidget({super.key}); @override StatelessElement createElement() => ObxElement(this); } /// a Component that can track changes in a reactive variable mixin StatelessObserverComponent on StatelessElement { List? disposers = []; void getUpdate() { // if (disposers != null && !dirty) { // markNeedsBuild(); // } if (disposers != null) { scheduleMicrotask(markNeedsBuild); } } @override Widget build() { return Notifier.instance.append( NotifyData(disposers: disposers!, updater: getUpdate), super.build); } @override void unmount() { super.unmount(); for (final disposer in disposers!) { disposer(); } disposers!.clear(); disposers = null; } } ================================================ FILE: lib/get_utils/get_utils.dart ================================================ export 'src/equality/equality.dart'; export 'src/extensions/export.dart'; export 'src/get_utils/get_utils.dart'; export 'src/platform/platform.dart'; export 'src/queue/get_queue.dart'; ================================================ FILE: lib/get_utils/src/equality/equality.dart ================================================ library; import 'dart:collection'; mixin Equality { List get props; @override bool operator ==(Object other) { return identical(this, other) || runtimeType == other.runtimeType && other is Equality && const DeepCollectionEquality().equals(props, other.props); } @override int get hashCode { return runtimeType.hashCode ^ const DeepCollectionEquality().hash(props); } } const int _hashMask = 0x7fffffff; /// A generic equality relation on objects. abstract class IEquality { const factory IEquality() = DefaultEquality; /// Compare two elements for being equal. /// /// This should be a proper equality relation. bool equals(E e1, E e2); /// Get a hashcode of an element. /// /// The hashcode should be compatible with [equals], so that if /// `equals(a, b)` then `hash(a) == hash(b)`. int hash(E e); /// Test whether an object is a valid argument to [equals] and [hash]. /// /// Some implementations may be restricted to only work on specific types /// of objects. bool isValidKey(Object? o); } class DefaultEquality implements IEquality { const DefaultEquality(); @override bool equals(Object? e1, Object? e2) => e1 == e2; @override int hash(Object? e) => e.hashCode; @override bool isValidKey(Object? o) => true; } /// Equality of objects that compares only the identity of the objects. class IdentityEquality implements IEquality { const IdentityEquality(); @override bool equals(E e1, E e2) => identical(e1, e2); @override int hash(E e) => identityHashCode(e); @override bool isValidKey(Object? o) => true; } class DeepCollectionEquality implements IEquality { final IEquality _base = const DefaultEquality(); final bool _unordered = false; const DeepCollectionEquality(); @override bool equals(e1, e2) { if (e1 is Set) { return e2 is Set && SetEquality(this).equals(e1, e2); } if (e1 is Map) { return e2 is Map && MapEquality(keys: this, values: this).equals(e1, e2); } if (e1 is List) { return e2 is List && ListEquality(this).equals(e1, e2); } if (e1 is Iterable) { return e2 is Iterable && IterableEquality(this).equals(e1, e2); } return _base.equals(e1, e2); } @override int hash(Object? o) { if (o is Set) return SetEquality(this).hash(o); if (o is Map) return MapEquality(keys: this, values: this).hash(o); if (!_unordered) { if (o is List) return ListEquality(this).hash(o); if (o is Iterable) return IterableEquality(this).hash(o); } else if (o is Iterable) { return UnorderedIterableEquality(this).hash(o); } return _base.hash(o); } @override bool isValidKey(Object? o) => o is Iterable || o is Map || _base.isValidKey(o); } /// Equality on lists. /// /// Two lists are equal if they have the same length and their elements /// at each index are equal. class ListEquality implements IEquality> { final IEquality _elementEquality; const ListEquality( [IEquality elementEquality = const DefaultEquality()]) : _elementEquality = elementEquality; @override bool equals(List? list1, List? list2) { if (identical(list1, list2)) return true; if (list1 == null || list2 == null) return false; var length = list1.length; if (length != list2.length) return false; for (var i = 0; i < length; i++) { if (!_elementEquality.equals(list1[i], list2[i])) return false; } return true; } @override int hash(List? list) { if (list == null) return null.hashCode; // Jenkins's one-at-a-time hash function. // This code is almost identical to the one in IterableEquality, except // that it uses indexing instead of iterating to get the elements. var hash = 0; for (var i = 0; i < list.length; i++) { var c = _elementEquality.hash(list[i]); hash = (hash + c) & _hashMask; hash = (hash + (hash << 10)) & _hashMask; hash ^= (hash >> 6); } hash = (hash + (hash << 3)) & _hashMask; hash ^= (hash >> 11); hash = (hash + (hash << 15)) & _hashMask; return hash; } @override bool isValidKey(Object? o) => o is List; } /// Equality on maps. /// /// Two maps are equal if they have the same number of entries, and if the /// entries of the two maps are pairwise equal on both key and value. class MapEquality implements IEquality> { final IEquality _keyEquality; final IEquality _valueEquality; const MapEquality( {IEquality keys = const DefaultEquality(), IEquality values = const DefaultEquality()}) : _keyEquality = keys, _valueEquality = values; @override bool equals(Map? map1, Map? map2) { if (identical(map1, map2)) return true; if (map1 == null || map2 == null) return false; var length = map1.length; if (length != map2.length) return false; Map<_MapEntry, int> equalElementCounts = HashMap(); for (var key in map1.keys) { var entry = _MapEntry(this, key, map1[key]); var count = equalElementCounts[entry] ?? 0; equalElementCounts[entry] = count + 1; } for (var key in map2.keys) { var entry = _MapEntry(this, key, map2[key]); var count = equalElementCounts[entry]; if (count == null || count == 0) return false; equalElementCounts[entry] = count - 1; } return true; } @override int hash(Map? map) { if (map == null) return null.hashCode; var hash = 0; for (var key in map.keys) { var keyHash = _keyEquality.hash(key); var valueHash = _valueEquality.hash(map[key] as V); hash = (hash + 3 * keyHash + 7 * valueHash) & _hashMask; } hash = (hash + (hash << 3)) & _hashMask; hash ^= (hash >> 11); hash = (hash + (hash << 15)) & _hashMask; return hash; } @override bool isValidKey(Object? o) => o is Map; } class _MapEntry { final MapEquality equality; final Object? key; final Object? value; _MapEntry(this.equality, this.key, this.value); @override int get hashCode => (3 * equality._keyEquality.hash(key) + 7 * equality._valueEquality.hash(value)) & _hashMask; @override bool operator ==(Object other) => other is _MapEntry && equality._keyEquality.equals(key, other.key) && equality._valueEquality.equals(value, other.value); } /// Equality on iterables. /// /// Two iterables are equal if they have the same elements in the same order. class IterableEquality implements IEquality> { final IEquality _elementEquality; const IterableEquality( [IEquality elementEquality = const DefaultEquality()]) : _elementEquality = elementEquality; @override bool equals(Iterable? elements1, Iterable? elements2) { if (identical(elements1, elements2)) return true; if (elements1 == null || elements2 == null) return false; var it1 = elements1.iterator; var it2 = elements2.iterator; while (true) { var hasNext = it1.moveNext(); if (hasNext != it2.moveNext()) return false; if (!hasNext) return true; if (!_elementEquality.equals(it1.current, it2.current)) return false; } } @override int hash(Iterable? elements) { if (elements == null) return null.hashCode; // Jenkins's one-at-a-time hash function. var hash = 0; for (var element in elements) { var c = _elementEquality.hash(element); hash = (hash + c) & _hashMask; hash = (hash + (hash << 10)) & _hashMask; hash ^= (hash >> 6); } hash = (hash + (hash << 3)) & _hashMask; hash ^= (hash >> 11); hash = (hash + (hash << 15)) & _hashMask; return hash; } @override bool isValidKey(Object? o) => o is Iterable; } /// Equality of sets. /// /// Two sets are considered equal if they have the same number of elements, /// and the elements of one set can be paired with the elements /// of the other set, so that each pair are equal. class SetEquality extends _UnorderedEquality> { const SetEquality([super.elementEquality = const DefaultEquality()]); @override bool isValidKey(Object? o) => o is Set; } abstract class _UnorderedEquality> implements IEquality { final IEquality _elementEquality; const _UnorderedEquality(this._elementEquality); @override bool equals(T? elements1, T? elements2) { if (identical(elements1, elements2)) return true; if (elements1 == null || elements2 == null) return false; var counts = HashMap( equals: _elementEquality.equals, hashCode: _elementEquality.hash, isValidKey: _elementEquality.isValidKey); var length = 0; for (var e in elements1) { var count = counts[e] ?? 0; counts[e] = count + 1; length++; } for (var e in elements2) { var count = counts[e]; if (count == null || count == 0) return false; counts[e] = count - 1; length--; } return length == 0; } @override int hash(T? elements) { if (elements == null) return null.hashCode; var hash = 0; for (E element in elements) { var c = _elementEquality.hash(element); hash = (hash + c) & _hashMask; } hash = (hash + (hash << 3)) & _hashMask; hash ^= (hash >> 11); hash = (hash + (hash << 15)) & _hashMask; return hash; } } /// Equality of the elements of two iterables without considering order. /// /// Two iterables are considered equal if they have the same number of elements, /// and the elements of one set can be paired with the elements /// of the other iterable, so that each pair are equal. class UnorderedIterableEquality extends _UnorderedEquality> { const UnorderedIterableEquality( [super.elementEquality = const DefaultEquality()]); @override bool isValidKey(Object? o) => o is Iterable; } ================================================ FILE: lib/get_utils/src/extensions/context_extensions.dart ================================================ import 'package:flutter/material.dart'; extension ContextExt on BuildContext { /// The same of [MediaQuery.sizeOf(context)] Size get mediaQuerySize => MediaQuery.sizeOf(this); /// The same of [MediaQuery.of(context).size.height] /// Note: updates when you resize your screen (like on a browser or /// desktop window) double get height => mediaQuerySize.height; /// The same of [MediaQuery.of(context).size.width] /// Note: updates when you resize your screen (like on a browser or /// desktop window) double get width => mediaQuerySize.width; /// Gives you the power to get a portion of the height. /// Useful for responsive applications. /// /// [dividedBy] is for when you want to have a portion of the value you /// would get like for example: if you want a value that represents a third /// of the screen you can set it to 3, and you will get a third of the height /// /// [reducedBy] is a percentage value of how much of the height you want /// if you for example want 46% of the height, then you reduce it by 56%. double heightTransformer({double dividedBy = 1, double reducedBy = 0.0}) { return (mediaQuerySize.height - ((mediaQuerySize.height / 100) * reducedBy)) / dividedBy; } /// Gives you the power to get a portion of the width. /// Useful for responsive applications. /// /// [dividedBy] is for when you want to have a portion of the value you /// would get like for example: if you want a value that represents a third /// of the screen you can set it to 3, and you will get a third of the width /// /// [reducedBy] is a percentage value of how much of the width you want /// if you for example want 46% of the width, then you reduce it by 56%. double widthTransformer({double dividedBy = 1, double reducedBy = 0.0}) { return (mediaQuerySize.width - ((mediaQuerySize.width / 100) * reducedBy)) / dividedBy; } /// Divide the height proportionally by the given value double ratio({ double dividedBy = 1, double reducedByW = 0.0, double reducedByH = 0.0, }) { return heightTransformer(dividedBy: dividedBy, reducedBy: reducedByH) / widthTransformer(dividedBy: dividedBy, reducedBy: reducedByW); } /// similar to [MediaQuery.of(context).padding] ThemeData get theme => Theme.of(this); /// Check if dark mode theme is enable bool get isDarkMode => (theme.brightness == Brightness.dark); /// give access to Theme.of(context).iconTheme.color Color? get iconColor => theme.iconTheme.color; /// similar to [MediaQuery.of(context).padding] TextTheme get textTheme => Theme.of(this).textTheme; /// similar to [MediaQuery.paddingOf(context)] EdgeInsets get mediaQueryPadding => MediaQuery.paddingOf(this); /// similar to [MediaQuery.of(context).padding] MediaQueryData get mediaQuery => MediaQuery.of(this); /// similar to [MediaQuery.viewPaddingOf(context)] EdgeInsets get mediaQueryViewPadding => MediaQuery.viewPaddingOf(this); /// similar to [MediaQuery.viewInsetsOf(context)] EdgeInsets get mediaQueryViewInsets => MediaQuery.viewInsetsOf(this); /// similar to [MediaQuery.orientationOf(context)] Orientation get orientation => MediaQuery.orientationOf(this); /// check if device is on landscape mode bool get isLandscape => orientation == Orientation.landscape; /// check if device is on portrait mode bool get isPortrait => orientation == Orientation.portrait; /// similar to [MediaQuery.devicePixelRatioOf(context)] double get devicePixelRatio => MediaQuery.devicePixelRatioOf(this); /// similar to [MediaQuery.textScaleFactorOf(context)] TextScaler get textScaleFactor => MediaQuery.textScalerOf(this); /// get the shortestSide from screen double get mediaQueryShortestSide => mediaQuerySize.shortestSide; /// True if width be larger than 800 bool get showNavbar => (width > 800); /// True if the width is smaller than 600p bool get isPhoneOrLess => width <= 600; /// True if the width is higher than 600p bool get isPhoneOrWider => width >= 600; /// True if the shortestSide is smaller than 600p bool get isPhone => (mediaQueryShortestSide < 600); /// True if the width is smaller than 600p bool get isSmallTabletOrLess => width <= 600; /// True if the width is higher than 600p bool get isSmallTabletOrWider => width >= 600; /// True if the shortestSide is largest than 600p bool get isSmallTablet => (mediaQueryShortestSide >= 600); /// True if the shortestSide is largest than 720p bool get isLargeTablet => (mediaQueryShortestSide >= 720); /// True if the width is smaller than 720p bool get isLargeTabletOrLess => width <= 720; /// True if the width is higher than 720p bool get isLargeTabletOrWider => width >= 720; /// True if the current device is Tablet bool get isTablet => isSmallTablet || isLargeTablet; /// True if the width is smaller than 1200p bool get isDesktopOrLess => width <= 1200; /// True if the width is higher than 1200p bool get isDesktopOrWider => width >= 1200; /// same as [isDesktopOrLess] bool get isDesktop => isDesktopOrLess; /// Returns a specific value according to the screen size /// if the device width is higher than or equal to 1200 return /// [desktop] value. if the device width is higher than or equal to 600 /// and less than 1200 return [tablet] value. /// if the device width is less than 300 return [watch] value. /// in other cases return [mobile] value. T responsiveValue({ T? watch, T? mobile, T? tablet, T? desktop, }) { assert( watch != null || mobile != null || tablet != null || desktop != null); var deviceWidth = mediaQuerySize.width; //big screen width can display smaller sizes final strictValues = [ if (deviceWidth >= 1200) desktop, //desktop is allowed if (deviceWidth >= 600) tablet, //tablet is allowed if (deviceWidth >= 300) mobile, //mobile is allowed watch, //watch is allowed ].whereType(); final looseValues = [ watch, mobile, tablet, desktop, ].whereType(); return strictValues.firstOrNull ?? looseValues.first; } } extension IterableExt on Iterable { /// The first element, or `null` if the iterable is empty. T? get firstOrNull { var iterator = this.iterator; if (iterator.moveNext()) return iterator.current; return null; } } ================================================ FILE: lib/get_utils/src/extensions/double_extensions.dart ================================================ import 'dart:math'; extension DoubleExt on double { double toPrecision(int fractionDigits) { var mod = pow(10, fractionDigits.toDouble()).toDouble(); return ((this * mod).round().toDouble() / mod); } Duration get milliseconds => Duration(microseconds: (this * 1000).round()); Duration get ms => milliseconds; Duration get seconds => Duration(milliseconds: (this * 1000).round()); Duration get minutes => Duration(seconds: (this * Duration.secondsPerMinute).round()); Duration get hours => Duration(minutes: (this * Duration.minutesPerHour).round()); Duration get days => Duration(hours: (this * Duration.hoursPerDay).round()); } ================================================ FILE: lib/get_utils/src/extensions/duration_extensions.dart ================================================ import 'dart:async'; /// Duration utilities. extension GetDurationUtils on Duration { /// Utility to delay some callback (or code execution). /// /// Sample: /// ``` /// void main() async { /// final _delay = 3.seconds; /// print('+ wait $_delay'); /// await _delay.delay(); /// print('- finish wait $_delay'); /// print('+ callback in 700ms'); /// await 0.7.seconds.delay(() { /// } ///``` Future delay([FutureOr Function()? callback]) async => Future.delayed(this, callback); } ================================================ FILE: lib/get_utils/src/extensions/dynamic_extensions.dart ================================================ import '../get_utils/get_utils.dart'; extension GetDynamicUtils on dynamic { bool? get isBlank => GetUtils.isBlank(this); void printError( {String info = '', Function logFunction = GetUtils.printFunction}) => // ignore: unnecessary_this logFunction('Error: ${this.runtimeType}', this, info, isError: true); void printInfo( {String info = '', Function printFunction = GetUtils.printFunction}) => // ignore: unnecessary_this printFunction('Info: ${this.runtimeType}', this, info); } ================================================ FILE: lib/get_utils/src/extensions/event_loop_extensions.dart ================================================ import 'dart:async'; import '../../../get_core/src/get_interface.dart'; extension LoopEventsExt on GetInterface { Future toEnd(FutureOr Function() computation) async { await Future.delayed(Duration.zero); final val = computation(); return val; } FutureOr asap(T Function() computation, {bool Function()? condition}) async { T val; if (condition == null || !condition()) { await Future.delayed(Duration.zero); val = computation(); } else { val = computation(); } return val; } } ================================================ FILE: lib/get_utils/src/extensions/export.dart ================================================ export 'context_extensions.dart'; export 'double_extensions.dart'; export 'duration_extensions.dart'; export 'dynamic_extensions.dart'; export 'event_loop_extensions.dart'; export 'int_extensions.dart'; export 'internacionalization.dart' hide FirstWhereExt; export 'iterable_extensions.dart'; export 'num_extensions.dart'; export 'string_extensions.dart'; export 'widget_extensions.dart'; ================================================ FILE: lib/get_utils/src/extensions/int_extensions.dart ================================================ extension DurationExt on int { Duration get seconds => Duration(seconds: this); Duration get days => Duration(days: this); Duration get hours => Duration(hours: this); Duration get minutes => Duration(minutes: this); Duration get milliseconds => Duration(milliseconds: this); Duration get microseconds => Duration(microseconds: this); Duration get ms => milliseconds; } ================================================ FILE: lib/get_utils/src/extensions/internacionalization.dart ================================================ import 'dart:ui'; import '../../../get_core/get_core.dart'; class _IntlHost { Locale? locale; Locale? fallbackLocale; Map> translations = {}; } extension FirstWhereExt on List { /// The first element satisfying [test], or `null` if there are none. T? firstWhereOrNull(bool Function(T element) test) { for (var element in this) { if (test(element)) return element; } return null; } } extension LocalesIntl on GetInterface { static final _intlHost = _IntlHost(); Locale? get locale => _intlHost.locale; Locale? get fallbackLocale => _intlHost.fallbackLocale; set locale(Locale? newLocale) => _intlHost.locale = newLocale; set fallbackLocale(Locale? newLocale) => _intlHost.fallbackLocale = newLocale; Map> get translations => _intlHost.translations; void addTranslations(Map> tr) { translations.addAll(tr); } void clearTranslations() { translations.clear(); } void appendTranslations(Map> tr) { tr.forEach((key, map) { if (translations.containsKey(key)) { translations[key]!.addAll(map); } else { translations[key] = map; } }); } } extension Trans on String { // Checks whether the language code and country code are present, and // whether the key is also present. bool get _fullLocaleAndKey { return Get.translations.containsKey( "${Get.locale!.languageCode}_${Get.locale!.countryCode}") && Get.translations[ "${Get.locale!.languageCode}_${Get.locale!.countryCode}"]! .containsKey(this); } // Checks if there is a callback language in the absence of the specific // country, and if it contains that key. Map? get _getSimilarLanguageTranslation { final translationsWithNoCountry = Get.translations .map((key, value) => MapEntry(key.split("_").first, value)); final containsKey = translationsWithNoCountry .containsKey(Get.locale!.languageCode.split("_").first); if (!containsKey) { return null; } return translationsWithNoCountry[Get.locale!.languageCode.split("_").first]; } String get tr { // print('language'); // print(Get.locale!.languageCode); // print('contains'); // print(Get.translations.containsKey(Get.locale!.languageCode)); // print(Get.translations.keys); // Returns the key if locale is null. if (Get.locale?.languageCode == null) return this; if (_fullLocaleAndKey) { return Get.translations[ "${Get.locale!.languageCode}_${Get.locale!.countryCode}"]![this]!; } final similarTranslation = _getSimilarLanguageTranslation; if (similarTranslation != null && similarTranslation.containsKey(this)) { return similarTranslation[this]!; // If there is no corresponding language or corresponding key, return // the key. } else if (Get.fallbackLocale != null) { final fallback = Get.fallbackLocale!; final key = "${fallback.languageCode}_${fallback.countryCode}"; if (Get.translations.containsKey(key) && Get.translations[key]!.containsKey(this)) { return Get.translations[key]![this]!; } if (Get.translations.containsKey(fallback.languageCode) && Get.translations[fallback.languageCode]!.containsKey(this)) { return Get.translations[fallback.languageCode]![this]!; } return this; } else { return this; } } String trArgs([List args = const []]) { var key = tr; if (args.isNotEmpty) { for (final arg in args) { key = key.replaceFirst(RegExp(r'%s'), arg.toString()); } } return key; } String trPlural([String? pluralKey, int? i, List args = const []]) { return i == 1 ? trArgs(args) : pluralKey!.trArgs(args); } String trParams([Map params = const {}]) { var trans = tr; if (params.isNotEmpty) { params.forEach((key, value) { trans = trans.replaceAll('@$key', value); }); } return trans; } String trPluralParams( [String? pluralKey, int? i, Map params = const {}]) { return i == 1 ? trParams(params) : pluralKey!.trParams(params); } } ================================================ FILE: lib/get_utils/src/extensions/iterable_extensions.dart ================================================ extension IterableExtensions on Iterable { Iterable mapMany( Iterable? Function(T item) selector) sync* { for (var item in this) { final res = selector(item); if (res != null) yield* res; } } } ================================================ FILE: lib/get_utils/src/extensions/num_extensions.dart ================================================ import 'dart:async'; import '../get_utils/get_utils.dart'; extension GetNumUtils on num { bool isLowerThan(num b) => GetUtils.isLowerThan(this, b); bool isGreaterThan(num b) => GetUtils.isGreaterThan(this, b); bool isEqual(num b) => GetUtils.isEqual(this, b); /// Utility to delay some callback (or code execution). /// TODO: Add a separated implementation of delay() with the ability /// to stop it. /// /// Sample: /// ``` /// void main() async { /// print('+ wait for 2 seconds'); /// await 2.delay(); /// print('- 2 seconds completed'); /// print('+ callback in 1.2sec'); /// 1.delay(() => print('- 1.2sec callback called')); /// print('currently running callback 1.2sec'); /// } ///``` Future delay([FutureOr Function()? callback]) async => Future.delayed( Duration(milliseconds: (this * 1000).round()), callback, ); } ================================================ FILE: lib/get_utils/src/extensions/string_extensions.dart ================================================ import '../get_utils/get_utils.dart'; extension GetStringUtils on String { /// Discover if the String is a valid number bool get isNum => GetUtils.isNum(this); /// Discover if the String is numeric only bool get isNumericOnly => GetUtils.isNumericOnly(this); String numericOnly({bool firstWordOnly = false}) => GetUtils.numericOnly(this, firstWordOnly: firstWordOnly); /// Discover if the String is alphanumeric only bool get isAlphabetOnly => GetUtils.isAlphabetOnly(this); /// Discover if the String is a boolean bool get isBool => GetUtils.isBool(this); /// Discover if the String is a vector bool get isVectorFileName => GetUtils.isVector(this); /// Discover if the String is a ImageFileName bool get isImageFileName => GetUtils.isImage(this); /// Discover if the String is a AudioFileName bool get isAudioFileName => GetUtils.isAudio(this); /// Discover if the String is a VideoFileName bool get isVideoFileName => GetUtils.isVideo(this); /// Discover if the String is a TxtFileName bool get isTxtFileName => GetUtils.isTxt(this); /// Discover if the String is a Document Word bool get isDocumentFileName => GetUtils.isWord(this); /// Discover if the String is a Document Excel bool get isExcelFileName => GetUtils.isExcel(this); /// Discover if the String is a Document Powerpoint bool get isPPTFileName => GetUtils.isPPT(this); /// Discover if the String is a APK File bool get isAPKFileName => GetUtils.isAPK(this); /// Discover if the String is a PDF file bool get isPDFFileName => GetUtils.isPDF(this); /// Discover if the String is a HTML file bool get isHTMLFileName => GetUtils.isHTML(this); /// Discover if the String is a URL file bool get isURL => GetUtils.isURL(this); /// Discover if the String is a Email bool get isEmail => GetUtils.isEmail(this); /// Discover if the String is a Phone Number bool get isPhoneNumber => GetUtils.isPhoneNumber(this); /// Discover if the String is a DateTime bool get isDateTime => GetUtils.isDateTime(this); /// Discover if the String is a MD5 Hash bool get isMD5 => GetUtils.isMD5(this); /// Discover if the String is a SHA1 Hash bool get isSHA1 => GetUtils.isSHA1(this); /// Discover if the String is a SHA256 Hash bool get isSHA256 => GetUtils.isSHA256(this); /// Discover if the String is a binary value bool get isBinary => GetUtils.isBinary(this); /// Discover if the String is a ipv4 bool get isIPv4 => GetUtils.isIPv4(this); bool get isIPv6 => GetUtils.isIPv6(this); /// Discover if the String is a Hexadecimal bool get isHexadecimal => GetUtils.isHexadecimal(this); /// Discover if the String is a palindrome bool get isPalindrome => GetUtils.isPalindrome(this); /// Discover if the String is a passport number bool get isPassport => GetUtils.isPassport(this); /// Discover if the String is a currency bool get isCurrency => GetUtils.isCurrency(this); /// Discover if the String is a CPF number bool get isCpf => GetUtils.isCpf(this); /// Discover if the String is a CNPJ number bool get isCnpj => GetUtils.isCnpj(this); /// Discover if the String is a case insensitive bool isCaseInsensitiveContains(String b) => GetUtils.isCaseInsensitiveContains(this, b); /// Discover if the String is a case sensitive and contains any value bool isCaseInsensitiveContainsAny(String b) => GetUtils.isCaseInsensitiveContainsAny(this, b); /// capitalize the String String get capitalize => GetUtils.capitalize(this); /// Capitalize the first letter of the String String get capitalizeFirst => GetUtils.capitalizeFirst(this); /// remove all whitespace from the String String get removeAllWhitespace => GetUtils.removeAllWhitespace(this); /// converter the String String? get camelCase => GetUtils.camelCase(this); /// Discover if the String is a valid URL String? get paramCase => GetUtils.paramCase(this); /// add segments to the String String createPath([Iterable? segments]) { final path = startsWith('/') ? this : '/$this'; return GetUtils.createPath(path, segments); } /// capitalize only first letter in String words to upper case String capitalizeAllWordsFirstLetter() => GetUtils.capitalizeAllWordsFirstLetter(this); } ================================================ FILE: lib/get_utils/src/extensions/widget_extensions.dart ================================================ import 'package:flutter/widgets.dart'; /// add Padding Property to widget extension WidgetPaddingX on Widget { Widget paddingAll(double padding) => Padding(padding: EdgeInsets.all(padding), child: this); Widget paddingSymmetric({double horizontal = 0.0, double vertical = 0.0}) => Padding( padding: EdgeInsets.symmetric(horizontal: horizontal, vertical: vertical), child: this); Widget paddingOnly({ double left = 0.0, double top = 0.0, double right = 0.0, double bottom = 0.0, }) => Padding( padding: EdgeInsets.only( top: top, left: left, right: right, bottom: bottom), child: this); Widget get paddingZero => Padding(padding: EdgeInsets.zero, child: this); } /// Add margin property to widget extension WidgetMarginX on Widget { Widget marginAll(double margin) => Container(margin: EdgeInsets.all(margin), child: this); Widget marginSymmetric({double horizontal = 0.0, double vertical = 0.0}) => Container( margin: EdgeInsets.symmetric(horizontal: horizontal, vertical: vertical), child: this); Widget marginOnly({ double left = 0.0, double top = 0.0, double right = 0.0, double bottom = 0.0, }) => Container( margin: EdgeInsets.only( top: top, left: left, right: right, bottom: bottom), child: this); Widget get marginZero => Container(margin: EdgeInsets.zero, child: this); } /// Allows you to insert widgets inside a CustomScrollView extension WidgetSliverBoxX on Widget { Widget get sliverBox => SliverToBoxAdapter(child: this); } ================================================ FILE: lib/get_utils/src/get_utils/get_utils.dart ================================================ import '../../../get_core/get_core.dart'; /// Returns whether a dynamic value PROBABLY /// has the isEmpty getter/method by checking /// standard dart types that contains it. /// /// This is here to for the 'DRY' bool? _isEmpty(dynamic value) { if (value is String) { return value.toString().trim().isEmpty; } if (value is Iterable || value is Map) { return value.isEmpty as bool?; } return false; } /// Returns whether a dynamic value PROBABLY /// has the length getter/method by checking /// standard dart types that contains it. /// /// This is here to for the 'DRY' bool _hasLength(dynamic value) { return value is Iterable || value is String || value is Map; } /// Obtains a length of a dynamic value /// by previously validating it's type /// /// Note: if [value] is double/int /// it will be taking the .toString /// length of the given value. /// /// Note 2: **this may return null!** /// /// Note 3: null [value] returns null. int? _obtainDynamicLength(dynamic value) { if (value == null) { // ignore: avoid_returning_null return null; } if (_hasLength(value)) { return value.length as int?; } if (value is int) { return value.toString().length; } if (value is double) { return value.toString().replaceAll('.', '').length; } // ignore: avoid_returning_null return null; } class GetUtils { GetUtils._(); /// Checks if data is null. static bool isNull(dynamic value) => value == null; /// In dart2js (in flutter v1.17) a var by default is undefined. /// *Use this only if you are in version <- 1.17*. /// So we assure the null type in json conversions to avoid the /// "value":value==null?null:value; someVar.nil will force the null type /// if the var is null or undefined. /// `nil` taken from ObjC just to have a shorter syntax. static dynamic nil(dynamic s) => s; /// Checks if data is null or blank (empty or only contains whitespace). static bool? isNullOrBlank(dynamic value) { if (isNull(value)) { return true; } // Pretty sure that isNullOrBlank should't be validating // iterables... but I'm going to keep this for compatibility. return _isEmpty(value); } /// Checks if data is null or blank (empty or only contains whitespace). static bool? isBlank(dynamic value) { return _isEmpty(value); } /// Checks if string is int or double. static bool isNum(String value) { if (isNull(value)) { return false; } return num.tryParse(value) is num; } /// Checks if string consist only numeric. /// Numeric only doesn't accepting "." which double data type have static bool isNumericOnly(String s) => hasMatch(s, r'^\d+$'); /// Checks if string consist only Alphabet. (No Whitespace) static bool isAlphabetOnly(String s) => hasMatch(s, r'^[a-zA-Z]+$'); /// Checks if string contains at least one Capital Letter static bool hasCapitalLetter(String s) => hasMatch(s, r'[A-Z]'); /// Checks if string is boolean. static bool isBool(String value) { if (isNull(value)) { return false; } return (value == 'true' || value == 'false'); } /// Checks if string is an video file. static bool isVideo(String filePath) { var ext = filePath.toLowerCase(); return ext.endsWith(".mp4") || ext.endsWith(".avi") || ext.endsWith(".wmv") || ext.endsWith(".rmvb") || ext.endsWith(".mpg") || ext.endsWith(".mpeg") || ext.endsWith(".3gp"); } /// Checks if string is an image file. static bool isImage(String filePath) { final ext = filePath.toLowerCase(); return ext.endsWith(".jpg") || ext.endsWith(".jpeg") || ext.endsWith(".png") || ext.endsWith(".gif") || ext.endsWith(".bmp"); } /// Checks if string is an audio file. static bool isAudio(String filePath) { final ext = filePath.toLowerCase(); return ext.endsWith(".mp3") || ext.endsWith(".wav") || ext.endsWith(".wma") || ext.endsWith(".amr") || ext.endsWith(".ogg"); } /// Checks if string is an powerpoint file. static bool isPPT(String filePath) { final ext = filePath.toLowerCase(); return ext.endsWith(".ppt") || ext.endsWith(".pptx"); } /// Checks if string is an word file. static bool isWord(String filePath) { final ext = filePath.toLowerCase(); return ext.endsWith(".doc") || ext.endsWith(".docx"); } /// Checks if string is an excel file. static bool isExcel(String filePath) { final ext = filePath.toLowerCase(); return ext.endsWith(".xls") || ext.endsWith(".xlsx"); } /// Checks if string is an apk file. static bool isAPK(String filePath) { return filePath.toLowerCase().endsWith(".apk"); } /// Checks if string is an pdf file. static bool isPDF(String filePath) { return filePath.toLowerCase().endsWith(".pdf"); } /// Checks if string is an txt file. static bool isTxt(String filePath) { return filePath.toLowerCase().endsWith(".txt"); } /// Checks if string is an chm file. static bool isChm(String filePath) { return filePath.toLowerCase().endsWith(".chm"); } /// Checks if string is a vector file. static bool isVector(String filePath) { return filePath.toLowerCase().endsWith(".svg"); } /// Checks if string is an html file. static bool isHTML(String filePath) { return filePath.toLowerCase().endsWith(".html"); } /// Checks if string is a valid username. static bool isUsername(String s) => hasMatch(s, r'^[a-zA-Z0-9][a-zA-Z0-9_.]+[a-zA-Z0-9]$'); /// Checks if string is URL. static bool isURL(String s) => hasMatch(s, r"^((((H|h)(T|t)|(F|f))(T|t)(P|p)((S|s)?))\://)?(www.|[a-zA-Z0-9].)[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,7}(\:[0-9]{1,5})*(/($|[a-zA-Z0-9\.\,\;\?\'\\\+&%\$#\=~_\-]+))*$"); /// Checks if string is email. static bool isEmail(String s) => hasMatch(s, r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$'); /// Checks if string is phone number. static bool isPhoneNumber(String s) { if (s.length > 16 || s.length < 9) return false; return hasMatch(s, r'^[+]*[(]{0,1}[0-9]{1,4}[)]{0,1}[-\s\./0-9]*$'); } /// Checks if string is DateTime (UTC or Iso8601). static bool isDateTime(String s) => hasMatch(s, r'^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}:\d{2}.\d{3}Z?$'); /// Checks if string is MD5 hash. static bool isMD5(String s) => hasMatch(s, r'^[a-f0-9]{32}$'); /// Checks if string is SHA1 hash. static bool isSHA1(String s) => hasMatch(s, r'(([A-Fa-f0-9]{2}\:){19}[A-Fa-f0-9]{2}|[A-Fa-f0-9]{40})'); /// Checks if string is SHA256 hash. static bool isSHA256(String s) => hasMatch(s, r'([A-Fa-f0-9]{2}\:){31}[A-Fa-f0-9]{2}|[A-Fa-f0-9]{64}'); /// Checks if string is SSN (Social Security Number). static bool isSSN(String s) => hasMatch(s, r'^(?!0{3}|6{3}|9[0-9]{2})[0-9]{3}-?(?!0{2})[0-9]{2}-?(?!0{4})[0-9]{4}$'); /// Checks if string is binary. static bool isBinary(String s) => hasMatch(s, r'^[0-1]+$'); /// Checks if string is IPv4. static bool isIPv4(String s) => hasMatch(s, r'^(?:(?:^|\.)(?:2(?:5[0-5]|[0-4]\d)|1?\d?\d)){4}$'); /// Checks if string is IPv6. static bool isIPv6(String s) => hasMatch(s, r'^((([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(([0-9A-Fa-f]{1,4}:){0,5}:((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})|(::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){1,7}:))$'); /// Checks if string is hexadecimal. /// Example: HexColor => #12F static bool isHexadecimal(String s) => hasMatch(s, r'^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$'); /// Checks if string is Palindrome. static bool isPalindrome(String string) { final cleanString = string .toLowerCase() .replaceAll(RegExp(r"\s+"), '') .replaceAll(RegExp(r"[^0-9a-zA-Z]+"), ""); for (var i = 0; i < cleanString.length; i++) { if (cleanString[i] != cleanString[cleanString.length - i - 1]) { return false; } } return true; } /// Checks if all data have same value. /// Example: 111111 -> true, wwwww -> true, 1,1,1,1 -> true static bool isOneAKind(dynamic value) { if ((value is String || value is List) && !isNullOrBlank(value)!) { final first = value[0]; final len = value.length as num; for (var i = 0; i < len; i++) { if (value[i] != first) { return false; } } return true; } if (value is int) { final stringValue = value.toString(); final first = stringValue[0]; for (var i = 0; i < stringValue.length; i++) { if (stringValue[i] != first) { return false; } } return true; } return false; } /// Checks if string is Passport No. static bool isPassport(String s) => hasMatch(s, r'^(?!^0+$)[a-zA-Z0-9]{6,9}$'); /// Checks if string is Currency. static bool isCurrency(String s) => hasMatch(s, r'^(S?\$|\₩|Rp|\¥|\€|\₹|\₽|fr|R\$|R)?[ ]?[-]?([0-9]{1,3}[,.]([0-9]{3}[,.])*[0-9]{3}|[0-9]+)([,.][0-9]{1,2})?( ?(USD?|AUD|NZD|CAD|CHF|GBP|CNY|EUR|JPY|IDR|MXN|NOK|KRW|TRY|INR|RUB|BRL|ZAR|SGD|MYR))?$'); /// Checks if length of data is GREATER than maxLength. static bool isLengthGreaterThan(dynamic value, int maxLength) { final length = _obtainDynamicLength(value); if (length == null) { return false; } return length > maxLength; } /// Checks if length of data is GREATER OR EQUAL to maxLength. static bool isLengthGreaterOrEqual(dynamic value, int maxLength) { final length = _obtainDynamicLength(value); if (length == null) { return false; } return length >= maxLength; } /// Checks if length of data is LESS than maxLength. static bool isLengthLessThan(dynamic value, int maxLength) { final length = _obtainDynamicLength(value); if (length == null) { return false; } return length < maxLength; } /// Checks if length of data is LESS OR EQUAL to maxLength. static bool isLengthLessOrEqual(dynamic value, int maxLength) { final length = _obtainDynamicLength(value); if (length == null) { return false; } return length <= maxLength; } /// Checks if length of data is EQUAL to maxLength. static bool isLengthEqualTo(dynamic value, int otherLength) { final length = _obtainDynamicLength(value); if (length == null) { return false; } return length == otherLength; } /// Checks if length of data is BETWEEN minLength to maxLength. static bool isLengthBetween(dynamic value, int minLength, int maxLength) { if (isNull(value)) { return false; } return isLengthGreaterOrEqual(value, minLength) && isLengthLessOrEqual(value, maxLength); } /// Checks if a contains b (Treating or interpreting upper- and lowercase /// letters as being the same). static bool isCaseInsensitiveContains(String a, String b) { return a.toLowerCase().contains(b.toLowerCase()); } /// Checks if a contains b or b contains a (Treating or /// interpreting upper- and lowercase letters as being the same). static bool isCaseInsensitiveContainsAny(String a, String b) { final lowA = a.toLowerCase(); final lowB = b.toLowerCase(); return lowA.contains(lowB) || lowB.contains(lowA); } /// Checks if num a LOWER than num b. static bool isLowerThan(num a, num b) => a < b; /// Checks if num a GREATER than num b. static bool isGreaterThan(num a, num b) => a > b; /// Checks if num a EQUAL than num b. static bool isEqual(num a, num b) => a == b; // Checks if num is a cnpj static bool isCnpj(String cnpj) { // Get only the numbers from the CNPJ final numbers = cnpj.replaceAll(RegExp(r'[^0-9]'), ''); // Test if the CNPJ has 14 digits if (numbers.length != 14) { return false; } // Test if all digits of the CNPJ are the same if (RegExp(r'^(\d)\1*$').hasMatch(numbers)) { return false; } // Divide digits final digits = numbers.split('').map(int.parse).toList(); // Calculate the first check digit var calcDv1 = 0; var j = 0; for (var i in Iterable.generate(12, (i) => i < 4 ? 5 - i : 13 - i)) { calcDv1 += digits[j++] * i; } calcDv1 %= 11; final dv1 = calcDv1 < 2 ? 0 : 11 - calcDv1; // Test the first check digit if (digits[12] != dv1) { return false; } // Calculate the second check digit var calcDv2 = 0; j = 0; for (var i in Iterable.generate(13, (i) => i < 5 ? 6 - i : 14 - i)) { calcDv2 += digits[j++] * i; } calcDv2 %= 11; final dv2 = calcDv2 < 2 ? 0 : 11 - calcDv2; // Test the second check digit if (digits[13] != dv2) { return false; } return true; } /// Checks if the cpf is valid. static bool isCpf(String cpf) { // if (cpf == null) { // return false; // } // get only the numbers final numbers = cpf.replaceAll(RegExp(r'[^0-9]'), ''); // Test if the CPF has 11 digits if (numbers.length != 11) { return false; } // Test if all CPF digits are the same if (RegExp(r'^(\d)\1*$').hasMatch(numbers)) { return false; } // split the digits final digits = numbers.split('').map(int.parse).toList(); // Calculate the first verifier digit var calcDv1 = 0; for (var i in Iterable.generate(9, (i) => 10 - i)) { calcDv1 += digits[10 - i] * i; } calcDv1 %= 11; final dv1 = calcDv1 < 2 ? 0 : 11 - calcDv1; // Tests the first verifier digit if (digits[9] != dv1) { return false; } // Calculate the second verifier digit var calcDv2 = 0; for (var i in Iterable.generate(10, (i) => 11 - i)) { calcDv2 += digits[11 - i] * i; } calcDv2 %= 11; final dv2 = calcDv2 < 2 ? 0 : 11 - calcDv2; // Test the second verifier digit if (digits[10] != dv2) { return false; } return true; } /// Capitalize each word inside string /// Example: your name => Your Name, your name => Your name static String capitalize(String value) { if (isBlank(value)!) return value; return value.split(' ').map(capitalizeFirst).join(' '); } /// Uppercase first letter inside string and let the others lowercase /// Example: your name => Your name static String capitalizeFirst(String s) { if (isBlank(s)!) return s; return s[0].toUpperCase() + s.substring(1).toLowerCase(); } /// Remove all whitespace inside string /// Example: your name => yourname static String removeAllWhitespace(String value) { return value.replaceAll(' ', ''); } /// camelCase string /// Example: your name => yourName static String? camelCase(String value) { if (isNullOrBlank(value)!) { return null; } final separatedWords = value.split(RegExp(r'[!@#<>?":`~;[\]\\|=+)(*&^%-\s_]+')); var newString = ''; for (final word in separatedWords) { newString += word[0].toUpperCase() + word.substring(1).toLowerCase(); } return newString[0].toLowerCase() + newString.substring(1); } /// credits to "ReCase" package. static final RegExp _upperAlphaRegex = RegExp(r'[A-Z]'); static final _symbolSet = {' ', '.', '/', '_', '\\', '-'}; static List _groupIntoWords(String text) { var sb = StringBuffer(); var words = []; var isAllCaps = text.toUpperCase() == text; for (var i = 0; i < text.length; i++) { var char = text[i]; var nextChar = i + 1 == text.length ? null : text[i + 1]; if (_symbolSet.contains(char)) { continue; } sb.write(char); var isEndOfWord = nextChar == null || (_upperAlphaRegex.hasMatch(nextChar) && !isAllCaps) || _symbolSet.contains(nextChar); if (isEndOfWord) { words.add('$sb'); sb.clear(); } } return words; } /// snake_case static String? snakeCase(String? text, {String separator = '_'}) { if (isNullOrBlank(text)!) { return null; } return _groupIntoWords(text!) .map((word) => word.toLowerCase()) .join(separator); } /// param-case static String? paramCase(String? text) => snakeCase(text, separator: '-'); /// Extract numeric value of string /// Example: OTP 12312 27/04/2020 => 1231227042020ß /// If firstWordOnly is true, then the example return is "12312" /// (first found numeric word) static String numericOnly(String s, {bool firstWordOnly = false}) { var numericOnlyStr = ''; for (var i = 0; i < s.length; i++) { if (isNumericOnly(s[i])) { numericOnlyStr += s[i]; } if (firstWordOnly && numericOnlyStr.isNotEmpty && s[i] == " ") { break; } } return numericOnlyStr; } /// Capitalize only the first letter of each word in a string /// Example: getx will make it easy => Getx Will Make It Easy /// Example 2 : this is an example text => This Is An Example Text static String capitalizeAllWordsFirstLetter(String s) { String lowerCasedString = s.toLowerCase(); String stringWithoutExtraSpaces = lowerCasedString.trim(); if (stringWithoutExtraSpaces.isEmpty) { return ""; } if (stringWithoutExtraSpaces.length == 1) { return stringWithoutExtraSpaces.toUpperCase(); } List stringWordsList = stringWithoutExtraSpaces.split(" "); List capitalizedWordsFirstLetter = stringWordsList .map( (word) { if (word.trim().isEmpty) return ""; return word.trim(); }, ) .where( (word) => word != "", ) .map( (word) { if (word.startsWith(RegExp(r'[\n\t\r]'))) { return word; } return word[0].toUpperCase() + word.substring(1).toLowerCase(); }, ) .toList(); String finalResult = capitalizedWordsFirstLetter.join(" "); return finalResult; } static bool hasMatch(String? value, String pattern) { return (value == null) ? false : RegExp(pattern).hasMatch(value); } static String createPath(String path, [Iterable? segments]) { if (segments == null || segments.isEmpty) { return path; } final list = segments.map((e) => '/$e'); return path + list.join(); } static void printFunction( String prefix, dynamic value, String info, { bool isError = false, }) { Get.log('$prefix $value $info'.trim(), isError: isError); } } typedef PrintFunctionCallback = void Function( String prefix, dynamic value, String info, { bool? isError, }); ================================================ FILE: lib/get_utils/src/platform/platform.dart ================================================ import 'platform_stub.dart' if (dart.library.js_interop) 'platform_web.dart' if (dart.library.io) 'platform_io.dart'; // ignore: avoid_classes_with_only_static_members class GetPlatform { static bool get isWeb => GeneralPlatform.isWeb; static bool get isMacOS => GeneralPlatform.isMacOS; static bool get isWindows => GeneralPlatform.isWindows; static bool get isLinux => GeneralPlatform.isLinux; static bool get isAndroid => GeneralPlatform.isAndroid; static bool get isIOS => GeneralPlatform.isIOS; static bool get isFuchsia => GeneralPlatform.isFuchsia; static bool get isMobile => GetPlatform.isIOS || GetPlatform.isAndroid; static bool get isDesktop => GetPlatform.isMacOS || GetPlatform.isWindows || GetPlatform.isLinux; } ================================================ FILE: lib/get_utils/src/platform/platform_io.dart ================================================ import 'dart:io'; // ignore: avoid_classes_with_only_static_members class GeneralPlatform { static bool get isWeb => false; static bool get isMacOS => Platform.isMacOS; static bool get isWindows => Platform.isWindows; static bool get isLinux => Platform.isLinux; static bool get isAndroid => Platform.isAndroid; static bool get isIOS => Platform.isIOS; static bool get isFuchsia => Platform.isFuchsia; static bool get isDesktop => Platform.isMacOS || Platform.isWindows || Platform.isLinux; } ================================================ FILE: lib/get_utils/src/platform/platform_stub.dart ================================================ class GeneralPlatform { static bool get isWeb => throw UnimplementedError(); static bool get isMacOS => throw UnimplementedError(); static bool get isWindows => throw UnimplementedError(); static bool get isLinux => throw UnimplementedError(); static bool get isAndroid => throw UnimplementedError(); static bool get isIOS => throw UnimplementedError(); static bool get isFuchsia => throw UnimplementedError(); static bool get isDesktop => throw UnimplementedError(); } ================================================ FILE: lib/get_utils/src/platform/platform_web.dart ================================================ import 'package:web/web.dart' as html; import '../../get_utils.dart'; html.Navigator _navigator = html.window.navigator; // ignore: avoid_classes_with_only_static_members class GeneralPlatform { static bool get isWeb => true; static bool get isMacOS => _navigator.appVersion.contains('Mac OS') && !GeneralPlatform.isIOS; static bool get isWindows => _navigator.appVersion.contains('Win'); static bool get isLinux => (_navigator.appVersion.contains('Linux') || _navigator.appVersion.contains('x11')) && !isAndroid; // @check https://developer.chrome.com/multidevice/user-agent static bool get isAndroid => _navigator.appVersion.contains('Android '); static bool get isIOS { // maxTouchPoints is needed to separate iPad iOS13 vs new MacOS return GetUtils.hasMatch(_navigator.platform, r'/iPad|iPhone|iPod/') || (_navigator.platform == 'MacIntel' && _navigator.maxTouchPoints > 1); } static bool get isFuchsia => false; static bool get isDesktop => isMacOS || isWindows || isLinux; } ================================================ FILE: lib/get_utils/src/queue/get_queue.dart ================================================ import 'dart:async'; class GetMicrotask { int _version = 0; int _microtask = 0; int get microtask => _microtask; int get version => _version; void exec(Function callback) { if (_microtask == _version) { _microtask++; scheduleMicrotask(() { _version++; _microtask = _version; callback(); }); } } } class GetQueue { final List<_Item> _queue = []; bool _active = false; Future add(Function job) { var completer = Completer(); _queue.add(_Item(completer, job)); _check(); return completer.future; } void cancelAllJobs() { _queue.clear(); } void _check() async { if (!_active && _queue.isNotEmpty) { _active = true; var item = _queue.removeAt(0); try { item.completer.complete(await item.job()); } on Exception catch (e) { item.completer.completeError(e); } _active = false; _check(); } } } class _Item { final dynamic completer; final dynamic job; _Item(this.completer, this.job); } ================================================ FILE: lib/get_utils/src/widgets/optimized_listview.dart ================================================ import 'package:flutter/material.dart'; class OptimizedListView extends StatelessWidget { final List list; final Axis scrollDirection; final bool reverse; final ScrollController? controller; final bool? primary; final ScrollPhysics? physics; final bool shrinkWrap; final Widget onEmpty; final int length; final Widget Function(BuildContext context, ValueKey key, T item) builder; const OptimizedListView({ super.key, required this.list, required this.builder, this.scrollDirection = Axis.vertical, this.reverse = false, this.controller, this.primary, this.physics, this.onEmpty = const SizedBox.shrink(), this.shrinkWrap = false, }) : length = list.length; @override Widget build(BuildContext context) { if (list.isEmpty) return onEmpty; return CustomScrollView( controller: controller, reverse: reverse, scrollDirection: scrollDirection, primary: primary, physics: physics, shrinkWrap: shrinkWrap, slivers: [ SliverList( delegate: SliverChildBuilderDelegate( (context, i) { final item = list[i]; final key = ValueKey(item); return builder(context, key, item); }, childCount: list.length, addAutomaticKeepAlives: true, findChildIndexCallback: (key) { return list.indexWhere((m) => m == (key as ValueKey).value); }, ), ), ], ); } } ================================================ FILE: lib/instance_manager.dart ================================================ /// Get Instance Manager is a modern and intelligent dependency injector /// that injects and removes dependencies seasonally. library; export 'get_core/get_core.dart'; export 'get_instance/get_instance.dart'; ================================================ FILE: lib/route_manager.dart ================================================ ///Get Navigator allows you to navigate routes, open snackbars, ///dialogs and bottomsheets easily, and without the need for context. library; export 'get_core/get_core.dart'; export 'get_navigation/get_navigation.dart'; ================================================ FILE: lib/src/responsive/size_percent_extension.dart ================================================ import '../../get.dart'; //Converts a double value to a percentage extension PercentSized on double { // height: 50.0.hp = 50% double get hp => (Get.height * (this / 100)); // width: 30.0.hp = 30% double get wp => (Get.width * (this / 100)); } ================================================ FILE: lib/state_manager.dart ================================================ /// Get State Manager is a light, modern and powerful state manager to Flutter library; export 'get_core/get_core.dart'; export 'get_rx/get_rx.dart'; export 'get_state_manager/get_state_manager.dart'; ================================================ FILE: lib/utils.dart ================================================ /// Get utils is a set of tools that allows you to access high-level /// APIs and obtain validation tools for Flutter and GetX library; export 'get_core/get_core.dart'; export 'get_utils/get_utils.dart'; ================================================ FILE: pubspec.yaml ================================================ name: get description: Open screens/snackbars/dialogs without context, manage states and inject dependencies easily with GetX. version: 5.0.0-release-candidate-9.3.2 homepage: https://github.com/jonataslaw/getx environment: sdk: ">=3.4.0 <4.0.0" flutter: ">=3.22.0" dependencies: flutter: sdk: flutter flutter_web_plugins: sdk: flutter web: ">=1.0.0 <2.0.0" dev_dependencies: flutter_lints: ^6.0.0 flutter_test: sdk: flutter ================================================ FILE: test/animations/extensions_test.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:get/get.dart'; void main() { group('Animation Extension', () { Widget buildWidget() { return Container( width: 100, height: 100, color: Colors.red, ); } testWidgets('fadeIn() and fadeOut() can not be used sequentially', (WidgetTester tester) async { final widget = buildWidget(); expect(() => widget.fadeIn().fadeOut(), throwsAssertionError); expect(() => widget.fadeOut().fadeIn(), throwsAssertionError); expect(() => widget.fadeIn(isSequential: true).fadeOut(), throwsAssertionError); expect(() => widget.fadeOut(isSequential: true).fadeIn(), throwsAssertionError); }); testWidgets('can not use delay when isSequential is true', (WidgetTester tester) async { final widget = buildWidget(); expect( () => widget.fadeIn( isSequential: true, delay: const Duration(seconds: 1)), throwsAssertionError); }); testWidgets( 'fadeIn() and fadeOut() can be used together when isSequential is true', (WidgetTester tester) async { final widget = buildWidget(); expect( () => widget.fadeIn(isSequential: true).fadeOut(isSequential: true), isNot(throwsException)); expect(() => widget.fadeIn().fadeOut(isSequential: true), isNot(throwsException)); }); testWidgets('fadeIn() returns a FadeInAnimation', (WidgetTester tester) async { final widget = buildWidget(); const begin = 0.0; const end = 1.0; final animation = widget.fadeIn(); expect(animation, isA()); _testDefaultValues( animation: animation, widget: widget, begin: begin, end: end); }); testWidgets('fadeOut() returns a animation', (WidgetTester tester) async { final widget = buildWidget(); const begin = 1.0; const end = 0.0; final animation = widget.fadeOut(); expect(animation, isA()); _testDefaultValues( animation: animation, widget: widget, begin: begin, end: end); }); testWidgets('rotate() returns a RotateAnimation', (WidgetTester tester) async { const begin = 0.9; const end = 1.1; final widget = buildWidget(); final animation = widget.rotate(begin: begin, end: end); expect(animation, isA()); _testDefaultValues( animation: animation, widget: widget, begin: begin, end: end); }); testWidgets('scale() returns a ScaleAnimation', (WidgetTester tester) async { const begin = 0.9; const end = 1.1; final widget = buildWidget(); final animation = widget.scale(begin: begin, end: end); expect(animation, isA()); _testDefaultValues( animation: animation, widget: widget, begin: begin, end: end); }); testWidgets('slide() returns a SlideAnimation', (WidgetTester tester) async { const begin = 0; const end = 1; final widget = buildWidget(); final animation = widget.slide(offset: (_, __) => const Offset(0, 0)); expect(animation, isA()); _testDefaultValues( animation: animation, widget: widget, begin: begin, end: end); }); testWidgets('bounce() returns a BounceAnimation', (WidgetTester tester) async { const begin = 0.9; const end = 1.1; final widget = buildWidget(); final animation = widget.bounce(begin: begin, end: end); expect(animation, isA()); _testDefaultValues( animation: animation, widget: widget, begin: begin, end: end, curve: Curves.bounceOut, ); }); testWidgets('spin() returns a SpinAnimation', (WidgetTester tester) async { final widget = buildWidget(); const begin = 0.0; const end = 360; final animation = widget.spin(); expect(animation, isA()); _testDefaultValues( animation: animation, widget: widget, begin: begin, end: end); }); testWidgets('size() returns a SizeAnimation', (WidgetTester tester) async { final widget = buildWidget(); const begin = 0.9; const end = 1.1; final animation = widget.size(begin: begin, end: end); expect(animation, isA()); _testDefaultValues( animation: animation, widget: widget, begin: begin, end: end); }); testWidgets('blur() returns a BlurAnimation', (WidgetTester tester) async { final widget = buildWidget(); const begin = 0.9; const end = 1.1; final animation = widget.blur(begin: begin, end: end); expect(animation, isA()); _testDefaultValues( animation: animation, widget: widget, begin: begin, end: end); }); testWidgets('flip() returns a FlipAnimation', (WidgetTester tester) async { final widget = buildWidget(); const begin = 0.9; const end = 1.1; final animation = widget.flip(begin: begin, end: end); expect(animation, isA()); _testDefaultValues( animation: animation, widget: widget, begin: begin, end: end); }); testWidgets('wave() returns a FlipAnimation', (WidgetTester tester) async { final widget = buildWidget(); const begin = 0.9; const end = 1.1; final animation = widget.wave(begin: begin, end: end); expect(animation, isA()); _testDefaultValues( animation: animation, widget: widget, begin: begin, end: end); }); }); } void _testDefaultValues({ required GetAnimatedBuilder animation, required Widget widget, required T begin, required T end, Curve curve = Curves.linear, }) { expect(animation.tween.begin, begin); expect(animation.tween.end, end); if (animation.idleValue is Offset) { expect(animation.idleValue, Offset.zero); } else if (animation is FadeOutAnimation) { expect(animation.idleValue, 1); } else { expect(animation.idleValue, 0); } expect(animation.delay, Duration.zero); expect(animation.child, widget); expect(animation.curve, curve); } ================================================ FILE: test/animations/get_animated_builder_test.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:get/get.dart'; class _Wrapper extends StatelessWidget { const _Wrapper({required this.child}); final Widget child; @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold(body: child), ); } } void main() { testWidgets('GetAnimatedBuilder defaults', (WidgetTester tester) async { await tester.pumpWidget( _Wrapper( child: GetAnimatedBuilder( duration: const Duration(milliseconds: 500), tween: Tween(begin: 0, end: 10), idleValue: 0, builder: (_, value, __) => Text(value.toString()), delay: Duration.zero, child: Container(), ), ), ); // Verify that the widget starts with the idle value. expect(find.text('0'), findsOneWidget); // Wait for the animation to complete. await tester.pumpAndSettle(const Duration(milliseconds: 500)); // Verify that the widget ends with the final value. expect(find.text('10'), findsOneWidget); }); testWidgets('GetAnimatedBuilder changes value over time', (tester) async { await tester.pumpWidget( _Wrapper( child: GetAnimatedBuilder( duration: const Duration(milliseconds: 500), tween: Tween(begin: 0.0, end: 1.0), idleValue: 0.0, builder: (context, value, child) { return Opacity(opacity: value); }, delay: const Duration(milliseconds: 500), child: Container( width: 100, height: 100, color: Colors.red, ), ), ), ); // Initial state is idleValue expect(tester.widget(find.byType(Opacity)).opacity, 0.0); // Wait for the delay to finish await tester.pump(const Duration(milliseconds: 500)); // Verify that the value changes over time await tester.pump(const Duration(milliseconds: 100)); expect(tester.widget(find.byType(Opacity)).opacity, closeTo(0.2, 0.01)); await tester.pump(const Duration(milliseconds: 100)); expect(tester.widget(find.byType(Opacity)).opacity, closeTo(0.4, 0.01)); await tester.pump(const Duration(milliseconds: 100)); expect(tester.widget(find.byType(Opacity)).opacity, closeTo(0.6, 0.01)); await tester.pump(const Duration(milliseconds: 100)); expect(tester.widget(find.byType(Opacity)).opacity, closeTo(0.8, 0.01)); await tester.pump(const Duration(milliseconds: 100)); expect(tester.widget(find.byType(Opacity)).opacity, closeTo(1.0, 0.01)); }); testWidgets('onComplete callback is called when animation finishes', (WidgetTester tester) async { AnimationController? controller; var onCompleteCalled = false; await tester.pumpWidget( _Wrapper( child: GetAnimatedBuilder( duration: const Duration(milliseconds: 500), tween: Tween(begin: 0, end: 10), idleValue: 0, builder: (_, value, __) => Text(value.toString()), delay: Duration.zero, onComplete: (c) { onCompleteCalled = true; controller = c; }, child: Container(), ), ), ); expect(onCompleteCalled, isFalse); // Wait for the animation to complete. await tester.pumpAndSettle(const Duration(milliseconds: 500)); // Verify that the onComplete callback was called. expect(controller, isNotNull); expect(onCompleteCalled, isTrue); }); testWidgets('onStart callback is called when animation starts', (WidgetTester tester) async { var onStartCalled = false; await tester.pumpWidget( _Wrapper( child: GetAnimatedBuilder( duration: const Duration(seconds: 1), delay: Duration.zero, tween: Tween(begin: 0, end: 1), idleValue: 0, builder: (context, value, child) => Container(), child: Container(), onStart: (_) { onStartCalled = true; }, ), ), ); expect(onStartCalled, isFalse); await tester.pump(const Duration(milliseconds: 500)); expect(onStartCalled, isTrue); }); testWidgets('GetAnimatedBuilder delay', (WidgetTester tester) async { await tester.pumpWidget( _Wrapper( child: GetAnimatedBuilder( duration: const Duration(milliseconds: 500), tween: Tween(begin: 0, end: 10), idleValue: 0, builder: (_, value, __) => Text(value.toString()), delay: const Duration(milliseconds: 500), child: Container(), ), ), ); // Verify that the widget starts with the idle value. expect(find.text('0'), findsOneWidget); // Wait for the delay to pass. await tester.pump(const Duration(milliseconds: 500)); // Verify that the animation has started. expect(find.text('0'), findsOneWidget); // Wait for the animation to complete. await tester.pumpAndSettle(const Duration(milliseconds: 500)); // Verify that the widget ends with the final value. expect(find.text('10'), findsOneWidget); }); testWidgets( 'FadeInAnimation in idle should be visible, but not visible when the animation starts', (WidgetTester tester) async { await tester.pumpWidget( _Wrapper( child: FadeInAnimation( delay: const Duration(milliseconds: 500), duration: const Duration(milliseconds: 500), idleValue: 0, child: const Text('Hello'), ), ), ); // in idle, the opacity should be 1.0 expect(tester.widget(find.byType(Opacity)).opacity, 1.0); // Wait for the delay to finish await tester.pump(const Duration(milliseconds: 500)); // When the animation starts the opacity should not be visible expect(tester.widget(find.byType(Opacity)).opacity, 0.0); // Verify that the value changes over time await tester.pump(const Duration(milliseconds: 100)); // The value should be updated expect(tester.widget(find.byType(Opacity)).opacity, closeTo(0.2, 0.01)); await tester.pump(const Duration(milliseconds: 100)); expect(tester.widget(find.byType(Opacity)).opacity, closeTo(0.4, 0.01)); await tester.pump(const Duration(milliseconds: 100)); expect(tester.widget(find.byType(Opacity)).opacity, closeTo(0.6, 0.01)); await tester.pump(const Duration(milliseconds: 100)); expect(tester.widget(find.byType(Opacity)).opacity, closeTo(0.8, 0.01)); await tester.pump(const Duration(milliseconds: 100)); expect(tester.widget(find.byType(Opacity)).opacity, closeTo(1.0, 0.01)); }); testWidgets( 'willResetOnDispose should false when fadeOut is the last animation in a sequential animation', (WidgetTester tester) async { await tester.pumpWidget( _Wrapper( child: const Text('Hello') .fadeIn( isSequential: true, duration: const Duration(milliseconds: 500), ) .fadeOut( isSequential: true, duration: const Duration(milliseconds: 500), ), ), ); // The variable starts as false expect( tester .state(find.byType(FadeOutAnimation)) .willResetOnDispose, false); // Jump to middle of next animation await tester.pump(const Duration(milliseconds: 500)); // The value should be false expect( tester .state(find.byType(FadeOutAnimation)) .willResetOnDispose, false); await tester.pumpAndSettle(); }); testWidgets( 'willResetOnDispose should true when fadeOut is not last animation in a sequential animation', (WidgetTester tester) async { await tester.pumpWidget( _Wrapper( child: const Text('Hello') .fadeOut( isSequential: true, duration: const Duration(milliseconds: 500), ) .fadeIn( isSequential: true, duration: const Duration(milliseconds: 500), ), ), ); // The variable starts as true expect( tester .state(find.byType(FadeOutAnimation)) .willResetOnDispose, true); // Jump to middle of next animation await tester.pump(const Duration(milliseconds: 500)); // The value should be true expect( tester .state(find.byType(FadeOutAnimation)) .willResetOnDispose, true); await tester.pumpAndSettle(); }); testWidgets('RotateAnimation', (WidgetTester tester) async { await tester.pumpWidget( RotateAnimation( duration: const Duration(seconds: 1), delay: Duration.zero, begin: 0.0, end: 360.0, child: Container(), ), ); expect(find.byType(RotateAnimation), findsOneWidget); await tester.pumpAndSettle(); }); testWidgets('ScaleAnimation', (WidgetTester tester) async { await tester.pumpWidget( ScaleAnimation( duration: const Duration(seconds: 1), delay: Duration.zero, begin: 1.0, end: 2.0, child: Container(), ), ); expect(find.byType(ScaleAnimation), findsOneWidget); await tester.pumpAndSettle(); }); testWidgets('WaveAnimation', (WidgetTester tester) async { await tester.pumpWidget( WaveAnimation( duration: const Duration(seconds: 1), delay: Duration.zero, begin: 1.0, end: 2.0, child: Container(), ), ); expect(find.byType(WaveAnimation), findsOneWidget); await tester.pumpAndSettle(); }); testWidgets('WobbleAnimation', (WidgetTester tester) async { await tester.pumpWidget( WobbleAnimation( duration: const Duration(seconds: 1), delay: Duration.zero, begin: 1.0, end: 2.0, child: Container(), ), ); expect(find.byType(WobbleAnimation), findsOneWidget); await tester.pumpAndSettle(); }); testWidgets('SlideAnimation', (WidgetTester tester) async { await tester.pumpWidget( SlideAnimation( offsetBuild: (p0, p1) => const Offset(1.0, 1.0), duration: const Duration(seconds: 1), delay: Duration.zero, begin: 0, end: 1, child: Container(), ), ); expect(find.byType(SlideAnimation), findsOneWidget); await tester.pumpAndSettle(); }); testWidgets('SlideInLeftAnimation', (WidgetTester tester) async { await tester.pumpWidget( _Wrapper( child: SlideInLeftAnimation( duration: const Duration(seconds: 1), delay: Duration.zero, begin: 1.0, end: 2.0, child: Container(), ), ), ); expect(find.byType(SlideInLeftAnimation), findsOneWidget); await tester.pumpAndSettle(); }); testWidgets('SlideInRightAnimation', (WidgetTester tester) async { await tester.pumpWidget( _Wrapper( child: SlideInRightAnimation( duration: const Duration(seconds: 1), delay: Duration.zero, begin: 1.0, end: 2.0, child: Container(), ), ), ); expect(find.byType(SlideInRightAnimation), findsOneWidget); await tester.pumpAndSettle(); }); testWidgets('SlideInUpAnimation', (WidgetTester tester) async { await tester.pumpWidget( _Wrapper( child: SlideInUpAnimation( duration: const Duration(seconds: 1), delay: Duration.zero, begin: 1.0, end: 2.0, child: Container(), ), ), ); expect(find.byType(SlideInUpAnimation), findsOneWidget); await tester.pumpAndSettle(); }); testWidgets('SlideInDownAnimation', (WidgetTester tester) async { await tester.pumpWidget( _Wrapper( child: SlideInDownAnimation( duration: const Duration(seconds: 1), delay: Duration.zero, begin: 1.0, end: 2.0, child: Container(), ), ), ); expect(find.byType(SlideInDownAnimation), findsOneWidget); await tester.pumpAndSettle(); }); testWidgets('BounceAnimation', (WidgetTester tester) async { await tester.pumpWidget( BounceAnimation( duration: const Duration(seconds: 1), delay: Duration.zero, begin: 0.0, end: 1.0, child: Container(), ), ); expect(find.byType(BounceAnimation), findsOneWidget); await tester.pumpAndSettle(); }); testWidgets('SpinAnimation', (WidgetTester tester) async { await tester.pumpWidget( SpinAnimation( duration: const Duration(seconds: 1), delay: Duration.zero, child: Container(), ), ); expect(find.byType(SpinAnimation), findsOneWidget); await tester.pumpAndSettle(); }); testWidgets('ColorAnimation', (WidgetTester tester) async { await tester.pumpWidget( ColorAnimation( duration: const Duration(seconds: 1), delay: Duration.zero, begin: Colors.blue, end: Colors.red, child: Container(), ), ); expect(find.byType(ColorAnimation), findsOneWidget); await tester.pumpAndSettle(); }); testWidgets('SizeAnimation', (WidgetTester tester) async { await tester.pumpWidget( SizeAnimation( duration: const Duration(seconds: 1), delay: Duration.zero, begin: 1.0, end: 2.0, child: Container(), ), ); expect(find.byType(SizeAnimation), findsOneWidget); await tester.pumpAndSettle(); }); testWidgets('BlurAnimation', (WidgetTester tester) async { await tester.pumpWidget( BlurAnimation( duration: const Duration(seconds: 1), delay: Duration.zero, begin: 1.0, end: 2.0, child: Container(), ), ); expect(find.byType(BlurAnimation), findsOneWidget); await tester.pumpAndSettle(); }); testWidgets('FlipAnimation', (WidgetTester tester) async { await tester.pumpWidget( FlipAnimation( duration: const Duration(seconds: 1), delay: Duration.zero, begin: 1.0, end: 2.0, child: Container(), ), ); expect(find.byType(FlipAnimation), findsOneWidget); await tester.pumpAndSettle(); }); } ================================================ FILE: test/benchmarks/benckmark_test.dart ================================================ import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:get/state_manager.dart'; int times = 30; void printValue(String value) { // ignore: avoid_print print(value); } Future valueNotifier() { final c = Completer(); final value = ValueNotifier(0); final timer = Stopwatch(); timer.start(); value.addListener(() { if (times == value.value) { timer.stop(); printValue( """${value.value} listeners notified | [VALUE_NOTIFIER] time: ${timer.elapsedMicroseconds}ms"""); c.complete(timer.elapsedMicroseconds); } }); for (var i = 0; i < times + 1; i++) { value.value = i; } return c.future; } Future getValue() { final c = Completer(); final value = Value(0); final timer = Stopwatch(); timer.start(); value.addListener(() { if (times == value.value) { timer.stop(); printValue( """${value.value} listeners notified | [GETX_VALUE] time: ${timer.elapsedMicroseconds}ms"""); c.complete(timer.elapsedMicroseconds); } }); for (var i = 0; i < times + 1; i++) { value.value = i; } return c.future; } Future stream() { final c = Completer(); final value = StreamController(); final timer = Stopwatch(); timer.start(); value.stream.listen((v) { if (times == v) { timer.stop(); printValue( """$v listeners notified | [STREAM] time: ${timer.elapsedMicroseconds}ms"""); c.complete(timer.elapsedMicroseconds); value.close(); } }); for (var i = 0; i < times + 1; i++) { value.add(i); } return c.future; } // Future getStream() { // final c = Completer(); // final value = GetStream(); // final timer = Stopwatch(); // timer.start(); // value.listen((v) { // if (times == v) { // timer.stop(); // printValue( // """$v listeners notified | // [GET_STREAM] time: ${timer.elapsedMicroseconds}ms"""); // c.complete(timer.elapsedMicroseconds); // } // }); // for (var i = 0; i < times + 1; i++) { // value.add(i); // } // return c.future; // } Future miniStream() { final c = Completer(); final value = MiniStream(); final timer = Stopwatch(); timer.start(); value.listen((v) { if (times == v) { timer.stop(); printValue( """$v listeners notified | [MINI_STREAM] time: ${timer.elapsedMicroseconds}ms"""); c.complete(timer.elapsedMicroseconds); } }); for (var i = 0; i < times + 1; i++) { value.add(i); } return c.future; } void main() { test('percentage test', () { printValue('============================================'); printValue('PERCENTAGE TEST'); const referenceValue = 200; const requestedValue = 100; printValue(''' referenceValue is ${calculePercentage(referenceValue, requestedValue)}% more than requestedValue'''); expect(calculePercentage(referenceValue, requestedValue), 100); }); test('run benchmarks from ValueNotifier', () async { times = 30; printValue('============================================'); printValue('VALUE_NOTIFIER X GETX_VALUE TEST'); printValue('-----------'); await getValue(); await valueNotifier(); printValue('-----------'); times = 30000; final getx = await getValue(); final dart = await valueNotifier(); printValue('-----------'); printValue('ValueNotifier delay $dart ms to made $times requests'); printValue('GetValue delay $getx ms to made $times requests'); printValue('-----------'); printValue(''' GetValue is ${calculePercentage(dart, getx).round()}% faster than Default ValueNotifier with $times requests'''); }); test('run benchmarks from Streams', () async { times = 30; printValue('============================================'); printValue('DART STREAM X GET_STREAM X GET_MINI_STREAM TEST'); printValue('-----------'); // var getx = await getStream(); var mini = await miniStream(); var dart = await stream(); printValue('-----------'); printValue(''' GetStream is ${calculePercentage(dart, mini).round()}% faster than Default Stream with $times requests'''); printValue('-----------'); times = 30000; dart = await stream(); // getx = await getStream(); mini = await miniStream(); times = 60000; dart = await stream(); // getx = await getStream(); mini = await miniStream(); printValue('-----------'); printValue('dart_stream delay $dart ms to made $times requests'); // printValue('getx_stream delay $getx ms to made $times requests'); printValue('getx_mini_stream delay $mini ms to made $times requests'); printValue('-----------'); printValue(''' GetStream is ${calculePercentage(dart, mini).round()}% faster than Default Stream with $times requests'''); }); } int calculePercentage(int dart, int getx) { return (dart / getx * 100).round() - 100; } ================================================ FILE: test/instance/get_instance_test.dart ================================================ // ignore_for_file: avoid_classes_with_only_static_members import 'package:flutter_test/flutter_test.dart'; import 'package:get/get.dart'; import 'util/matcher.dart' as m; class Mock { static Future test() async { await Future.delayed(Duration.zero); return 'test'; } } abstract class MyController with GetLifeCycleMixin {} class DisposableController extends MyController {} // ignore: one_member_abstracts abstract class Service { String post(); } class Api implements Service { @override String post() { return 'test'; } } void main() { TestWidgetsFlutterBinding.ensureInitialized(); test('Get.put test', () async { final instance = Get.put(Controller()); expect(instance, Get.find()); Get.reset(); }); test('Get start and delete called just one time', () async { Get ..put(Controller()) ..put(Controller()); final controller = Get.find(); expect(controller.init, 1); Get ..delete() ..delete(); expect(controller.close, 1); Get.reset(); }); test('Get.put tag test', () async { final instance = Get.put(Controller(), tag: 'one'); final instance2 = Get.put(Controller(), tag: 'two'); expect(instance == instance2, false); expect(Get.find(tag: 'one') == Get.find(tag: 'two'), false); expect(Get.find(tag: 'one') == Get.find(tag: 'one'), true); expect(Get.find(tag: 'two') == Get.find(tag: 'two'), true); Get.reset(); }); test('Get.lazyPut tag test', () async { Get.lazyPut(() => Controller(), tag: 'one'); Get.lazyPut(() => Controller(), tag: 'two'); expect(Get.find(tag: 'one') == Get.find(tag: 'two'), false); expect(Get.find(tag: 'one') == Get.find(tag: 'one'), true); expect(Get.find(tag: 'two') == Get.find(tag: 'two'), true); Get.reset(); }); test('Get.lazyPut test', () async { final controller = Controller(); Get.lazyPut(() => controller); final ct1 = Get.find(); expect(ct1, controller); Get.reset(); }); test('Get.lazyPut fenix test', () async { Get.lazyPut(() => Controller(), fenix: true); Get.find().increment(); expect(Get.find().count, 1); Get.delete(); expect(Get.find().count, 0); Get.reset(); }); test('Get.lazyPut without fenix', () async { Get.lazyPut(() => Controller()); Get.find().increment(); expect(Get.find().count, 1); Get.delete(); expect( () => Get.find(), throwsA(const m.TypeMatcher())); Get.reset(); }); test('Get.reloadInstance test', () async { Get.lazyPut(() => Controller()); var ct1 = Get.find(); ct1.increment(); expect(ct1.count, 1); ct1 = Get.find(); expect(ct1.count, 1); Get.reload(); ct1 = Get.find(); expect(ct1.count, 0); Get.reset(); }); test('GetxService test', () async { Get.lazyPut(() => PermanentService()); var sv1 = Get.find(); var sv2 = Get.find(); expect(sv1, sv2); expect(Get.isRegistered(), true); Get.delete(); expect(Get.isRegistered(), true); Get.delete(force: true); expect(Get.isRegistered(), false); Get.reset(); }); test('Get.lazyPut with abstract class test', () async { final api = Api(); Get.lazyPut(() => api); final ct1 = Get.find(); expect(ct1, api); Get.reset(); }); test('Get.create with abstract class test', () async { Get.spawn(() => Api()); final ct1 = Get.find(); final ct2 = Get.find(); // expect(ct1 is Service, true); // expect(ct2 is Service, true); expect(ct1 == ct2, false); Get.reset(); }); group('test put, delete and check onInit execution', () { tearDownAll(Get.reset); test('Get.put test with init check', () async { final instance = Get.put(DisposableController()); expect(instance, Get.find()); expect(instance.initialized, true); }); test('Get.delete test with disposable controller', () async { // Get.put(DisposableController()); expect(Get.delete(), true); expect(() => Get.find(), throwsA(const m.TypeMatcher())); }); test('Get.put test after delete with disposable controller and init check', () async { final instance = Get.put(DisposableController()); expect(instance, Get.find()); expect(instance.initialized, true); }); }); group('Get.replace test for replacing parent instance that is', () { tearDown(Get.reset); test('temporary', () async { Get.put(DisposableController()); Get.replace(Controller()); final instance = Get.find(); expect(instance is Controller, isTrue); expect((instance as Controller).init, greaterThan(0)); }); test('permanent', () async { Get.put(DisposableController(), permanent: true); Get.replace(Controller()); final instance = Get.find(); expect(instance is Controller, isTrue); expect((instance as Controller).init, greaterThan(0)); }); test('tagged temporary', () async { const tag = 'tag'; Get.put(DisposableController(), tag: tag); Get.replace(Controller(), tag: tag); final instance = Get.find(tag: tag); expect(instance is Controller, isTrue); expect((instance as Controller).init, greaterThan(0)); }); test('tagged permanent', () async { const tag = 'tag'; Get.put(DisposableController(), permanent: true, tag: tag); Get.replace(Controller(), tag: tag); final instance = Get.find(tag: tag); expect(instance is Controller, isTrue); expect((instance as Controller).init, greaterThan(0)); }); test('a generic parent type', () async { const tag = 'tag'; Get.put(DisposableController(), permanent: true, tag: tag); Get.replace(Controller(), tag: tag); final instance = Get.find(tag: tag); expect(instance is Controller, isTrue); expect((instance as Controller).init, greaterThan(0)); }); }); group('Get.lazyReplace replaces parent instance', () { tearDown(Get.reset); test('without fenix', () async { Get.put(DisposableController()); Get.lazyReplace(() => Controller()); final instance = Get.find(); expect(instance, isA()); expect((instance as Controller).init, greaterThan(0)); }); test('with fenix', () async { Get.put(DisposableController()); Get.lazyReplace(() => Controller(), fenix: true); expect(Get.find(), isA()); (Get.find() as Controller).increment(); expect((Get.find() as Controller).count, 1); Get.delete(); expect((Get.find() as Controller).count, 0); }); test('with fenix when parent is permanent', () async { Get.put(DisposableController(), permanent: true); Get.lazyReplace(() => Controller()); final instance = Get.find(); expect(instance, isA()); (instance as Controller).increment(); expect((Get.find() as Controller).count, 1); Get.delete(); expect((Get.find() as Controller).count, 0); }); }); group('Get.findOrNull test', () { tearDown(Get.reset); test('checking results', () async { Get.put(1); int? result = Get.findOrNull(); expect(result, 1); Get.delete(); result = Get.findOrNull(); expect(result, null); }); }); } class PermanentService extends GetxService {} class Controller extends DisposableController { int init = 0; int close = 0; int count = 0; @override void onInit() { init++; super.onInit(); } @override void onClose() { close++; super.onClose(); } void increment() { count++; } } ================================================ FILE: test/instance/util/matcher.dart ================================================ // Copyright 2014, the Dart project authors. All rights reserved. // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following // disclaimer in the documentation and/or other materials provided // with the distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import 'package:flutter_test/flutter_test.dart'; class FunctionMatcher extends CustomMatcher { final Object Function(T value) _feature; FunctionMatcher(String name, this._feature, matcher) : super('`$name`:', '`$name`', matcher); @override Object featureValueOf(covariant T actual) => _feature(actual); } class HavingMatcher implements TypeMatcher { final TypeMatcher _parent; final List> _functionMatchers; HavingMatcher(TypeMatcher parent, String description, Object Function(T) feature, dynamic matcher, [Iterable>? existing]) : _parent = parent, _functionMatchers = [ ...?existing, FunctionMatcher(description, feature, matcher) ]; @override TypeMatcher having( Object Function(T) feature, String description, dynamic matcher) => HavingMatcher(_parent, description, feature, matcher, _functionMatchers); @override bool matches(dynamic item, Map matchState) { for (var matcher in [_parent].followedBy(_functionMatchers)) { if (!matcher.matches(item, matchState)) { addStateInfo(matchState, {'matcher': matcher}); return false; } } return true; } @override Description describeMismatch( dynamic item, Description mismatchDescription, Map matchState, bool verbose, ) { var matcher = matchState['matcher'] as Matcher; matcher.describeMismatch( item, mismatchDescription, matchState['state'] as Map, verbose); return mismatchDescription; } @override Description describe(Description description) => description .add('') .addDescriptionOf(_parent) .add(' with ') .addAll('', ' and ', '', _functionMatchers); } class TypeMatcher extends Matcher { const TypeMatcher(); TypeMatcher having( Object Function(T) feature, String description, dynamic matcher) => HavingMatcher(this, description, feature, matcher); @override Description describe(Description description) { var name = _stripDynamic(T); return description.add(""); } @override bool matches(Object? item, Map matchState) => item is T; @override Description describeMismatch( dynamic item, Description mismatchDescription, Map matchState, bool verbose, ) { var name = _stripDynamic(T); return mismatchDescription.add("is not an instance of '$name'"); } } String _stripDynamic(Type type) => type.toString().replaceAll(_dart2DynamicArgs, ''); final _dart2DynamicArgs = RegExp(''); ================================================ FILE: test/internationalization/internationalization_test.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:get/get.dart'; import '../navigation/utils/wrapper.dart'; void main() { testWidgets("Get.defaultDialog smoke test", (tester) async { await tester.pumpWidget( Wrapper(child: Container()), ); await tester.pumpAndSettle(); expect('covid'.tr, 'Corona Virus'); expect('total_confirmed'.tr, 'Total Confirmed'); expect('total_deaths'.tr, 'Total Deaths'); Get.updateLocale(const Locale('pt', 'BR')); await tester.pumpAndSettle(); expect('covid'.tr, 'Corona Vírus'); expect('total_confirmed'.tr, 'Total confirmado'); expect('total_deaths'.tr, 'Total de mortes'); Get.updateLocale(const Locale('en', 'EN')); await tester.pumpAndSettle(); expect('covid'.tr, 'Corona Virus'); expect('total_confirmed'.tr, 'Total Confirmed'); expect('total_deaths'.tr, 'Total Deaths'); }); } ================================================ FILE: test/navigation/bottomsheet_test.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:get/get.dart'; import 'utils/wrapper.dart'; void main() { testWidgets("Get.bottomSheet smoke test", (tester) async { await tester.pumpWidget( Wrapper(child: Container()), ); await tester.pump(); Get.bottomSheet(Wrap( children: [ ListTile( leading: const Icon(Icons.music_note), title: const Text('Music'), onTap: () {}, ), ], )); await tester.pumpAndSettle(); expect(find.byIcon(Icons.music_note), findsOneWidget); }); testWidgets("Get.bottomSheet close test", (tester) async { await tester.pumpWidget( Wrapper(child: Container()), ); await tester.pump(); Get.bottomSheet(Wrap( children: [ ListTile( leading: const Icon(Icons.music_note), title: const Text('Music'), onTap: () {}, ), ], )); await tester.pumpAndSettle(); expect(Get.isBottomSheetOpen, true); Get.backLegacy(); await tester.pumpAndSettle(); expect(Get.isBottomSheetOpen, false); // expect(() => Get.bottomSheet(Container(), isScrollControlled: null), // throwsAssertionError); // expect(() => Get.bottomSheet(Container(), isDismissible: null), // throwsAssertionError); // expect(() => Get.bottomSheet(Container(), enableDrag: null), // throwsAssertionError); await tester.pumpAndSettle(); }); // testWidgets( // "GetMaterialApp with debugShowMaterialGrid null", // (tester) async { // expect( // () => GetMaterialApp( // debugShowMaterialGrid: null, // ), // throwsAssertionError, // ); // }, // ); } ================================================ FILE: test/navigation/dialog_test.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:get/get.dart'; import 'utils/wrapper.dart'; void main() { testWidgets("Get.defaultDialog smoke test", (tester) async { await tester.pumpWidget( Wrapper(child: Container()), ); await tester.pump(); Get.defaultDialog( onConfirm: () {}, middleText: "Dialog made in 3 lines of code"); await tester.pumpAndSettle(); expect(find.text("Ok"), findsOneWidget); }); testWidgets("Get.dialog smoke test", (tester) async { await tester.pumpWidget( Wrapper(child: Container()), ); await tester.pump(); Get.dialog(const YourDialogWidget()); await tester.pumpAndSettle(); expect(find.byType(YourDialogWidget), findsOneWidget); }); group("Get dialog close tests", () { /// Set up the test by opening a dialog and checking to ensure state is correct Future setUpCloseTest(WidgetTester tester) async { await tester.pumpWidget( Wrapper(child: Container()), ); await tester.pump(); Get.dialog(const YourDialogWidget()); await tester.pumpAndSettle(); expect(find.byType(YourDialogWidget), findsOneWidget); expect(Get.isDialogOpen, true); } /// Tear down the test by checking after closing the dialog Future tearDownCloseTest(WidgetTester tester) async { await tester.pumpAndSettle(); expect(find.byType(YourDialogWidget), findsNothing); expect(Get.isDialogOpen, false); await tester.pumpAndSettle(); } testWidgets("Get dialog close - with backLegacy", (tester) async { await setUpCloseTest(tester); // Close using backLegacy Get.backLegacy(); await tearDownCloseTest(tester); }); testWidgets("Get dialog close - with closeDialog", (tester) async { await setUpCloseTest(tester); // Close using closeDialog Get.closeDialog(); await tearDownCloseTest(tester); }); }); group("Get.closeDialog", () { testWidgets("Get.closeDialog - closes dialog and returns value", (tester) async { await tester.pumpWidget( Wrapper(child: Container()), ); await tester.pump(); final result = Get.dialog(const YourDialogWidget()); await tester.pumpAndSettle(); expect(find.byType(YourDialogWidget), findsOneWidget); expect(Get.isDialogOpen, true); const dialogResult = "My dialog result"; Get.closeDialog(result: dialogResult); await tester.pumpAndSettle(); final returnedResult = await result; expect(returnedResult, dialogResult); expect(find.byType(YourDialogWidget), findsNothing); expect(Get.isDialogOpen, false); await tester.pumpAndSettle(); }); testWidgets("Get.closeDialog - does not close bottomsheets", (tester) async { await tester.pumpWidget( Wrapper(child: Container()), ); await tester.pump(); Get.bottomSheet(const YourDialogWidget()); await tester.pumpAndSettle(); expect(find.byType(YourDialogWidget), findsOneWidget); expect(Get.isDialogOpen, false); Get.closeDialog(); await tester.pumpAndSettle(); expect(find.byType(YourDialogWidget), findsOneWidget); }); }); } class YourDialogWidget extends StatelessWidget { const YourDialogWidget({super.key}); @override Widget build(BuildContext context) { return Container(); } } ================================================ FILE: test/navigation/dispose_dependencies_test.dart ================================================ import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:get/get.dart'; import 'utils/wrapper.dart'; void main() { testWidgets("Test dispose dependencies with unnamed routes", (tester) async { await tester.pumpWidget( Wrapper(child: Container()), ); expect(Get.isRegistered(), false); expect(Get.isRegistered(), false); Get.to(() => const First()); await tester.pumpAndSettle(); expect(find.byType(First), findsOneWidget); expect(Get.isRegistered(), true); Get.to(() => const Second()); await tester.pumpAndSettle(); expect(find.byType(Second), findsOneWidget); expect(Get.isRegistered(), true); expect(Get.isRegistered(), true); Get.back(); await tester.pumpAndSettle(); expect(find.byType(First), findsOneWidget); expect(Get.isRegistered(), true); expect(Get.isRegistered(), false); Get.back(); await tester.pumpAndSettle(); expect(Get.isRegistered(), false); expect(Get.isRegistered(), false); }); } class Controller extends GetxController {} class Controller2 extends GetxController {} class First extends StatelessWidget { const First({super.key}); @override Widget build(BuildContext context) { Get.put(Controller()); return const Center( child: Text("first"), ); } } class Second extends StatelessWidget { const Second({super.key}); @override Widget build(BuildContext context) { Get.put(Controller2()); return const Center( child: Text("second"), ); } } ================================================ FILE: test/navigation/get_main_test.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:get/get.dart'; import 'utils/wrapper.dart'; void main() { testWidgets("Get.to navigates to provided route", (tester) async { await tester.pumpWidget(Wrapper(child: Container())); Get.to(() => const FirstScreen()); await tester.pumpAndSettle(); expect(find.byType(FirstScreen), findsOneWidget); }); testWidgets("Get.toNamed navigates to provided named route", (tester) async { await tester.pumpWidget(GetMaterialApp( initialRoute: '/first', getPages: [ GetPage(page: () => const FirstScreen(), name: '/first'), GetPage(page: () => const SecondScreen(), name: '/second'), GetPage(page: () => const ThirdScreen(), name: '/third') ], )); Get.toNamed('/second'); await tester.pumpAndSettle(); expect(find.byType(SecondScreen), findsOneWidget); }); testWidgets("unknowroute", (tester) async { await tester.pumpWidget(GetMaterialApp( initialRoute: '/first', unknownRoute: GetPage(name: '/404', page: () => const Scaffold()), getPages: [ GetPage(page: () => const FirstScreen(), name: '/first'), GetPage(page: () => const SecondScreen(), name: '/second'), GetPage(page: () => const ThirdScreen(), name: '/third') ], )); Get.toNamed('/secondd'); await tester.pumpAndSettle(); expect(Get.rootController.rootDelegate.currentConfiguration?.route?.name, '/404'); }); testWidgets("Get.off navigates to provided route", (tester) async { await tester.pumpWidget(const Wrapper(child: FirstScreen())); // await tester.pump(); Get.off(() => const SecondScreen()); await tester.pumpAndSettle(); expect(find.byType(SecondScreen), findsOneWidget); }); testWidgets("Get.off removes current route", (tester) async { await tester.pumpWidget(const Wrapper(child: FirstScreen())); await tester.pump(); Get.off(() => const SecondScreen()); Get.back(); await tester.pumpAndSettle(); expect(find.byType(FirstScreen), findsNothing); }); testWidgets("Get.offNamed navigates to provided named route", (tester) async { await tester.pumpWidget(GetMaterialApp( initialRoute: '/first', getPages: [ GetPage(name: '/first', page: () => const FirstScreen()), GetPage(name: '/second', page: () => const SecondScreen()), GetPage(name: '/third', page: () => const ThirdScreen()), ], )); await tester.pump(); Get.offNamed('/second'); await tester.pumpAndSettle(); expect(find.byType(SecondScreen), findsOneWidget); }); testWidgets("Get.offNamed removes current route", (tester) async { await tester.pumpWidget(GetMaterialApp( initialRoute: '/first', getPages: [ GetPage(name: '/first', page: () => const FirstScreen()), GetPage(name: '/second', page: () => const SecondScreen()), GetPage(name: '/third', page: () => const ThirdScreen()), ], )); await tester.pump(); Get.offNamed('/second'); await tester.pumpAndSettle(); Get.back(); await tester.pumpAndSettle(); expect(find.byType(FirstScreen), findsNothing); }); testWidgets("Get.offNamed removes only current route", (tester) async { await tester.pumpWidget(GetMaterialApp( initialRoute: '/first', getPages: [ GetPage(name: '/first', page: () => const FirstScreen()), GetPage(name: '/second', page: () => const SecondScreen()), GetPage(name: '/third', page: () => const ThirdScreen()), ], )); // await tester.pump(); Get.toNamed('/second'); await tester.pumpAndSettle(); Get.offNamed('/third'); await tester.pumpAndSettle(); Get.back(); await tester.pumpAndSettle(); expect(find.byType(FirstScreen), findsOneWidget); await tester.pumpAndSettle(); }); testWidgets("Get.offAll navigates to provided route", (tester) async { await tester.pumpWidget(const Wrapper(child: FirstScreen())); await tester.pump(); Get.offAll(() => const SecondScreen()); await tester.pumpAndSettle(); expect(find.byType(SecondScreen), findsOneWidget); }); testWidgets("Get.offAll removes all previous routes", (tester) async { await tester.pumpWidget(const Wrapper(child: FirstScreen())); await tester.pump(); Get.to(() => const SecondScreen()); await tester.pumpAndSettle(); Get.offAll(() => const ThirdScreen()); await tester.pumpAndSettle(); Get.back(); await tester.pumpAndSettle(); expect(find.byType(SecondScreen), findsNothing); Get.back(); await tester.pumpAndSettle(); expect(find.byType(FirstScreen), findsNothing); }); testWidgets("Get.offAllNamed navigates to provided named route", (tester) async { await tester.pumpWidget(WrapperNamed( initialRoute: '/first', namedRoutes: [ GetPage(page: () => const FirstScreen(), name: '/first'), GetPage(page: () => const SecondScreen(), name: '/second'), GetPage(page: () => const ThirdScreen(), name: '/third') ], )); await tester.pump(); Get.toNamed('/second'); await tester.pumpAndSettle(); expect(find.byType(SecondScreen), findsOneWidget); }); testWidgets("Get.offAllNamed removes all previous routes", (tester) async { await tester.pumpWidget(WrapperNamed( initialRoute: '/first', namedRoutes: [ GetPage(page: () => const FirstScreen(), name: '/first'), GetPage(page: () => const SecondScreen(), name: '/second'), GetPage(page: () => const ThirdScreen(), name: '/third') ], )); await tester.pump(); Get.toNamed('/second'); await tester.pumpAndSettle(); Get.offAllNamed('/third'); await tester.pumpAndSettle(); Get.back(); await tester.pumpAndSettle(); expect(find.byType(SecondScreen), findsNothing); Get.back(); await tester.pumpAndSettle(); expect(find.byType(FirstScreen), findsNothing); }); testWidgets("Get.offAndToNamed navigates to provided route", (tester) async { await tester.pumpWidget(WrapperNamed( initialRoute: '/first', namedRoutes: [ GetPage(page: () => const FirstScreen(), name: '/first'), GetPage(page: () => const SecondScreen(), name: '/second'), GetPage(page: () => const ThirdScreen(), name: '/third') ], )); Get.offAndToNamed('/second'); await tester.pumpAndSettle(); expect(find.byType(SecondScreen), findsOneWidget); }); testWidgets("Get.offAndToNamed removes previous route", (tester) async { await tester.pumpWidget(WrapperNamed( initialRoute: '/first', namedRoutes: [ GetPage(page: () => const FirstScreen(), name: '/first'), GetPage(page: () => const SecondScreen(), name: '/second'), GetPage(page: () => const ThirdScreen(), name: '/third') ], )); Get.offAndToNamed('/second'); await tester.pumpAndSettle(); Get.back(); await tester.pumpAndSettle(); expect(find.byType(FirstScreen), findsNothing); }); testWidgets("Get.offUntil navigates to provided route", (tester) async { await tester.pumpWidget(Wrapper(child: Container())); Get.to(() => const FirstScreen()); await tester.pumpAndSettle(); Get.offUntil( () => const ThirdScreen(), (route) => route.name == '/FirstScreen'); await tester.pumpAndSettle(); expect(find.byType(ThirdScreen), findsOneWidget); }); testWidgets("Get.until removes each route that meet the predicate", (tester) async { await tester.pumpWidget(WrapperNamed( initialRoute: '/first', namedRoutes: [ GetPage(page: () => const FirstScreen(), name: '/first'), GetPage(page: () => const SecondScreen(), name: '/second'), GetPage(page: () => const ThirdScreen(), name: '/third') ], )); Get.toNamed('/second'); await tester.pumpAndSettle(); Get.toNamed('/third'); await tester.pumpAndSettle(); Get.until((route) => route.name == '/first'); await tester.pumpAndSettle(); expect(find.byType(FirstScreen), findsOneWidget); expect(find.byType(SecondScreen), findsNothing); expect(find.byType(ThirdScreen), findsNothing); }); testWidgets( "Get.offUntil removes previous routes if they don't match predicate", (tester) async { await tester.pumpWidget(Wrapper(child: Container())); Get.to(() => const FirstScreen()); await tester.pumpAndSettle(); Get.to(() => const SecondScreen()); await tester.pumpAndSettle(); Get.offUntil( () => const ThirdScreen(), (route) => route.name == '/FirstScreen'); await tester.pumpAndSettle(); Get.back(); await tester.pumpAndSettle(); expect(find.byType(SecondScreen), findsNothing); }); testWidgets( "Get.offUntil leaves previous routes that match provided predicate", (tester) async { await tester.pumpWidget(Wrapper(child: Container())); Get.to(() => const FirstScreen()); await tester.pumpAndSettle(); Get.to(() => const SecondScreen()); await tester.pumpAndSettle(); Get.offUntil( () => const ThirdScreen(), (route) => route.name == '/FirstScreen'); await tester.pumpAndSettle(); Get.back(); await tester.pumpAndSettle(); expect(find.byType(FirstScreen), findsOneWidget); }); group('Get.offNamedUntil Tests', () { testWidgets("Navigates to provided route", (tester) async { await tester.pumpWidget(WrapperNamed( initialRoute: '/first', namedRoutes: [ GetPage(page: () => const FirstScreen(), name: '/first'), GetPage(page: () => const SecondScreen(), name: '/second'), GetPage(page: () => const ThirdScreen(), name: '/third') ], )); Get.offNamedUntil('/second', (route) => route.name == '/first'); await tester.pumpAndSettle(); expect(find.byType(SecondScreen), findsOneWidget); expect(Get.currentRoute, '/second'); }); testWidgets("Removes routes that don't match predicate", (tester) async { await tester.pumpWidget(WrapperNamed( initialRoute: '/first', namedRoutes: [ GetPage(page: () => const FirstScreen(), name: '/first'), GetPage(page: () => const SecondScreen(), name: '/second'), GetPage(page: () => const ThirdScreen(), name: '/third') ], )); Get.toNamed('/second'); await tester.pumpAndSettle(); Get.offNamedUntil('/third', (route) => route.name == '/first'); await tester.pumpAndSettle(); expect(find.byType(ThirdScreen), findsOneWidget); expect(Get.currentRoute, '/third'); expect(Get.previousRoute, '/first'); }); testWidgets("Keeps routes that match predicate", (tester) async { await tester.pumpWidget(WrapperNamed( initialRoute: '/first', namedRoutes: [ GetPage(page: () => const FirstScreen(), name: '/first'), GetPage(page: () => const SecondScreen(), name: '/second'), GetPage(page: () => const ThirdScreen(), name: '/third'), ], )); Get.toNamed('/second'); await tester.pumpAndSettle(); Get.offNamedUntil('/third', (route) => route.name == '/first'); await tester.pumpAndSettle(); Get.back(); await tester.pumpAndSettle(); expect(find.byType(FirstScreen), findsOneWidget); expect(Get.currentRoute, '/first'); }); testWidgets("Handles predicate that never returns true", (tester) async { await tester.pumpWidget(WrapperNamed( initialRoute: '/first', namedRoutes: [ GetPage(page: () => const FirstScreen(), name: '/first'), GetPage(page: () => const SecondScreen(), name: '/second'), GetPage(page: () => const ThirdScreen(), name: '/third'), GetPage(page: () => const FourthScreen(), name: '/fourth'), ], )); Get.toNamed('/second'); await tester.pumpAndSettle(); Get.toNamed('/third'); await tester.pumpAndSettle(); Get.offNamedUntil('/fourth', (route) => false); await tester.pumpAndSettle(); expect(find.byType(FourthScreen), findsOneWidget); expect(Get.currentRoute, '/fourth'); expect(Get.previousRoute, '/first'); }); testWidgets("Handles complex navigation scenario", (tester) async { await tester.pumpWidget(WrapperNamed( initialRoute: '/first', namedRoutes: [ GetPage(page: () => const FirstScreen(), name: '/first'), GetPage(page: () => const SecondScreen(), name: '/second'), GetPage(page: () => const ThirdScreen(), name: '/third'), GetPage(page: () => const FourthScreen(), name: '/fourth'), ], )); Get.toNamed('/second'); await tester.pumpAndSettle(); Get.toNamed('/third'); await tester.pumpAndSettle(); Get.offNamedUntil('/fourth', (route) => route.name == '/first'); await tester.pumpAndSettle(); expect(find.byType(FourthScreen), findsOneWidget); expect(Get.currentRoute, '/fourth'); expect(Get.previousRoute, '/first'); Get.back(); await tester.pumpAndSettle(); expect(find.byType(FirstScreen), findsOneWidget); expect(Get.currentRoute, '/first'); }); }); testWidgets("Get.offNamedUntil navigates to provided route", (tester) async { await tester.pumpWidget(WrapperNamed( initialRoute: '/first', namedRoutes: [ GetPage(page: () => const FirstScreen(), name: '/first'), GetPage(page: () => const SecondScreen(), name: '/second'), GetPage(page: () => const ThirdScreen(), name: '/third') ], )); Get.offNamedUntil('/second', (route) => route.name == '/first'); await tester.pumpAndSettle(); expect(find.byType(SecondScreen), findsOneWidget); }); testWidgets( "Get.offNamedUntil removes previous routes if they don't match predicate", (tester) async { await tester.pumpWidget(WrapperNamed( initialRoute: '/first', namedRoutes: [ GetPage(page: () => const FirstScreen(), name: '/first'), GetPage(page: () => const SecondScreen(), name: '/second'), GetPage(page: () => const ThirdScreen(), name: '/third') ], )); Get.toNamed('/second'); await tester.pumpAndSettle(); Get.offNamedUntil('/third', (route) => route.name == '/first'); await tester.pumpAndSettle(); expect(find.byType(SecondScreen), findsNothing); }); testWidgets( "Get.offNamedUntil leaves previous routes that match provided predicate", (tester) async { await tester.pumpWidget(WrapperNamed( initialRoute: '/first', namedRoutes: [ GetPage(page: () => const FirstScreen(), name: '/first'), GetPage(page: () => const SecondScreen(), name: '/second'), GetPage(page: () => const ThirdScreen(), name: '/third'), ], )); Get.toNamed('/second'); await tester.pumpAndSettle(); Get.offNamedUntil('/third', (route) => route.name == '/first'); await tester.pumpAndSettle(); Get.back(); await tester.pumpAndSettle(); expect(find.byType(FirstScreen), findsOneWidget); }); testWidgets("Get.back navigates back", (tester) async { await tester.pumpWidget( Wrapper( defaultTransition: Transition.circularReveal, child: Container(), ), ); // await tester.pump(); Get.to(() => const FirstScreen()); await tester.pumpAndSettle(); Get.to(() => const SecondScreen()); await tester.pumpAndSettle(); Get.back(); await tester.pumpAndSettle(); expect(find.byType(FirstScreen), findsOneWidget); }); testWidgets( "Get.back with closeOverlays pops both snackbar and current route", (tester) async { await tester.pumpWidget( Wrapper( defaultTransition: Transition.circularReveal, child: Container(), ), ); // await tester.pump(); Get.to(() => const FirstScreen()); await tester.pumpAndSettle(); Get.to(() => const SecondScreen()); await tester.pumpAndSettle(); Get.snackbar('title', "message"); await tester.pumpAndSettle(); Get.backLegacy(closeOverlays: true); await tester.pumpAndSettle(); expect(Get.isSnackbarOpen, false); expect(find.byType(FirstScreen), findsOneWidget); }); testWidgets("Get.until", (tester) async { await tester.pumpWidget(WrapperNamed( initialRoute: '/first', namedRoutes: [ GetPage(page: () => const FirstScreen(), name: '/first'), GetPage(page: () => const SecondScreen(), name: '/second'), GetPage(page: () => const ThirdScreen(), name: '/third') ], )); await tester.pump(); Get.toNamed('/second'); await tester.pumpAndSettle(); Get.toNamed('/third'); await tester.pumpAndSettle(); Get.until((route) => route.name == '/first'); await tester.pumpAndSettle(); expect(find.byType(FirstScreen), findsOneWidget); }); group("Get.defaultTransition smoke test", () { testWidgets("fadeIn", (tester) async { await tester.pumpWidget( Wrapper( defaultTransition: Transition.fadeIn, child: Container(), ), ); Get.to(() => const FirstScreen()); await tester.pumpAndSettle(); expect(find.byType(FirstScreen), findsOneWidget); }); testWidgets("downToUp", (tester) async { await tester.pumpWidget( Wrapper( defaultTransition: Transition.downToUp, child: Container(), ), ); Get.to(() => const FirstScreen()); await tester.pumpAndSettle(); expect(find.byType(FirstScreen), findsOneWidget); }); testWidgets("fade", (tester) async { await tester.pumpWidget( Wrapper( defaultTransition: Transition.fade, child: Container(), ), ); Get.to(() => const FirstScreen()); await tester.pumpAndSettle(); expect(find.byType(FirstScreen), findsOneWidget); }); testWidgets("leftToRight", (tester) async { await tester.pumpWidget( Wrapper( defaultTransition: Transition.leftToRight, child: Container(), ), ); Get.to(() => const FirstScreen()); await tester.pumpAndSettle(); expect(find.byType(FirstScreen), findsOneWidget); }); testWidgets("leftToRightWithFade", (tester) async { await tester.pumpWidget( Wrapper( defaultTransition: Transition.leftToRightWithFade, child: Container(), ), ); Get.to(() => const FirstScreen()); await tester.pumpAndSettle(); expect(find.byType(FirstScreen), findsOneWidget); }); testWidgets("leftToRightWithFade", (tester) async { await tester.pumpWidget( Wrapper( defaultTransition: Transition.rightToLeft, child: Container(), ), ); Get.to(() => const FirstScreen()); await tester.pumpAndSettle(); expect(find.byType(FirstScreen), findsOneWidget); }); testWidgets("defaultTransition", (tester) async { await tester.pumpWidget( Wrapper( defaultTransition: Transition.rightToLeft, child: Container(), ), ); Get.to(() => const FirstScreen()); await tester.pumpAndSettle(); expect(find.byType(FirstScreen), findsOneWidget); }); testWidgets("rightToLeftWithFade", (tester) async { await tester.pumpWidget( Wrapper( defaultTransition: Transition.rightToLeftWithFade, child: Container(), ), ); Get.to(() => const FirstScreen()); await tester.pumpAndSettle(); expect(find.byType(FirstScreen), findsOneWidget); }); testWidgets("cupertino", (tester) async { await tester.pumpWidget( Wrapper( defaultTransition: Transition.cupertino, child: Container(), ), ); Get.to(() => const FirstScreen()); await tester.pumpAndSettle(); expect(find.byType(FirstScreen), findsOneWidget); }); testWidgets("size", (tester) async { await tester.pumpWidget( Wrapper( defaultTransition: Transition.size, child: Container(), ), ); Get.to(() => const FirstScreen()); await tester.pumpAndSettle(); expect(find.byType(FirstScreen), findsOneWidget); }); }); } class Home extends StatelessWidget { const Home({super.key}); @override Widget build(BuildContext context) { // ignore: avoid_unnecessary_containers return Container(child: const Text('Home')); } } class FirstScreen extends StatelessWidget { const FirstScreen({super.key}); @override Widget build(BuildContext context) { // ignore: avoid_unnecessary_containers return Container(child: const Text('FirstScreen')); } } class SecondScreen extends StatelessWidget { const SecondScreen({super.key}); @override Widget build(BuildContext context) { return Container(); } } class ThirdScreen extends StatelessWidget { const ThirdScreen({super.key}); @override Widget build(BuildContext context) { return Container(); } } class FourthScreen extends StatelessWidget { const FourthScreen({super.key}); @override Widget build(BuildContext context) { return Container(); } } ================================================ FILE: test/navigation/middleware_test.dart ================================================ import 'package:flutter_test/flutter_test.dart'; import 'package:get/get.dart'; import 'get_main_test.dart'; class RedirectMiddleware extends GetMiddleware { @override Future redirectDelegate(RouteDecoder route) async { return RouteDecoder.fromRoute('/second'); } } class Redirect2Middleware extends GetMiddleware { @override Future redirectDelegate(RouteDecoder route) async { return RouteDecoder.fromRoute('/first'); } } class RedirectMiddlewareNull extends GetMiddleware { @override Future redirectDelegate(RouteDecoder route) async { return null; } } class RedirectBypassMiddleware extends GetMiddleware { @override Future redirectDelegate(RouteDecoder route) async { return route; } } void main() { tearDown(() { Get.reset(); }); testWidgets("Middleware should redirect to second screen", (tester) async { // Test setup await tester.pumpWidget( GetMaterialApp( initialRoute: '/', getPages: [ GetPage(name: '/', page: () => const Home()), GetPage( name: '/first', page: () => const FirstScreen(), middlewares: [RedirectMiddleware()], ), GetPage(name: '/second', page: () => const SecondScreen()), GetPage(name: '/third', page: () => const ThirdScreen()), ], ), ); // Act Get.toNamed('/first'); await tester.pumpAndSettle(); // Assert expect(find.byType(SecondScreen), findsOneWidget); expect(find.byType(FirstScreen), findsNothing); expect(Get.currentRoute, '/second'); }); testWidgets("Middleware should stop navigation", (tester) async { // Test setup await tester.pumpWidget( GetMaterialApp( initialRoute: '/', getPages: [ GetPage(name: '/', page: () => const Home()), GetPage( name: '/first', page: () => const FirstScreen(), middlewares: [RedirectMiddlewareNull()], ), GetPage(name: '/second', page: () => const SecondScreen()), GetPage(name: '/third', page: () => const ThirdScreen()), ], ), ); // Act await tester.pumpAndSettle(); Get.toNamed('/first'); await tester.pumpAndSettle(); // Assert expect(find.byType(Home), findsOneWidget); expect(find.byType(FirstScreen), findsNothing); expect(Get.currentRoute, '/'); }); testWidgets("Middleware should be bypassed", (tester) async { // Test setup await tester.pumpWidget( GetMaterialApp( initialRoute: '/', getPages: [ GetPage(name: '/', page: () => const Home()), GetPage( name: '/first', page: () => const FirstScreen(), middlewares: [RedirectBypassMiddleware()], ), GetPage(name: '/second', page: () => const SecondScreen()), GetPage(name: '/third', page: () => const ThirdScreen()), ], ), ); // Act await tester.pumpAndSettle(); Get.toNamed('/first'); await tester.pumpAndSettle(); // Assert expect(find.byType(FirstScreen), findsOneWidget); expect(find.byType(SecondScreen), findsNothing); expect(find.byType(Home), findsNothing); expect(Get.currentRoute, '/first'); }); testWidgets("Middleware should redirect twice", (tester) async { // Test setup await tester.pumpWidget( GetMaterialApp( initialRoute: '/', getPages: [ GetPage(name: '/', page: () => const Home()), GetPage( name: '/first', page: () => const FirstScreen(), middlewares: [RedirectMiddleware()], ), GetPage(name: '/second', page: () => const SecondScreen()), GetPage(name: '/third', page: () => const ThirdScreen()), GetPage( name: '/fourth', page: () => const FourthScreen(), middlewares: [Redirect2Middleware()], ), ], ), ); // Act Get.toNamed('/fourth'); await tester.pumpAndSettle(); // Assert expect(find.byType(SecondScreen), findsOneWidget); expect(find.byType(FirstScreen), findsNothing); expect(Get.currentRoute, '/second'); }); testWidgets("Navigation history should be correct after redirects", (tester) async { // Test setup await tester.pumpWidget( GetMaterialApp( initialRoute: '/', getPages: [ GetPage(name: '/', page: () => const Home()), GetPage( name: '/first', page: () => const FirstScreen(), middlewares: [RedirectMiddleware()], ), GetPage(name: '/second', page: () => const SecondScreen()), ], ), ); // Act Get.toNamed('/first'); await tester.pumpAndSettle(); // Assert expect(Get.currentRoute, '/second'); expect(Get.previousRoute, '/'); // Act: go back Get.back(); await tester.pumpAndSettle(); // Assert expect(find.byType(Home), findsOneWidget); expect(Get.currentRoute, '/'); }); } ================================================ FILE: test/navigation/parse_route_test.dart ================================================ import 'package:flutter/cupertino.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:get/get.dart'; void main() { test('Parse Page with children', () { final testParams = {'hi': 'value'}; final pageTree = GetPage( name: '/city', page: () => Container(), children: [ GetPage( name: '/home', page: () => Container(), transition: Transition.rightToLeftWithFade, children: [ GetPage( name: '/bed-room', transition: Transition.size, page: () => Container(), ), GetPage( name: '/living-room', transition: Transition.topLevel, page: () => Container(), ), ], ), GetPage( name: '/work', transition: Transition.upToDown, page: () => Container(), children: [ GetPage( name: '/office', transition: Transition.zoom, page: () => Container(), children: [ GetPage( name: '/pen', transition: Transition.cupertino, page: () => Container(), parameters: testParams, ), GetPage( name: '/paper', page: () => Container(), transition: Transition.downToUp, ), ], ), GetPage( name: '/meeting-room', transition: Transition.fade, page: () => Container(), ), ], ), ], ); final tree = ParseRouteTree(routes: []); tree.addRoute(pageTree); // tree.addRoute(pageTree); const searchRoute = '/city/work/office/pen'; final match = tree.matchRoute(searchRoute); expect(match, isNotNull); expect(match.route!.name, searchRoute); final testRouteParam = match.route!.parameters!; for (final tParam in testParams.entries) { expect(testRouteParam[tParam.key], tParam.value); } }); test('Parse Page without children', () { final pageTree = [ GetPage( name: '/city', page: () => Container(), transition: Transition.cupertino), GetPage( name: '/city/home', page: () => Container(), transition: Transition.downToUp), GetPage( name: '/city/home/bed-room', page: () => Container(), transition: Transition.fade), GetPage( name: '/city/home/living-room', page: () => Container(), transition: Transition.fadeIn), GetPage( name: '/city/work', page: () => Container(), transition: Transition.leftToRight), GetPage( name: '/city/work/office', page: () => Container(), transition: Transition.leftToRightWithFade), GetPage( name: '/city/work/office/pen', page: () => Container(), transition: Transition.native), GetPage( name: '/city/work/office/paper', page: () => Container(), transition: Transition.noTransition), GetPage( name: '/city/work/meeting-room', page: () => Container(), transition: Transition.rightToLeft), ]; final tree = ParseRouteTree(routes: pageTree); // for (var p in pageTree) { // tree.addRoute(p); // } const searchRoute = '/city/work/office/pen'; final match = tree.matchRoute(searchRoute); expect(match, isNotNull); expect(match.route!.name, searchRoute); }); testWidgets( 'test params from dynamic route', (tester) async { await tester.pumpWidget(GetMaterialApp( initialRoute: '/first/juan', getPages: [ GetPage(page: () => Container(), name: '/first/:name'), GetPage(page: () => Container(), name: '/second/:id'), GetPage(page: () => Container(), name: '/third'), GetPage(page: () => Container(), name: '/last/:id/:name/profile'), GetPage(page: () => Container(), name: '/first/second/:token') ], )); expect(Get.parameters['name'], 'juan'); Get.toNamed('/second/1234'); await tester.pumpAndSettle(); expect(Get.parameters['id'], '1234'); Get.toNamed('/third?name=jonny&job=dev'); await tester.pumpAndSettle(); expect(Get.parameters['name'], 'jonny'); expect(Get.parameters['job'], 'dev'); Get.toNamed('/last/1234/ana/profile'); await tester.pumpAndSettle(); expect(Get.parameters['id'], '1234'); expect(Get.parameters['name'], 'ana'); Get.toNamed('/last/1234/ana/profile?job=dev'); await tester.pumpAndSettle(); expect(Get.parameters['id'], '1234'); expect(Get.parameters['name'], 'ana'); expect(Get.parameters['job'], 'dev'); Get.toNamed( 'https://www.example.com/first/second/fa9662f4-ec3f-11ee-a806-169a3915b383', ); await tester.pumpAndSettle(); expect(Get.parameters['token'], 'fa9662f4-ec3f-11ee-a806-169a3915b383'); }, ); testWidgets( 'params in url by parameters', (tester) async { await tester.pumpWidget(GetMaterialApp( initialRoute: '/first/juan', getPages: [ GetPage(page: () => Container(), name: '/first/:name'), GetPage(page: () => Container(), name: '/italy'), ], )); // Get.parameters = ({"varginias": "varginia", "vinis": "viniiss"}); var parameters = { "varginias": "varginia", "vinis": "viniiss" }; // print("Get.parameters: ${Get.parameters}"); parameters.addAll({"a": "b", "c": "d"}); Get.toNamed("/italy", parameters: parameters); await tester.pumpAndSettle(); expect(Get.parameters['varginias'], 'varginia'); expect(Get.parameters['vinis'], 'viniiss'); expect(Get.parameters['a'], 'b'); expect(Get.parameters['c'], 'd'); }, ); } ================================================ FILE: test/navigation/root_widget_test.dart ================================================ // import 'package:flutter_test/flutter_test.dart'; // import 'package:get/get.dart'; void main() { // testWidgets( // "GetMaterialApp with routes null", // (tester) async { // expect( // () => GetMaterialApp( // routes: null, // ), // throwsAssertionError); // }, // ); // testWidgets( // "GetMaterialApp with navigatorObservers null", // (tester) async { // expect( // () => GetMaterialApp( // navigatorObservers: null, // ), // throwsAssertionError); // }, // ); // testWidgets( // "GetMaterialApp with title null", // (tester) async { // expect( // () => GetMaterialApp( // title: null, // ), // throwsAssertionError); // }, // ); // testWidgets( // "GetMaterialApp with debugShowMaterialGrid null", // (test) async { // expect( // () => GetMaterialApp( // debugShowMaterialGrid: null, // ), // throwsAssertionError, // ); // }, // ); // testWidgets( // "GetMaterialApp with showPerformanceOverlay null", // (test) async { // expect( // () => GetMaterialApp( // showPerformanceOverlay: null, // ), // throwsAssertionError, // ); // }, // ); // testWidgets( // "GetMaterialApp with showSemanticsDebugger null", // (test) async { // expect( // () => GetMaterialApp( // showSemanticsDebugger: null, // ), // throwsAssertionError, // ); // }, // ); // testWidgets( // "GetMaterialApp with debugShowCheckedModeBanner null", // (tester) async { // expect( // () => GetMaterialApp( // debugShowCheckedModeBanner: null, // ), // throwsAssertionError); // }, // ); // testWidgets( // "GetMaterialApp with checkerboardRasterCacheImages null", // (tester) async { // expect( // () => GetMaterialApp( // checkerboardRasterCacheImages: null, // ), // throwsAssertionError); // }, // ); // testWidgets( // "GetMaterialApp with checkerboardOffscreenLayers null", // (tester) async { // expect( // () => GetMaterialApp( // checkerboardOffscreenLayers: null, // ), // throwsAssertionError, // ); // }, // ); } ================================================ FILE: test/navigation/routes_test.dart ================================================ void main() { // testWidgets('Back swipe dismiss interrupted by route push', // (tester) async { // // final scaffoldKey = GlobalKey(); // await tester.pumpWidget( // GetCupertinoApp( // popGesture: true, // home: CupertinoPageScaffold( // // key: scaffoldKey, // child: Center( // child: CupertinoButton( // onPressed: () { // Get.to( // () => CupertinoPageScaffold( // child: Center(child: Text('route')), // ), // preventDuplicateHandlingMode: // PreventDuplicateHandlingMode.Recreate); // }, // child: const Text('push'), // ), // ), // ), // ), // ); // await tester.pumpAndSettle(); // // Check the basic iOS back-swipe dismiss transition. Dragging the pushed // // route halfway across the screen will trigger the iOS dismiss animation // await tester.tap(find.text('push')); // await tester.pumpAndSettle(); // expect(find.text('route'), findsOneWidget); // expect(find.text('push'), findsNothing); // var gesture = await tester.startGesture(const Offset(5, 300)); // await gesture.moveBy(const Offset(400, 0)); // await gesture.up(); // await tester.pump(); // expect( // // The 'route' route has been dragged to the right, halfway across // // the screen // tester.getTopLeft(find.ancestor( // of: find.text('route'), // matching: find.byType(CupertinoPageScaffold))), // const Offset(400, 0), // ); // expect( // // The 'push' route is sliding in from the left. // tester // .getTopLeft(find.ancestor( // of: find.text('push'), // matching: find.byType(CupertinoPageScaffold))) // .dx, // moreOrLessEquals(-(400 / 3), epsilon: 1), // ); // await tester.pumpAndSettle(); // expect(find.text('push'), findsOneWidget); // expect( // tester.getTopLeft(find.ancestor( // of: find.text('push'), // matching: find.byType(CupertinoPageScaffold))), // Offset.zero, // ); // expect(find.text('route'), findsNothing); // // Run the dismiss animation 60%, which exposes the route "push" button, // // and then press the button. // await tester.tap(find.text('push')); // await tester.pumpAndSettle(); // expect(find.text('route'), findsOneWidget); // expect(find.text('push'), findsNothing); // gesture = await tester.startGesture(const Offset(5, 300)); // await gesture.moveBy(const Offset(400, 0)); // Drag halfway. // await gesture.up(); // // Trigger the snapping animation. // // Since the back swipe drag was brought to >=50% of the screen, it will // // self snap to finish the pop transition as the gesture is lifted. // // // // This drag drop animation is 400ms when dropped exactly halfway // // (800 / [pixel distance remaining], see // // _CupertinoBackGestureController.dragEnd). It follows a curve that is very // // steep initially. // await tester.pump(); // expect( // tester.getTopLeft(find.ancestor( // of: find.text('route'), // matching: find.byType(CupertinoPageScaffold))), // const Offset(400, 0), // ); // // Let the dismissing snapping animation go 60%. // await tester.pump(const Duration(milliseconds: 240)); // expect( // tester // .getTopLeft(find.ancestor( // of: find.text('route'), // matching: find.byType(CupertinoPageScaffold))) // .dx, // moreOrLessEquals(798, epsilon: 1), // ); // }); } ================================================ FILE: test/navigation/snackbar_test.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:get/get.dart'; void main() { testWidgets("test if Get.isSnackbarOpen works with Get.snackbar", (tester) async { await tester.pumpWidget( GetMaterialApp( popGesture: true, home: ElevatedButton( child: const Text('Open Snackbar'), onPressed: () { Get.snackbar( 'title', "message", duration: const Duration(seconds: 1), mainButton: TextButton(onPressed: () {}, child: const Text('button')), isDismissible: false, ); }, ), ), ); await tester.pump(); expect(Get.isSnackbarOpen, false); await tester.tap(find.text('Open Snackbar')); expect(Get.isSnackbarOpen, true); await tester.pump(const Duration(seconds: 1)); expect(Get.isSnackbarOpen, false); }); testWidgets("Get.rawSnackbar test", (tester) async { await tester.pumpWidget( GetMaterialApp( popGesture: true, home: ElevatedButton( child: const Text('Open Snackbar'), onPressed: () { Get.rawSnackbar( title: 'title', message: "message", onTap: (_) {}, shouldIconPulse: true, icon: const Icon(Icons.alarm), showProgressIndicator: true, duration: const Duration(seconds: 1), isDismissible: true, leftBarIndicatorColor: Colors.amber, overlayBlur: 1.0, ); }, ), ), ); await tester.pump(); expect(Get.isSnackbarOpen, false); await tester.tap( find.text('Open Snackbar'), ); expect(Get.isSnackbarOpen, true); await tester.pump(const Duration(seconds: 1)); expect(Get.isSnackbarOpen, false); }); testWidgets("test snackbar queue", (tester) async { const messageOne = Text('title'); const messageTwo = Text('titleTwo'); await tester.pumpWidget( GetMaterialApp( popGesture: true, home: ElevatedButton( child: const Text('Open Snackbar'), onPressed: () { Get.rawSnackbar( messageText: messageOne, duration: const Duration(seconds: 1)); Get.rawSnackbar( messageText: messageTwo, duration: const Duration(seconds: 1)); }, ), ), ); await tester.pump(); expect(Get.isSnackbarOpen, false); await tester.tap(find.text('Open Snackbar')); expect(Get.isSnackbarOpen, true); await tester.pump(const Duration(milliseconds: 500)); expect(find.text('title'), findsOneWidget); expect(find.text('titleTwo'), findsNothing); await tester.pump(const Duration(milliseconds: 500)); expect(find.text('title'), findsNothing); expect(find.text('titleTwo'), findsOneWidget); Get.closeAllSnackbars(); await tester.pumpAndSettle(); }); testWidgets("test snackbar dismissible", (tester) async { const dismissDirection = DismissDirection.down; const snackBarTapTarget = Key('snackbar-tap-target'); const GetSnackBar getBar = GetSnackBar( key: ValueKey('dismissible'), message: 'bar1', duration: Duration(seconds: 2), isDismissible: true, snackPosition: SnackPosition.bottom, dismissDirection: dismissDirection, ); await tester.pumpWidget(GetMaterialApp( home: Scaffold( body: Builder( builder: (context) { return Column( children: [ GestureDetector( key: snackBarTapTarget, onTap: () { Get.showSnackbar(getBar); }, behavior: HitTestBehavior.opaque, child: const SizedBox( height: 100.0, width: 100.0, ), ), ], ); }, ), ), )); await tester.pump(); expect(Get.isSnackbarOpen, false); expect(find.text('bar1'), findsNothing); await tester.tap(find.byKey(snackBarTapTarget)); await tester.pumpAndSettle(); expect(Get.isSnackbarOpen, true); await tester.pump(const Duration(milliseconds: 500)); expect(find.byWidget(getBar), findsOneWidget); await tester.ensureVisible(find.byWidget(getBar)); await tester.drag(find.byType(Dismissible), const Offset(0.0, 50.0)); await tester.pumpAndSettle(); await tester.pump(const Duration(milliseconds: 500)); expect(Get.isSnackbarOpen, false); }); testWidgets("test snackbar onTap", (tester) async { const dismissDirection = DismissDirection.vertical; const snackBarTapTarget = Key('snackbar-tap-target'); var counter = 0; late final GetSnackBar getBar; late final SnackbarController getBarController; await tester.pumpWidget(GetMaterialApp( home: Scaffold( body: Builder( builder: (context) { return Column( children: [ GestureDetector( key: snackBarTapTarget, onTap: () { getBar = GetSnackBar( message: 'bar1', onTap: (_) { counter++; }, duration: const Duration(seconds: 2), isDismissible: true, dismissDirection: dismissDirection, ); getBarController = Get.showSnackbar(getBar); }, behavior: HitTestBehavior.opaque, child: const SizedBox( height: 100.0, width: 100.0, ), ), ], ); }, ), ), )); await tester.pumpAndSettle(); expect(Get.isSnackbarOpen, false); expect(find.text('bar1'), findsNothing); await tester.tap(find.byKey(snackBarTapTarget)); await tester.pumpAndSettle(); expect(Get.isSnackbarOpen, true); await tester.pump(const Duration(milliseconds: 500)); expect(find.byWidget(getBar), findsOneWidget); await tester.ensureVisible(find.byWidget(getBar)); await tester.tap(find.byWidget(getBar)); expect(counter, 1); await tester.pump(const Duration(milliseconds: 3000)); await getBarController.close(withAnimations: false); }); testWidgets("Get test actions and icon", (tester) async { const icon = Icon(Icons.alarm); final action = TextButton(onPressed: () {}, child: const Text('button')); late final GetSnackBar getBar; await tester.pumpWidget(const GetMaterialApp(home: Scaffold())); await tester.pump(); expect(Get.isSnackbarOpen, false); expect(find.text('bar1'), findsNothing); getBar = GetSnackBar( message: 'bar1', icon: icon, mainButton: action, leftBarIndicatorColor: Colors.yellow, showProgressIndicator: true, // maxWidth: 100, borderColor: Colors.red, duration: const Duration(seconds: 1), isDismissible: false, ); Get.showSnackbar(getBar); expect(Get.isSnackbarOpen, true); await tester.pump(const Duration(milliseconds: 500)); expect(find.byWidget(getBar), findsOneWidget); expect(find.byWidget(icon), findsOneWidget); expect(find.byWidget(action), findsOneWidget); await tester.pump(const Duration(milliseconds: 500)); expect(Get.isSnackbarOpen, false); }); } ================================================ FILE: test/navigation/utils/wrapper.dart ================================================ import 'package:flutter/material.dart'; import 'package:get/get.dart'; class Wrapper extends StatelessWidget { final Widget? child; final List? namedRoutes; final String? initialRoute; final Transition? defaultTransition; const Wrapper({ super.key, this.child, this.namedRoutes, this.initialRoute, this.defaultTransition, }); @override Widget build(BuildContext context) { return GetMaterialApp( defaultTransition: defaultTransition, initialRoute: initialRoute, translations: WrapperTranslations(), locale: WrapperTranslations.locale, getPages: namedRoutes, home: namedRoutes == null ? Scaffold( body: child, ) : null, ); } } class WrapperNamed extends StatelessWidget { final Widget? child; final List? namedRoutes; final String? initialRoute; final Transition? defaultTransition; const WrapperNamed({ super.key, this.child, this.namedRoutes, this.initialRoute, this.defaultTransition, }); @override Widget build(BuildContext context) { return GetMaterialApp( defaultTransition: defaultTransition, initialRoute: initialRoute, getPages: namedRoutes, ); } } class WrapperTranslations extends Translations { static const fallbackLocale = Locale('en', 'US'); static Locale? get locale => const Locale('en', 'US'); @override Map> get keys => { 'en_US': { 'covid': 'Corona Virus', 'total_confirmed': 'Total Confirmed', 'total_deaths': 'Total Deaths', }, 'pt_BR': { 'covid': 'Corona Vírus', 'total_confirmed': 'Total confirmado', 'total_deaths': 'Total de mortes', }, }; } ================================================ FILE: test/rx/rx_workers_test.dart ================================================ import 'dart:async'; import 'package:flutter_test/flutter_test.dart'; import 'package:get/get.dart'; void main() { test('once', () async { final count = 0.obs; var result = -1; once(count, (dynamic val) { result = val as int; }); count.value++; await Future.delayed(Duration.zero); expect(1, result); count.value++; await Future.delayed(Duration.zero); expect(1, result); count.value++; await Future.delayed(Duration.zero); expect(1, result); }); test('ever', () async { final count = 0.obs; var result = -1; ever(count, (value) { result = value; }); count.value++; await Future.delayed(Duration.zero); expect(1, result); count.value++; await Future.delayed(Duration.zero); expect(2, result); count.value++; await Future.delayed(Duration.zero); expect(3, result); }); test('debounce', () async { final count = 0.obs; int? result = -1; debounce(count, (dynamic val) { // print(_); result = val as int?; }, time: const Duration(milliseconds: 100)); count.value++; count.value++; count.value++; count.value++; await Future.delayed(Duration.zero); expect(-1, result); await Future.delayed(const Duration(milliseconds: 100)); expect(4, result); }); test('interval', () async { final count = 0.obs; int? result = -1; interval(count, (v) { result = v; }, time: const Duration(milliseconds: 100)); count.value++; await Future.delayed(Duration.zero); await Future.delayed(const Duration(milliseconds: 100)); expect(result, 1); count.value++; count.value++; count.value++; await Future.delayed(Duration.zero); await Future.delayed(const Duration(milliseconds: 100)); expect(result, 2); count.value++; await Future.delayed(Duration.zero); await Future.delayed(const Duration(milliseconds: 100)); expect(result, 5); }); test('bindStream test', () async { int? count = 0; final controller = StreamController(); final rx = 0.obs; rx.listen((value) { count = value; }); rx.bindStream(controller.stream); expect(count, 0); controller.add(555); await Future.delayed(Duration.zero); expect(count, 555); controller.close(); }); test('Rx same value will not call the same listener when call', () async { var reactiveInteger = RxInt(2); var timesCalled = 0; reactiveInteger.listen((newInt) { timesCalled++; }); // we call 3 reactiveInteger.call(3); // then repeat twice reactiveInteger.call(3); reactiveInteger.call(3); await Future.delayed(const Duration(milliseconds: 100)); expect(1, timesCalled); }); test('Rx different value will call the listener when trigger', () async { var reactiveInteger = RxInt(0); var timesCalled = 0; reactiveInteger.listen((newInt) { timesCalled++; }); // we call 3 reactiveInteger.trigger(1); // then repeat twice reactiveInteger.trigger(2); reactiveInteger.trigger(3); await Future.delayed(const Duration(milliseconds: 100)); expect(3, timesCalled); }); test('Rx same value will call the listener when trigger', () async { var reactiveInteger = RxInt(2); var timesCalled = 0; reactiveInteger.listen((newInt) { timesCalled++; }); // we call 3 reactiveInteger.trigger(3); // then repeat twice reactiveInteger.trigger(3); reactiveInteger.trigger(3); reactiveInteger.trigger(1); await Future.delayed(const Duration(milliseconds: 100)); expect(4, timesCalled); }); test('Rx String with non null values', () async { final reactiveString = Rx("abc"); String? currentString; reactiveString.listen((newString) { currentString = newString; }); expect(reactiveString.endsWith("c"), true); // we call 3 reactiveString("b"); await Future.delayed(Duration.zero); expect(currentString, "b"); }); test('Rx String with null values', () async { var reactiveString = Rx(null); String? currentString; reactiveString.listen((newString) { currentString = newString; }); // we call 3 reactiveString("abc"); await Future.delayed(Duration.zero); expect(reactiveString.endsWith("c"), true); expect(currentString, "abc"); }); test('Number of times "ever" is called in RxList', () async { final list = [1, 2, 3].obs; var count = 0; ever>(list, (value) { count++; }); list.add(4); await Future.delayed(Duration.zero); expect(count, 1); count = 0; list.addAll([4, 5]); await Future.delayed(Duration.zero); expect(count, 1); count = 0; list.remove(2); await Future.delayed(Duration.zero); expect(count, 1); count = 0; list.removeWhere((element) => element == 2); await Future.delayed(Duration.zero); expect(count, 1); count = 0; list.retainWhere((element) => element == 1); await Future.delayed(Duration.zero); expect(count, 1); }); } ================================================ FILE: test/state_manager/get_mixin_state_test.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:get/get.dart'; import 'package:get/get_state_manager/src/simple/mixin_builder.dart'; void main() { testWidgets("MixinBuilder with reactive and not reactive", (tester) async { await tester.pumpWidget( MaterialApp( home: MixinBuilder( init: Controller(), builder: (controller) { return Column( children: [ Text( 'Count: ${controller.counter.value}', ), Text( 'Count2: ${controller.count}', ), Text( 'Double: ${controller.doubleNum.value}', ), Text( 'String: ${controller.string.value}', ), Text( 'List: ${controller.list.length}', ), Text( 'Bool: ${controller.boolean.value}', ), Text( 'Map: ${controller.map.length}', ), TextButton( child: const Text("increment"), onPressed: () => controller.increment(), ), TextButton( child: const Text("increment2"), onPressed: () => controller.increment2(), ) ], ); }, ), ), ); expect(find.text("Count: 0"), findsOneWidget); expect(find.text("Count2: 0"), findsOneWidget); expect(find.text("Double: 0.0"), findsOneWidget); expect(find.text("String: string"), findsOneWidget); expect(find.text("Bool: true"), findsOneWidget); expect(find.text("List: 0"), findsOneWidget); expect(find.text("Map: 0"), findsOneWidget); Controller.to.increment(); await tester.pump(); expect(find.text("Count: 1"), findsOneWidget); await tester.tap(find.text('increment')); await tester.pump(); expect(find.text("Count: 2"), findsOneWidget); await tester.tap(find.text('increment2')); await tester.pump(); expect(find.text("Count2: 1"), findsOneWidget); }); // testWidgets( // "MixinBuilder with build null", // (tester) async { // expect( // () => MixinBuilder( // init: Controller(), // builder: null, // ), // throwsAssertionError, // ); // }, // ); } class Controller extends GetxController { static Controller get to => Get.find(); int count = 0; RxInt counter = 0.obs; RxDouble doubleNum = 0.0.obs; RxString string = "string".obs; RxList list = [].obs; RxMap map = {}.obs; RxBool boolean = true.obs; void increment() { counter.value++; } void increment2() { count++; update(); } } ================================================ FILE: test/state_manager/get_obx_test.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:get/get.dart'; void main() { testWidgets("GetxController smoke test", (tester) async { final controller = Get.put(Controller()); await tester.pumpWidget( MaterialApp( home: Column( children: [ Obx( () => Column(children: [ Text('Count: ${controller.counter.value}'), Text('Double: ${controller.doubleNum.value}'), Text('String: ${controller.string.value}'), Text('List: ${controller.list.length}'), Text('Bool: ${controller.boolean.value}'), Text('Map: ${controller.map.length}'), TextButton( onPressed: controller.increment, child: const Text("increment"), ), Obx(() => Text('Obx: ${controller.map.length}')) ]), ), ], ), ), ); expect(find.text("Count: 0"), findsOneWidget); expect(find.text("Double: 0.0"), findsOneWidget); expect(find.text("String: string"), findsOneWidget); expect(find.text("Bool: true"), findsOneWidget); expect(find.text("List: 0"), findsOneWidget); expect(find.text("Map: 0"), findsOneWidget); expect(find.text("Obx: 0"), findsOneWidget); Controller.to.increment(); await tester.pump(); expect(find.text("Count: 1"), findsOneWidget); await tester.tap(find.text('increment')); await tester.pump(); expect(find.text("Count: 2"), findsOneWidget); }); } class Controller extends GetxController { static Controller get to => Get.find(); RxInt counter = 0.obs; RxDouble doubleNum = 0.0.obs; RxString string = "string".obs; RxList list = [].obs; RxMap map = {}.obs; RxBool boolean = true.obs; void increment() { counter.value++; } } ================================================ FILE: test/state_manager/get_rxstate_test.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:get/get.dart'; void main() { Get.lazyPut(() => Controller2()); testWidgets("GetxController smoke test", (tester) async { await tester.pumpWidget( MaterialApp( home: GetX( init: Controller(), builder: (controller) { return Column( children: [ Text( 'Count: ${controller.counter.value}', ), Text( 'Double: ${controller.doubleNum.value}', ), Text( 'String: ${controller.string.value}', ), Text( 'List: ${controller.list.length}', ), Text( 'Bool: ${controller.boolean.value}', ), Text( 'Map: ${controller.map.length}', ), TextButton( child: const Text("increment"), onPressed: () => controller.increment(), ), GetX(builder: (controller) { return Text('lazy ${controller.lazy.value}'); }), GetX( init: ControllerNonGlobal(), global: false, builder: (controller) { return Text('single ${controller.nonGlobal.value}'); }) ], ); }, ), ), ); expect(find.text("Count: 0"), findsOneWidget); expect(find.text("Double: 0.0"), findsOneWidget); expect(find.text("String: string"), findsOneWidget); expect(find.text("Bool: true"), findsOneWidget); expect(find.text("List: 0"), findsOneWidget); expect(find.text("Map: 0"), findsOneWidget); Controller.to.increment(); await tester.pump(); expect(find.text("Count: 1"), findsOneWidget); await tester.tap(find.text('increment')); await tester.pump(); expect(find.text("Count: 2"), findsOneWidget); expect(find.text("lazy 0"), findsOneWidget); expect(find.text("single 0"), findsOneWidget); }); } class Controller2 extends GetxController { RxInt lazy = 0.obs; } class ControllerNonGlobal extends GetxController { RxInt nonGlobal = 0.obs; } class Controller extends GetxController { static Controller get to => Get.find(); RxInt counter = 0.obs; RxDouble doubleNum = 0.0.obs; RxString string = "string".obs; RxList list = [].obs; RxMap map = {}.obs; RxBool boolean = true.obs; void increment() { counter.value++; } } ================================================ FILE: test/state_manager/get_state_test.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:get/get.dart'; void main() { Get.lazyPut(() => Controller2()); testWidgets("GetxController smoke test", (test) async { await test.pumpWidget( MaterialApp( home: GetBuilder( init: Controller(), builder: (controller) => Column( children: [ Text( '${controller.counter}', ), TextButton( child: const Text("increment"), onPressed: () => controller.increment(), ), TextButton( child: const Text("incrementWithId"), onPressed: () => controller.incrementWithId(), ), GetBuilder( id: '1', didChangeDependencies: (_) { // print("didChangeDependencies called"); }, builder: (controller) { return Text('id ${controller.counter}'); }), GetBuilder(builder: (controller) { return Text('lazy ${controller.test}'); }), GetBuilder( init: ControllerNonGlobal(), global: false, builder: (controller) { return Text('single ${controller.nonGlobal}'); }) ], ), ), ), ); expect(find.text("0"), findsOneWidget); Controller.to.increment(); await test.pump(); expect(find.text("1"), findsOneWidget); await test.tap(find.text('increment')); await test.pump(); expect(find.text("2"), findsOneWidget); await test.tap(find.text('incrementWithId')); await test.pump(); expect(find.text("id 3"), findsOneWidget); expect(find.text("lazy 0"), findsOneWidget); expect(find.text("single 0"), findsOneWidget); }); // testWidgets( // "MixinBuilder with build null", // (test) async { // expect( // () => GetBuilder( // init: Controller(), // builder: null, // ), // throwsAssertionError, // ); // }, // ); } class Controller extends GetxController { static Controller get to => Get.find(); int counter = 0; void increment() { counter++; update(); } void incrementWithId() { counter++; update(['1']); } } class Controller2 extends GetxController { int test = 0; } class ControllerNonGlobal extends GetxController { int nonGlobal = 0; } ================================================ FILE: test/utils/extensions/context_extensions_test.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:get/get.dart'; import '../../navigation/utils/wrapper.dart'; void main() { testWidgets("Get.defaultDialog smoke test", (tester) async { await tester.pumpWidget(Wrapper(child: Container())); await tester.pumpAndSettle(); final BuildContext context = tester.element(find.byType(Container)); var mediaQuery = MediaQuery.of(context); expect(mediaQuery, context.mediaQuery); var mediaQuerySize = mediaQuery.size; expect(mediaQuerySize, context.mediaQuerySize); var theme = Theme.of(context); expect(theme, context.theme); var textTheme = theme.textTheme; expect(textTheme, context.textTheme); var devicePixelRatio = mediaQuery.devicePixelRatio; expect(devicePixelRatio, context.devicePixelRatio); var height = mediaQuerySize.height; expect(height, context.height); final heightTransformer = (mediaQuerySize.height - ((mediaQuerySize.height / 100) * 0)) / 1; expect(heightTransformer, context.heightTransformer()); var iconColor = theme.iconTheme.color; expect(iconColor, context.iconColor); var isDarkMode = (theme.brightness == Brightness.dark); expect(isDarkMode, context.isDarkMode); var orientation = mediaQuery.orientation; expect(orientation, context.orientation); var isLandscape = orientation == Orientation.landscape; expect(isLandscape, context.isLandscape); var mediaQueryShortestSide = mediaQuerySize.shortestSide; expect(mediaQueryShortestSide, context.mediaQueryShortestSide); var width = mediaQuerySize.width; expect(width, context.width); var isLargeTabletOrWider = (width >= 720); expect(isLargeTabletOrWider, context.isLargeTabletOrWider); var isPhoneOrLess = (width < 600); expect(isPhoneOrLess, context.isPhoneOrLess); var isPortrait = orientation == Orientation.portrait; expect(isPortrait, context.isPortrait); var isSmallTabletOrWider = (width >= 600); expect(isSmallTabletOrWider, context.isSmallTabletOrWider); var isTablet = isSmallTabletOrWider || isLargeTabletOrWider; expect(isTablet, context.isSmallTabletOrWider); var mediaQueryPadding = mediaQuery.padding; expect(mediaQueryPadding, context.mediaQueryPadding); var mediaQueryViewInsets = mediaQuery.viewInsets; expect(mediaQueryViewInsets, context.mediaQueryViewInsets); var mediaQueryViewPadding = mediaQuery.viewPadding; expect(mediaQueryViewPadding, context.mediaQueryViewPadding); var widthTransformer = (mediaQuerySize.width - ((mediaQuerySize.width / 100) * 0)) / 1; expect(widthTransformer, context.widthTransformer()); var ratio = heightTransformer / widthTransformer; expect(ratio, context.ratio()); var showNavbar = (width > 800); expect(showNavbar, context.showNavbar); var textScaleFactor = mediaQuery.textScaler; expect(textScaleFactor, context.textScaleFactor); }); } ================================================ FILE: test/utils/extensions/double_extensions_test.dart ================================================ import 'package:flutter_test/flutter_test.dart'; import 'package:get/utils.dart'; void main() { group('DoubleExt', () { test('toPrecision', () { expect(3.14159.toPrecision(2), equals(3.14)); expect(3.14159.toPrecision(4), equals(3.1416)); expect(1.0.toPrecision(0), equals(1.0)); expect(123456789.123456789.toPrecision(4), equals(123456789.1235)); expect((-3.14159).toPrecision(2), equals(-3.14)); }); test('milliseconds', () { expect(1000.0.ms, equals(const Duration(milliseconds: 1000))); expect( 1.5.ms, equals(const Duration(milliseconds: 1, microseconds: 500))); expect((-2000.0).ms, equals(const Duration(milliseconds: -2000))); }); test('seconds', () { expect(60.0.seconds, equals(const Duration(seconds: 60))); expect(1.5.seconds, equals(const Duration(milliseconds: 1500))); expect((-120.0).seconds, equals(const Duration(seconds: -120))); }); test('minutes', () { expect(2.5.minutes, equals(const Duration(minutes: 2, seconds: 30))); expect(1.2.minutes, equals(const Duration(seconds: 72))); expect((-3.0).minutes, equals(const Duration(minutes: -3))); }); test('hours', () { expect(1.5.hours, equals(const Duration(hours: 1, minutes: 30))); expect(0.25.hours, equals(const Duration(minutes: 15))); expect((-2.0).hours, equals(const Duration(hours: -2))); }); test('days', () { expect(1.5.days, equals(const Duration(days: 1, hours: 12))); expect(0.25.days, equals(const Duration(hours: 6))); expect((-3.0).days, equals(const Duration(days: -3))); }); }); } ================================================ FILE: test/utils/extensions/dynamic_extensions_test.dart ================================================ import 'package:flutter_test/flutter_test.dart'; import 'package:get/utils.dart'; void main() { test('String test', () { var value = 'string'; var expected = ''; void logFunction(String prefix, dynamic value, String info, {bool isError = false}) { expected = '$prefix $value $info'.trim(); } value.printError(logFunction: logFunction); expect(expected, 'Error: String string'); }); test('Int test', () { var value = 1; var expected = ''; void logFunction(String prefix, dynamic value, String info, {bool isError = false}) { expected = '$prefix $value $info'.trim(); } value.printError(logFunction: logFunction); expect(expected, 'Error: int 1'); }); test('Double test', () { var value = 1.0; var expected = ''; void logFunction(String prefix, dynamic value, String info, {bool isError = false}) { expected = '$prefix $value $info'.trim(); } value.printError(logFunction: logFunction); expect(expected, 'Error: double 1.0'); }); } ================================================ FILE: test/utils/extensions/int_extensions_test.dart ================================================ import 'package:flutter_test/flutter_test.dart'; import 'package:get/get.dart'; void main() { group('DurationExt', () { test('seconds', () { expect(1.seconds, equals(const Duration(seconds: 1))); expect( 2.5.seconds, equals(const Duration(seconds: 2, milliseconds: 500))); expect((-1).seconds, equals(const Duration(seconds: -1))); }); test('days', () { expect(1.days, equals(const Duration(days: 1))); expect((-1).days, equals(const Duration(days: -1))); }); test('hours', () { expect(1.hours, equals(const Duration(hours: 1))); expect((-1).hours, equals(const Duration(hours: -1))); }); test('minutes', () { expect(1.minutes, equals(const Duration(minutes: 1))); expect((-1).minutes, equals(const Duration(minutes: -1))); }); test('milliseconds', () { expect(1.milliseconds, equals(const Duration(milliseconds: 1))); expect((-1).milliseconds, equals(const Duration(milliseconds: -1))); }); test('microseconds', () { expect(1.microseconds, equals(const Duration(microseconds: 1))); expect((-1).microseconds, equals(const Duration(microseconds: -1))); }); test('ms', () { expect(1.ms, equals(const Duration(milliseconds: 1))); expect((-1).ms, equals(const Duration(milliseconds: -1))); }); }); } ================================================ FILE: test/utils/extensions/num_extensions_test.dart ================================================ import 'package:flutter_test/flutter_test.dart'; import 'package:get/utils.dart'; void main() { num x = 5; num y = 7; num z = 5; var doubleX = 2.1; var doubleY = 3.1; var doubleZ = 5.0; var pi = 3.14159265359; group('num extensions text', () { test('var.isLowerThan(value)', () { expect(x.isLowerThan(y), true); expect(y.isLowerThan(x), false); expect(x.isLowerThan(z), false); expect(doubleX.isLowerThan(doubleY), true); expect(doubleY.isLowerThan(pi), true); expect(x.isLowerThan(doubleX), false); expect(z.isLowerThan(doubleZ), false); }); test('var.isGreaterThan(value)', () { expect(x.isGreaterThan(y), false); expect(y.isGreaterThan(x), true); expect(x.isGreaterThan(z), false); expect(doubleX.isGreaterThan(doubleY), false); expect(doubleY.isGreaterThan(pi), false); expect(pi.isGreaterThan(3.14159265359), false); expect(y.isGreaterThan(doubleY), true); expect(z.isGreaterThan(doubleZ), false); }); test('var.isEqual(value)', () { expect(x.isEqual(y), false); expect(y.isEqual(x), false); expect(x.isEqual(5), true); expect(y.isEqual(7), true); expect(doubleX.isEqual(doubleY), false); expect(doubleY.isEqual(pi), false); expect(pi.isEqual(3.14159265359), true); expect(z.isEqual(doubleZ), true); }); }); } ================================================ FILE: test/utils/extensions/string_extensions_test.dart ================================================ import 'package:flutter_test/flutter_test.dart'; import 'package:get/utils.dart'; void main() { group('String extensions', () { const text = "oi"; const digit = "5"; const specialCaracters = "#\$!%@"; const alphaNumeric = "123asd"; const numbers = "123"; const letters = "foo"; // String notInitializedVar; test('var.isNum', () { expect(digit.isNum, true); expect(text.isNum, false); }); test('var.capitalizeAllWordsFirstLetter()', () { final List sentences = [ "getx", "this is an example sentence", "this is an example sentence with a number 5", "this is an example sentence with a number 5 and a special character #", "this is an example sentence with a number 5 and a special character # and b letter C", " emm, lemme think !", "Bro, $letters is a good word", "THIS IS A SENTENCE WITH ALL CAPITAL LETTERS", "" ]; expect(text.capitalizeAllWordsFirstLetter(), "Oi"); expect(digit.capitalizeAllWordsFirstLetter(), "5"); expect(specialCaracters.capitalizeAllWordsFirstLetter(), "#\$!%@"); expect(alphaNumeric.capitalizeAllWordsFirstLetter(), "123asd"); expect(numbers.capitalizeAllWordsFirstLetter(), "123"); expect(letters.capitalizeAllWordsFirstLetter(), "Foo"); expect(sentences[0].capitalizeAllWordsFirstLetter(), "Getx"); expect(sentences[1].capitalizeAllWordsFirstLetter(), "This Is An Example Sentence"); expect(sentences[2].capitalizeAllWordsFirstLetter(), "This Is An Example Sentence With A Number 5"); expect(sentences[3].capitalizeAllWordsFirstLetter(), "This Is An Example Sentence With A Number 5 And A Special Character #"); expect(sentences[4].capitalizeAllWordsFirstLetter(), "This Is An Example Sentence With A Number 5 And A Special Character # And B Letter C"); expect( sentences[5].capitalizeAllWordsFirstLetter(), "Emm, Lemme Think !"); expect(sentences[6].capitalizeAllWordsFirstLetter(), "Bro, Foo Is A Good Word"); expect(sentences[7].capitalizeAllWordsFirstLetter(), "This Is A Sentence With All Capital Letters"); expect(sentences[8].capitalizeAllWordsFirstLetter(), ""); }); test('var.isNumericOnly', () { expect(numbers.isNumericOnly, true); expect(letters.isNumericOnly, false); expect(specialCaracters.isNumericOnly, false); expect(alphaNumeric.isNumericOnly, false); }); test('var.isAlphabetOnly', () { expect(alphaNumeric.isAlphabetOnly, false); expect(numbers.isAlphabetOnly, false); expect(letters.isAlphabetOnly, true); }); test('var.isBool', () { const trueString = 'true'; // expect(notInitializedVar.isBool, false); expect(letters.isBool, false); expect(trueString.isBool, true); }); test('var.isVectorFileName', () { const path = "logo.svg"; const fullPath = "C:/Users/Getx/Documents/logo.svg"; expect(path.isVectorFileName, true); expect(fullPath.isVectorFileName, true); expect(alphaNumeric.isVectorFileName, false); }); test('var.isImageFileName', () { const jpgPath = "logo.jpg"; const jpegPath = "logo.jpeg"; const pngPath = "logo.png"; const gifPath = "logo.gif"; const bmpPath = "logo.bmp"; const svgPath = "logo.svg"; expect(jpgPath.isImageFileName, true); expect(jpegPath.isImageFileName, true); expect(pngPath.isImageFileName, true); expect(gifPath.isImageFileName, true); expect(bmpPath.isImageFileName, true); expect(svgPath.isImageFileName, false); }); test('var.isAudioFileName', () { const mp3Path = "logo.mp3"; const wavPath = "logo.wav"; const wmaPath = "logo.wma"; const amrPath = "logo.amr"; const oggPath = "logo.ogg"; const svgPath = "logo.svg"; expect(mp3Path.isAudioFileName, true); expect(wavPath.isAudioFileName, true); expect(wmaPath.isAudioFileName, true); expect(amrPath.isAudioFileName, true); expect(oggPath.isAudioFileName, true); expect(svgPath.isAudioFileName, false); }); test('var.isVideoFileName', () { const mp4Path = "logo.mp4"; const aviPath = "logo.avi"; const wmvPath = "logo.wmv"; const rmvbPath = "logo.rmvb"; const mpgPath = "logo.mpg"; const mpegPath = "logo.mpeg"; const threegpPath = "logo.3gp"; const svgPath = "logo.svg"; expect(mp4Path.isVideoFileName, true); expect(aviPath.isVideoFileName, true); expect(wmvPath.isVideoFileName, true); expect(rmvbPath.isVideoFileName, true); expect(mpgPath.isVideoFileName, true); expect(mpegPath.isVideoFileName, true); expect(threegpPath.isVideoFileName, true); expect(svgPath.isAudioFileName, false); }); test('var.isTxtFileName', () { const txtPath = 'file.txt'; expect(txtPath.isTxtFileName, true); expect(alphaNumeric.isTxtFileName, false); }); test('var.isDocumentFileName', () { const docPath = "file.doc"; const docxPath = "file.docx"; expect(docPath.isDocumentFileName, true); expect(docxPath.isDocumentFileName, true); expect(alphaNumeric.isDocumentFileName, false); }); test('var.isExcelFileName', () { const xlsPath = "file.xls"; const xlsxPath = "file.xlsx"; expect(xlsPath.isExcelFileName, true); expect(xlsxPath.isExcelFileName, true); expect(alphaNumeric.isExcelFileName, false); }); test('var.isPPTFileName', () { const pptPath = "file.ppt"; const pptxPath = "file.pptx"; expect(pptPath.isPPTFileName, true); expect(pptxPath.isPPTFileName, true); expect(alphaNumeric.isPPTFileName, false); }); test('var.isAPKFileName', () { const apkPath = "file.apk"; expect(apkPath.isAPKFileName, true); expect(alphaNumeric.isAPKFileName, false); }); test('var.isPDFFileName', () { const pdfPath = "file.pdf"; expect(pdfPath.isPDFFileName, true); expect(alphaNumeric.isPDFFileName, false); }); test('var.isHTMLFileName', () { const htmlPath = "file.html"; expect(htmlPath.isHTMLFileName, true); expect(alphaNumeric.isHTMLFileName, false); }); test('var.isURL', () { // Url's generated in https://www.randomlists.com/urls final urls = [ 'http://www.example.com/aunt/babies.aspx#act', 'http://adjustment.example.com/bedroom/animal.htm', 'http://blade.example.com/arch/basketball', 'https://www.example.com/air/advice.php', 'http://www.example.com/balance/arch.html?blow=aftermath&bait=bath', 'http://authority.example.com/', 'http://example.com/advice.html', 'https://www.example.com/', 'https://www.example.com/bee?act=art&bells=board', 'http://example.org/', 'https://www.example.com/', 'https://example.com/bed', 'https://www.example.edu/acoustics', 'https://www.example.com/bells', 'http://board.example.com/', 'http://book.example.com/afterthought?advertisement=ball&birth=argument', 'http://birds.example.org/ball.aspx?apparatus=border&brother=aftermath', 'https://www.example.org/books/book?bedroom=birds', 'http://advice.example.com/', 'http://example.com/', 'http://example.com/bedroom/alarm', 'https://example.com/advice/approval', 'http://anger.example.net/?breath=brother&air=bell#ball', 'http://appliance.example.com/bee/badge', 'http://www.example.org/berry.aspx', 'http://example.org/', 'http://birds.example/', ]; for (final url in urls) { expect(url.isURL, true); } expect(alphaNumeric.isURL, false); }); test('var.isEmail', () { final emails = [ 'hellfire@comcast.net', 'hllam@icloud.com', 'tskirvin@live.com', 'choset@comcast.net', 'parksh@live.com', 'kassiesa@yahoo.com', 'kramulous@comcast.net', 'froodian@me.com', 'shawnce@yahoo.ca', 'cgreuter@gmail.com', 'aprakash@verizon.net', 'dhrakar@gmail.com', 'wmszeliga@yahoo.ca', 'bmorrow@icloud.com', 'seurat@comcast.net', 'dialworld@yahoo.ca', 'johndo@yahoo.ca', 'empathy@yahoo.com.pt', 'openldap@verizon.net', 'elflord@outlook.com', 'kaiser@me.com', 'carcus@att.net', 'garland@hotmail.com', 'clkao@yahoo.ca', 'daveed@mac.com', 'parasite@icloud.com', 'drolsky@aol.com', 'reziac@outlook.com', 'storerm@yahoo.ca', 'johnbob@hotmail.com.br', ]; for (final email in emails) { expect(email.isEmail, true); } expect(alphaNumeric.isEmail, false); }); test('var.isPhoneNumber', () { final phoneNumbers = [ '+1202-555-0145', '+1202-555-0139', '+1202-555-0101', '+1202-555-0136', '+1202-555-0190', '+1202-555-0156', '(738) 952-5253', '(861) 965-1597', '(732) 372-9760', '(532) 766-4719', '(987) 472-7813', '(455) 443-8171', '(915) 685-8658', '(572) 207-1898', '(81) 6 2499-9538', '(31) 32304-4263', '(64) 25242-6375', '(41) 19308-7925', '(67) 61684-0395', '(60) 54706-3569', '(31) 33110055', '(11) 3344-5599', '(31) 977447788', '(31) 66557744', '(21) 946576541', '(11) 3432-3333', '02131973585858' ]; for (final phone in phoneNumbers) { // print('testing $phone'); expect(phone.isPhoneNumber, true); } const bigRandomNumber = '168468468465241327987624987327987'; expect(bigRandomNumber.isPhoneNumber, false); expect(alphaNumeric.isPhoneNumber, false); }); test('var.isDateTime', () { final dateTimes = [ '2003-07-05 05:51:47.000Z', '1991-05-11 11:57:30.000Z', '2002-01-04 10:00:41.000Z', '1995-11-04 19:43:25.000Z', '2006-07-12 20:06:46.000Z', '2000-08-10 00:06:23.000Z', '1998-07-31 10:56:50.000Z', '1995-04-27 11:49:34.000Z', '1998-07-26 15:43:11.000Z', '1999-02-04 10:03:01.000Z', '1998-05-02 12:17:55.000Z', '2013-05-26 10:47:22.000Z', '1991-07-07 20:25:42.000Z', '2018-11-03 09:27:38.000Z', '1992-12-22 08:20:26.000Z', '1997-07-01 23:11:59.000Z', '2012-04-13 16:00:04.000Z', '1997-01-06 18:37:51.000Z', '2008-08-23 11:11:29.000Z', '1996-02-06 03:46:43.000Z', '2016-01-03 10:57:15.000Z', '2014-04-16 17:20:50.000Z', '1994-07-13 03:55:16.000Z', '2004-11-15 03:45:11.000Z', '2007-12-18 18:21:21.000Z', '1995-01-31 03:55:44.000Z', '2013-08-09 04:48:37.000Z', '2001-09-07 17:13:55.000Z', '1993-06-18 13:21:21.000Z', '1991-02-06 03:05:47.000Z', '2000-09-22 18:48:55.000Z', '2000-06-01 02:13:57.000Z', '1991-08-07 21:08:35.000Z', '1998-08-15 07:27:12.000Z', '2002-07-03 10:34:25.000Z', '2013-10-05 00:37:45.000Z', '2012-09-10 20:07:21.000Z', '2017-06-18 14:38:06.000Z', '2000-03-09 11:27:49.000Z', '2016-01-16 22:01:20.000Z', ]; for (final dateTime in dateTimes) { // print('testing $dateTime'); expect(dateTime.isDateTime, true); } expect(alphaNumeric.isDateTime, false); }); test('var.isMD5', () { final md5s = [ '176cfa006065a2a2bd8d3f1f83531b64', '713fca6d088132e863497a79d1bd9572', '7decc2fb2aca5cbd8a2cae5de1b50edb', '85ed9bc4e4a8ae65add67886f5dfe02f', 'e4f0097f84a11f0298c83ecf6aa0fec3', '70a2712b47127b431d7119b3a511b145', 'dd54069e3f97787e79592f6e3a307e93', '5b64677b69da7370ee69523281ce935c', '6150ce23f4b071e1cde49021b57c5a17', '2781566b09a84a695297482cdcb1ffd0', '54fe4ce16862aac01768b5831390d557', '2747268afaa9898a320b8cc5580f143e', 'ce3c2d105fb2740c5bf59347b47603a8', 'cccef3bbc8cd2530c6de78af586ebcee', '502115b10c767f50ab55270be095512e', 'b084ea385e849eaedf4fefaf6dd5f1a9', '1f167339977225fe63a86388083fc64f', '6abd5f472dba5e4688ad6dd14f975870', '7e7f9ef53fb6e3ce2a4fb56665548eb8', 'de134fce82421dd2b3fab751fbfa190d', 'b0d8492572a52d1f2360535612c5dc82', ]; for (final md5 in md5s) { expect(md5.isMD5, true); } expect(alphaNumeric.isMD5, false); }); test('var.isSHA1', () { final sha1s = [ '1A310CF5DC8CE513586F74EE19CE90BD4BCC5AED', 'B458B077B5075C316CFD03619D627F529A0555BF', '902C6A2850B4348BE445D637689CCAE5C5EF3552', '8DC86C0DD0FD2960D62573AB142F90572A7421D5', '7E18C8EA5F05BB2F385A9E34657B8D439A83BF82', '3EC857A133E801C0B3198371C17C1A3A3D73DFE8', 'E32590E41805BEFD524205DAE0A56F429DCCC4E7', '943A9164A126457203680B49F0309B5F15F0117E', 'C5E1442484AF49A92E1CC51F95AE4E8305F49DB6', 'B0C3B071F8ADBEE2222AA07ECFF51C3C040AA0A0', '722ED6929057BF801F29590C423A40F4EF8C710E', 'F484FA4DC5EC1E063F0752112D9BF3B9763D6E41', '2A87522644011223A27FD62C87FA926A1838F271', ]; for (final sha1 in sha1s) { expect(sha1.isSHA1, true); } expect(alphaNumeric.isSHA1, false); }); test('var.isSHA256', () { final sha256s = [ 'FC694FFE78167EAE21EA4EBF072D8AB6ECF847162D1F65600BF019BA9805DB2D', '3B64F1C349B548E72688A8EEFFA2F418A62BA2E22CF5BD954B4B1912C963D7FA', 'EF69D763148B8A222980BD164943F754937DF12771083889DDB69C18245C2904', '9896D3134156E546FFC003C2C9CFED88D46C2BC214B39CF21192EAAF875A7C0A', '2C70E9735D7DAB56427BAA09E6C63912BEAD9C7938F6B16C4954B78F46D1C3CF', '423E095C8074BC1C440D874D999C18025445CD39211D98362E827E55863DD0B2', '4FCDB44D5521663F713A5821DE9401D64D44050C2AF62EBA758B1D128AC4C279', 'BD91C9BBC044C94C283D0DF3AA1E8CDBF1BF35BF325E8196BA15FCA5238A3A40', '7B9434447F3B1236221D40CB707D1909886CC9E8CA25EB18DCCFDC70F0A3AE9F', 'FBD3A0B1C5F9906EF3BFB5EDD846F77BA252070E036EC1F4F57BDD912F02987D', 'B8369EE116ADE797285DC973DDAA69433F255DC0AEEC7936378D4D08B2A7FDD2', '6B6FE6891A5DFCCF2900A2A1F513196827AF5A95AB2DE1590B878BEFCCF12603', 'F2CB3614CD070450912EBEC399C63527D2A839C4E5BB2FE281BA1C5D5EA64257', '822805E8FA05909AD7D3D6DBFBB1AE61D7A3C70209DB2A37C415BD5E11764866', '40DAC792B52101BB1506AD880F0378EFACF46B019427A3D0E01DE2B09B06B6ED', 'E765A129579AF2F31C681973844490F8EA146DA8ADC07671F9FB71F0FE10E296', '2B5B6DC7EF398D1420D23327295BCDCDDA8AAEDB7FE6C6129D1D31432B676CB7', '67F20826370162C472791055E10E44624D40E35F29E60592B239692836474323', 'BD16B1ED024E353B5B2334201EC63C0B3E181F0DFD226A36825EF18F6A7D8D97', 'AC98F969AA56810BE672C770BE30EF79F7F77AE6EFB2A90D56FA2AD5506D8BD7', ]; for (final sha256 in sha256s) { expect(sha256.isSHA256, true); } expect(alphaNumeric.isSHA256, false); }); test('var.isBinary', () { final binaries = [ '00111100', '00001111', '10110110', '01101110', '01110101', '00010100', '11100010', '11000001', '11000110', '11011101', '10001101', '10101110', '11001110', '10001011', '11111101', '11010110', '11110011', '01111010', '11110011', '01000111', ]; for (final binary in binaries) { expect(binary.isBinary, true); } expect(alphaNumeric.isBinary, false); }); test('var.isIPv4', () { final ipv4s = [ '155.162.247.250', '121.99.222.180', '142.197.183.237', '176.60.213.134', '12.190.123.58', '105.75.28.173', '121.120.116.138', '20.195.194.189', '234.171.207.97', '153.122.129.170', '224.226.28.80', '236.196.62.84', '122.71.160.46', '151.24.85.63', '37.109.242.32', '235.47.62.53', '151.1.242.190', '227.197.221.85', '12.118.136.231', '51.73.246.208', ]; for (final ipv4 in ipv4s) { expect(ipv4.isIPv4, true); } expect(alphaNumeric.isIPv4, false); }); test('var.isIPv6', () { final ipv6s = [ 'f856:62fc:9091:e649:e928:d771:f40c:1439', 'b8d5:3f85:5ae5:c63a:6b5f:f7e6:ea6b:871d', '2f91:979a:90b0:55d1:40b7:3e6f:a210:598e', 'd35d:49fc:fbe4:9841:e4d3:f006:b04b:e242', '2e0f:2912:e4e8:33d5:e833:0ac5:c73a:30b3', '6af9:878a:a80f:f520:fc2b:a05c:b0dd:b93f', '3329:1ce5:ab09:0120:945c:057b:ed4a:7869', 'b77d:5523:2f1b:ff07:93a5:378f:a9c7:e2f2', 'b669:64fa:1be7:af47:28fc:07f4:38bd:ae05', 'aa77:1f7e:8539:a01a:706d:6f74:7fc3:8407', '16f9:9bcc:32d6:96de:5087:620b:c0c0:25cb', 'baad:273f:7e63:29cd:c742:c1ed:d0f9:062d', 'ae62:5b09:05fa:4611:5da9:a40a:f1ef:2a9d', '4d2a:353a:9f6b:2070:9605:ab97:92c0:7956', 'bfcb:39f8:5119:458f:85fa:9e54:8c53:acd5', '0c1a:c6f3:06af:9588:23b4:e7fb:c307:febd', 'ddaa:3c91:f554:dbe5:8447:9464:a9ae:2200', '8787:c939:5002:a4f6:19b2:6521:4cde:8111', 'b515:5c17:6590:46dd:4ca8:1db3:a86c:e006', '1083:d492:f42e:2c99:f050:f67f:07c5:23f9', ]; for (final ipv6 in ipv6s) { expect(ipv6.isIPv6, true); } expect(alphaNumeric.isIPv6, false); }); test('var.isHexadecimal', () { final hexadecimals = [ '#56E97B', '#597E2A', '#F45D5C', '#A350DC', '#2DA48E', '#98CB3C', '#F7DCD1', '#B1F9BE', '#D17855', '#6F35CB', '#DCBE21', '#4C2E46', '#145F3F', '#F9776D', '#62E9DC', '#2F1030', '#C4F888', '#8E6D85', '#8C64CE', '#4DFF4E', ]; for (final hexadecimal in hexadecimals) { expect(hexadecimal.isHexadecimal, true); } expect(alphaNumeric.isHexadecimal, false); }); test('var.isPalindrome', () { final palindroms = [ 'Anna', 'Civic', 'Kayak', 'Level', 'Madam', 'Mom', 'Noon', 'Racecar', 'Radar', 'Redder', 'Refer', 'Repaper', 'Don\'t nod.', 'I did, did I?', 'My gym', 'Red rum, sir, is murder', 'Step on no pets', 'Top spot', 'Was it a cat I saw?', 'Eva, can I see bees in a cave?', 'No lemon, no melon', 'A base do teto desaba.', 'A cara rajada da jararaca.', 'Acuda cadela da Leda caduca.', 'A dama admirou o rim da amada.', 'A Daniela ama a lei? Nada!', // TODO make isPalindrome regex support UTF8 characters // 'Adias a data da saída.', // 'A diva em Argel alegra-me a vida.', // 'A droga do dote é todo da gorda.', // 'A gorda ama a droga.', // 'A grama é amarga.', // 'Aí, Lima falou: “Olá, família!”.', // 'anã', // 'anilina', // 'ata', // 'arara', // 'asa', // 'ele', // 'esse', // 'mamam', // 'matam', // 'metem', // 'mirim', // 'oco', // 'omissíssimo', ]; for (final palindrom in palindroms) { // print("testing $palindrom"); expect(palindrom.isPalindrome, true); } expect(alphaNumeric.isPalindrome, false); }); test('var.isPassport', () { final passports = [ '12ss46', 'jdmg5dg', '5f7fj5d7', 'w8a9s6f3z', ]; for (final passport in passports) { expect(passport.isPassport, true); } expect(specialCaracters.isPassport, false); }); test('var.isCurrency', () { final currencies = [ 'R\$50.58', '\$82.48', '₩54.24', '¥81.04', '€4.06', '₹37.40', '₽18.12', 'fr95.15', 'R81.04', '9.35USD', '98.48AUD', '29.20NZD', '50.58CAD', '82.48CHF', '54.24GBP', '81.04CNY', '4.06EUR', '37.40JPY', '18.12IDR', '95.15MXN', '81.04NOK', '9.35KRW', '98.48TRY', '29.20INR', ]; for (final currency in currencies) { // print('currency $currency'); expect(currency.isCurrency, true); } expect(specialCaracters.isCurrency, false); }); test('var.isCpf', () { final cpfs = [ '370.559.380-31', '055.878.430-50', '655.232.870-24', '86497047000', '12341309046', '31496294033', ]; for (final cpf in cpfs) { expect(cpf.isCpf, true); } expect(specialCaracters.isCpf, false); }); test('var.isCnpj', () { final cnpjs = [ '11.066.893/0001-94', '21.883.660/0001-38', '59.705.218/0001-94', ]; for (final cnpj in cnpjs) { expect(cnpj.isCnpj, true); } expect(specialCaracters.isCnpj, false); }); test('var.isCaseInsensitiveContains(string)', () { const phrase = 'Back to Square One'; expect(phrase.isCaseInsensitiveContains('to'), true); expect(phrase.isCaseInsensitiveContains('square'), true); expect(phrase.isCaseInsensitiveContains('On'), true); expect(phrase.isCaseInsensitiveContains('foo'), false); }); test('var.isCaseInsensitiveContainsAny(string)', () { const phrase = 'Back to Square One'; expect(phrase.isCaseInsensitiveContainsAny('to'), true); expect(phrase.isCaseInsensitiveContainsAny('square'), true); expect(phrase.isCaseInsensitiveContainsAny('On'), true); expect(phrase.isCaseInsensitiveContainsAny('foo'), false); expect('to'.isCaseInsensitiveContainsAny(phrase), true); expect('square'.isCaseInsensitiveContainsAny('qu'), true); }); test('var.capitalize', () { expect('foo bar'.capitalize, 'Foo Bar'); expect('FoO bAr'.capitalize, 'Foo Bar'); expect('FOO BAR'.capitalize, 'Foo Bar'); // expect(null.capitalize, null); expect(''.capitalize, ''); expect('foo bar '.capitalize, 'Foo Bar '); }); test('var.capitalizeFirst', () { expect('foo bar'.capitalizeFirst, 'Foo bar'); expect('FoO bAr'.capitalizeFirst, 'Foo bar'); expect('FOO BAR'.capitalizeFirst, 'Foo bar'); expect(''.capitalizeFirst, ''); }); test('var.removeAllWhitespace', () { //late String nullString; expect('foo bar'.removeAllWhitespace, 'foobar'); expect('foo'.removeAllWhitespace, 'foo'); expect(''.removeAllWhitespace, ''); // expect(nullString.removeAllWhitespace, null); }); test('var.camelCase', () { expect('foo bar'.camelCase, 'fooBar'); expect('the fox jumped in the water'.camelCase, 'theFoxJumpedInTheWater'); expect('foo_bar'.camelCase, 'fooBar'); expect(''.camelCase, null); }); test('var.numericOnly()', () { expect('date: 2020/09/13, time: 00:00'.numericOnly(), '202009130000'); expect( 'and 1, and 2, and 1 2 3'.numericOnly(), '12123', ); expect(''.numericOnly(), ''); }); }); } ================================================ FILE: test/utils/extensions/widget_extensions_test.dart ================================================ import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:get/utils.dart'; class Foo extends StatelessWidget { const Foo({super.key}); @override Widget build(BuildContext context) { return const SizedBox.shrink(); } } void main() { group('Group test for PaddingX Extension', () { testWidgets('Test of paddingAll', (tester) async { Widget containerTest = const Foo(); expect(find.byType(Padding), findsNothing); await tester.pumpWidget(containerTest.paddingAll(16)); expect(find.byType(Padding), findsOneWidget); }); testWidgets('Test of paddingOnly', (tester) async { Widget containerTest = const Foo(); expect(find.byType(Padding), findsNothing); await tester.pumpWidget(containerTest.paddingOnly(top: 16)); expect(find.byType(Padding), findsOneWidget); }); testWidgets('Test of paddingSymmetric', (tester) async { Widget containerTest = const Foo(); expect(find.byType(Padding), findsNothing); await tester.pumpWidget(containerTest.paddingSymmetric(vertical: 16)); expect(find.byType(Padding), findsOneWidget); }); testWidgets('Test of paddingZero', (tester) async { Widget containerTest = const Foo(); expect(find.byType(Padding), findsNothing); await tester.pumpWidget(containerTest.paddingZero); expect(find.byType(Padding), findsOneWidget); }); }); group('Group test for MarginX Extension', () { testWidgets('Test of marginAll', (tester) async { Widget containerTest = const Foo(); await tester.pumpWidget(containerTest.marginAll(16)); expect(find.byType(Container), findsOneWidget); }); testWidgets('Test of marginOnly', (tester) async { Widget containerTest = const Foo(); await tester.pumpWidget(containerTest.marginOnly(top: 16)); expect(find.byType(Container), findsOneWidget); }); testWidgets('Test of marginSymmetric', (tester) async { Widget containerTest = const Foo(); await tester.pumpWidget(containerTest.marginSymmetric(vertical: 16)); expect(find.byType(Container), findsOneWidget); }); testWidgets('Test of marginZero', (tester) async { Widget containerTest = const Foo(); await tester.pumpWidget(containerTest.marginZero); expect(find.byType(Container), findsOneWidget); }); }); } ================================================ FILE: test/utils/get_utils_test.dart ================================================ import 'package:flutter_test/flutter_test.dart'; import 'package:get/get.dart'; class TestClass { final name = "John"; } class EmptyClass {} void main() { dynamic newId(dynamic e) => e; test('null isNullOrBlank should be true for null', () { expect(GetUtils.isNullOrBlank(null), true); }); test('isNullOrBlank should be false for unsupported types', () { expect(GetUtils.isNullOrBlank(5), false); expect(GetUtils.isNullOrBlank(0), false); expect(GetUtils.isNullOrBlank(5.0), equals(false)); expect(GetUtils.isNullOrBlank(0.0), equals(false)); TestClass? testClass; expect(GetUtils.isNullOrBlank(testClass), equals(true)); expect(GetUtils.isNullOrBlank(TestClass()), equals(false)); expect(GetUtils.isNullOrBlank(EmptyClass()), equals(false)); }); test('isNullOrBlank should validate strings', () { expect(GetUtils.isNullOrBlank(""), true); expect(GetUtils.isNullOrBlank(" "), true); expect(GetUtils.isNullOrBlank("foo"), false); expect(GetUtils.isNullOrBlank(" foo "), false); expect(GetUtils.isNullOrBlank("null"), false); }); test('isNullOrBlank should validate iterables', () { expect(GetUtils.isNullOrBlank([].map(newId)), true); expect(GetUtils.isNullOrBlank([1].map(newId)), false); }); test('isNullOrBlank should validate lists', () { expect(GetUtils.isNullOrBlank(const []), true); expect(GetUtils.isNullOrBlank(['oi', 'foo']), false); expect(GetUtils.isNullOrBlank([{}, {}]), false); expect(GetUtils.isNullOrBlank(['foo'][0]), false); }); test('isNullOrBlank should validate sets', () { expect(GetUtils.isNullOrBlank({}), true); expect(GetUtils.isNullOrBlank({1}), false); expect(GetUtils.isNullOrBlank({'fluorine', 'chlorine', 'bromine'}), false); }); test('isNullOrBlank should validate maps', () { expect(GetUtils.isNullOrBlank({}), true); expect(GetUtils.isNullOrBlank({1: 1}), false); expect(GetUtils.isNullOrBlank({"other": "thing"}), false); final map = {"foo": 'bar', "one": "um"}; expect(GetUtils.isNullOrBlank(map["foo"]), false); expect(GetUtils.isNullOrBlank(map["other"]), true); }); group('GetUtils.isLength* functions', () { test('isLengthEqualTo should validate iterable lengths', () { // iterables should cover list and set expect(GetUtils.isLengthEqualTo([].map(newId), 0), true); expect(GetUtils.isLengthEqualTo([1, 2].map(newId), 2), true); expect(GetUtils.isLengthEqualTo({}, 0), true); expect(GetUtils.isLengthEqualTo({1: 1, 2: 1}, 2), true); expect(GetUtils.isLengthEqualTo({}, 2), false); expect(GetUtils.isLengthEqualTo("", 0), true); expect(GetUtils.isLengthEqualTo("a", 0), false); expect(GetUtils.isLengthEqualTo("a", 1), true); }); test('isLengthGreaterOrEqual should validate lengths', () { // iterables should cover list and set expect(GetUtils.isLengthGreaterOrEqual([].map(newId), 0), true); expect(GetUtils.isLengthGreaterOrEqual([1, 2].map(newId), 2), true); expect(GetUtils.isLengthGreaterOrEqual([1, 2].map(newId), 1), true); expect(GetUtils.isLengthGreaterOrEqual({}, 0), true); expect(GetUtils.isLengthGreaterOrEqual({1: 1, 2: 1}, 1), true); expect(GetUtils.isLengthGreaterOrEqual({1: 1, 2: 1}, 2), true); expect(GetUtils.isLengthGreaterOrEqual({}, 2), false); expect(GetUtils.isLengthGreaterOrEqual("", 0), true); expect(GetUtils.isLengthGreaterOrEqual("a", 0), true); expect(GetUtils.isLengthGreaterOrEqual("", 1), false); }); test('isLengthLessOrEqual should validate lengths', () { // iterables should cover list and set expect(GetUtils.isLengthLessOrEqual([].map(newId), 0), true); expect(GetUtils.isLengthLessOrEqual([1, 2].map(newId), 2), true); expect(GetUtils.isLengthLessOrEqual([1, 2].map(newId), 1), false); expect(GetUtils.isLengthLessOrEqual({}, 0), true); expect(GetUtils.isLengthLessOrEqual({1: 1, 2: 1}, 1), false); expect(GetUtils.isLengthLessOrEqual({1: 1, 2: 1}, 3), true); expect(GetUtils.isLengthLessOrEqual({}, 2), true); expect(GetUtils.isLengthLessOrEqual("", 0), true); expect(GetUtils.isLengthLessOrEqual("a", 2), true); expect(GetUtils.isLengthLessOrEqual("a", 0), false); }); }); } ================================================ FILE: test/utils/platform_test.dart ================================================ @TestOn('vm') library; import 'dart:io'; import 'package:flutter_test/flutter_test.dart'; import 'package:get/get.dart'; void main() { test('Platform test', () { expect(GetPlatform.isAndroid, Platform.isAndroid); expect(GetPlatform.isIOS, Platform.isIOS); expect(GetPlatform.isFuchsia, Platform.isFuchsia); expect(GetPlatform.isLinux, Platform.isLinux); expect(GetPlatform.isMacOS, Platform.isMacOS); expect(GetPlatform.isWindows, Platform.isWindows); expect(GetPlatform.isWeb, false); }); } ================================================ FILE: test/utils/platform_web_test.dart ================================================ @TestOn('browser') library; import 'dart:io'; import 'package:flutter_test/flutter_test.dart'; import 'package:get/get.dart'; void main() { test('Platform test', () { expect(GetPlatform.isAndroid, Platform.isAndroid); expect(GetPlatform.isIOS, Platform.isIOS); expect(GetPlatform.isFuchsia, Platform.isFuchsia); expect(GetPlatform.isLinux, Platform.isLinux); expect(GetPlatform.isMacOS, Platform.isMacOS); expect(GetPlatform.isWindows, Platform.isWindows); expect(GetPlatform.isWeb, true); }); }