From da372fb214c3c8609ca3f310a9106802b8f215db Mon Sep 17 00:00:00 2001 From: Remi Rousselet Date: Sat, 10 Feb 2024 16:21:05 +0100 Subject: [PATCH] Merge with master --- .github/PULL_REQUEST_TEMPLATE.md | 34 + CONTRIBUTING.md | 4 +- all_lint_rules.yaml | 2 + examples/counter/README.md | 2 +- examples/counter/pubspec.yaml | 2 +- .../marvel/lib/src/configuration.freezed.dart | 4 +- examples/marvel/lib/src/marvel.freezed.dart | 12 +- examples/marvel/lib/src/result.freezed.dart | 6 +- examples/marvel/lib/src/screens/home.dart | 7 +- .../marvel/lib/src/screens/home.freezed.dart | 6 +- examples/marvel/pubspec.yaml | 2 +- examples/pub/lib/pub_repository.freezed.dart | 20 +- .../stackoverflow/lib/question.freezed.dart | 8 +- examples/stackoverflow/lib/tag.freezed.dart | 4 +- examples/stackoverflow/lib/user.freezed.dart | 6 +- packages/flutter_riverpod/CHANGELOG.md | 5 + .../flutter_riverpod/lib/src/consumer.dart | 3 - .../flutter_riverpod/lib/src/framework.dart | 2 +- packages/flutter_riverpod/pubspec.yaml | 4 +- packages/hooks_riverpod/CHANGELOG.md | 5 + packages/hooks_riverpod/pubspec.yaml | 4 +- packages/riverpod/CHANGELOG.md | 638 +++++++++-------- .../riverpod/example/lib/models.freezed.dart | 10 +- packages/riverpod/lib/src/common.dart | 25 +- .../lib/src/framework/foundation.dart | 5 + packages/riverpod/lib/src/result.dart | 4 +- packages/riverpod/pubspec.yaml | 4 +- .../test/framework/async_value_test.dart | 41 ++ packages/riverpod_analyzer_utils/CHANGELOG.md | 6 +- packages/riverpod_analyzer_utils/pubspec.yaml | 2 +- .../generator_provider_declaration_test.dart | 2 +- packages/riverpod_annotation/CHANGELOG.md | 4 + .../lib/src/riverpod_annotation.dart | 71 +- packages/riverpod_generator/CHANGELOG.md | 11 + .../src/templates/class_based_provider.dart | 4 + .../lib/src/templates/family.dart | 3 + .../lib/src/validation.dart | 31 + .../riverpod_generator/test/error_test.dart | 147 ++++ packages/riverpod_lint/CHANGELOG.md | 11 + .../convert_to_stateful_base_widget.dart | 124 ++++ .../convert_to_stateless_base_widget.dart | 270 +++++-- .../src/assists/convert_to_widget_utils.dart | 30 + .../lints/async_value_nullable_pattern.dart | 10 +- packages/riverpod_lint/pubspec.yaml | 2 +- .../riverpod_lint_flutter_test/pubspec.yaml | 3 +- .../pubspec_overrides.yaml | 4 +- ...rt_class_based_provider_to_functional.diff | 63 ++ ...rt_class_based_provider_to_functional.json | 1 - ...ass_based_provider_to_functional_test.dart | 16 +- ...rt_functional_provider_to_class_based.diff | 33 + ...rt_functional_provider_to_class_based.json | 1 - ...nctional_provider_to_class_based_test.dart | 16 +- .../convert_to_consumer_stateful_widget.diff | 308 ++++++++ .../convert_to_consumer_stateful_widget.json | 1 - .../convert_to_consumer_widget.diff | 330 +++++++++ .../convert_to_consumer_widget.json | 1 - .../convert_to_hook_consumer_widget.diff | 318 +++++++++ .../convert_to_hook_consumer_widget.json | 1 - .../convert_to_hook_widget.diff | 314 ++++++++ .../convert_to_hook_widget.json | 1 - ...vert_to_stateful_hook_consumer_widget.diff | 320 +++++++++ ...vert_to_stateful_hook_consumer_widget.json | 1 - .../convert_to_stateful_hook_widget.diff | 346 +++++++++ .../convert_to_stateful_hook_widget.json | 1 - .../convert_to_stateful_widget.diff | 245 +++++++ .../convert_to_stateful_widget.json | 1 - .../convert_to_stateless_widget.diff | 240 +++++++ .../convert_to_stateless_widget.json | 1 - .../convert_to_widget/convert_to_widget.dart | 85 +++ .../convert_to_widget_test.dart | 76 +- .../test/assists/empty.diff | 0 .../test/assists/empty.json | 1 - .../assists/wrap_widget/wrap_widget_test.dart | 22 +- .../wrap_widget/wrap_with_consumer.diff | 28 + .../wrap_widget/wrap_with_consumer.json | 1 - .../wrap_widget/wrap_with_provider_scope.diff | 28 + .../wrap_widget/wrap_with_provider_scope.json | 1 - .../test/golden.dart | 45 +- .../async_value_nullable_pattern.dart | 23 + .../fix/async_value_nullable_pattern.diff | 24 + .../fix/async_value_nullable_pattern.json | 1 - .../async_value_nullable_pattern_test.dart | 16 +- .../notifier_build/fix/notifier_build.diff | 16 + .../notifier_build/fix/notifier_build.json | 1 - .../fix/notifier_build_test.dart | 15 +- .../notifier_extends/notifier_extends.diff | 24 + .../notifier_extends/notifier_extends.json | 1 - .../notifier_extends_test.dart | 15 +- .../provider_dependencies.diff | 137 ++++ .../provider_dependencies.json | 1 - .../provider_dependencies_test.dart | 15 +- .../lints/provider_parameters.freezed.dart | 4 +- website/docs/case_studies/cancel.mdx | 2 +- .../cancel/detail_screen/codegen.freezed.dart | 4 +- .../activity/codegen.freezed.dart | 4 +- .../full_app/codegen.freezed.dart | 4 +- .../todo_list_provider/codegen.freezed.dart | 4 +- .../why_immutability/codegen.freezed.dart | 4 +- .../docs/essentials/combining_requests.mdx | 22 +- website/docs/essentials/faq.mdx | 2 +- website/docs/essentials/first_request.mdx | 2 +- .../codegen/activity.freezed.dart | 4 +- website/docs/essentials/side_effects.mdx | 2 +- .../codegen/todo_list_notifier.freezed.dart | 4 +- .../codegen/todo_list_provider.freezed.dart | 4 +- website/docs/essentials/testing.mdx | 2 +- .../from_provider/helpers/item.freezed.dart | 4 +- website/docs/from_provider/quickstart.mdx | 5 +- .../remote_todos/codegen.freezed.dart | 4 +- .../todos/codegen.freezed.dart | 4 +- .../current.json | 6 +- .../current/cookbooks/refresh.mdx | 10 + .../current/cookbooks/search_as_we_type.mdx | 133 ++++ .../current/cookbooks/testing_dart.dart | 51 ++ .../current/cookbooks/testing_flutter.dart | 39 + .../current/cookbooks/testing_full.dart | 98 +++ .../cookbooks/testing_original_test_dart.dart | 64 ++ .../testing_original_test_flutter.dart | 56 ++ .../cookbooks/testing_override_info.dart | 42 ++ .../current/cookbooks/testing_repository.dart | 22 + .../remote_todos/codegen.freezed.dart | 4 +- .../todos/codegen.freezed.dart | 4 +- .../current.json | 20 + .../current/advanced/select.mdx | 65 ++ .../advanced/select/select/codegen.dart | 30 + .../advanced/select/select/codegen.g.dart | 26 + .../current/advanced/select/select/index.ts | 4 + .../current/advanced/select/select/raw.dart | 29 + .../advanced/select/select_async/codegen.dart | 26 + .../select/select_async/codegen.g.dart | 26 + .../advanced/select/select_async/index.ts | 4 + .../advanced/select/select_async/raw.dart | 23 + .../current/case_studies/cancel.mdx | 139 ++++ .../cancel/detail_screen/codegen.dart | 61 ++ .../cancel/detail_screen/codegen.freezed.dart | 209 ++++++ .../cancel/detail_screen/codegen.g.dart | 46 ++ .../cancel/detail_screen/index.ts | 4 + .../cancel/detail_screen/raw.dart | 65 ++ .../cancel/detail_screen_cancel/codegen.dart | 28 + .../detail_screen_cancel/codegen.g.dart | 26 + .../cancel/detail_screen_cancel/index.ts | 4 + .../cancel/detail_screen_cancel/raw.dart | 25 + .../detail_screen_debounce/codegen.dart | 38 + .../detail_screen_debounce/codegen.g.dart | 26 + .../cancel/detail_screen_debounce/index.ts | 4 + .../cancel/detail_screen_debounce/raw.dart | 35 + .../case_studies/cancel/extension.dart | 32 + .../case_studies/cancel/home_screen.dart | 39 + .../provider_with_extension/codegen.dart | 26 + .../provider_with_extension/codegen.g.dart | 26 + .../cancel/provider_with_extension/index.ts | 4 + .../cancel/provider_with_extension/raw.dart | 23 + .../current/case_studies/pull_to_refresh.mdx | 127 ++++ .../pull_to_refresh/activity/codegen.dart | 19 + .../activity/codegen.freezed.dart | 209 ++++++ .../pull_to_refresh/activity/codegen.g.dart | 25 + .../pull_to_refresh/activity/index.ts | 4 + .../pull_to_refresh/activity/raw.dart | 24 + .../pull_to_refresh/display_activity.dart | 22 + .../pull_to_refresh/display_activity2.dart | 28 + .../pull_to_refresh/display_activity3.dart | 31 + .../pull_to_refresh/display_activity4.dart | 35 + .../fetch_activity/codegen.dart | 20 + .../fetch_activity/codegen.g.dart | 26 + .../pull_to_refresh/fetch_activity/index.ts | 4 + .../pull_to_refresh/fetch_activity/raw.dart | 17 + .../pull_to_refresh/full_app/codegen.dart | 69 ++ .../full_app/codegen.freezed.dart | 209 ++++++ .../pull_to_refresh/full_app/codegen.g.dart | 46 ++ .../pull_to_refresh/full_app/index.ts | 4 + .../pull_to_refresh/full_app/raw.dart | 73 ++ .../concepts/about_code_generation.mdx | 372 ++++++++++ .../current/concepts}/about_codegen/main.dart | 0 .../concepts/about_codegen/main.g.dart | 159 +++++ .../provider_type/async_class_future.dart | 14 + .../provider_type/async_class_future.g.dart | 27 + .../provider_type/async_class_stream.dart | 14 + .../provider_type/async_class_stream.g.dart | 27 + .../provider_type/async_fn_future.dart | 9 + .../provider_type/async_fn_future.g.dart | 26 + .../provider_type/async_fn_stream.dart | 9 + .../provider_type/async_fn_stream.g.dart | 26 + .../provider_type/auto_dispose.dart | 12 + .../provider_type/auto_dispose.g.dart | 40 ++ .../about_codegen/provider_type/family.dart | 7 + .../about_codegen/provider_type/family.g.dart | 159 +++++ .../provider_type/family_class.dart | 17 + .../provider_type/family_class.g.dart | 195 +++++ .../provider_type/family_fn.dart | 13 + .../provider_type/family_fn.g.dart | 176 +++++ .../non_code_gen/async_notifier_provider.dart | 15 + .../non_code_gen/auto_dispose.dart | 12 + .../provider_type/non_code_gen/family.dart | 6 + .../non_code_gen/future_provider.dart | 7 + .../non_code_gen/notifier_provider.dart | 15 + .../provider_type/non_code_gen/provider.dart | 8 + .../stream_notifier_provider.dart | 15 + .../non_code_gen/stream_provider.dart | 7 + .../provider_type/sync_class.dart | 14 + .../provider_type/sync_class.g.dart | 26 + .../about_codegen/provider_type/sync_fn.dart | 9 + .../provider_type/sync_fn.g.dart | 26 + .../current/concepts}/about_codegen/raw.dart | 2 +- .../current/concepts/about_hooks.mdx | 317 +++++++++ .../about_hooks/hook_and_consumer.dart | 28 + .../concepts/about_hooks/hook_consumer.dart | 26 + .../about_hooks/hook_consumer_widget.dart | 23 + .../concepts/async_initialization.dart | 58 ++ .../characters_provider/codegen.dart | 29 + .../characters_provider/codegen.g.dart | 40 ++ .../characters_provider/index.tsx | 9 + .../characters_provider/models.dart | 17 + .../characters_provider/raw.dart | 25 + .../city_provider/codegen.dart | 7 + .../city_provider/codegen.g.dart | 26 + .../city_provider/index.tsx | 9 + .../city_provider/raw.dart | 4 + .../filtered_todo_list_provider/codegen.dart | 31 + .../codegen.g.dart | 27 + .../filtered_todo_list_provider/index.tsx | 9 + .../filtered_todo_list_provider/raw.dart | 26 + .../read_in_provider/codegen.dart | 17 + .../read_in_provider/codegen.g.dart | 40 ++ .../read_in_provider/index.tsx | 9 + .../read_in_provider/raw.dart | 13 + .../select_async_provider/codegen.dart | 25 + .../select_async_provider/codegen.g.dart | 40 ++ .../select_async_provider/index.tsx | 9 + .../select_async_provider/models.dart | 21 + .../select_async_provider/raw.dart | 20 + .../todo_list_provider/codegen.dart | 24 + .../todo_list_provider/codegen.freezed.dart | 166 +++++ .../todo_list_provider/codegen.g.dart | 27 + .../todo_list_provider/index.tsx | 9 + .../todo_list_provider/raw.dart | 36 + .../weather_provider/codegen.dart | 20 + .../weather_provider/codegen.g.dart | 40 ++ .../weather_provider/index.tsx | 9 + .../weather_provider/raw.dart | 18 + .../whole_object_provider/codegen.dart | 24 + .../whole_object_provider/codegen.g.dart | 40 ++ .../whole_object_provider/index.tsx | 9 + .../whole_object_provider/models.dart | 16 + .../whole_object_provider/raw.dart | 20 + .../current/concepts/combining_providers.mdx | 278 +++----- .../current/concepts/dialog_scope.dart | 58 ++ .../lifecycle_on_dispose/codegen.dart | 21 + .../lifecycle_on_dispose/codegen.g.dart | 26 + .../concepts/lifecycle_on_dispose/index.tsx | 9 + .../concepts/lifecycle_on_dispose/raw.dart} | 2 +- .../concepts/modifiers/auto_dispose.mdx | 101 +-- .../current/concepts/modifiers/family.mdx | 93 +-- .../current/concepts/provider_lifecycles.mdx | 107 +++ .../current/concepts/provider_observer.mdx | 37 +- .../concepts/provider_observer_logger.dart | 5 +- .../current/concepts/providers.mdx | 199 +++--- .../creating_a_provider/codegen.dart | 12 + .../creating_a_provider/codegen.g.dart | 26 + .../providers/creating_a_provider/index.tsx | 9 + .../providers/creating_a_provider/raw.dart | 9 + .../declaring_many_providers/codegen.dart | 11 + .../declaring_many_providers/codegen.g.dart | 40 ++ .../declaring_many_providers/index.tsx | 9 + .../declaring_many_providers/raw.dart | 6 + .../current/concepts/reading.mdx | 472 +++++++------ .../consumer_hook.dart} | 7 +- .../consumer_stateful_widget/hooks.dart} | 11 +- .../consumer_stateful_widget/index.tsx | 9 + .../consumer_stateful_widget/raw.dart} | 7 +- .../consumer_widget/hooks.dart} | 7 +- .../reading/consumer_widget/index.tsx | 9 + .../consumer_widget/raw.dart} | 4 +- .../concepts/reading/counter/codegen.dart | 23 + .../concepts/reading/counter/codegen.g.dart | 26 + .../concepts/reading/counter/index.tsx | 9 + .../counter/raw.dart} | 2 +- .../concepts/reading/listen/codegen.dart | 16 + .../concepts/reading/listen/codegen.g.dart | 26 + .../current/concepts/reading/listen/index.tsx | 9 + .../current/concepts/reading/listen/raw.dart} | 6 +- .../reading/listen_build/codegen.dart | 28 + .../reading/listen_build/codegen.g.dart | 26 + .../reading/listen_build/codegen_hooks.dart | 34 + .../reading/listen_build/codegen_hooks.g.dart | 26 + .../concepts/reading/listen_build/index.tsx | 11 + .../listen_build/raw.dart} | 9 +- .../reading/listen_build/raw_hooks.dart | 29 + .../concepts/reading/provider/codegen.dart | 20 + .../concepts/reading/provider/codegen.g.dart | 40 ++ .../concepts/reading/provider/index.tsx | 9 + .../concepts/reading/provider/raw.dart | 19 + .../concepts/reading/read/codegen.dart | 32 + .../concepts/reading/read/codegen.g.dart | 26 + .../concepts/reading/read/codegen_hooks.dart | 36 + .../reading/read/codegen_hooks.g.dart | 26 + .../current/concepts/reading/read/index.tsx | 11 + .../read/raw.dart} | 6 +- .../concepts/reading/read/raw_hooks.dart | 31 + .../concepts/reading/read_build/codegen.dart | 25 + .../reading/read_build/codegen.g.dart | 26 + .../concepts/reading/read_build/index.tsx | 9 + .../read_build/raw.dart} | 2 +- .../reading/read_notifier_build/codegen.dart | 24 + .../read_notifier_build/codegen.g.dart | 26 + .../reading/read_notifier_build/index.tsx | 9 + .../reading/read_notifier_build/raw.dart} | 2 +- .../concepts/reading/watch/codegen.dart | 45 ++ .../concepts/reading/watch/codegen.g.dart | 55 ++ .../current/concepts/reading/watch/index.tsx | 9 + .../watch/raw.dart} | 6 +- .../concepts/reading/watch_build/codegen.dart | 39 + .../reading/watch_build/codegen.g.dart | 41 ++ .../reading/watch_build/codegen_hooks.dart | 43 ++ .../reading/watch_build/codegen_hooks.g.dart | 41 ++ .../concepts/reading/watch_build/index.tsx | 11 + .../watch_build/raw.dart} | 2 +- .../reading/watch_build/raw_hooks.dart | 37 + .../reading/watch_notifier_build/codegen.dart | 24 + .../watch_notifier_build/codegen.g.dart | 26 + .../reading/watch_notifier_build/index.tsx | 9 + .../watch_notifier_build/raw.dart} | 0 .../current/concepts/scopes.mdx | 180 +++++ .../current/concepts/subtree_scope.dart | 78 ++ .../current/concepts/theme_scope.dart | 68 ++ .../current/concepts/why_immutability.mdx | 92 +++ .../concepts/why_immutability/codegen.dart | 58 ++ .../why_immutability/codegen.freezed.dart | 151 ++++ .../concepts/why_immutability/codegen.g.dart | 28 + .../concepts/why_immutability/index.tsx | 9 + .../concepts/why_immutability/raw.dart | 67 ++ .../current/cookbooks/search_as_we_type.mdx | 69 +- .../current/cookbooks/testing.mdx | 130 ++-- .../current/cookbooks/testing_dart.dart | 60 +- .../current/cookbooks/testing_flutter.dart | 32 +- .../current/cookbooks/testing_full.dart | 38 +- .../cookbooks/testing_original_test_dart.dart | 26 +- .../testing_original_test_flutter.dart | 16 +- .../cookbooks/testing_override_info.dart | 2 +- .../current/cookbooks/testing_repository.dart | 10 +- .../current/essentials/auto_dispose.mdx | 166 +++++ .../auto_dispose/cache_for_extension.dart | 18 + .../auto_dispose/cache_for_usage/codegen.dart | 17 + .../cache_for_usage/codegen.g.dart | 26 + .../auto_dispose/cache_for_usage/index.ts | 4 + .../auto_dispose/cache_for_usage/raw.dart | 15 + .../auto_dispose/codegen_keep_alive.dart | 11 + .../auto_dispose/codegen_keep_alive.g.dart | 26 + .../auto_dispose/invalidate_example.dart | 23 + .../invalidate_family_example/codegen.dart | 24 + .../invalidate_family_example/codegen.g.dart | 159 +++++ .../invalidate_family_example/index.ts | 4 + .../invalidate_family_example/raw.dart | 20 + .../auto_dispose/keep_alive/codegen.dart | 21 + .../auto_dispose/keep_alive/codegen.g.dart | 26 + .../auto_dispose/keep_alive/index.ts | 4 + .../auto_dispose/keep_alive/raw.dart | 19 + .../on_dispose_example/codegen.dart | 23 + .../on_dispose_example/codegen.g.dart | 40 ++ .../auto_dispose/on_dispose_example/index.ts | 4 + .../auto_dispose/on_dispose_example/raw.dart | 17 + .../auto_dispose/raw_auto_dispose.dart | 7 + .../current/essentials/combining_requests.mdx | 140 ++++ .../functional_ref/codegen.dart | 18 + .../functional_ref/codegen.g.dart | 40 ++ .../functional_ref/index.ts | 4 + .../functional_ref/raw.dart | 14 + .../listen_example/codegen.dart | 17 + .../listen_example/codegen.g.dart | 26 + .../listen_example/index.ts | 4 + .../listen_example/raw.dart | 13 + .../notifier_ref/codegen.dart | 21 + .../notifier_ref/codegen.g.dart | 40 ++ .../combining_requests/notifier_ref/index.ts | 4 + .../combining_requests/notifier_ref/raw.dart | 19 + .../read_example/codegen.dart | 23 + .../read_example/codegen.g.dart | 27 + .../combining_requests/read_example/index.ts | 4 + .../combining_requests/read_example/raw.dart | 22 + .../watch_example/codegen.dart | 44 ++ .../watch_example/codegen.g.dart | 44 ++ .../combining_requests/watch_example/index.ts | 4 + .../combining_requests/watch_example/raw.dart | 40 ++ .../watch_placement/codegen.dart | 37 + .../watch_placement/codegen.g.dart | 41 ++ .../watch_placement/index.ts | 4 + .../watch_placement/raw.dart | 35 + .../current/essentials/do_dont.mdx | 151 ++++ .../essentials/eager_initialization.mdx | 64 ++ .../async_consumer_example.dart | 28 + .../consumer_example.dart | 36 + .../require_value/codegen.dart | 24 + .../require_value/codegen.g.dart | 26 + .../require_value/index.ts | 4 + .../require_value/raw.dart | 20 + .../current/essentials/faq.mdx | 183 +++++ .../current/essentials/first_request.mdx | 333 +++++++++ .../essentials/first_request/activity.ts | 9 + .../first_request/codegen/activity.dart | 24 + .../codegen/activity.freezed.dart | 237 +++++++ .../first_request/codegen/activity.g.dart | 27 + .../first_request/codegen/provider.dart | 23 + .../first_request/codegen/provider.g.dart | 29 + .../essentials/first_request/consumer.ts | 8 + .../first_request/consumer_stateful_widget.ts | 8 + .../first_request/consumer_widget.ts | 8 + .../first_request/hook_consumer_widget.ts | 8 + .../first_request/legend/DocuCode.scss | 18 + .../first_request/legend/legend.tsx | 88 +++ .../essentials/first_request/provider.ts | 9 + .../first_request/raw/activity.dart | 30 + .../first_request/raw/consumer.dart | 42 ++ .../raw/consumer_stateful_widget.dart | 43 ++ .../first_request/raw/consumer_widget.dart | 25 + .../raw/hook_consumer_widget.dart | 28 + .../first_request/raw/provider.dart | 17 + .../current/essentials/passing_args.mdx | 155 ++++ .../passing_args/codegen/family.dart | 31 + .../passing_args/codegen/family.g.dart | 159 +++++ .../passing_args/codegen/provider.dart | 33 + .../passing_args/codegen/provider.g.dart | 191 +++++ .../passing_args/no_arg_provider.ts | 9 + .../passing_args/raw/consumer_family.dart | 24 + .../raw/consumer_list_family.dart | 23 + .../passing_args/raw/consumer_provider.dart | 20 + .../raw/consumer_tuple_family.dart | 24 + .../essentials/passing_args/raw/family.dart | 41 ++ .../raw/multiple_consumer_family.dart | 37 + .../essentials/passing_args/raw/provider.dart | 28 + .../passing_args/raw/tuple_family.dart | 33 + .../current/essentials/provider_observer.mdx | 49 ++ .../provider_observer/provider_observer.dart | 43 ++ .../current/essentials/side_effects.mdx | 426 +++++++++++ .../codegen/todo_list_notifier.dart | 28 + .../codegen/todo_list_notifier.freezed.dart | 166 +++++ .../codegen/todo_list_notifier.g.dart | 42 ++ .../codegen/todo_list_notifier_add_todo.dart | 26 + .../todo_list_notifier_add_todo.g.dart | 27 + .../codegen/todo_list_provider.dart | 23 + .../codegen/todo_list_provider.freezed.dart | 148 ++++ .../codegen/todo_list_provider.g.dart | 26 + .../raw/consumer_add_todo_call.dart | 23 + .../raw/invalidate_self_add_todo.dart | 37 + .../side_effects/raw/manual_add_todo.dart | 40 ++ .../raw/mutable_manual_add_todo.dart | 34 + .../side_effects/raw/render_add_todo.dart | 77 ++ .../raw/render_add_todo_hooks.dart | 68 ++ .../side_effects/raw/rest_add_todo.dart | 38 + .../side_effects/raw/todo_list_notifier.dart | 43 ++ .../raw/todo_list_notifier_add_todo.dart | 27 + .../side_effects/raw/todo_list_provider.dart | 27 + .../side_effects/render_add_todo.ts | 9 + .../side_effects/todo_list_notifier.ts | 9 + .../todo_list_notifier_add_todo.ts | 9 + .../side_effects/todo_list_provider.ts | 7 + .../current/essentials/testing.mdx | 160 +++++ .../testing/auto_dispose_listen.dart | 24 + .../essentials/testing/await_future.dart | 30 + .../essentials/testing/create_container.dart | 22 + .../essentials/testing/full_widget_test.dart | 33 + .../essentials/testing/listen_provider.dart | 22 + .../essentials/testing/mock_provider.dart | 45 ++ .../testing/notifier_mock/codegen.dart | 17 + .../testing/notifier_mock/codegen.g.dart | 27 + .../essentials/testing/notifier_mock/index.ts | 4 + .../essentials/testing/notifier_mock/raw.dart | 14 + .../testing/provider_to_mock/codegen.dart | 9 + .../testing/provider_to_mock/codegen.g.dart | 26 + .../testing/provider_to_mock/index.ts | 4 + .../testing/provider_to_mock/raw.dart | 6 + .../current/essentials/testing/unit_test.dart | 23 + .../testing/widget_container_of.dart | 15 + .../essentials/testing/widget_test.dart | 22 + .../current/essentials/websockets_sync.mdx | 108 +++ .../change_notifier_provider.dart | 11 + .../websockets_sync/pipe_change_notifier.dart | 22 + .../pipe_change_notifier.g.dart | 29 + .../essentials/websockets_sync/raw_usage.dart | 30 + .../websockets_sync/raw_usage.g.dart | 26 + .../shared_pipe_change_notifier.dart | 29 + .../shared_pipe_change_notifier.g.dart | 42 ++ .../stream_provider/codegen.dart | 34 + .../stream_provider/codegen.g.dart | 27 + .../websockets_sync/stream_provider/index.ts | 4 + .../websockets_sync/stream_provider/raw.dart | 30 + .../websockets_sync/sync_consumer.dart | 19 + .../sync_definition/codegen.dart | 10 + .../sync_definition/codegen.g.dart | 28 + .../websockets_sync/sync_definition/index.ts | 4 + .../websockets_sync/sync_definition/raw.dart | 7 + .../current/from_provider/family/family.dart | 11 + .../from_provider/family/family.g.dart | 174 +++++ .../current/from_provider/family/index.tsx | 9 + .../current/from_provider/family/raw.dart | 27 + .../current/from_provider/helpers/item.dart | 12 + .../from_provider/helpers/item.freezed.dart | 146 ++++ .../current/from_provider/helpers/item.g.dart | 18 + .../current/from_provider/helpers/json.dart | 1 + .../motivation/async_values/async_values.dart | 29 + .../async_values/async_values.g.dart | 40 ++ .../motivation/async_values/index.tsx | 9 + .../motivation/async_values/raw.dart | 25 + .../motivation/auto_dispose/auto_dispose.dart | 28 + .../auto_dispose/auto_dispose.g.dart | 41 ++ .../motivation/auto_dispose/index.tsx | 9 + .../motivation/auto_dispose/raw.dart | 23 + .../motivation/combine/combine.dart | 19 + .../motivation/combine/combine.g.dart | 40 ++ .../motivation/combine/index.tsx | 9 + .../from_provider/motivation/combine/raw.dart | 15 + .../from_provider/motivation/motivation.mdx | 189 +++++ .../motivation/override/index.tsx | 9 + .../motivation/override/override.dart | 16 + .../motivation/override/raw.dart | 16 + .../motivation/same_type/index.tsx | 9 + .../motivation/same_type/raw.dart | 15 + .../motivation/same_type/same_type.dart | 19 + .../motivation/same_type/same_type.g.dart | 40 ++ .../motivation/side_effects/index.tsx | 9 + .../motivation/side_effects/raw.dart | 24 + .../motivation/side_effects/side_effects.dart | 24 + .../from_provider/provider_vs_riverpod.mdx | 401 +++++++++++ .../current/from_provider/quickstart.mdx | 197 ++++++ .../current/getting_started.mdx | 237 ------- .../current/getting_started_hello_world.dart | 20 - .../getting_started_hello_world_hooks.dart | 38 - .../current/introduction/getting_started.mdx | 195 +++++ .../dart_hello_world/index.tsx | 0 .../dart_hello_world/main.dart | 8 +- .../dart_hello_world/main.g.dart | 0 .../getting_started/dart_hello_world/raw.dart | 0 .../getting_started/dart_pub_add.tsx | 32 + .../getting_started/dart_pubspec.tsx | 12 +- .../hello_world/hooks_codegen/main.dart | 46 ++ .../hello_world/hooks_codegen}/main.g.dart | 0 .../getting_started/hello_world/index.tsx | 4 +- .../getting_started/hello_world/main.dart | 16 +- .../getting_started/hello_world}/main.g.dart | 0 .../getting_started/hello_world/raw.dart} | 16 +- .../hello_world/raw_hooks.dart | 40 ++ .../introduction/getting_started/pub_add.tsx | 39 + .../introduction/getting_started/pubspec.tsx | 51 ++ .../current/introduction/why_riverpod.mdx | 60 ++ .../introduction/why_riverpod/codegen.dart | 31 + .../introduction/why_riverpod/codegen.g.dart | 178 +++++ .../introduction/why_riverpod/index.tsx | 9 + .../introduction/why_riverpod/raw.dart | 27 + .../current/migration/0.13.0_to_0.14.0.mdx | 62 +- .../current/migration/0.14.0_to_1.0.0.mdx | 113 +-- .../migration/from_change_notifier.mdx | 132 ++++ .../declaration/declaration.dart | 33 + .../declaration/declaration.g.dart | 27 + .../declaration/index.tsx | 9 + .../from_change_notifier/declaration/raw.dart | 33 + .../initialization/index.tsx | 9 + .../initialization/initialization.dart | 29 + .../initialization/initialization.g.dart | 27 + .../initialization/raw.dart | 29 + .../from_change_notifier/migrated/index.tsx | 9 + .../migrated/migrated.dart | 37 + .../migrated/migrated.g.dart | 27 + .../from_change_notifier/migrated/raw.dart | 37 + .../migration/from_change_notifier/old.dart | 60 ++ .../current/migration/from_state_notifier.mdx | 264 +++++++ .../add_listener/add_listener.dart | 18 + .../add_listener/add_listener.g.dart | 27 + .../add_listener/index.tsx | 9 + .../from_state_notifier/add_listener/raw.dart | 17 + .../from_state_notifier/add_listener_old.dart | 24 + .../async_notifier/async_notifier.dart | 28 + .../async_notifier/async_notifier.g.dart | 29 + .../async_notifier/index.tsx | 9 + .../async_notifier/raw.dart | 29 + .../async_notifier_old.dart | 30 + .../build_init/build_init.dart | 15 + .../build_init/build_init.g.dart | 28 + .../from_state_notifier/build_init/index.tsx | 9 + .../from_state_notifier/build_init/raw.dart | 15 + .../from_state_notifier/build_init_old.dart | 13 + .../consumers_dont_change.dart | 36 + .../family_and_dispose.dart | 24 + .../family_and_dispose.g.dart | 178 +++++ .../family_and_dispose/index.tsx | 9 + .../family_and_dispose/raw.dart | 27 + .../family_and_dispose_old.dart | 30 + .../from_state_provider.dart | 14 + .../from_state_provider.g.dart | 28 + .../from_state_provider/index.tsx | 9 + .../from_state_provider/raw.dart | 13 + .../from_state_provider_old.dart | 6 + .../obtain_notifier_on_tests.dart | 33 + .../old_lifecycles/index.tsx | 9 + .../old_lifecycles/old_lifecycles.dart | 36 + .../old_lifecycles/old_lifecycles.g.dart | 27 + .../old_lifecycles/raw.dart | 35 + .../old_lifecycles_final/index.tsx | 9 + .../old_lifecycles_final.dart | 38 + .../old_lifecycles_final.g.dart | 27 + .../old_lifecycles_final/raw.dart | 36 + .../old_lifecycles_old.dart | 42 ++ .../current/migration/utils.dart | 23 + .../providers/change_notifier_provider.mdx | 55 ++ .../change_notifier_provider/todos.dart | 45 ++ .../todos_consumer.dart | 32 + .../current/providers/future_provider.mdx | 65 +- .../config_consumer/codegen.dart | 16 + .../config_consumer/hooks.dart | 21 + .../config_consumer/hooks_codegen.dart | 22 + .../future_provider/config_consumer/index.tsx | 11 + .../future_provider/config_consumer/raw.dart | 18 + .../config_provider/codegen.dart | 22 + .../config_provider/codegen.g.dart | 29 + .../future_provider/config_provider/index.tsx | 9 + .../future_provider/config_provider/raw.dart | 22 + .../current/providers/notifier_provider.mdx | 55 ++ .../remote_todos/codegen.dart | 82 +++ .../remote_todos/codegen.freezed.dart | 184 +++++ .../remote_todos/codegen.g.dart | 44 ++ .../notifier_provider/remote_todos/index.tsx | 9 + .../notifier_provider/remote_todos/raw.dart | 101 +++ .../remote_todos/todos_consumer.dart | 37 + .../notifier_provider/todos/codegen.dart | 68 ++ .../todos/codegen.freezed.dart | 166 +++++ .../notifier_provider/todos/codegen.g.dart | 26 + .../notifier_provider/todos/index.tsx | 9 + .../notifier_provider/todos/raw.dart | 85 +++ .../todos/todos_consumer.dart | 32 + .../current/providers/provider.mdx | 130 ++-- .../completed_todos/completed_todos.dart | 15 + .../completed_todos/completed_todos.g.dart | 27 + .../provider/completed_todos/index.tsx | 9 + .../raw.dart} | 6 +- .../optimized_previous_button/index.tsx | 9 + .../optimized_previous_button.dart | 50 ++ .../optimized_previous_button.g.dart | 42 ++ .../optimized_previous_button/raw.dart} | 4 +- .../current/providers/provider/todo/index.tsx | 9 + .../provider/{todo.dart => todo/raw.dart} | 11 +- .../current/providers/provider/todo/todo.dart | 25 + .../providers/provider/todo/todo.g.dart | 26 + .../providers/provider/todos_consumer.dart | 14 +- .../unoptimized_previous_button/index.tsx | 9 + .../raw.dart} | 6 +- .../unoptimized_previous_button.dart | 39 + .../unoptimized_previous_button.g.dart | 26 + .../providers/state_notifier_provider.mdx | 40 +- .../state_notifier_provider/todos.dart | 68 +- .../todos_consumer.dart | 7 +- .../current/providers/state_provider.mdx | 131 ++-- .../state_provider/connected_dropdown.dart | 21 +- .../providers/state_provider/dropdown.dart | 2 +- .../providers/state_provider/full.dart | 12 +- .../providers/state_provider/product.dart | 20 + .../state_provider/product_list_view.dart | 22 + .../state_provider/sort_provider.dart | 2 +- .../sorted_product_provider.dart | 24 + .../state_provider/update_read_once.dart | 23 + .../state_provider/update_read_twice.dart | 4 +- .../current/providers/stream_provider.mdx | 61 +- .../live_stream_chat_consumer.dart | 25 + .../live_stream_chat_provider.dart | 18 + .../live_stream_chat_provider/codegen.dart | 23 + .../live_stream_chat_provider/codegen.g.dart | 26 + .../live_stream_chat_provider/index.tsx | 9 + .../live_stream_chat_provider/raw.dart | 18 + website/i18n/ko/NOTE.md | 22 + website/i18n/ko/code.json | 462 ++++++++---- .../options.json | 2 +- .../current.json | 120 ++-- .../current/about_code_generation.mdx | 67 -- .../current/about_hooks.mdx | 283 -------- .../current/advanced/select.mdx | 59 ++ .../advanced/select/select/codegen.dart | 30 + .../advanced/select/select/codegen.g.dart | 26 + .../current/advanced/select/select/index.ts | 4 + .../current/advanced/select/select/raw.dart | 29 + .../advanced/select/select_async/codegen.dart | 26 + .../select/select_async/codegen.g.dart | 26 + .../advanced/select/select_async/index.ts | 4 + .../advanced/select/select_async/raw.dart | 23 + .../current/case_studies/cancel.mdx | 118 ++++ .../cancel/detail_screen/codegen.dart | 61 ++ .../cancel/detail_screen/codegen.freezed.dart | 209 ++++++ .../cancel/detail_screen/codegen.g.dart | 46 ++ .../cancel/detail_screen/index.ts | 4 + .../cancel/detail_screen/raw.dart | 65 ++ .../cancel/detail_screen_cancel/codegen.dart | 28 + .../detail_screen_cancel/codegen.g.dart | 26 + .../cancel/detail_screen_cancel/index.ts | 4 + .../cancel/detail_screen_cancel/raw.dart | 25 + .../detail_screen_debounce/codegen.dart | 39 + .../detail_screen_debounce/codegen.g.dart | 26 + .../cancel/detail_screen_debounce/index.ts | 4 + .../cancel/detail_screen_debounce/raw.dart | 35 + .../case_studies/cancel/extension.dart | 31 + .../case_studies/cancel/home_screen.dart | 39 + .../provider_with_extension/codegen.dart | 25 + .../provider_with_extension/codegen.g.dart | 26 + .../cancel/provider_with_extension/index.ts | 4 + .../cancel/provider_with_extension/raw.dart | 23 + .../current/case_studies/pull_to_refresh.mdx | 119 ++++ .../pull_to_refresh/activity/codegen.dart | 19 + .../activity/codegen.freezed.dart | 209 ++++++ .../pull_to_refresh/activity/codegen.g.dart | 25 + .../pull_to_refresh/activity/index.ts | 4 + .../pull_to_refresh/activity/raw.dart | 24 + .../pull_to_refresh/display_activity.dart | 22 + .../pull_to_refresh/display_activity2.dart | 28 + .../pull_to_refresh/display_activity3.dart | 31 + .../pull_to_refresh/display_activity4.dart | 35 + .../fetch_activity/codegen.dart | 20 + .../fetch_activity/codegen.g.dart | 26 + .../pull_to_refresh/fetch_activity/index.ts | 4 + .../pull_to_refresh/fetch_activity/raw.dart | 17 + .../pull_to_refresh/full_app/codegen.dart | 69 ++ .../full_app/codegen.freezed.dart | 209 ++++++ .../pull_to_refresh/full_app/codegen.g.dart | 46 ++ .../pull_to_refresh/full_app/index.ts | 4 + .../pull_to_refresh/full_app/raw.dart | 73 ++ .../concepts/about_code_generation.mdx | 355 ++++++++++ .../current/concepts}/about_codegen/main.dart | 0 .../{ => concepts}/about_codegen/main.g.dart | 0 .../provider_type/async_class_future.dart | 14 + .../provider_type/async_class_future.g.dart | 27 + .../provider_type/async_class_stream.dart | 14 + .../provider_type/async_class_stream.g.dart | 27 + .../provider_type/async_fn_future.dart | 9 + .../provider_type/async_fn_future.g.dart | 26 + .../provider_type/async_fn_stream.dart | 9 + .../provider_type/async_fn_stream.g.dart | 26 + .../provider_type/auto_dispose.dart | 12 + .../provider_type/auto_dispose.g.dart | 40 ++ .../about_codegen/provider_type/family.dart | 7 + .../about_codegen/provider_type/family.g.dart | 159 +++++ .../provider_type/family_class.dart | 17 + .../provider_type/family_class.g.dart | 195 +++++ .../provider_type/family_fn.dart | 13 + .../provider_type/family_fn.g.dart | 176 +++++ .../non_code_gen/async_notifier_provider.dart | 15 + .../non_code_gen/auto_dispose.dart | 12 + .../provider_type/non_code_gen/family.dart | 6 + .../non_code_gen/future_provider.dart | 7 + .../non_code_gen/notifier_provider.dart | 15 + .../provider_type/non_code_gen/provider.dart | 8 + .../stream_notifier_provider.dart | 15 + .../non_code_gen/stream_provider.dart | 6 + .../provider_type/sync_class.dart | 14 + .../provider_type/sync_class.g.dart | 26 + .../about_codegen/provider_type/sync_fn.dart | 9 + .../provider_type/sync_fn.g.dart | 26 + .../current/concepts}/about_codegen/raw.dart | 2 +- .../current/concepts/about_hooks.mdx | 310 ++++++++ .../about_hooks/hook_and_consumer.dart | 28 + .../concepts/about_hooks/hook_consumer.dart | 26 + .../about_hooks/hook_consumer_widget.dart | 23 + .../concepts/async_initialization.dart | 58 ++ .../characters_provider/codegen.dart | 29 + .../characters_provider/codegen.g.dart | 40 ++ .../characters_provider/index.tsx | 9 + .../characters_provider/models.dart | 17 + .../characters_provider/raw.dart | 25 + .../city_provider/codegen.dart | 7 + .../city_provider/codegen.g.dart | 26 + .../city_provider/index.tsx | 9 + .../city_provider/raw.dart | 4 + .../filtered_todo_list_provider/codegen.dart | 31 + .../codegen.g.dart | 27 + .../filtered_todo_list_provider/index.tsx | 9 + .../filtered_todo_list_provider/raw.dart | 26 + .../read_in_provider/codegen.dart | 17 + .../read_in_provider/codegen.g.dart | 40 ++ .../read_in_provider/index.tsx | 9 + .../read_in_provider/raw.dart | 13 + .../select_async_provider/codegen.dart | 25 + .../select_async_provider/codegen.g.dart | 40 ++ .../select_async_provider/index.tsx | 9 + .../select_async_provider/models.dart | 21 + .../select_async_provider/raw.dart | 20 + .../todo_list_provider/codegen.dart | 24 + .../todo_list_provider/codegen.freezed.dart | 166 +++++ .../todo_list_provider/codegen.g.dart | 27 + .../todo_list_provider/index.tsx | 9 + .../todo_list_provider/raw.dart | 36 + .../weather_provider/codegen.dart | 20 + .../weather_provider/codegen.g.dart | 40 ++ .../weather_provider/index.tsx | 9 + .../weather_provider/raw.dart | 18 + .../whole_object_provider/codegen.dart | 24 + .../whole_object_provider/codegen.g.dart | 40 ++ .../whole_object_provider/index.tsx | 9 + .../whole_object_provider/models.dart | 16 + .../whole_object_provider/raw.dart | 20 + .../current/concepts/combining_providers.mdx | 267 +++---- .../current/concepts/dialog_scope.dart | 58 ++ .../lifecycle_on_dispose/codegen.dart | 21 + .../lifecycle_on_dispose/codegen.g.dart | 26 + .../concepts/lifecycle_on_dispose/index.tsx | 9 + .../concepts/lifecycle_on_dispose/raw.dart | 17 + .../concepts/modifiers/auto_dispose.mdx | 103 +-- .../current/concepts/modifiers/family.mdx | 91 +-- .../current/concepts/provider_lifecycles.mdx | 108 +++ .../current/concepts/provider_observer.mdx | 36 +- .../concepts/provider_observer_logger.dart | 6 +- .../current/concepts/providers.mdx | 203 +++--- .../creating_a_provider/codegen.dart | 12 + .../creating_a_provider/codegen.g.dart | 26 + .../providers/creating_a_provider/index.tsx | 9 + .../providers/creating_a_provider/raw.dart | 9 + .../declaring_many_providers/codegen.dart | 11 + .../declaring_many_providers/codegen.g.dart | 40 ++ .../declaring_many_providers/index.tsx | 9 + .../declaring_many_providers/raw.dart | 6 + .../current/concepts/reading.mdx | 436 +++++++----- .../consumer_hook.dart} | 6 +- .../consumer_stateful_widget/hooks.dart | 34 + .../consumer_stateful_widget/index.tsx | 9 + .../consumer_stateful_widget/raw.dart} | 6 +- .../consumer_widget/hooks.dart} | 6 +- .../reading/consumer_widget/index.tsx | 9 + .../consumer_widget/raw.dart} | 4 +- .../concepts/reading/counter/codegen.dart | 23 + .../concepts/reading/counter/codegen.g.dart | 26 + .../concepts/reading/counter/index.tsx | 9 + .../counter/raw.dart} | 2 +- .../concepts/reading/listen/codegen.dart | 16 + .../concepts/reading/listen/codegen.g.dart | 26 + .../current/concepts/reading/listen/index.tsx | 9 + .../current/concepts/reading/listen/raw.dart} | 6 +- .../reading/listen_build/codegen.dart | 28 + .../reading/listen_build/codegen.g.dart | 26 + .../reading/listen_build/codegen_hooks.dart | 34 + .../reading/listen_build/codegen_hooks.g.dart | 26 + .../concepts/reading/listen_build/index.tsx | 11 + .../listen_build/raw.dart} | 9 +- .../reading/listen_build/raw_hooks.dart | 29 + .../concepts/reading/provider/codegen.dart | 20 + .../concepts/reading/provider/codegen.g.dart | 40 ++ .../concepts/reading/provider/index.tsx | 9 + .../concepts/reading/provider/raw.dart | 19 + .../concepts/reading/read/codegen.dart | 32 + .../concepts/reading/read/codegen.g.dart | 26 + .../concepts/reading/read/codegen_hooks.dart | 36 + .../reading/read/codegen_hooks.g.dart | 26 + .../current/concepts/reading/read/index.tsx | 11 + .../read/raw.dart} | 7 +- .../concepts/reading/read/raw_hooks.dart | 31 + .../concepts/reading/read_build/codegen.dart | 25 + .../reading/read_build/codegen.g.dart | 26 + .../concepts/reading/read_build/index.tsx | 9 + .../read_build/raw.dart} | 2 +- .../reading/read_notifier_build/codegen.dart | 24 + .../read_notifier_build/codegen.g.dart | 26 + .../reading/read_notifier_build/index.tsx | 9 + .../reading/read_notifier_build/raw.dart} | 2 +- .../concepts/reading/watch/codegen.dart | 45 ++ .../concepts/reading/watch/codegen.g.dart | 55 ++ .../current/concepts/reading/watch/index.tsx | 9 + .../watch/raw.dart} | 6 +- .../concepts/reading/watch_build/codegen.dart | 39 + .../reading/watch_build/codegen.g.dart | 41 ++ .../reading/watch_build/codegen_hooks.dart | 43 ++ .../reading/watch_build/codegen_hooks.g.dart | 41 ++ .../concepts/reading/watch_build/index.tsx | 11 + .../watch_build/raw.dart} | 2 +- .../reading/watch_build/raw_hooks.dart | 37 + .../reading/watch_notifier_build/codegen.dart | 24 + .../watch_notifier_build/codegen.g.dart | 26 + .../reading/watch_notifier_build/index.tsx | 9 + .../watch_notifier_build/raw.dart} | 0 .../current/concepts/scopes.mdx | 180 +++++ .../current/concepts/subtree_scope.dart | 78 ++ .../current/concepts/theme_scope.dart | 68 ++ .../current/concepts/why_immutability.mdx | 140 ++-- .../concepts/why_immutability/codegen.dart | 58 ++ .../why_immutability/codegen.freezed.dart | 151 ++++ .../concepts/why_immutability/codegen.g.dart | 28 + .../concepts/why_immutability/index.tsx | 9 + .../concepts/why_immutability/raw.dart | 67 ++ .../current/cookbooks/refresh.mdx | 10 - .../current/cookbooks/search_as_we_type.mdx | 30 +- .../current/cookbooks/testing.mdx | 112 +-- .../current/cookbooks/testing_dart.dart | 60 +- .../current/cookbooks/testing_flutter.dart | 32 +- .../current/cookbooks/testing_full.dart | 34 +- .../cookbooks/testing_original_test_dart.dart | 26 +- .../testing_original_test_flutter.dart | 19 +- .../cookbooks/testing_override_info.dart | 2 +- .../current/cookbooks/testing_repository.dart | 10 +- .../current/essentials/auto_dispose.mdx | 153 ++++ .../auto_dispose/cache_for_extension.dart | 17 + .../auto_dispose/cache_for_usage/codegen.dart | 17 + .../cache_for_usage/codegen.g.dart | 26 + .../auto_dispose/cache_for_usage/index.ts | 4 + .../auto_dispose/cache_for_usage/raw.dart | 15 + .../auto_dispose/codegen_keep_alive.dart | 11 + .../auto_dispose/codegen_keep_alive.g.dart | 26 + .../auto_dispose/invalidate_example.dart | 23 + .../invalidate_family_example/codegen.dart | 24 + .../invalidate_family_example/codegen.g.dart | 159 +++++ .../invalidate_family_example/index.ts | 4 + .../invalidate_family_example/raw.dart | 20 + .../auto_dispose/keep_alive/codegen.dart | 20 + .../auto_dispose/keep_alive/codegen.g.dart | 26 + .../auto_dispose/keep_alive/index.ts | 4 + .../auto_dispose/keep_alive/raw.dart | 19 + .../on_dispose_example/codegen.dart | 23 + .../on_dispose_example/codegen.g.dart | 40 ++ .../auto_dispose/on_dispose_example/index.ts | 4 + .../auto_dispose/on_dispose_example/raw.dart | 17 + .../auto_dispose/raw_auto_dispose.dart | 7 + .../current/essentials/combining_requests.mdx | 126 ++++ .../functional_ref/codegen.dart | 18 + .../functional_ref/codegen.g.dart | 40 ++ .../functional_ref/index.ts | 4 + .../functional_ref/raw.dart | 14 + .../listen_example/codegen.dart | 17 + .../listen_example/codegen.g.dart | 26 + .../listen_example/index.ts | 4 + .../listen_example/raw.dart | 13 + .../notifier_ref/codegen.dart | 21 + .../notifier_ref/codegen.g.dart | 40 ++ .../combining_requests/notifier_ref/index.ts | 4 + .../combining_requests/notifier_ref/raw.dart | 19 + .../read_example/codegen.dart | 23 + .../read_example/codegen.g.dart | 27 + .../combining_requests/read_example/index.ts | 4 + .../combining_requests/read_example/raw.dart | 22 + .../watch_example/codegen.dart | 43 ++ .../watch_example/codegen.g.dart | 44 ++ .../combining_requests/watch_example/index.ts | 4 + .../combining_requests/watch_example/raw.dart | 41 ++ .../watch_placement/codegen.dart | 37 + .../watch_placement/codegen.g.dart | 41 ++ .../watch_placement/index.ts | 4 + .../watch_placement/raw.dart | 35 + .../current/essentials/do_dont.mdx | 142 ++++ .../essentials/eager_initialization.mdx | 58 ++ .../async_consumer_example.dart | 28 + .../consumer_example.dart | 36 + .../require_value/codegen.dart | 24 + .../require_value/codegen.g.dart | 26 + .../require_value/index.ts | 4 + .../require_value/raw.dart | 20 + .../current/essentials/faq.mdx | 157 ++++ .../current/essentials/first_request.mdx | 320 +++++++++ .../essentials/first_request/activity.ts | 9 + .../first_request/codegen/activity.dart | 24 + .../codegen/activity.freezed.dart | 237 +++++++ .../first_request/codegen/activity.g.dart | 27 + .../first_request/codegen/provider.dart | 21 + .../first_request/codegen/provider.g.dart | 29 + .../essentials/first_request/consumer.ts | 8 + .../first_request/consumer_stateful_widget.ts | 8 + .../first_request/consumer_widget.ts | 8 + .../first_request/hook_consumer_widget.ts | 8 + .../first_request/legend/DocuCode.scss | 18 + .../first_request/legend/legend.tsx | 88 +++ .../essentials/first_request/provider.ts | 9 + .../first_request/raw/activity.dart | 30 + .../first_request/raw/consumer.dart | 41 ++ .../raw/consumer_stateful_widget.dart | 43 ++ .../first_request/raw/consumer_widget.dart | 25 + .../raw/hook_consumer_widget.dart | 28 + .../first_request/raw/provider.dart | 15 + .../current/essentials/passing_args.mdx | 137 ++++ .../passing_args/codegen/family.dart | 29 + .../passing_args/codegen/family.g.dart | 159 +++++ .../passing_args/codegen/provider.dart | 32 + .../passing_args/codegen/provider.g.dart | 191 +++++ .../passing_args/no_arg_provider.ts | 9 + .../passing_args/raw/consumer_family.dart | 24 + .../raw/consumer_list_family.dart | 23 + .../passing_args/raw/consumer_provider.dart | 20 + .../raw/consumer_tuple_family.dart | 23 + .../essentials/passing_args/raw/family.dart | 40 ++ .../raw/multiple_consumer_family.dart | 37 + .../essentials/passing_args/raw/provider.dart | 26 + .../passing_args/raw/tuple_family.dart | 34 + .../current/essentials/provider_observer.mdx | 48 ++ .../provider_observer/provider_observer.dart | 43 ++ .../current/essentials/side_effects.mdx | 407 +++++++++++ .../codegen/todo_list_notifier.dart | 28 + .../codegen/todo_list_notifier.freezed.dart | 166 +++++ .../codegen/todo_list_notifier.g.dart | 42 ++ .../codegen/todo_list_notifier_add_todo.dart | 26 + .../todo_list_notifier_add_todo.g.dart | 27 + .../codegen/todo_list_provider.dart | 23 + .../codegen/todo_list_provider.freezed.dart | 148 ++++ .../codegen/todo_list_provider.g.dart | 26 + .../raw/consumer_add_todo_call.dart | 25 + .../raw/invalidate_self_add_todo.dart | 38 + .../side_effects/raw/manual_add_todo.dart | 39 + .../raw/mutable_manual_add_todo.dart | 34 + .../side_effects/raw/render_add_todo.dart | 75 ++ .../raw/render_add_todo_hooks.dart | 68 ++ .../side_effects/raw/rest_add_todo.dart | 39 + .../side_effects/raw/todo_list_notifier.dart | 44 ++ .../raw/todo_list_notifier_add_todo.dart | 28 + .../side_effects/raw/todo_list_provider.dart | 27 + .../side_effects/render_add_todo.ts | 9 + .../side_effects/todo_list_notifier.ts | 9 + .../todo_list_notifier_add_todo.ts | 9 + .../side_effects/todo_list_provider.ts | 7 + .../current/essentials/testing.mdx | 148 ++++ .../testing/auto_dispose_listen.dart | 24 + .../essentials/testing/await_future.dart | 29 + .../essentials/testing/create_container.dart | 22 + .../essentials/testing/full_widget_test.dart | 33 + .../essentials/testing/listen_provider.dart | 22 + .../essentials/testing/mock_provider.dart | 45 ++ .../testing/notifier_mock/codegen.dart | 17 + .../testing/notifier_mock/codegen.g.dart | 27 + .../essentials/testing/notifier_mock/index.ts | 4 + .../essentials/testing/notifier_mock/raw.dart | 15 + .../testing/provider_to_mock/codegen.dart | 9 + .../testing/provider_to_mock/codegen.g.dart | 26 + .../testing/provider_to_mock/index.ts | 4 + .../testing/provider_to_mock/raw.dart | 6 + .../current/essentials/testing/unit_test.dart | 23 + .../testing/widget_container_of.dart | 15 + .../essentials/testing/widget_test.dart | 22 + .../current/essentials/websockets_sync.mdx | 100 +++ .../change_notifier_provider.dart | 11 + .../websockets_sync/pipe_change_notifier.dart | 21 + .../pipe_change_notifier.g.dart | 28 + .../essentials/websockets_sync/raw_usage.dart | 28 + .../websockets_sync/raw_usage.g.dart | 26 + .../shared_pipe_change_notifier.dart | 29 + .../shared_pipe_change_notifier.g.dart | 42 ++ .../stream_provider/codegen.dart | 34 + .../stream_provider/codegen.g.dart | 27 + .../websockets_sync/stream_provider/index.ts | 4 + .../websockets_sync/stream_provider/raw.dart | 30 + .../websockets_sync/sync_consumer.dart | 19 + .../sync_definition/codegen.dart | 10 + .../sync_definition/codegen.g.dart | 28 + .../websockets_sync/sync_definition/index.ts | 4 + .../websockets_sync/sync_definition/raw.dart | 7 + .../current/from_provider/family/family.dart | 11 + .../from_provider/family/family.g.dart | 174 +++++ .../current/from_provider/family/index.tsx | 9 + .../current/from_provider/family/raw.dart | 27 + .../current/from_provider/helpers/item.dart | 12 + .../from_provider/helpers/item.freezed.dart | 146 ++++ .../current/from_provider/helpers/item.g.dart | 18 + .../current/from_provider/helpers/json.dart | 1 + .../motivation/async_values/async_values.dart | 29 + .../async_values/async_values.g.dart | 40 ++ .../motivation/async_values/index.tsx | 9 + .../motivation/async_values/raw.dart | 25 + .../motivation/auto_dispose/auto_dispose.dart | 29 + .../auto_dispose/auto_dispose.g.dart | 41 ++ .../motivation/auto_dispose/index.tsx | 9 + .../motivation/auto_dispose/raw.dart | 24 + .../motivation/combine/combine.dart | 19 + .../motivation/combine/combine.g.dart | 40 ++ .../motivation/combine/index.tsx | 9 + .../from_provider/motivation/combine/raw.dart | 15 + .../from_provider/motivation/motivation.mdx | 192 +++++ .../motivation/override/index.tsx | 9 + .../motivation/override/override.dart | 16 + .../motivation/override/raw.dart | 16 + .../motivation/same_type/index.tsx | 9 + .../motivation/same_type/raw.dart | 15 + .../motivation/same_type/same_type.dart | 19 + .../motivation/same_type/same_type.g.dart | 40 ++ .../motivation/side_effects/index.tsx | 9 + .../motivation/side_effects/raw.dart | 24 + .../motivation/side_effects/side_effects.dart | 24 + .../from_provider/provider_vs_riverpod.mdx | 419 +++++++++++ .../current/from_provider/quickstart.mdx | 190 +++++ .../current/getting_started.mdx | 154 ---- .../getting_started/dart_hello_world/raw.dart | 19 - .../getting_started/dart_pubspec/codegen.yaml | 11 - .../getting_started/dart_pubspec/index.tsx | 9 - .../getting_started/dart_pubspec/raw.yaml | 6 - .../getting_started/pubspec/codegen.yaml | 14 - .../getting_started/pubspec/hooks.yaml | 10 - .../pubspec/hooks_codegen.yaml | 15 - .../current/getting_started/pubspec/index.tsx | 11 - .../current/getting_started/pubspec/raw.yaml | 9 - .../current/introduction.mdx | 76 -- .../current/introduction/getting_started.mdx | 191 +++++ .../dart_hello_world}/index.tsx | 0 .../dart_hello_world/main.dart | 25 + .../dart_hello_world/main.g.dart | 26 + .../dart_hello_world/raw.dart} | 8 +- .../getting_started/dart_pub_add.tsx | 32 + .../getting_started/dart_pubspec.tsx | 40 ++ .../hello_world/hooks_codegen/main.dart} | 16 +- .../hello_world/hooks_codegen/main.g.dart | 26 + .../getting_started/hello_world/index.tsx | 11 + .../getting_started/hello_world/main.dart | 12 +- .../getting_started/hello_world/main.g.dart | 26 + .../getting_started/hello_world/raw.dart | 0 .../hello_world/raw_hooks.dart | 0 .../introduction/getting_started/pub_add.tsx | 39 + .../introduction/getting_started/pubspec.tsx | 51 ++ .../current/introduction/why_riverpod.mdx | 56 ++ .../introduction/why_riverpod/codegen.dart | 31 + .../introduction/why_riverpod/codegen.g.dart | 178 +++++ .../introduction/why_riverpod/index.tsx | 9 + .../introduction/why_riverpod/raw.dart | 27 + .../current/migration/0.13.0_to_0.14.0.mdx | 59 +- .../current/migration/0.14.0_to_1.0.0.mdx | 102 +-- .../migration/from_change_notifier.mdx | 117 +++ .../declaration/declaration.dart | 33 + .../declaration/declaration.g.dart | 27 + .../declaration/index.tsx | 9 + .../from_change_notifier/declaration/raw.dart | 33 + .../initialization/index.tsx | 9 + .../initialization/initialization.dart | 29 + .../initialization/initialization.g.dart | 27 + .../initialization/raw.dart | 29 + .../from_change_notifier/migrated/index.tsx | 9 + .../migrated/migrated.dart | 37 + .../migrated/migrated.g.dart | 27 + .../from_change_notifier/migrated/raw.dart | 37 + .../migration/from_change_notifier/old.dart | 60 ++ .../current/migration/from_state_notifier.mdx | 243 +++++++ .../add_listener/add_listener.dart | 18 + .../add_listener/add_listener.g.dart | 27 + .../add_listener/index.tsx | 9 + .../from_state_notifier/add_listener/raw.dart | 17 + .../from_state_notifier/add_listener_old.dart | 24 + .../async_notifier/async_notifier.dart | 28 + .../async_notifier/async_notifier.g.dart | 29 + .../async_notifier/index.tsx | 9 + .../async_notifier/raw.dart | 29 + .../async_notifier_old.dart | 30 + .../build_init/build_init.dart | 15 + .../build_init/build_init.g.dart | 28 + .../from_state_notifier/build_init/index.tsx | 9 + .../from_state_notifier/build_init/raw.dart | 15 + .../from_state_notifier/build_init_old.dart | 13 + .../consumers_dont_change.dart | 36 + .../family_and_dispose.dart | 24 + .../family_and_dispose.g.dart | 178 +++++ .../family_and_dispose/index.tsx | 9 + .../family_and_dispose/raw.dart | 27 + .../family_and_dispose_old.dart | 30 + .../from_state_provider.dart | 14 + .../from_state_provider.g.dart | 28 + .../from_state_provider/index.tsx | 9 + .../from_state_provider/raw.dart | 13 + .../from_state_provider_old.dart | 6 + .../obtain_notifier_on_tests.dart | 33 + .../old_lifecycles/index.tsx | 9 + .../old_lifecycles/old_lifecycles.dart | 36 + .../old_lifecycles/old_lifecycles.g.dart | 27 + .../old_lifecycles/raw.dart | 35 + .../old_lifecycles_final/index.tsx | 9 + .../old_lifecycles_final.dart | 38 + .../old_lifecycles_final.g.dart | 27 + .../old_lifecycles_final/raw.dart | 36 + .../old_lifecycles_old.dart | 42 ++ .../current/migration/utils.dart | 23 + .../providers/change_notifier_provider.mdx | 13 +- .../change_notifier_provider/todos.dart | 11 +- .../current/providers/future_provider.mdx | 20 +- .../config_consumer/codegen.dart | 13 +- .../config_consumer/hooks.dart | 11 +- .../config_consumer/hooks_codegen.dart | 17 +- .../future_provider/config_consumer/raw.dart | 12 +- .../current/providers/notifier_provider.mdx | 55 ++ .../remote_todos/codegen.dart | 82 +++ .../remote_todos/codegen.freezed.dart | 184 +++++ .../remote_todos/codegen.g.dart | 44 ++ .../notifier_provider/remote_todos/index.tsx | 9 + .../notifier_provider/remote_todos/raw.dart | 101 +++ .../remote_todos/todos_consumer.dart | 37 + .../notifier_provider/todos/codegen.dart | 68 ++ .../todos/codegen.freezed.dart | 166 +++++ .../notifier_provider/todos/codegen.g.dart | 26 + .../notifier_provider/todos/index.tsx | 9 + .../notifier_provider/todos/raw.dart | 85 +++ .../todos/todos_consumer.dart | 32 + .../current/providers/provider.mdx | 32 +- .../completed_todos/completed_todos.dart | 15 + .../completed_todos/completed_todos.g.dart | 27 + .../provider/completed_todos/index.tsx | 9 + .../raw.dart} | 6 +- .../optimized_previous_button/index.tsx | 9 + .../optimized_previous_button.dart | 50 ++ .../optimized_previous_button.g.dart | 42 ++ .../optimized_previous_button/raw.dart} | 15 +- .../current/providers/provider/todo/index.tsx | 9 + .../provider/{todo.dart => todo/raw.dart} | 11 +- .../current/providers/provider/todo/todo.dart | 25 + .../providers/provider/todo/todo.g.dart | 26 + .../providers/provider/todos_consumer.dart | 14 +- .../unoptimized_previous_button/index.tsx | 9 + .../raw.dart} | 6 +- .../unoptimized_previous_button.dart | 39 + .../unoptimized_previous_button.g.dart | 26 + .../providers/state_notifier_provider.mdx | 16 +- .../current/providers/state_provider.mdx | 20 +- .../current/providers/stream_provider.mdx | 27 +- .../live_stream_chat_consumer.dart | 25 + .../live_stream_chat_provider.dart | 18 + .../live_stream_chat_provider/codegen.dart | 23 + .../live_stream_chat_provider/codegen.g.dart | 26 + .../live_stream_chat_provider/index.tsx | 9 + .../live_stream_chat_provider/raw.dart | 18 + .../current/riverpod_for_provider_users.mdx | 401 ----------- .../ko/docusaurus-theme-classic/footer.json | 34 +- .../ko/docusaurus-theme-classic/navbar.json | 6 +- website/i18n/zh-Hans/code.json | 119 ++-- .../current.json | 80 ++- .../current/about_code_generation.mdx | 58 -- .../current/about_hooks.mdx | 272 ------- .../current/advanced/select.mdx | 129 ++++ .../advanced/select/select/codegen.dart | 30 + .../advanced/select/select/codegen.g.dart | 26 + .../current/advanced/select/select/index.ts | 4 + .../current/advanced/select/select/raw.dart | 29 + .../advanced/select/select_async/codegen.dart | 26 + .../select/select_async/codegen.g.dart | 26 + .../advanced/select/select_async/index.ts | 4 + .../advanced/select/select_async/raw.dart | 23 + .../current/case_studies/cancel.mdx | 278 ++++++++ .../cancel/detail_screen/codegen.dart | 61 ++ .../cancel/detail_screen/codegen.freezed.dart | 209 ++++++ .../cancel/detail_screen/codegen.g.dart | 46 ++ .../cancel/detail_screen/index.ts | 4 + .../cancel/detail_screen/raw.dart | 65 ++ .../cancel/detail_screen_cancel/codegen.dart | 28 + .../detail_screen_cancel/codegen.g.dart | 26 + .../cancel/detail_screen_cancel/index.ts | 4 + .../cancel/detail_screen_cancel/raw.dart | 25 + .../detail_screen_debounce/codegen.dart | 38 + .../detail_screen_debounce/codegen.g.dart | 26 + .../cancel/detail_screen_debounce/index.ts | 4 + .../cancel/detail_screen_debounce/raw.dart | 35 + .../case_studies/cancel/extension.dart | 32 + .../case_studies/cancel/home_screen.dart | 39 + .../provider_with_extension/codegen.dart | 25 + .../provider_with_extension/codegen.g.dart | 26 + .../cancel/provider_with_extension/index.ts | 4 + .../cancel/provider_with_extension/raw.dart | 22 + .../current/case_studies/pull_to_refresh.mdx | 253 +++++++ .../pull_to_refresh/activity/codegen.dart | 19 + .../activity/codegen.freezed.dart | 209 ++++++ .../pull_to_refresh/activity/codegen.g.dart | 25 + .../pull_to_refresh/activity/index.ts | 4 + .../pull_to_refresh/activity/raw.dart | 24 + .../pull_to_refresh/display_activity.dart | 22 + .../pull_to_refresh/display_activity2.dart | 28 + .../pull_to_refresh/display_activity3.dart | 30 + .../pull_to_refresh/display_activity4.dart | 36 + .../fetch_activity/codegen.dart | 20 + .../fetch_activity/codegen.g.dart | 26 + .../pull_to_refresh/fetch_activity/index.ts | 4 + .../pull_to_refresh/fetch_activity/raw.dart | 17 + .../pull_to_refresh/full_app/codegen.dart | 69 ++ .../full_app/codegen.freezed.dart | 209 ++++++ .../pull_to_refresh/full_app/codegen.g.dart | 46 ++ .../pull_to_refresh/full_app/index.ts | 4 + .../pull_to_refresh/full_app/raw.dart | 73 ++ .../concepts/about_code_generation.mdx | 488 +++++++++++++ .../current/concepts/about_codegen/main.dart | 22 + .../{ => concepts}/about_codegen/main.g.dart | 0 .../provider_type/async_class_future.dart | 14 + .../provider_type/async_class_future.g.dart | 27 + .../provider_type/async_class_stream.dart | 14 + .../provider_type/async_class_stream.g.dart | 27 + .../provider_type/async_fn_future.dart | 9 + .../provider_type/async_fn_future.g.dart | 26 + .../provider_type/async_fn_stream.dart | 9 + .../provider_type/async_fn_stream.g.dart | 26 + .../provider_type/auto_dispose.dart | 12 + .../provider_type/auto_dispose.g.dart | 40 ++ .../about_codegen/provider_type/family.dart | 7 + .../about_codegen/provider_type/family.g.dart | 159 +++++ .../provider_type/family_class.dart | 17 + .../provider_type/family_class.g.dart | 195 +++++ .../provider_type/family_fn.dart | 13 + .../provider_type/family_fn.g.dart | 176 +++++ .../non_code_gen/async_notifier_provider.dart | 16 + .../non_code_gen/auto_dispose.dart | 12 + .../provider_type/non_code_gen/family.dart | 6 + .../non_code_gen/future_provider.dart | 7 + .../non_code_gen/notifier_provider.dart | 15 + .../provider_type/non_code_gen/provider.dart | 8 + .../stream_notifier_provider.dart | 16 + .../non_code_gen/stream_provider.dart | 7 + .../provider_type/sync_class.dart | 14 + .../provider_type/sync_class.g.dart | 26 + .../about_codegen/provider_type/sync_fn.dart | 9 + .../provider_type/sync_fn.g.dart | 26 + .../current/concepts/about_codegen/raw.dart | 19 + .../current/concepts/about_hooks.mdx | 570 +++++++++++++++ .../about_hooks/hook_and_consumer.dart | 28 + .../concepts/about_hooks/hook_consumer.dart | 26 + .../about_hooks/hook_consumer_widget.dart | 23 + .../concepts/async_initialization.dart | 58 ++ .../characters_provider/codegen.dart | 29 + .../characters_provider/codegen.g.dart | 40 ++ .../characters_provider/index.tsx | 9 + .../characters_provider/models.dart | 17 + .../characters_provider/raw.dart | 25 + .../city_provider/codegen.dart | 7 + .../city_provider/codegen.g.dart | 26 + .../city_provider/index.tsx | 9 + .../city_provider/raw.dart | 4 + .../filtered_todo_list_provider/codegen.dart | 31 + .../codegen.g.dart | 27 + .../filtered_todo_list_provider/index.tsx | 9 + .../filtered_todo_list_provider/raw.dart | 26 + .../read_in_provider/codegen.dart | 17 + .../read_in_provider/codegen.g.dart | 40 ++ .../read_in_provider/index.tsx | 9 + .../read_in_provider/raw.dart | 13 + .../select_async_provider/codegen.dart | 25 + .../select_async_provider/codegen.g.dart | 40 ++ .../select_async_provider/index.tsx | 9 + .../select_async_provider/models.dart | 21 + .../select_async_provider/raw.dart | 20 + .../todo_list_provider/codegen.dart | 24 + .../todo_list_provider/codegen.freezed.dart | 166 +++++ .../todo_list_provider/codegen.g.dart | 27 + .../todo_list_provider/index.tsx | 9 + .../todo_list_provider/raw.dart | 36 + .../weather_provider/codegen.dart | 20 + .../weather_provider/codegen.g.dart | 40 ++ .../weather_provider/index.tsx | 9 + .../weather_provider/raw.dart | 18 + .../whole_object_provider/codegen.dart | 24 + .../whole_object_provider/codegen.g.dart | 40 ++ .../whole_object_provider/index.tsx | 9 + .../whole_object_provider/models.dart | 16 + .../whole_object_provider/raw.dart | 20 + .../current/concepts/combining_providers.mdx | 229 +++--- .../current/concepts/dialog_scope.dart | 58 ++ .../concepts/modifiers/auto_dispose.mdx | 88 ++- .../current/concepts/modifiers/family.mdx | 75 +- .../current/concepts/provider_lifecycles.mdx | 109 +-- .../current/concepts/provider_observer.mdx | 37 +- .../concepts/provider_observer_logger.dart | 61 ++ .../current/concepts/providers.mdx | 178 +++-- .../creating_a_provider/codegen.dart | 12 + .../creating_a_provider/codegen.g.dart | 26 + .../providers/creating_a_provider/index.tsx | 9 + .../providers/creating_a_provider/raw.dart | 9 + .../declaring_many_providers/codegen.dart | 11 + .../declaring_many_providers/codegen.g.dart | 40 ++ .../declaring_many_providers/index.tsx | 9 + .../declaring_many_providers/raw.dart | 6 + .../current/concepts/reading.mdx | 339 +++++---- .../consumer_stateful_widget/hooks.dart | 6 +- .../reading/consumer_stateful_widget/raw.dart | 4 +- .../reading/consumer_widget/hooks.dart | 4 +- .../concepts/reading/consumer_widget/raw.dart | 2 +- .../concepts/reading/counter/codegen.dart | 2 +- .../current/concepts/reading/counter/raw.dart | 2 +- .../reading/listen_build/codegen_hooks.dart | 34 + .../reading/listen_build/codegen_hooks.g.dart | 26 + .../concepts/reading/listen_build/index.tsx | 6 +- .../reading/listen_build/raw_hooks.dart | 29 + .../concepts/reading/provider/codegen.dart | 2 +- .../concepts/reading/provider/raw.dart | 2 +- .../concepts/reading/read/codegen.dart | 2 +- .../concepts/reading/read/codegen_hooks.dart | 36 + .../reading/read/codegen_hooks.g.dart | 26 + .../current/concepts/reading/read/index.tsx | 6 +- .../current/concepts/reading/read/raw.dart | 5 +- .../concepts/reading/read/raw_hooks.dart | 31 + .../concepts/reading/read_build/codegen.dart | 2 +- .../concepts/reading/read_build/raw.dart | 2 +- .../concepts/reading/watch/codegen.dart | 6 +- .../current/concepts/reading/watch/raw.dart | 6 +- .../concepts/reading/watch_build/codegen.dart | 2 +- .../reading/watch_build/codegen_hooks.dart | 43 ++ .../reading/watch_build/codegen_hooks.g.dart | 41 ++ .../concepts/reading/watch_build/index.tsx | 6 +- .../concepts/reading/watch_build/raw.dart | 2 +- .../reading/watch_build/raw_hooks.dart | 37 + .../current/concepts/scopes.mdx | 183 ++--- .../current/concepts/subtree_scope.dart | 78 ++ .../current/concepts/theme_scope.dart | 68 ++ .../current/concepts/why_immutability.mdx | 144 ++-- .../concepts/why_immutability/codegen.dart | 58 ++ .../why_immutability/codegen.freezed.dart | 151 ++++ .../concepts/why_immutability/codegen.g.dart | 28 + .../concepts/why_immutability/index.tsx | 9 + .../concepts/why_immutability/raw.dart | 67 ++ .../current/cookbooks/search_as_we_type.mdx | 148 ++++ .../current/cookbooks/testing.mdx | 106 +-- .../current/cookbooks/testing_dart.dart | 51 ++ .../current/cookbooks/testing_flutter.dart | 40 ++ .../current/cookbooks/testing_full.dart | 99 +++ .../cookbooks/testing_original_test_dart.dart | 63 ++ .../testing_original_test_flutter.dart | 56 ++ .../cookbooks/testing_override_info.dart | 43 ++ .../current/cookbooks/testing_repository.dart | 22 + .../current/essentials/auto_dispose.mdx | 306 ++++++++ .../auto_dispose/cache_for_extension.dart | 18 + .../auto_dispose/cache_for_usage/codegen.dart | 17 + .../cache_for_usage/codegen.g.dart | 26 + .../auto_dispose/cache_for_usage/index.ts | 4 + .../auto_dispose/cache_for_usage/raw.dart | 15 + .../auto_dispose/codegen_keep_alive.dart | 10 + .../auto_dispose/codegen_keep_alive.g.dart | 26 + .../auto_dispose/invalidate_example.dart | 23 + .../invalidate_family_example/codegen.dart | 24 + .../invalidate_family_example/codegen.g.dart | 159 +++++ .../invalidate_family_example/index.ts | 4 + .../invalidate_family_example/raw.dart | 20 + .../auto_dispose/keep_alive/codegen.dart | 21 + .../auto_dispose/keep_alive/codegen.g.dart | 26 + .../auto_dispose/keep_alive/index.ts | 4 + .../auto_dispose/keep_alive/raw.dart | 19 + .../on_dispose_example/codegen.dart | 23 + .../on_dispose_example/codegen.g.dart | 40 ++ .../auto_dispose/on_dispose_example/index.ts | 4 + .../auto_dispose/on_dispose_example/raw.dart | 17 + .../auto_dispose/raw_auto_dispose.dart | 7 + .../current/essentials/combining_requests.mdx | 275 +++++++ .../functional_ref/codegen.dart | 18 + .../functional_ref/codegen.g.dart | 40 ++ .../functional_ref/index.ts | 4 + .../functional_ref/raw.dart | 14 + .../listen_example/codegen.dart | 17 + .../listen_example/codegen.g.dart | 26 + .../listen_example/index.ts | 4 + .../listen_example/raw.dart | 13 + .../notifier_ref/codegen.dart | 21 + .../notifier_ref/codegen.g.dart | 40 ++ .../combining_requests/notifier_ref/index.ts | 4 + .../combining_requests/notifier_ref/raw.dart | 19 + .../read_example/codegen.dart | 23 + .../read_example/codegen.g.dart | 27 + .../combining_requests/read_example/index.ts | 4 + .../combining_requests/read_example/raw.dart | 22 + .../watch_example/codegen.dart | 44 ++ .../watch_example/codegen.g.dart | 44 ++ .../combining_requests/watch_example/index.ts | 4 + .../combining_requests/watch_example/raw.dart | 41 ++ .../watch_placement/codegen.dart | 37 + .../watch_placement/codegen.g.dart | 41 ++ .../watch_placement/index.ts | 4 + .../watch_placement/raw.dart | 35 + .../current/essentials/do_dont.mdx | 260 +++++++ .../essentials/eager_initialization.mdx | 117 +++ .../async_consumer_example.dart | 28 + .../consumer_example.dart | 36 + .../require_value/codegen.dart | 24 + .../require_value/codegen.g.dart | 26 + .../require_value/index.ts | 4 + .../require_value/raw.dart | 20 + .../current/essentials/faq.mdx | 355 ++++++++++ .../current/essentials/first_request.mdx | 580 +++++++++++++++ .../essentials/first_request/activity.ts | 9 + .../first_request/codegen/activity.dart | 23 + .../codegen/activity.freezed.dart | 237 +++++++ .../first_request/codegen/activity.g.dart | 27 + .../first_request/codegen/provider.dart | 19 + .../first_request/codegen/provider.g.dart | 29 + .../essentials/first_request/consumer.ts | 8 + .../first_request/consumer_stateful_widget.ts | 8 + .../first_request/consumer_widget.ts | 8 + .../first_request/hook_consumer_widget.ts | 8 + .../first_request/legend/DocuCode.scss | 18 + .../first_request/legend/legend.tsx | 88 +++ .../essentials/first_request/provider.ts | 9 + .../first_request/raw/activity.dart | 28 + .../first_request/raw/consumer.dart | 39 + .../raw/consumer_stateful_widget.dart | 42 ++ .../first_request/raw/consumer_widget.dart | 23 + .../raw/hook_consumer_widget.dart | 26 + .../first_request/raw/provider.dart | 13 + .../current/essentials/passing_args.mdx | 265 +++++++ .../passing_args/codegen/family.dart | 29 + .../passing_args/codegen/family.g.dart | 159 +++++ .../passing_args/codegen/provider.dart | 32 + .../passing_args/codegen/provider.g.dart | 191 +++++ .../passing_args/no_arg_provider.ts | 9 + .../passing_args/raw/consumer_family.dart | 24 + .../raw/consumer_list_family.dart | 23 + .../passing_args/raw/consumer_provider.dart | 20 + .../raw/consumer_tuple_family.dart | 23 + .../essentials/passing_args/raw/family.dart | 42 ++ .../raw/multiple_consumer_family.dart | 37 + .../essentials/passing_args/raw/provider.dart | 26 + .../passing_args/raw/tuple_family.dart | 32 + .../current/essentials/provider_observer.mdx | 79 +++ .../provider_observer/provider_observer.dart | 43 ++ .../current/essentials/side_effects.mdx | 668 ++++++++++++++++++ .../codegen/todo_list_notifier.dart | 28 + .../codegen/todo_list_notifier.freezed.dart | 166 +++++ .../codegen/todo_list_notifier.g.dart | 42 ++ .../codegen/todo_list_notifier_add_todo.dart | 26 + .../todo_list_notifier_add_todo.g.dart | 27 + .../codegen/todo_list_provider.dart | 23 + .../codegen/todo_list_provider.freezed.dart | 148 ++++ .../codegen/todo_list_provider.g.dart | 26 + .../raw/consumer_add_todo_call.dart | 25 + .../raw/invalidate_self_add_todo.dart | 38 + .../side_effects/raw/manual_add_todo.dart | 39 + .../raw/mutable_manual_add_todo.dart | 35 + .../side_effects/raw/render_add_todo.dart | 78 ++ .../raw/render_add_todo_hooks.dart | 68 ++ .../side_effects/raw/rest_add_todo.dart | 39 + .../side_effects/raw/todo_list_notifier.dart | 44 ++ .../raw/todo_list_notifier_add_todo.dart | 28 + .../side_effects/raw/todo_list_provider.dart | 27 + .../side_effects/render_add_todo.ts | 9 + .../side_effects/todo_list_notifier.ts | 9 + .../todo_list_notifier_add_todo.ts | 9 + .../side_effects/todo_list_provider.ts | 7 + .../current/essentials/testing.mdx | 303 ++++++++ .../testing/auto_dispose_listen.dart | 24 + .../essentials/testing/await_future.dart | 32 + .../essentials/testing/create_container.dart | 22 + .../essentials/testing/full_widget_test.dart | 33 + .../essentials/testing/listen_provider.dart | 22 + .../essentials/testing/mock_provider.dart | 45 ++ .../testing/notifier_mock/codegen.dart | 17 + .../testing/notifier_mock/codegen.g.dart | 27 + .../essentials/testing/notifier_mock/index.ts | 4 + .../essentials/testing/notifier_mock/raw.dart | 14 + .../testing/provider_to_mock/codegen.dart | 9 + .../testing/provider_to_mock/codegen.g.dart | 26 + .../testing/provider_to_mock/index.ts | 4 + .../testing/provider_to_mock/raw.dart | 6 + .../current/essentials/testing/unit_test.dart | 23 + .../testing/widget_container_of.dart | 15 + .../essentials/testing/widget_test.dart | 22 + .../current/essentials/websockets_sync.mdx | 197 ++++++ .../change_notifier_provider.dart | 11 + .../websockets_sync/pipe_change_notifier.dart | 21 + .../pipe_change_notifier.g.dart | 28 + .../essentials/websockets_sync/raw_usage.dart | 30 + .../websockets_sync/raw_usage.g.dart | 26 + .../shared_pipe_change_notifier.dart | 29 + .../shared_pipe_change_notifier.g.dart | 42 ++ .../stream_provider/codegen.dart | 34 + .../stream_provider/codegen.g.dart | 27 + .../websockets_sync/stream_provider/index.ts | 4 + .../websockets_sync/stream_provider/raw.dart | 30 + .../websockets_sync/sync_consumer.dart | 19 + .../sync_definition/codegen.dart | 10 + .../sync_definition/codegen.g.dart | 28 + .../websockets_sync/sync_definition/index.ts | 4 + .../websockets_sync/sync_definition/raw.dart | 7 + .../current/from_provider/family/family.dart | 11 + .../from_provider/family/family.g.dart | 174 +++++ .../current/from_provider/family/index.tsx | 9 + .../current/from_provider/family/raw.dart | 27 + .../current/from_provider/helpers/item.dart | 12 + .../from_provider/helpers/item.freezed.dart | 146 ++++ .../current/from_provider/helpers/item.g.dart | 18 + .../current/from_provider/helpers/json.dart | 1 + .../motivation/async_values/async_values.dart | 29 + .../async_values/async_values.g.dart | 40 ++ .../motivation/async_values/index.tsx | 9 + .../motivation/async_values/raw.dart | 25 + .../motivation/auto_dispose/auto_dispose.dart | 24 + .../auto_dispose/auto_dispose.g.dart | 41 ++ .../motivation/auto_dispose/index.tsx | 9 + .../motivation/auto_dispose/raw.dart | 20 + .../motivation/combine/combine.dart | 19 + .../motivation/combine/combine.g.dart | 40 ++ .../motivation/combine/index.tsx | 9 + .../from_provider/motivation/combine/raw.dart | 15 + .../from_provider/motivation/motivation.mdx | 434 ++++++++++++ .../motivation/override/index.tsx | 9 + .../motivation/override/override.dart | 16 + .../motivation/override/raw.dart | 15 + .../motivation/same_type/index.tsx | 9 + .../motivation/same_type/raw.dart | 15 + .../motivation/same_type/same_type.dart | 19 + .../motivation/same_type/same_type.g.dart | 40 ++ .../motivation/side_effects/index.tsx | 9 + .../motivation/side_effects/raw.dart | 24 + .../motivation/side_effects/side_effects.dart | 24 + .../from_provider/provider_vs_riverpod.mdx | 653 +++++++++++++++++ .../current/from_provider/quickstart.mdx | 399 +++++++++++ .../current/getting_started.mdx | 189 ----- .../current/getting_started/dart_pub_add.tsx | 10 - .../current/getting_started/pub_add.tsx | 14 - .../current/introduction.mdx | 65 -- .../current/introduction/getting_started.mdx | 296 ++++++++ .../dart_hello_world/index.tsx | 0 .../dart_hello_world/main.dart | 25 + .../dart_hello_world}/main.g.dart | 0 .../getting_started/dart_hello_world/raw.dart | 19 + .../getting_started/dart_pub_add.tsx | 32 + .../getting_started/dart_pubspec.tsx | 40 ++ .../hello_world/hooks_codegen/main.dart | 46 ++ .../hello_world/hooks_codegen/main.g.dart} | 2 +- .../getting_started/hello_world/index.tsx | 11 + .../getting_started/hello_world/main.dart | 42 ++ .../getting_started/hello_world/main.g.dart | 26 + .../getting_started/hello_world/raw.dart | 16 +- .../hello_world/raw_hooks.dart | 40 ++ .../introduction/getting_started/pub_add.tsx | 39 + .../getting_started/pubspec.tsx | 16 +- .../current/introduction/why_riverpod.mdx | 125 ++++ .../introduction/why_riverpod/codegen.dart | 29 + .../introduction/why_riverpod/codegen.g.dart | 178 +++++ .../introduction/why_riverpod/index.tsx | 9 + .../introduction/why_riverpod/raw.dart | 25 + .../current/migration/0.13.0_to_0.14.0.mdx | 120 ++++ .../current/migration/0.14.0_to_1.0.0.mdx | 226 ++++++ .../migration/from_change_notifier.mdx | 259 +++++++ .../declaration/declaration.dart | 33 + .../declaration/declaration.g.dart | 27 + .../declaration/index.tsx | 9 + .../from_change_notifier/declaration/raw.dart | 33 + .../initialization/index.tsx | 9 + .../initialization/initialization.dart | 29 + .../initialization/initialization.g.dart | 27 + .../initialization/raw.dart | 29 + .../from_change_notifier/migrated/index.tsx | 9 + .../migrated/migrated.dart | 37 + .../migrated/migrated.g.dart | 27 + .../from_change_notifier/migrated/raw.dart | 37 + .../migration/from_change_notifier/old.dart | 60 ++ .../current/migration/from_state_notifier.mdx | 547 ++++++++++++++ .../add_listener/add_listener.dart | 18 + .../add_listener/add_listener.g.dart | 27 + .../add_listener/index.tsx | 9 + .../from_state_notifier/add_listener/raw.dart | 17 + .../from_state_notifier/add_listener_old.dart | 24 + .../async_notifier/async_notifier.dart | 28 + .../async_notifier/async_notifier.g.dart | 29 + .../async_notifier/index.tsx | 9 + .../async_notifier/raw.dart | 29 + .../async_notifier_old.dart | 30 + .../build_init/build_init.dart | 15 + .../build_init/build_init.g.dart | 28 + .../from_state_notifier/build_init/index.tsx | 9 + .../from_state_notifier/build_init/raw.dart | 15 + .../from_state_notifier/build_init_old.dart | 13 + .../consumers_dont_change.dart | 36 + .../family_and_dispose.dart | 24 + .../family_and_dispose.g.dart | 178 +++++ .../family_and_dispose/index.tsx | 9 + .../family_and_dispose/raw.dart | 27 + .../family_and_dispose_old.dart | 30 + .../from_state_provider.dart | 14 + .../from_state_provider.g.dart | 28 + .../from_state_provider/index.tsx | 9 + .../from_state_provider/raw.dart | 13 + .../from_state_provider_old.dart | 6 + .../obtain_notifier_on_tests.dart | 35 + .../old_lifecycles/index.tsx | 9 + .../old_lifecycles/old_lifecycles.dart | 36 + .../old_lifecycles/old_lifecycles.g.dart | 27 + .../old_lifecycles/raw.dart | 35 + .../old_lifecycles_final/index.tsx | 9 + .../old_lifecycles_final.dart | 38 + .../old_lifecycles_final.g.dart | 27 + .../old_lifecycles_final/raw.dart | 37 + .../old_lifecycles_old.dart | 42 ++ .../current/migration/utils.dart | 23 + .../providers/change_notifier_provider.mdx | 53 +- .../change_notifier_provider/todos.dart | 11 +- .../current/providers/future_provider.mdx | 58 +- .../config_consumer/codegen.dart | 13 +- .../config_consumer/hooks.dart | 11 +- .../config_consumer/hooks_codegen.dart | 17 +- .../future_provider/config_consumer/raw.dart | 12 +- .../current/providers/notifier_provider.mdx | 55 +- .../remote_todos/codegen.dart | 22 +- .../remote_todos/codegen.freezed.dart | 4 +- .../notifier_provider/remote_todos/raw.dart | 26 +- .../remote_todos/todos_consumer.dart | 35 +- .../notifier_provider/todos/codegen.dart | 42 +- .../todos/codegen.freezed.dart | 4 +- .../notifier_provider/todos/raw.dart | 51 +- .../todos/todos_consumer.dart | 6 +- .../current/providers/provider.mdx | 98 ++- .../completed_todos/completed_todos.dart | 2 +- .../provider/completed_todos/raw.dart | 4 +- .../optimized_previous_button.dart | 6 +- .../optimized_previous_button/raw.dart | 6 +- .../current/providers/provider/todo/raw.dart | 11 +- .../current/providers/provider/todo/todo.dart | 1 + .../providers/provider/todos_consumer.dart | 2 +- .../unoptimized_previous_button/raw.dart | 2 +- .../unoptimized_previous_button.dart | 2 +- .../providers/state_notifier_provider.mdx | 35 +- .../current/providers/state_provider.mdx | 118 ++-- .../current/providers/stream_provider.mdx | 67 +- .../live_stream_chat_consumer.dart | 21 +- .../live_stream_chat_provider/codegen.dart | 23 + .../live_stream_chat_provider/codegen.g.dart | 26 + .../live_stream_chat_provider/index.tsx | 9 + .../live_stream_chat_provider/raw.dart | 18 + .../current/riverpod_for_provider_users.mdx | 367 ---------- .../docusaurus-theme-classic/footer.json | 30 +- .../docusaurus-theme-classic/navbar.json | 4 + website/src/documents_meta.js | 207 +++--- website/static/snippets/combine.dart | 2 +- website/yarn.lock | 6 +- 1696 files changed, 66767 insertions(+), 6668 deletions(-) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 packages/riverpod_generator/lib/src/validation.dart create mode 100644 packages/riverpod_generator/test/error_test.dart create mode 100644 packages/riverpod_lint_flutter_test/test/assists/convert_class_based_provider_to_functional/convert_class_based_provider_to_functional.diff delete mode 100644 packages/riverpod_lint_flutter_test/test/assists/convert_class_based_provider_to_functional/convert_class_based_provider_to_functional.json create mode 100644 packages/riverpod_lint_flutter_test/test/assists/convert_functional_provider_to_class_based/convert_functional_provider_to_class_based.diff delete mode 100644 packages/riverpod_lint_flutter_test/test/assists/convert_functional_provider_to_class_based/convert_functional_provider_to_class_based.json create mode 100644 packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_consumer_stateful_widget.diff delete mode 100644 packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_consumer_stateful_widget.json create mode 100644 packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_consumer_widget.diff delete mode 100644 packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_consumer_widget.json create mode 100644 packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_hook_consumer_widget.diff delete mode 100644 packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_hook_consumer_widget.json create mode 100644 packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_hook_widget.diff delete mode 100644 packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_hook_widget.json create mode 100644 packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_stateful_hook_consumer_widget.diff delete mode 100644 packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_stateful_hook_consumer_widget.json create mode 100644 packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_stateful_hook_widget.diff delete mode 100644 packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_stateful_hook_widget.json create mode 100644 packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_stateful_widget.diff delete mode 100644 packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_stateful_widget.json create mode 100644 packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_stateless_widget.diff delete mode 100644 packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_stateless_widget.json create mode 100644 packages/riverpod_lint_flutter_test/test/assists/empty.diff delete mode 100644 packages/riverpod_lint_flutter_test/test/assists/empty.json create mode 100644 packages/riverpod_lint_flutter_test/test/assists/wrap_widget/wrap_with_consumer.diff delete mode 100644 packages/riverpod_lint_flutter_test/test/assists/wrap_widget/wrap_with_consumer.json create mode 100644 packages/riverpod_lint_flutter_test/test/assists/wrap_widget/wrap_with_provider_scope.diff delete mode 100644 packages/riverpod_lint_flutter_test/test/assists/wrap_widget/wrap_with_provider_scope.json create mode 100644 packages/riverpod_lint_flutter_test/test/lints/async_value_nullable_pattern/fix/async_value_nullable_pattern.diff delete mode 100644 packages/riverpod_lint_flutter_test/test/lints/async_value_nullable_pattern/fix/async_value_nullable_pattern.json create mode 100644 packages/riverpod_lint_flutter_test/test/lints/notifier_build/fix/notifier_build.diff delete mode 100644 packages/riverpod_lint_flutter_test/test/lints/notifier_build/fix/notifier_build.json create mode 100644 packages/riverpod_lint_flutter_test/test/lints/notifier_extends/notifier_extends.diff delete mode 100644 packages/riverpod_lint_flutter_test/test/lints/notifier_extends/notifier_extends.json create mode 100644 packages/riverpod_lint_flutter_test/test/lints/provider_dependencies/provider_dependencies.diff delete mode 100644 packages/riverpod_lint_flutter_test/test/lints/provider_dependencies/provider_dependencies.json create mode 100644 website/i18n/fr/docusaurus-plugin-content-docs/current/cookbooks/refresh.mdx create mode 100644 website/i18n/fr/docusaurus-plugin-content-docs/current/cookbooks/search_as_we_type.mdx create mode 100644 website/i18n/fr/docusaurus-plugin-content-docs/current/cookbooks/testing_dart.dart create mode 100644 website/i18n/fr/docusaurus-plugin-content-docs/current/cookbooks/testing_flutter.dart create mode 100644 website/i18n/fr/docusaurus-plugin-content-docs/current/cookbooks/testing_full.dart create mode 100644 website/i18n/fr/docusaurus-plugin-content-docs/current/cookbooks/testing_original_test_dart.dart create mode 100644 website/i18n/fr/docusaurus-plugin-content-docs/current/cookbooks/testing_original_test_flutter.dart create mode 100644 website/i18n/fr/docusaurus-plugin-content-docs/current/cookbooks/testing_override_info.dart create mode 100644 website/i18n/fr/docusaurus-plugin-content-docs/current/cookbooks/testing_repository.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/advanced/select.mdx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/advanced/select/select/codegen.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/advanced/select/select/codegen.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/advanced/select/select/index.ts create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/advanced/select/select/raw.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/advanced/select/select_async/codegen.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/advanced/select/select_async/codegen.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/advanced/select/select_async/index.ts create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/advanced/select/select_async/raw.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/case_studies/cancel.mdx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen/codegen.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen/codegen.freezed.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen/codegen.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen/index.ts create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen/raw.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_cancel/codegen.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_cancel/codegen.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_cancel/index.ts create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_cancel/raw.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_debounce/codegen.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_debounce/codegen.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_debounce/index.ts create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_debounce/raw.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/case_studies/cancel/extension.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/case_studies/cancel/home_screen.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/case_studies/cancel/provider_with_extension/codegen.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/case_studies/cancel/provider_with_extension/codegen.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/case_studies/cancel/provider_with_extension/index.ts create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/case_studies/cancel/provider_with_extension/raw.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh.mdx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/activity/codegen.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/activity/codegen.freezed.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/activity/codegen.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/activity/index.ts create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/activity/raw.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/display_activity.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/display_activity2.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/display_activity3.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/display_activity4.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/fetch_activity/codegen.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/fetch_activity/codegen.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/fetch_activity/index.ts create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/fetch_activity/raw.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/full_app/codegen.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/full_app/codegen.freezed.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/full_app/codegen.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/full_app/index.ts create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/full_app/raw.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/about_code_generation.mdx rename website/i18n/{ko/docusaurus-plugin-content-docs/current => it/docusaurus-plugin-content-docs/current/concepts}/about_codegen/main.dart (100%) create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/about_codegen/main.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_class_future.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_class_future.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_class_stream.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_class_stream.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_fn_future.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_fn_future.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_fn_stream.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_fn_stream.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/auto_dispose.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/auto_dispose.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/family.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/family.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/family_class.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/family_class.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/family_fn.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/family_fn.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/async_notifier_provider.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/auto_dispose.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/family.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/future_provider.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/notifier_provider.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/provider.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/stream_notifier_provider.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/stream_provider.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/sync_class.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/sync_class.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/sync_fn.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/sync_fn.g.dart rename website/i18n/{ko/docusaurus-plugin-content-docs/current => it/docusaurus-plugin-content-docs/current/concepts}/about_codegen/raw.dart (93%) create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/about_hooks.mdx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/about_hooks/hook_and_consumer.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/about_hooks/hook_consumer.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/about_hooks/hook_consumer_widget.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/async_initialization.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/characters_provider/codegen.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/characters_provider/codegen.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/characters_provider/index.tsx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/characters_provider/models.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/characters_provider/raw.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/city_provider/codegen.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/city_provider/codegen.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/city_provider/index.tsx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/city_provider/raw.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/filtered_todo_list_provider/codegen.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/filtered_todo_list_provider/codegen.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/filtered_todo_list_provider/index.tsx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/filtered_todo_list_provider/raw.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/read_in_provider/codegen.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/read_in_provider/codegen.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/read_in_provider/index.tsx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/read_in_provider/raw.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/select_async_provider/codegen.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/select_async_provider/codegen.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/select_async_provider/index.tsx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/select_async_provider/models.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/select_async_provider/raw.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/todo_list_provider/codegen.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/todo_list_provider/codegen.freezed.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/todo_list_provider/codegen.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/todo_list_provider/index.tsx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/todo_list_provider/raw.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/weather_provider/codegen.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/weather_provider/codegen.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/weather_provider/index.tsx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/weather_provider/raw.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/whole_object_provider/codegen.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/whole_object_provider/codegen.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/whole_object_provider/index.tsx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/whole_object_provider/models.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/whole_object_provider/raw.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/dialog_scope.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/lifecycle_on_dispose/codegen.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/lifecycle_on_dispose/codegen.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/lifecycle_on_dispose/index.tsx rename website/i18n/{zh-Hans/docusaurus-plugin-content-docs/current/concepts/lifecycle_on_dispose.dart => it/docusaurus-plugin-content-docs/current/concepts/lifecycle_on_dispose/raw.dart} (85%) create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/provider_lifecycles.mdx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/providers/creating_a_provider/codegen.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/providers/creating_a_provider/codegen.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/providers/creating_a_provider/index.tsx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/providers/creating_a_provider/raw.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/providers/declaring_many_providers/codegen.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/providers/declaring_many_providers/codegen.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/providers/declaring_many_providers/index.tsx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/providers/declaring_many_providers/raw.dart rename website/i18n/it/docusaurus-plugin-content-docs/current/concepts/{reading_consumer_hook.dart => reading/consumer_hook.dart} (74%) rename website/i18n/it/docusaurus-plugin-content-docs/current/concepts/{reading_stateful_hook_consumer_widget.dart => reading/consumer_stateful_widget/hooks.dart} (69%) create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/reading/consumer_stateful_widget/index.tsx rename website/i18n/it/docusaurus-plugin-content-docs/current/concepts/{reading_consumer_stateful_widget.dart => reading/consumer_stateful_widget/raw.dart} (72%) rename website/i18n/it/docusaurus-plugin-content-docs/current/concepts/{reading_consumer_hook_widget.dart => reading/consumer_widget/hooks.dart} (70%) create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/reading/consumer_widget/index.tsx rename website/i18n/it/docusaurus-plugin-content-docs/current/concepts/{reading_consumer_widget.dart => reading/consumer_widget/raw.dart} (80%) create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/reading/counter/codegen.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/reading/counter/codegen.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/reading/counter/index.tsx rename website/i18n/it/docusaurus-plugin-content-docs/current/concepts/{reading_counter.dart => reading/counter/raw.dart} (89%) create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/reading/listen/codegen.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/reading/listen/codegen.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/reading/listen/index.tsx rename website/i18n/{ko/docusaurus-plugin-content-docs/current/concepts/reading_listen.dart => it/docusaurus-plugin-content-docs/current/concepts/reading/listen/raw.dart} (75%) create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/reading/listen_build/codegen.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/reading/listen_build/codegen.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/reading/listen_build/codegen_hooks.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/reading/listen_build/codegen_hooks.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/reading/listen_build/index.tsx rename website/i18n/it/docusaurus-plugin-content-docs/current/concepts/{reading_listen_build.dart => reading/listen_build/raw.dart} (82%) create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/reading/listen_build/raw_hooks.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/reading/provider/codegen.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/reading/provider/codegen.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/reading/provider/index.tsx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/reading/provider/raw.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/reading/read/codegen.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/reading/read/codegen.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/reading/read/codegen_hooks.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/reading/read/codegen_hooks.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/reading/read/index.tsx rename website/i18n/it/docusaurus-plugin-content-docs/current/concepts/{reading_read.dart => reading/read/raw.dart} (76%) create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/reading/read/raw_hooks.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/reading/read_build/codegen.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/reading/read_build/codegen.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/reading/read_build/index.tsx rename website/i18n/it/docusaurus-plugin-content-docs/current/concepts/{reading_read_build.dart => reading/read_build/raw.dart} (87%) create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/reading/read_notifier_build/codegen.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/reading/read_notifier_build/codegen.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/reading/read_notifier_build/index.tsx rename website/i18n/{ko/docusaurus-plugin-content-docs/current/concepts/reading_read_notifier_build.dart => it/docusaurus-plugin-content-docs/current/concepts/reading/read_notifier_build/raw.dart} (99%) create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/reading/watch/codegen.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/reading/watch/codegen.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/reading/watch/index.tsx rename website/i18n/it/docusaurus-plugin-content-docs/current/concepts/{reading_watch.dart => reading/watch/raw.dart} (84%) create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/reading/watch_build/codegen.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/reading/watch_build/codegen.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/reading/watch_build/codegen_hooks.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/reading/watch_build/codegen_hooks.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/reading/watch_build/index.tsx rename website/i18n/it/docusaurus-plugin-content-docs/current/concepts/{reading_watch_build.dart => reading/watch_build/raw.dart} (93%) create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/reading/watch_build/raw_hooks.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/reading/watch_notifier_build/codegen.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/reading/watch_notifier_build/codegen.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/reading/watch_notifier_build/index.tsx rename website/i18n/it/docusaurus-plugin-content-docs/current/concepts/{reading_watch_notifier_build.dart => reading/watch_notifier_build/raw.dart} (100%) create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/scopes.mdx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/subtree_scope.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/theme_scope.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/why_immutability.mdx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/why_immutability/codegen.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/why_immutability/codegen.freezed.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/why_immutability/codegen.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/why_immutability/index.tsx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/concepts/why_immutability/raw.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/auto_dispose.mdx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/auto_dispose/cache_for_extension.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/auto_dispose/cache_for_usage/codegen.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/auto_dispose/cache_for_usage/codegen.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/auto_dispose/cache_for_usage/index.ts create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/auto_dispose/cache_for_usage/raw.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/auto_dispose/codegen_keep_alive.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/auto_dispose/codegen_keep_alive.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/auto_dispose/invalidate_example.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/auto_dispose/invalidate_family_example/codegen.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/auto_dispose/invalidate_family_example/codegen.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/auto_dispose/invalidate_family_example/index.ts create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/auto_dispose/invalidate_family_example/raw.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/auto_dispose/keep_alive/codegen.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/auto_dispose/keep_alive/codegen.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/auto_dispose/keep_alive/index.ts create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/auto_dispose/keep_alive/raw.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/auto_dispose/on_dispose_example/codegen.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/auto_dispose/on_dispose_example/codegen.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/auto_dispose/on_dispose_example/index.ts create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/auto_dispose/on_dispose_example/raw.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/auto_dispose/raw_auto_dispose.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/combining_requests.mdx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/combining_requests/functional_ref/codegen.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/combining_requests/functional_ref/codegen.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/combining_requests/functional_ref/index.ts create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/combining_requests/functional_ref/raw.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/combining_requests/listen_example/codegen.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/combining_requests/listen_example/codegen.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/combining_requests/listen_example/index.ts create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/combining_requests/listen_example/raw.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/combining_requests/notifier_ref/codegen.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/combining_requests/notifier_ref/codegen.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/combining_requests/notifier_ref/index.ts create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/combining_requests/notifier_ref/raw.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/combining_requests/read_example/codegen.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/combining_requests/read_example/codegen.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/combining_requests/read_example/index.ts create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/combining_requests/read_example/raw.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_example/codegen.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_example/codegen.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_example/index.ts create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_example/raw.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_placement/codegen.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_placement/codegen.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_placement/index.ts create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_placement/raw.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/do_dont.mdx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/eager_initialization.mdx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/eager_initialization/async_consumer_example.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/eager_initialization/consumer_example.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/eager_initialization/require_value/codegen.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/eager_initialization/require_value/codegen.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/eager_initialization/require_value/index.ts create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/eager_initialization/require_value/raw.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/faq.mdx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/first_request.mdx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/first_request/activity.ts create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/first_request/codegen/activity.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/first_request/codegen/activity.freezed.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/first_request/codegen/activity.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/first_request/codegen/provider.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/first_request/codegen/provider.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/first_request/consumer.ts create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/first_request/consumer_stateful_widget.ts create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/first_request/consumer_widget.ts create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/first_request/hook_consumer_widget.ts create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/first_request/legend/DocuCode.scss create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/first_request/legend/legend.tsx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/first_request/provider.ts create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/first_request/raw/activity.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/first_request/raw/consumer.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/first_request/raw/consumer_stateful_widget.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/first_request/raw/consumer_widget.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/first_request/raw/hook_consumer_widget.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/first_request/raw/provider.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/passing_args.mdx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/passing_args/codegen/family.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/passing_args/codegen/family.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/passing_args/codegen/provider.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/passing_args/codegen/provider.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/passing_args/no_arg_provider.ts create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/consumer_family.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/consumer_list_family.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/consumer_provider.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/consumer_tuple_family.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/family.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/multiple_consumer_family.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/provider.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/tuple_family.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/provider_observer.mdx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/provider_observer/provider_observer.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects.mdx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier.freezed.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier_add_todo.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier_add_todo.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_provider.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_provider.freezed.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_provider.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/consumer_add_todo_call.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/invalidate_self_add_todo.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/manual_add_todo.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/mutable_manual_add_todo.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/render_add_todo.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/render_add_todo_hooks.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/rest_add_todo.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/todo_list_notifier.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/todo_list_notifier_add_todo.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/todo_list_provider.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/render_add_todo.ts create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/todo_list_notifier.ts create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/todo_list_notifier_add_todo.ts create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/todo_list_provider.ts create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing.mdx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/auto_dispose_listen.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/await_future.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/create_container.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/full_widget_test.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/listen_provider.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/mock_provider.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/notifier_mock/codegen.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/notifier_mock/codegen.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/notifier_mock/index.ts create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/notifier_mock/raw.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/provider_to_mock/codegen.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/provider_to_mock/codegen.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/provider_to_mock/index.ts create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/provider_to_mock/raw.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/unit_test.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/widget_container_of.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/widget_test.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync.mdx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/change_notifier_provider.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/pipe_change_notifier.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/pipe_change_notifier.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/raw_usage.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/raw_usage.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/shared_pipe_change_notifier.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/shared_pipe_change_notifier.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/stream_provider/codegen.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/stream_provider/codegen.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/stream_provider/index.ts create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/stream_provider/raw.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_consumer.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_definition/codegen.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_definition/codegen.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_definition/index.ts create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_definition/raw.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/family/family.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/family/family.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/family/index.tsx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/family/raw.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/helpers/item.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/helpers/item.freezed.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/helpers/item.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/helpers/json.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/async_values/async_values.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/async_values/async_values.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/async_values/index.tsx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/async_values/raw.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/auto_dispose/auto_dispose.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/auto_dispose/auto_dispose.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/auto_dispose/index.tsx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/auto_dispose/raw.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/combine/combine.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/combine/combine.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/combine/index.tsx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/combine/raw.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/motivation.mdx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/override/index.tsx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/override/override.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/override/raw.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/same_type/index.tsx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/same_type/raw.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/same_type/same_type.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/same_type/same_type.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/side_effects/index.tsx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/side_effects/raw.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/side_effects/side_effects.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/provider_vs_riverpod.mdx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/quickstart.mdx delete mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/getting_started.mdx delete mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/getting_started_hello_world.dart delete mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/getting_started_hello_world_hooks.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/introduction/getting_started.mdx rename website/i18n/{ko/docusaurus-plugin-content-docs/current => it/docusaurus-plugin-content-docs/current/introduction}/getting_started/dart_hello_world/index.tsx (100%) rename website/i18n/{zh-Hans/docusaurus-plugin-content-docs/current => it/docusaurus-plugin-content-docs/current/introduction}/getting_started/dart_hello_world/main.dart (56%) rename website/i18n/{ko/docusaurus-plugin-content-docs/current => it/docusaurus-plugin-content-docs/current/introduction}/getting_started/dart_hello_world/main.g.dart (100%) rename website/i18n/{zh-Hans/docusaurus-plugin-content-docs/current => it/docusaurus-plugin-content-docs/current/introduction}/getting_started/dart_hello_world/raw.dart (100%) create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/introduction/getting_started/dart_pub_add.tsx rename website/i18n/{zh-Hans/docusaurus-plugin-content-docs/current => it/docusaurus-plugin-content-docs/current/introduction}/getting_started/dart_pubspec.tsx (78%) create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/hooks_codegen/main.dart rename website/i18n/{ko/docusaurus-plugin-content-docs/current/getting_started/hello_world => it/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/hooks_codegen}/main.g.dart (100%) rename website/i18n/{zh-Hans/docusaurus-plugin-content-docs/current => it/docusaurus-plugin-content-docs/current/introduction}/getting_started/hello_world/index.tsx (67%) rename website/i18n/{ko/docusaurus-plugin-content-docs/current => it/docusaurus-plugin-content-docs/current/introduction}/getting_started/hello_world/main.dart (57%) rename website/i18n/{zh-Hans/docusaurus-plugin-content-docs/current/getting_started/dart_hello_world => it/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world}/main.g.dart (100%) rename website/i18n/it/docusaurus-plugin-content-docs/current/{getting_started_hello_world_flutter.dart => introduction/getting_started/hello_world/raw.dart} (54%) create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/raw_hooks.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/introduction/getting_started/pub_add.tsx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/introduction/getting_started/pubspec.tsx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/introduction/why_riverpod.mdx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/introduction/why_riverpod/codegen.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/introduction/why_riverpod/codegen.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/introduction/why_riverpod/index.tsx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/introduction/why_riverpod/raw.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_change_notifier.mdx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_change_notifier/declaration/declaration.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_change_notifier/declaration/declaration.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_change_notifier/declaration/index.tsx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_change_notifier/declaration/raw.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_change_notifier/initialization/index.tsx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_change_notifier/initialization/initialization.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_change_notifier/initialization/initialization.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_change_notifier/initialization/raw.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_change_notifier/migrated/index.tsx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_change_notifier/migrated/migrated.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_change_notifier/migrated/migrated.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_change_notifier/migrated/raw.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_change_notifier/old.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier.mdx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener/add_listener.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener/add_listener.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener/index.tsx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener/raw.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener_old.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier/async_notifier.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier/async_notifier.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier/index.tsx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier/raw.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier_old.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init/build_init.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init/build_init.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init/index.tsx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init/raw.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init_old.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/consumers_dont_change.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose/family_and_dispose.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose/family_and_dispose.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose/index.tsx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose/raw.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose_old.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider/from_state_provider.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider/from_state_provider.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider/index.tsx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider/raw.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider_old.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/obtain_notifier_on_tests.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles/index.tsx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles/old_lifecycles.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles/old_lifecycles.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles/raw.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_final/index.tsx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_final/old_lifecycles_final.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_final/old_lifecycles_final.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_final/raw.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_old.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/migration/utils.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/providers/change_notifier_provider.mdx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/providers/change_notifier_provider/todos.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/providers/change_notifier_provider/todos_consumer.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/providers/future_provider/config_consumer/codegen.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/providers/future_provider/config_consumer/hooks.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/providers/future_provider/config_consumer/hooks_codegen.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/providers/future_provider/config_consumer/index.tsx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/providers/future_provider/config_consumer/raw.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/providers/future_provider/config_provider/codegen.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/providers/future_provider/config_provider/codegen.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/providers/future_provider/config_provider/index.tsx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/providers/future_provider/config_provider/raw.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/providers/notifier_provider.mdx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/codegen.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/codegen.freezed.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/codegen.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/index.tsx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/raw.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/todos_consumer.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/codegen.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/codegen.freezed.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/codegen.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/index.tsx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/raw.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/todos_consumer.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/completed_todos/completed_todos.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/completed_todos/completed_todos.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/completed_todos/index.tsx rename website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/{completed_todos.dart => completed_todos/raw.dart} (65%) create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/optimized_previous_button/index.tsx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/optimized_previous_button/optimized_previous_button.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/optimized_previous_button/optimized_previous_button.g.dart rename website/i18n/{ko/docusaurus-plugin-content-docs/current/providers/provider/optimized_previous_button.dart => it/docusaurus-plugin-content-docs/current/providers/provider/optimized_previous_button/raw.dart} (87%) create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/todo/index.tsx rename website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/{todo.dart => todo/raw.dart} (59%) create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/todo/todo.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/todo/todo.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/unoptimized_previous_button/index.tsx rename website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/{unoptimized_previous_button.dart => unoptimized_previous_button/raw.dart} (76%) create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/unoptimized_previous_button/unoptimized_previous_button.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/unoptimized_previous_button/unoptimized_previous_button.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/providers/state_provider/product.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/providers/state_provider/product_list_view.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/providers/state_provider/sorted_product_provider.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/providers/state_provider/update_read_once.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_consumer.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_provider.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_provider/codegen.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_provider/codegen.g.dart create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_provider/index.tsx create mode 100644 website/i18n/it/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_provider/raw.dart create mode 100644 website/i18n/ko/NOTE.md delete mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/about_code_generation.mdx delete mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/about_hooks.mdx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/advanced/select.mdx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/advanced/select/select/codegen.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/advanced/select/select/codegen.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/advanced/select/select/index.ts create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/advanced/select/select/raw.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/advanced/select/select_async/codegen.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/advanced/select/select_async/codegen.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/advanced/select/select_async/index.ts create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/advanced/select/select_async/raw.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel.mdx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen/codegen.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen/codegen.freezed.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen/codegen.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen/index.ts create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen/raw.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_cancel/codegen.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_cancel/codegen.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_cancel/index.ts create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_cancel/raw.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_debounce/codegen.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_debounce/codegen.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_debounce/index.ts create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_debounce/raw.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/extension.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/home_screen.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/provider_with_extension/codegen.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/provider_with_extension/codegen.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/provider_with_extension/index.ts create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/provider_with_extension/raw.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh.mdx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/activity/codegen.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/activity/codegen.freezed.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/activity/codegen.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/activity/index.ts create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/activity/raw.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/display_activity.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/display_activity2.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/display_activity3.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/display_activity4.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/fetch_activity/codegen.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/fetch_activity/codegen.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/fetch_activity/index.ts create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/fetch_activity/raw.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/full_app/codegen.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/full_app/codegen.freezed.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/full_app/codegen.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/full_app/index.ts create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/full_app/raw.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_code_generation.mdx rename website/i18n/{zh-Hans/docusaurus-plugin-content-docs/current => ko/docusaurus-plugin-content-docs/current/concepts}/about_codegen/main.dart (100%) rename website/i18n/ko/docusaurus-plugin-content-docs/current/{ => concepts}/about_codegen/main.g.dart (100%) create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_class_future.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_class_future.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_class_stream.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_class_stream.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_fn_future.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_fn_future.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_fn_stream.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_fn_stream.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/auto_dispose.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/auto_dispose.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/family.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/family.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/family_class.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/family_class.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/family_fn.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/family_fn.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/async_notifier_provider.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/auto_dispose.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/family.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/future_provider.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/notifier_provider.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/provider.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/stream_notifier_provider.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/stream_provider.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/sync_class.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/sync_class.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/sync_fn.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/sync_fn.g.dart rename website/i18n/{zh-Hans/docusaurus-plugin-content-docs/current => ko/docusaurus-plugin-content-docs/current/concepts}/about_codegen/raw.dart (93%) create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_hooks.mdx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_hooks/hook_and_consumer.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_hooks/hook_consumer.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_hooks/hook_consumer_widget.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/async_initialization.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/characters_provider/codegen.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/characters_provider/codegen.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/characters_provider/index.tsx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/characters_provider/models.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/characters_provider/raw.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/city_provider/codegen.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/city_provider/codegen.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/city_provider/index.tsx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/city_provider/raw.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/filtered_todo_list_provider/codegen.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/filtered_todo_list_provider/codegen.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/filtered_todo_list_provider/index.tsx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/filtered_todo_list_provider/raw.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/read_in_provider/codegen.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/read_in_provider/codegen.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/read_in_provider/index.tsx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/read_in_provider/raw.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/select_async_provider/codegen.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/select_async_provider/codegen.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/select_async_provider/index.tsx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/select_async_provider/models.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/select_async_provider/raw.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/todo_list_provider/codegen.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/todo_list_provider/codegen.freezed.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/todo_list_provider/codegen.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/todo_list_provider/index.tsx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/todo_list_provider/raw.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/weather_provider/codegen.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/weather_provider/codegen.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/weather_provider/index.tsx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/weather_provider/raw.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/whole_object_provider/codegen.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/whole_object_provider/codegen.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/whole_object_provider/index.tsx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/whole_object_provider/models.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/whole_object_provider/raw.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/dialog_scope.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/lifecycle_on_dispose/codegen.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/lifecycle_on_dispose/codegen.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/lifecycle_on_dispose/index.tsx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/lifecycle_on_dispose/raw.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/provider_lifecycles.mdx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/providers/creating_a_provider/codegen.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/providers/creating_a_provider/codegen.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/providers/creating_a_provider/index.tsx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/providers/creating_a_provider/raw.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/providers/declaring_many_providers/codegen.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/providers/declaring_many_providers/codegen.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/providers/declaring_many_providers/index.tsx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/providers/declaring_many_providers/raw.dart rename website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/{reading_consumer_hook.dart => reading/consumer_hook.dart} (71%) create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/consumer_stateful_widget/hooks.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/consumer_stateful_widget/index.tsx rename website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/{reading_consumer_stateful_widget.dart => reading/consumer_stateful_widget/raw.dart} (69%) rename website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/{reading_consumer_hook_widget.dart => reading/consumer_widget/hooks.dart} (66%) create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/consumer_widget/index.tsx rename website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/{reading_consumer_widget.dart => reading/consumer_widget/raw.dart} (78%) create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/counter/codegen.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/counter/codegen.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/counter/index.tsx rename website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/{reading_counter.dart => reading/counter/raw.dart} (82%) create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/listen/codegen.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/listen/codegen.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/listen/index.tsx rename website/i18n/{it/docusaurus-plugin-content-docs/current/concepts/reading_listen.dart => ko/docusaurus-plugin-content-docs/current/concepts/reading/listen/raw.dart} (75%) create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/listen_build/codegen.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/listen_build/codegen.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/listen_build/codegen_hooks.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/listen_build/codegen_hooks.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/listen_build/index.tsx rename website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/{reading_listen_build.dart => reading/listen_build/raw.dart} (82%) create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/listen_build/raw_hooks.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/provider/codegen.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/provider/codegen.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/provider/index.tsx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/provider/raw.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/read/codegen.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/read/codegen.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/read/codegen_hooks.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/read/codegen_hooks.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/read/index.tsx rename website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/{reading_read.dart => reading/read/raw.dart} (72%) create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/read/raw_hooks.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/read_build/codegen.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/read_build/codegen.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/read_build/index.tsx rename website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/{reading_read_build.dart => reading/read_build/raw.dart} (84%) create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/read_notifier_build/codegen.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/read_notifier_build/codegen.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/read_notifier_build/index.tsx rename website/i18n/{it/docusaurus-plugin-content-docs/current/concepts/reading_read_notifier_build.dart => ko/docusaurus-plugin-content-docs/current/concepts/reading/read_notifier_build/raw.dart} (99%) create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/watch/codegen.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/watch/codegen.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/watch/index.tsx rename website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/{reading_watch.dart => reading/watch/raw.dart} (80%) create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/watch_build/codegen.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/watch_build/codegen.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/watch_build/codegen_hooks.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/watch_build/codegen_hooks.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/watch_build/index.tsx rename website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/{reading_watch_build.dart => reading/watch_build/raw.dart} (91%) create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/watch_build/raw_hooks.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/watch_notifier_build/codegen.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/watch_notifier_build/codegen.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/watch_notifier_build/index.tsx rename website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/{reading_watch_notifier_build.dart => reading/watch_notifier_build/raw.dart} (100%) create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/scopes.mdx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/subtree_scope.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/theme_scope.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/why_immutability/codegen.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/why_immutability/codegen.freezed.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/why_immutability/codegen.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/why_immutability/index.tsx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/why_immutability/raw.dart delete mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/cookbooks/refresh.mdx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose.mdx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/cache_for_extension.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/cache_for_usage/codegen.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/cache_for_usage/codegen.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/cache_for_usage/index.ts create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/cache_for_usage/raw.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/codegen_keep_alive.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/codegen_keep_alive.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/invalidate_example.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/invalidate_family_example/codegen.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/invalidate_family_example/codegen.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/invalidate_family_example/index.ts create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/invalidate_family_example/raw.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/keep_alive/codegen.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/keep_alive/codegen.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/keep_alive/index.ts create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/keep_alive/raw.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/on_dispose_example/codegen.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/on_dispose_example/codegen.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/on_dispose_example/index.ts create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/on_dispose_example/raw.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/raw_auto_dispose.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests.mdx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/functional_ref/codegen.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/functional_ref/codegen.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/functional_ref/index.ts create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/functional_ref/raw.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/listen_example/codegen.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/listen_example/codegen.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/listen_example/index.ts create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/listen_example/raw.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/notifier_ref/codegen.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/notifier_ref/codegen.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/notifier_ref/index.ts create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/notifier_ref/raw.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/read_example/codegen.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/read_example/codegen.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/read_example/index.ts create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/read_example/raw.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_example/codegen.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_example/codegen.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_example/index.ts create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_example/raw.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_placement/codegen.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_placement/codegen.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_placement/index.ts create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_placement/raw.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/do_dont.mdx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/eager_initialization.mdx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/eager_initialization/async_consumer_example.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/eager_initialization/consumer_example.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/eager_initialization/require_value/codegen.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/eager_initialization/require_value/codegen.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/eager_initialization/require_value/index.ts create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/eager_initialization/require_value/raw.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/faq.mdx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request.mdx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/activity.ts create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/codegen/activity.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/codegen/activity.freezed.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/codegen/activity.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/codegen/provider.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/codegen/provider.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/consumer.ts create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/consumer_stateful_widget.ts create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/consumer_widget.ts create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/hook_consumer_widget.ts create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/legend/DocuCode.scss create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/legend/legend.tsx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/provider.ts create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/raw/activity.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/raw/consumer.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/raw/consumer_stateful_widget.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/raw/consumer_widget.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/raw/hook_consumer_widget.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/raw/provider.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/passing_args.mdx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/passing_args/codegen/family.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/passing_args/codegen/family.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/passing_args/codegen/provider.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/passing_args/codegen/provider.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/passing_args/no_arg_provider.ts create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/consumer_family.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/consumer_list_family.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/consumer_provider.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/consumer_tuple_family.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/family.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/multiple_consumer_family.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/provider.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/tuple_family.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/provider_observer.mdx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/provider_observer/provider_observer.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects.mdx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier.freezed.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier_add_todo.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier_add_todo.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_provider.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_provider.freezed.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_provider.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/consumer_add_todo_call.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/invalidate_self_add_todo.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/manual_add_todo.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/mutable_manual_add_todo.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/render_add_todo.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/render_add_todo_hooks.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/rest_add_todo.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/todo_list_notifier.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/todo_list_notifier_add_todo.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/todo_list_provider.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/render_add_todo.ts create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/todo_list_notifier.ts create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/todo_list_notifier_add_todo.ts create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/todo_list_provider.ts create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing.mdx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/auto_dispose_listen.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/await_future.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/create_container.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/full_widget_test.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/listen_provider.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/mock_provider.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/notifier_mock/codegen.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/notifier_mock/codegen.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/notifier_mock/index.ts create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/notifier_mock/raw.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/provider_to_mock/codegen.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/provider_to_mock/codegen.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/provider_to_mock/index.ts create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/provider_to_mock/raw.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/unit_test.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/widget_container_of.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/widget_test.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync.mdx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/change_notifier_provider.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/pipe_change_notifier.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/pipe_change_notifier.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/raw_usage.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/raw_usage.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/shared_pipe_change_notifier.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/shared_pipe_change_notifier.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/stream_provider/codegen.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/stream_provider/codegen.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/stream_provider/index.ts create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/stream_provider/raw.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_consumer.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_definition/codegen.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_definition/codegen.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_definition/index.ts create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_definition/raw.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/family/family.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/family/family.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/family/index.tsx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/family/raw.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/helpers/item.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/helpers/item.freezed.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/helpers/item.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/helpers/json.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/async_values/async_values.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/async_values/async_values.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/async_values/index.tsx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/async_values/raw.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/auto_dispose/auto_dispose.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/auto_dispose/auto_dispose.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/auto_dispose/index.tsx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/auto_dispose/raw.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/combine/combine.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/combine/combine.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/combine/index.tsx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/combine/raw.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/motivation.mdx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/override/index.tsx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/override/override.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/override/raw.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/same_type/index.tsx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/same_type/raw.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/same_type/same_type.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/same_type/same_type.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/side_effects/index.tsx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/side_effects/raw.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/side_effects/side_effects.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/provider_vs_riverpod.mdx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/quickstart.mdx delete mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/getting_started.mdx delete mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/getting_started/dart_hello_world/raw.dart delete mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/getting_started/dart_pubspec/codegen.yaml delete mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/getting_started/dart_pubspec/index.tsx delete mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/getting_started/dart_pubspec/raw.yaml delete mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/getting_started/pubspec/codegen.yaml delete mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/getting_started/pubspec/hooks.yaml delete mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/getting_started/pubspec/hooks_codegen.yaml delete mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/getting_started/pubspec/index.tsx delete mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/getting_started/pubspec/raw.yaml delete mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/introduction.mdx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/getting_started.mdx rename website/i18n/ko/docusaurus-plugin-content-docs/current/{getting_started/hello_world => introduction/getting_started/dart_hello_world}/index.tsx (100%) create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/getting_started/dart_hello_world/main.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/getting_started/dart_hello_world/main.g.dart rename website/i18n/ko/docusaurus-plugin-content-docs/current/{getting_started/dart_hello_world/main.dart => introduction/getting_started/dart_hello_world/raw.dart} (76%) create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/getting_started/dart_pub_add.tsx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/getting_started/dart_pubspec.tsx rename website/i18n/{zh-Hans/docusaurus-plugin-content-docs/current/getting_started/hello_world/main_hooks.dart => ko/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/hooks_codegen/main.dart} (59%) create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/hooks_codegen/main.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/index.tsx rename website/i18n/{zh-Hans/docusaurus-plugin-content-docs/current => ko/docusaurus-plugin-content-docs/current/introduction}/getting_started/hello_world/main.dart (60%) create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/main.g.dart rename website/i18n/{zh-Hans/docusaurus-plugin-content-docs/current => ko/docusaurus-plugin-content-docs/current/introduction}/getting_started/hello_world/raw.dart (100%) rename website/i18n/{zh-Hans/docusaurus-plugin-content-docs/current => ko/docusaurus-plugin-content-docs/current/introduction}/getting_started/hello_world/raw_hooks.dart (100%) create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/getting_started/pub_add.tsx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/getting_started/pubspec.tsx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/why_riverpod.mdx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/why_riverpod/codegen.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/why_riverpod/codegen.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/why_riverpod/index.tsx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/why_riverpod/raw.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_change_notifier.mdx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_change_notifier/declaration/declaration.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_change_notifier/declaration/declaration.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_change_notifier/declaration/index.tsx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_change_notifier/declaration/raw.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_change_notifier/initialization/index.tsx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_change_notifier/initialization/initialization.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_change_notifier/initialization/initialization.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_change_notifier/initialization/raw.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_change_notifier/migrated/index.tsx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_change_notifier/migrated/migrated.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_change_notifier/migrated/migrated.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_change_notifier/migrated/raw.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_change_notifier/old.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier.mdx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener/add_listener.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener/add_listener.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener/index.tsx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener/raw.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener_old.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier/async_notifier.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier/async_notifier.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier/index.tsx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier/raw.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier_old.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init/build_init.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init/build_init.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init/index.tsx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init/raw.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init_old.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/consumers_dont_change.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose/family_and_dispose.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose/family_and_dispose.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose/index.tsx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose/raw.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose_old.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider/from_state_provider.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider/from_state_provider.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider/index.tsx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider/raw.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider_old.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/obtain_notifier_on_tests.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles/index.tsx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles/old_lifecycles.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles/old_lifecycles.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles/raw.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_final/index.tsx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_final/old_lifecycles_final.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_final/old_lifecycles_final.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_final/raw.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_old.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/migration/utils.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/providers/notifier_provider.mdx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/codegen.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/codegen.freezed.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/codegen.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/index.tsx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/raw.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/todos_consumer.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/codegen.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/codegen.freezed.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/codegen.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/index.tsx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/raw.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/todos_consumer.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/completed_todos/completed_todos.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/completed_todos/completed_todos.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/completed_todos/index.tsx rename website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/{completed_todos.dart => completed_todos/raw.dart} (60%) create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/optimized_previous_button/index.tsx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/optimized_previous_button/optimized_previous_button.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/optimized_previous_button/optimized_previous_button.g.dart rename website/i18n/{it/docusaurus-plugin-content-docs/current/providers/provider/optimized_previous_button.dart => ko/docusaurus-plugin-content-docs/current/providers/provider/optimized_previous_button/raw.dart} (67%) create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/todo/index.tsx rename website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/{todo.dart => todo/raw.dart} (59%) create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/todo/todo.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/todo/todo.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/unoptimized_previous_button/index.tsx rename website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/{unoptimized_previous_button.dart => unoptimized_previous_button/raw.dart} (74%) create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/unoptimized_previous_button/unoptimized_previous_button.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/unoptimized_previous_button/unoptimized_previous_button.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_consumer.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_provider.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_provider/codegen.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_provider/codegen.g.dart create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_provider/index.tsx create mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_provider/raw.dart delete mode 100644 website/i18n/ko/docusaurus-plugin-content-docs/current/riverpod_for_provider_users.mdx delete mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/about_code_generation.mdx delete mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/about_hooks.mdx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/advanced/select.mdx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/advanced/select/select/codegen.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/advanced/select/select/codegen.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/advanced/select/select/index.ts create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/advanced/select/select/raw.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/advanced/select/select_async/codegen.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/advanced/select/select_async/codegen.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/advanced/select/select_async/index.ts create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/advanced/select/select_async/raw.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel.mdx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen/codegen.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen/codegen.freezed.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen/codegen.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen/index.ts create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen/raw.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_cancel/codegen.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_cancel/codegen.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_cancel/index.ts create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_cancel/raw.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_debounce/codegen.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_debounce/codegen.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_debounce/index.ts create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_debounce/raw.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/extension.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/home_screen.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/provider_with_extension/codegen.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/provider_with_extension/codegen.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/provider_with_extension/index.ts create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/provider_with_extension/raw.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh.mdx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/activity/codegen.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/activity/codegen.freezed.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/activity/codegen.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/activity/index.ts create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/activity/raw.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/display_activity.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/display_activity2.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/display_activity3.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/display_activity4.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/fetch_activity/codegen.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/fetch_activity/codegen.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/fetch_activity/index.ts create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/fetch_activity/raw.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/full_app/codegen.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/full_app/codegen.freezed.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/full_app/codegen.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/full_app/index.ts create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/full_app/raw.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_code_generation.mdx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/main.dart rename website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/{ => concepts}/about_codegen/main.g.dart (100%) create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_class_future.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_class_future.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_class_stream.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_class_stream.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_fn_future.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_fn_future.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_fn_stream.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_fn_stream.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/auto_dispose.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/auto_dispose.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/family.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/family.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/family_class.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/family_class.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/family_fn.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/family_fn.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/async_notifier_provider.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/auto_dispose.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/family.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/future_provider.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/notifier_provider.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/provider.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/stream_notifier_provider.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/stream_provider.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/sync_class.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/sync_class.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/sync_fn.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/sync_fn.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/raw.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_hooks.mdx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_hooks/hook_and_consumer.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_hooks/hook_consumer.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_hooks/hook_consumer_widget.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/async_initialization.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/characters_provider/codegen.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/characters_provider/codegen.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/characters_provider/index.tsx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/characters_provider/models.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/characters_provider/raw.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/city_provider/codegen.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/city_provider/codegen.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/city_provider/index.tsx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/city_provider/raw.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/filtered_todo_list_provider/codegen.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/filtered_todo_list_provider/codegen.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/filtered_todo_list_provider/index.tsx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/filtered_todo_list_provider/raw.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/read_in_provider/codegen.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/read_in_provider/codegen.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/read_in_provider/index.tsx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/read_in_provider/raw.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/select_async_provider/codegen.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/select_async_provider/codegen.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/select_async_provider/index.tsx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/select_async_provider/models.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/select_async_provider/raw.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/todo_list_provider/codegen.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/todo_list_provider/codegen.freezed.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/todo_list_provider/codegen.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/todo_list_provider/index.tsx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/todo_list_provider/raw.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/weather_provider/codegen.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/weather_provider/codegen.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/weather_provider/index.tsx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/weather_provider/raw.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/whole_object_provider/codegen.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/whole_object_provider/codegen.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/whole_object_provider/index.tsx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/whole_object_provider/models.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/whole_object_provider/raw.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/dialog_scope.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/provider_observer_logger.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/providers/creating_a_provider/codegen.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/providers/creating_a_provider/codegen.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/providers/creating_a_provider/index.tsx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/providers/creating_a_provider/raw.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/providers/declaring_many_providers/codegen.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/providers/declaring_many_providers/codegen.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/providers/declaring_many_providers/index.tsx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/providers/declaring_many_providers/raw.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/listen_build/codegen_hooks.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/listen_build/codegen_hooks.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/listen_build/raw_hooks.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/read/codegen_hooks.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/read/codegen_hooks.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/read/raw_hooks.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/watch_build/codegen_hooks.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/watch_build/codegen_hooks.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/watch_build/raw_hooks.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/subtree_scope.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/theme_scope.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/why_immutability/codegen.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/why_immutability/codegen.freezed.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/why_immutability/codegen.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/why_immutability/index.tsx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/why_immutability/raw.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/cookbooks/search_as_we_type.mdx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/cookbooks/testing_dart.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/cookbooks/testing_flutter.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/cookbooks/testing_full.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/cookbooks/testing_original_test_dart.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/cookbooks/testing_original_test_flutter.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/cookbooks/testing_override_info.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/cookbooks/testing_repository.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose.mdx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/cache_for_extension.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/cache_for_usage/codegen.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/cache_for_usage/codegen.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/cache_for_usage/index.ts create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/cache_for_usage/raw.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/codegen_keep_alive.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/codegen_keep_alive.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/invalidate_example.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/invalidate_family_example/codegen.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/invalidate_family_example/codegen.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/invalidate_family_example/index.ts create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/invalidate_family_example/raw.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/keep_alive/codegen.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/keep_alive/codegen.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/keep_alive/index.ts create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/keep_alive/raw.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/on_dispose_example/codegen.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/on_dispose_example/codegen.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/on_dispose_example/index.ts create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/on_dispose_example/raw.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/raw_auto_dispose.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests.mdx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/functional_ref/codegen.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/functional_ref/codegen.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/functional_ref/index.ts create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/functional_ref/raw.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/listen_example/codegen.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/listen_example/codegen.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/listen_example/index.ts create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/listen_example/raw.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/notifier_ref/codegen.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/notifier_ref/codegen.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/notifier_ref/index.ts create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/notifier_ref/raw.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/read_example/codegen.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/read_example/codegen.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/read_example/index.ts create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/read_example/raw.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_example/codegen.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_example/codegen.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_example/index.ts create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_example/raw.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_placement/codegen.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_placement/codegen.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_placement/index.ts create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_placement/raw.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/do_dont.mdx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/eager_initialization.mdx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/eager_initialization/async_consumer_example.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/eager_initialization/consumer_example.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/eager_initialization/require_value/codegen.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/eager_initialization/require_value/codegen.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/eager_initialization/require_value/index.ts create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/eager_initialization/require_value/raw.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/faq.mdx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request.mdx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/activity.ts create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/codegen/activity.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/codegen/activity.freezed.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/codegen/activity.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/codegen/provider.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/codegen/provider.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/consumer.ts create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/consumer_stateful_widget.ts create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/consumer_widget.ts create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/hook_consumer_widget.ts create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/legend/DocuCode.scss create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/legend/legend.tsx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/provider.ts create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/raw/activity.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/raw/consumer.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/raw/consumer_stateful_widget.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/raw/consumer_widget.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/raw/hook_consumer_widget.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/raw/provider.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/passing_args.mdx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/passing_args/codegen/family.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/passing_args/codegen/family.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/passing_args/codegen/provider.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/passing_args/codegen/provider.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/passing_args/no_arg_provider.ts create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/consumer_family.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/consumer_list_family.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/consumer_provider.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/consumer_tuple_family.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/family.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/multiple_consumer_family.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/provider.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/tuple_family.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/provider_observer.mdx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/provider_observer/provider_observer.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects.mdx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier.freezed.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier_add_todo.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier_add_todo.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_provider.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_provider.freezed.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_provider.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/consumer_add_todo_call.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/invalidate_self_add_todo.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/manual_add_todo.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/mutable_manual_add_todo.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/render_add_todo.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/render_add_todo_hooks.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/rest_add_todo.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/todo_list_notifier.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/todo_list_notifier_add_todo.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/todo_list_provider.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/render_add_todo.ts create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/todo_list_notifier.ts create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/todo_list_notifier_add_todo.ts create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/todo_list_provider.ts create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing.mdx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/auto_dispose_listen.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/await_future.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/create_container.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/full_widget_test.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/listen_provider.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/mock_provider.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/notifier_mock/codegen.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/notifier_mock/codegen.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/notifier_mock/index.ts create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/notifier_mock/raw.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/provider_to_mock/codegen.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/provider_to_mock/codegen.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/provider_to_mock/index.ts create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/provider_to_mock/raw.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/unit_test.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/widget_container_of.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/widget_test.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync.mdx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/change_notifier_provider.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/pipe_change_notifier.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/pipe_change_notifier.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/raw_usage.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/raw_usage.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/shared_pipe_change_notifier.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/shared_pipe_change_notifier.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/stream_provider/codegen.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/stream_provider/codegen.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/stream_provider/index.ts create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/stream_provider/raw.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_consumer.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_definition/codegen.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_definition/codegen.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_definition/index.ts create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_definition/raw.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/family/family.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/family/family.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/family/index.tsx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/family/raw.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/helpers/item.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/helpers/item.freezed.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/helpers/item.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/helpers/json.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/async_values/async_values.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/async_values/async_values.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/async_values/index.tsx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/async_values/raw.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/auto_dispose/auto_dispose.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/auto_dispose/auto_dispose.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/auto_dispose/index.tsx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/auto_dispose/raw.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/combine/combine.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/combine/combine.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/combine/index.tsx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/combine/raw.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/motivation.mdx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/override/index.tsx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/override/override.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/override/raw.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/same_type/index.tsx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/same_type/raw.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/same_type/same_type.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/same_type/same_type.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/side_effects/index.tsx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/side_effects/raw.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/side_effects/side_effects.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/provider_vs_riverpod.mdx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/quickstart.mdx delete mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/getting_started.mdx delete mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/getting_started/dart_pub_add.tsx delete mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/getting_started/pub_add.tsx delete mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction.mdx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/getting_started.mdx rename website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/{ => introduction}/getting_started/dart_hello_world/index.tsx (100%) create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/getting_started/dart_hello_world/main.dart rename website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/{getting_started/hello_world => introduction/getting_started/dart_hello_world}/main.g.dart (100%) create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/getting_started/dart_hello_world/raw.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/getting_started/dart_pub_add.tsx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/getting_started/dart_pubspec.tsx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/hooks_codegen/main.dart rename website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/{getting_started/hello_world/main_hooks.g.dart => introduction/getting_started/hello_world/hooks_codegen/main.g.dart} (97%) create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/index.tsx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/main.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/main.g.dart rename website/i18n/{ko/docusaurus-plugin-content-docs/current => zh-Hans/docusaurus-plugin-content-docs/current/introduction}/getting_started/hello_world/raw.dart (52%) create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/raw_hooks.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/getting_started/pub_add.tsx rename website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/{ => introduction}/getting_started/pubspec.tsx (79%) create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/why_riverpod.mdx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/why_riverpod/codegen.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/why_riverpod/codegen.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/why_riverpod/index.tsx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/why_riverpod/raw.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/0.13.0_to_0.14.0.mdx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/0.14.0_to_1.0.0.mdx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_change_notifier.mdx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_change_notifier/declaration/declaration.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_change_notifier/declaration/declaration.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_change_notifier/declaration/index.tsx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_change_notifier/declaration/raw.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_change_notifier/initialization/index.tsx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_change_notifier/initialization/initialization.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_change_notifier/initialization/initialization.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_change_notifier/initialization/raw.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_change_notifier/migrated/index.tsx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_change_notifier/migrated/migrated.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_change_notifier/migrated/migrated.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_change_notifier/migrated/raw.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_change_notifier/old.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier.mdx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener/add_listener.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener/add_listener.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener/index.tsx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener/raw.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener_old.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier/async_notifier.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier/async_notifier.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier/index.tsx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier/raw.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier_old.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init/build_init.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init/build_init.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init/index.tsx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init/raw.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init_old.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/consumers_dont_change.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose/family_and_dispose.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose/family_and_dispose.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose/index.tsx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose/raw.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose_old.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider/from_state_provider.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider/from_state_provider.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider/index.tsx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider/raw.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider_old.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/obtain_notifier_on_tests.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles/index.tsx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles/old_lifecycles.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles/old_lifecycles.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles/raw.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_final/index.tsx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_final/old_lifecycles_final.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_final/old_lifecycles_final.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_final/raw.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_old.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/utils.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_provider/codegen.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_provider/codegen.g.dart create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_provider/index.tsx create mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_provider/raw.dart delete mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/riverpod_for_provider_users.mdx diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..9d1e2066e --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,34 @@ +## Related Issues + +fixes #your-issue-number + + + +## Checklist + +Before you create this PR confirm that it meets all requirements listed below by checking the relevant checkboxes (`[x]`). + +- [ ] I have updated the `CHANGELOG.md` of the relevant packages. + Changelog files must be edited under the form: + + ```md + ## Unreleased fix/major/minor + + - Description of your change. (thanks to @yourGithubId) + ``` + +- [ ] If this contains new features or behavior changes, + I have updated the documentation to match those changes. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ee9fb65fc..5cb0bcbfa 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -68,7 +68,7 @@ Finally, head to `localhost:3000` ### Adding new language Riverpod supports multiple languages. -To add new languages, it is recommended to follow the [i18n](https://docusaurus.io/fr/docs/i18n/introduction) +To add new languages, it is recommended to follow the [i18n](https://docusaurus.io/docs/i18n/introduction) documentation of Docusaurus. The English documentation is hosted in [`/website/docs`](https://github.com/rrousselGit/riverpod/tree/master/website/docs). @@ -119,4 +119,4 @@ your code snippets should support those. This is done by having a separate `.dart` file for each possible scenario. Again, look at the [Getting Started] for an example. -[Getting Started]: https://github.com/rrousselGit/riverpod/blob/rework-flow/website/docs/introduction/getting_started.mdx +[Getting Started]: https://github.com/rrousselGit/riverpod/blob/master/website/docs/introduction/getting_started.mdx diff --git a/all_lint_rules.yaml b/all_lint_rules.yaml index fb224fcbd..49d1bb67f 100644 --- a/all_lint_rules.yaml +++ b/all_lint_rules.yaml @@ -7,6 +7,7 @@ linter: - always_specify_types - always_use_package_imports - annotate_overrides + - annotate_redeclares - avoid_annotating_with_dynamic - avoid_bool_literals_in_conditional_expressions - avoid_catches_without_on_clauses @@ -44,6 +45,7 @@ linter: - avoid_types_as_parameter_names - avoid_types_on_closure_parameters - avoid_unnecessary_containers + - avoid_unstable_final_fields - avoid_unused_constructor_parameters - avoid_void_async - avoid_web_libraries_in_flutter diff --git a/examples/counter/README.md b/examples/counter/README.md index 7895be08b..7837608b0 100644 --- a/examples/counter/README.md +++ b/examples/counter/README.md @@ -1,6 +1,6 @@ The standard Flutter Counter example built with [Riverpod] -This uses `ProvidedScope`. +This uses `ProviderScope`. [riverpod]: https://github.com/rrousselGit/riverpod diff --git a/examples/counter/pubspec.yaml b/examples/counter/pubspec.yaml index 1fbc08ec0..270a678a0 100644 --- a/examples/counter/pubspec.yaml +++ b/examples/counter/pubspec.yaml @@ -24,7 +24,7 @@ dependencies: dev_dependencies: build_runner: ^2.3.3 - custom_lint: ^0.5.2 + custom_lint: ^0.6.0 flutter_test: sdk: flutter freezed: ^2.3.2 diff --git a/examples/marvel/lib/src/configuration.freezed.dart b/examples/marvel/lib/src/configuration.freezed.dart index ea54df0fb..3bc79273e 100644 --- a/examples/marvel/lib/src/configuration.freezed.dart +++ b/examples/marvel/lib/src/configuration.freezed.dart @@ -12,7 +12,7 @@ part of 'configuration.dart'; T _$identity(T value) => value; final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); Configuration _$ConfigurationFromJson(Map json) { return _Configuration.fromJson(json); @@ -125,7 +125,7 @@ class _$ConfigurationImpl implements _Configuration { } @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && other is _$ConfigurationImpl && diff --git a/examples/marvel/lib/src/marvel.freezed.dart b/examples/marvel/lib/src/marvel.freezed.dart index 698d3ff9e..c3950b0c2 100644 --- a/examples/marvel/lib/src/marvel.freezed.dart +++ b/examples/marvel/lib/src/marvel.freezed.dart @@ -12,7 +12,7 @@ part of 'marvel.dart'; T _$identity(T value) => value; final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); /// @nodoc mixin _$MarvelListCharactersResponse { @@ -130,7 +130,7 @@ class _$MarvelListCharactersResponseImpl } @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && other is _$MarvelListCharactersResponseImpl && @@ -307,7 +307,7 @@ class _$CharacterImpl implements _Character { } @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && other is _$CharacterImpl && @@ -465,7 +465,7 @@ class _$ThumbnailImpl extends _Thumbnail { } @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && other is _$ThumbnailImpl && @@ -621,7 +621,7 @@ class _$MarvelResponseImpl implements _MarvelResponse { } @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && other is _$MarvelResponseImpl && @@ -778,7 +778,7 @@ class _$MarvelDataImpl implements _MarvelData { } @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && other is _$MarvelDataImpl && diff --git a/examples/marvel/lib/src/result.freezed.dart b/examples/marvel/lib/src/result.freezed.dart index 1b853ca34..ea52cc1de 100644 --- a/examples/marvel/lib/src/result.freezed.dart +++ b/examples/marvel/lib/src/result.freezed.dart @@ -12,7 +12,7 @@ part of 'result.dart'; T _$identity(T value) => value; final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); /// @nodoc mixin _$Result { @@ -118,7 +118,7 @@ class _$ResultDataImpl extends _ResultData { } @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && other is _$ResultDataImpl && @@ -257,7 +257,7 @@ class _$ResultErrorImpl extends _ResultError { } @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && other is _$ResultErrorImpl && diff --git a/examples/marvel/lib/src/screens/home.dart b/examples/marvel/lib/src/screens/home.dart index 78c864e32..a5bfc3fa8 100644 --- a/examples/marvel/lib/src/screens/home.dart +++ b/examples/marvel/lib/src/screens/home.dart @@ -92,7 +92,12 @@ class Home extends HookConsumerWidget { return Scaffold( appBar: AppBar(title: const Text('Error')), body: Center( - child: Text('$err'), + child: Text( + switch (err) { + DioException() => err.message ?? '$err', + _ => '$err', + }, + ), ), ); }, diff --git a/examples/marvel/lib/src/screens/home.freezed.dart b/examples/marvel/lib/src/screens/home.freezed.dart index b59d28598..93450e7ed 100644 --- a/examples/marvel/lib/src/screens/home.freezed.dart +++ b/examples/marvel/lib/src/screens/home.freezed.dart @@ -12,7 +12,7 @@ part of 'home.dart'; T _$identity(T value) => value; final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); /// @nodoc mixin _$CharacterPagination { @@ -116,7 +116,7 @@ class _$CharacterPaginationImpl implements _CharacterPagination { } @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && other is _$CharacterPaginationImpl && @@ -252,7 +252,7 @@ class _$CharacterOffsetImpl implements _CharacterOffset { } @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && other is _$CharacterOffsetImpl && diff --git a/examples/marvel/pubspec.yaml b/examples/marvel/pubspec.yaml index 6d141bdfe..978c51ce7 100644 --- a/examples/marvel/pubspec.yaml +++ b/examples/marvel/pubspec.yaml @@ -3,7 +3,7 @@ description: A new Flutter project. publish_to: "none" environment: - sdk: ">=3.0.0-0.0-dev <4.0.0" + sdk: ">=3.0.0 <4.0.0" flutter: ">=1.17.0" dependencies: diff --git a/examples/pub/lib/pub_repository.freezed.dart b/examples/pub/lib/pub_repository.freezed.dart index 526229bc9..b86c3f5d8 100644 --- a/examples/pub/lib/pub_repository.freezed.dart +++ b/examples/pub/lib/pub_repository.freezed.dart @@ -12,7 +12,7 @@ part of 'pub_repository.dart'; T _$identity(T value) => value; final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); PackageMetricsScore _$PackageMetricsScoreFromJson(Map json) { return _PackageMetricsScore.fromJson(json); @@ -198,7 +198,7 @@ class _$PackageMetricsScoreImpl } @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && other is _$PackageMetricsScoreImpl && @@ -385,7 +385,7 @@ class _$PackageMetricsResponseImpl } @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && other is _$PackageMetricsResponseImpl && @@ -547,7 +547,7 @@ class _$PackageDetailsImpl } @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && other is _$PackageDetailsImpl && @@ -721,7 +721,7 @@ class _$PackageImpl with DiagnosticableTreeMixin implements _Package { } @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && other is _$PackageImpl && @@ -883,7 +883,7 @@ class _$LikedPackageImpl with DiagnosticableTreeMixin implements _LikedPackage { } @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && other is _$LikedPackageImpl && @@ -1044,7 +1044,7 @@ class _$LikesPackagesResponseImpl } @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && other is _$LikesPackagesResponseImpl && @@ -1201,7 +1201,7 @@ class _$PubPackagesResponseImpl } @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && other is _$PubPackagesResponseImpl && @@ -1350,7 +1350,7 @@ class _$SearchPackageImpl } @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && other is _$SearchPackageImpl && @@ -1502,7 +1502,7 @@ class _$PubSearchResponseImpl } @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && other is _$PubSearchResponseImpl && diff --git a/examples/stackoverflow/lib/question.freezed.dart b/examples/stackoverflow/lib/question.freezed.dart index 16fca8a77..5d31e4eff 100644 --- a/examples/stackoverflow/lib/question.freezed.dart +++ b/examples/stackoverflow/lib/question.freezed.dart @@ -12,7 +12,7 @@ part of 'question.dart'; T _$identity(T value) => value; final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); QuestionsResponse _$QuestionsResponseFromJson(Map json) { return _QuestionsResponse.fromJson(json); @@ -132,7 +132,7 @@ class _$QuestionsResponseImpl implements _QuestionsResponse { } @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && other is _$QuestionsResponseImpl && @@ -476,7 +476,7 @@ class _$QuestionImpl implements _Question { } @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && other is _$QuestionImpl && @@ -683,7 +683,7 @@ class _$QuestionThemeImpl implements _QuestionTheme { } @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && other is _$QuestionThemeImpl && diff --git a/examples/stackoverflow/lib/tag.freezed.dart b/examples/stackoverflow/lib/tag.freezed.dart index 77c51997b..b96ae82e2 100644 --- a/examples/stackoverflow/lib/tag.freezed.dart +++ b/examples/stackoverflow/lib/tag.freezed.dart @@ -12,7 +12,7 @@ part of 'tag.dart'; T _$identity(T value) => value; final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); /// @nodoc mixin _$TagTheme { @@ -153,7 +153,7 @@ class _$TagThemeImpl implements _TagTheme { } @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && other is _$TagThemeImpl && diff --git a/examples/stackoverflow/lib/user.freezed.dart b/examples/stackoverflow/lib/user.freezed.dart index efc55d2ad..f276b487d 100644 --- a/examples/stackoverflow/lib/user.freezed.dart +++ b/examples/stackoverflow/lib/user.freezed.dart @@ -12,7 +12,7 @@ part of 'user.dart'; T _$identity(T value) => value; final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); User _$UserFromJson(Map json) { return _User.fromJson(json); @@ -208,7 +208,7 @@ class _$UserImpl implements _User { } @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && other is _$UserImpl && @@ -396,7 +396,7 @@ class _$BadgeCountImpl implements _BadgeCount { } @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && other is _$BadgeCountImpl && diff --git a/packages/flutter_riverpod/CHANGELOG.md b/packages/flutter_riverpod/CHANGELOG.md index bcfc27ae2..2a43f4f76 100644 --- a/packages/flutter_riverpod/CHANGELOG.md +++ b/packages/flutter_riverpod/CHANGELOG.md @@ -27,6 +27,11 @@ Fix exceptions when using multiple root `ProviderContainers`/`ProviderScopes`. - **Breaking**: Removed everything marked as "deprecated" - Bumped minimum Dart SDK to >= 3.0.0-dev +## 2.4.10 - 2024-02-03 + +- Fix out of date `pub.dev` description +- Add `test` argument to `AsyncValue.guard` method. (thanks to @utamori) + ## 2.4.9 - 2023-11-27 - Fix "pending timer" issue inside tests when using `ref.keepAlive()`. diff --git a/packages/flutter_riverpod/lib/src/consumer.dart b/packages/flutter_riverpod/lib/src/consumer.dart index 665a6e596..53b06afab 100644 --- a/packages/flutter_riverpod/lib/src/consumer.dart +++ b/packages/flutter_riverpod/lib/src/consumer.dart @@ -471,9 +471,6 @@ abstract class ConsumerWidget extends ConsumerStatefulWidget { } class _ConsumerState extends ConsumerState { - @override - WidgetRef get ref => context as WidgetRef; - @override Widget build(BuildContext context) { return widget.build(context, ref); diff --git a/packages/flutter_riverpod/lib/src/framework.dart b/packages/flutter_riverpod/lib/src/framework.dart index 99f76ba1c..c6e592ea1 100644 --- a/packages/flutter_riverpod/lib/src/framework.dart +++ b/packages/flutter_riverpod/lib/src/framework.dart @@ -25,7 +25,7 @@ import 'internals.dart'; /// } /// ``` /// -/// It optionally possible to specify `overrides` to change the behavior of +/// It's optionally possible to specify `overrides` to change the behavior of /// some providers. This can be useful for testing purposes: /// /// ```dart diff --git a/packages/flutter_riverpod/pubspec.yaml b/packages/flutter_riverpod/pubspec.yaml index c84922abe..313841192 100644 --- a/packages/flutter_riverpod/pubspec.yaml +++ b/packages/flutter_riverpod/pubspec.yaml @@ -1,7 +1,7 @@ name: flutter_riverpod description: > - A simple way to access state from anywhere in your application - while robust and testable. + A reactive caching and data-binding framework. + Riverpod makes working with asynchronous code a breeze. version: 3.0.0-dev.3 homepage: https://riverpod.dev repository: https://github.com/rrousselGit/riverpod diff --git a/packages/hooks_riverpod/CHANGELOG.md b/packages/hooks_riverpod/CHANGELOG.md index 83ded1fb5..51319b528 100644 --- a/packages/hooks_riverpod/CHANGELOG.md +++ b/packages/hooks_riverpod/CHANGELOG.md @@ -27,6 +27,11 @@ Fix exceptions when using multiple root `ProviderContainers`/`ProviderScopes`. - **Breaking**: Removed everything marked as "deprecated" - Bumped minimum Dart SDK to >= 3.0.0-dev +## 2.4.10 - 2024-02-03 + +- Fix out of date `pub.dev` description +- Add `test` argument to `AsyncValue.guard` method. (thanks to @utamori) + ## 2.4.9 - 2023-11-27 - Fix "pending timer" issue inside tests when using `ref.keepAlive()`. diff --git a/packages/hooks_riverpod/pubspec.yaml b/packages/hooks_riverpod/pubspec.yaml index b1d897f5d..eb4d45f5b 100644 --- a/packages/hooks_riverpod/pubspec.yaml +++ b/packages/hooks_riverpod/pubspec.yaml @@ -1,7 +1,7 @@ name: hooks_riverpod description: > - A simple way to access state from anywhere in your application - while robust and testable. + A reactive caching and data-binding framework. + Riverpod makes working with asynchronous code a breeze. version: 3.0.0-dev.3 homepage: https://riverpod.dev repository: https://github.com/rrousselGit/riverpod diff --git a/packages/riverpod/CHANGELOG.md b/packages/riverpod/CHANGELOG.md index 6361af5a8..51541761f 100644 --- a/packages/riverpod/CHANGELOG.md +++ b/packages/riverpod/CHANGELOG.md @@ -1,37 +1,9 @@ -## 3.0.0-dev.3 - 2023-11-27 - -- Fix "pending timer" issue inside tests when using `ref.keepAlive()`. -- Fix `Ref.invalidate`/`Ref.refresh` not throwing on circular dependency. -- Fix an infinite loop caused by `ref.keepAlive` if the `KeepAliveLink` is immediately closed. -- Fix `container.exists(provider)` on nested containers not checking their - parent containers. - -## 3.0.0-dev.2 - 2023-11-20 - -Fix exceptions when using multiple root `ProviderContainers`/`ProviderScopes`. - -## 3.0.0-dev.1 - 2023-11-20 - -- All notifier properties now throw an error if used after the notifier - has been disposed. -- The error thrown when a notifier property is used inside the constructor - of a notifier has been improved. -- Fix `ProviderObserver.didUpdateProvider` being called with an incorrect - "provider" parameter when the provider is overridden. - -## 3.0.0-dev.0 - 2023-10-29 - -- **Breaking**: `AsyncValue` is now "sealed" and `AsyncData/AsyncLoading/AsyncError` - are "final". This means that it is no-longer possible to subclass - `AsyncValue` or the associated classes. -- **Breaking**: Removed everything marked as "deprecated" -- Bumped minimum Dart SDK to >= 3.0.0-dev - ## 2.4.9 - 2023-11-27 - Fix "pending timer" issue inside tests when using `ref.keepAlive()`. - Fix `Ref.invalidate`/`Ref.refresh` not throwing on circular dependency. -- Fix an infinite loop caused by `ref.keepAlive` if the `KeepAliveLink` is immediately closed. +- Fix an infinite loop caused by `ref.keepAlive` if the `KeepAliveLink` is + immediately closed. - Fix `container.exists(provider)` on nested containers not checking their parent containers. @@ -46,13 +18,14 @@ Fix exceptions when using multiple root `ProviderContainers`/`ProviderScopes`. ## 2.4.6 - 2023-11-13 -- Exceptions in asynchronous providers are now correctly received - by `ProviderObserver.providerDidFail`. +- Exceptions in asynchronous providers are now correctly received by + `ProviderObserver.providerDidFail`. - Fix exception when a `ProviderScope` is rebuilt with a different `key`. ## 2.4.4 - 2023-10-15 -- Update the documentation of `provider.argument` to match the behavior of generated providers. +- Update the documentation of `provider.argument` to match the behavior of + generated providers. ## 2.4.3 - 2023-10-06 @@ -64,9 +37,8 @@ Fix exceptions when using multiple root `ProviderContainers`/`ProviderScopes`. ## 2.4.0 - 2023-09-04 -- Added `Notifier.stateOrNull`. - This will return `null` if used when the notifier has yet to be initialized - or is in error state. +- Added `Notifier.stateOrNull`. This will return `null` if used when the + notifier has yet to be initialized or is in error state. ## 2.3.10 - 2023-08-28 @@ -98,26 +70,26 @@ Riverpod now requires package:meta >=1.9.0 ## 2.3.3 - 2023-04-06 -- The debugger no-longer pauses on uncaught exceptions inside providers. - This was voluntary, but too many people have complained that it often - is a false positive. +- The debugger no-longer pauses on uncaught exceptions inside providers. This + was voluntary, but too many people have complained that it often is a false + positive. - Removed unused dependency (thanks to @passsy) ## 2.3.2 - 2023-03-13 -- Deprecated the generic parameter of `Family`. - This will enable implementing generic providers in `riverpod_generator` once - it is removed. +- Deprecated the generic parameter of `Family`. This will enable implementing + generic providers in `riverpod_generator` once it is removed. - Updated documentation ## 2.3.1 - 2023-03-09 -- Updated `AsyncValue.value/valueOrNull` docs to cover the "previous value" cases (thanks to @AhmedLSayed9) +- Updated `AsyncValue.value/valueOrNull` docs to cover the "previous value" + cases (thanks to @AhmedLSayed9) ## 2.3.0 -- Added `StreamNotifier` + `StreamNotifierProvider`. - This is for building a `StreamProvider` while exposing ways to modify the stream. +- Added `StreamNotifier` + `StreamNotifierProvider`. This is for building a + `StreamProvider` while exposing ways to modify the stream. It is primarily meant to be used using code-generation via riverpod_generator, by writing: @@ -132,8 +104,7 @@ Riverpod now requires package:meta >=1.9.0 } ``` -- Deprecated `StreamProvider.stream` - Instead of: +- Deprecated `StreamProvider.stream` Instead of: ```dart ref.watch(provider.stream).listen(...) @@ -162,15 +133,17 @@ Riverpod now requires package:meta >=1.9.0 }) ``` -- Some restrictions on the `dependencies` parameter of providers have been lifted. - It is no-longer necessary to include providers which do not themselves specify `dependencies`. - All providers should specify `dependencies` if they are scoped at any point. +- Some restrictions on the `dependencies` parameter of providers have been + lifted. It is no-longer necessary to include providers which do not themselves + specify `dependencies`. All providers should specify `dependencies` if they + are scoped at any point. - Annotated `Notifier.state` setter as protected. ## 2.2.0 -- Improve type-inference when using `AsyncValue.whenOrNull` (thanks to @AhmedLSayed9) +- Improve type-inference when using `AsyncValue.whenOrNull` (thanks to + @AhmedLSayed9) - Fixed AsyncValue.asError incorrectly not preserving the generic type - Internal refactoring for riverpod_generator @@ -182,8 +155,10 @@ Fixes an issue with `FutureProvider` (#2028) - Update dependencies. - fixes an exception on newer Dart versions -- fixes an edge-case where `FutureProvider`/`AsyncNotifier` did not emit the new state when the created `Future` completed (#1997) -- fixes errors inside FutureProvider/AsyncNotifier/StreamProvider not preserving the previous state (if any). +- fixes an edge-case where `FutureProvider`/`AsyncNotifier` did not emit the new + state when the created `Future` completed (#1997) +- fixes errors inside FutureProvider/AsyncNotifier/StreamProvider not preserving + the previous state (if any). ## 2.1.1 @@ -195,38 +170,46 @@ A small release adding missing utilities and fixing some web related issues. - Added `provider.overrideWith((ref) => state`) - Added `FutureProviderRef.future`. -- Deprecated `StateProvider.state` - Instead, use either `ref.watch(stateProvider)` or `ref.read(stateProvider.notifier).state =` -- Deprecated `provider.overrideWithProvider`. Instead use `provider.overrideWith` -- Added `Ref.notifyListeners()` to forcibly notify dependents. - This can be useful for mutable state. +- Deprecated `StateProvider.state` Instead, use either + `ref.watch(stateProvider)` or `ref.read(stateProvider.notifier).state =` +- Deprecated `provider.overrideWithProvider`. Instead use + `provider.overrideWith` +- Added `Ref.notifyListeners()` to forcibly notify dependents. This can be + useful for mutable state. - Added `@useResult` to `Ref.refresh`/`WidgetRef.refresh` - Added `Ref.exists` to check whether a provider is initialized or not. -- `FutureProvider`, `StreamProvider` and `AsyncNotifierProvider` now preserve the - previous data/error when going back to loading. - This is done by allowing `AsyncLoading` to optionally contain a value/error. -- Added `AsyncValue.when(skipLoadingOnReload: bool, skipLoadingOnRefresh: bool, skipError: bool)` - flags to give fine control over whether the UI should show `loading` - or `data/error` cases. +- `FutureProvider`, `StreamProvider` and `AsyncNotifierProvider` now preserve + the previous data/error when going back to loading. This is done by allowing + `AsyncLoading` to optionally contain a value/error. +- Added + `AsyncValue.when(skipLoadingOnReload: bool, skipLoadingOnRefresh: bool, skipError: bool)` + flags to give fine control over whether the UI should show `loading` or + `data/error` cases. - Add `AsyncValue.requireValue`, to forcibly obtain the `value` and throw if in loading/error state -- Doing `ref.watch(futureProvider.future)` can no-longer return a `SynchronousFuture`. - That behavior could break various `Future` utilities, such as `Future.wait` +- Doing `ref.watch(futureProvider.future)` can no-longer return a + `SynchronousFuture`. That behavior could break various `Future` utilities, + such as `Future.wait` - Add `AsyncValue.copyWithPrevious(..., isRefresh: false)` to differentiate rebuilds from `ref.watch` vs rebuilds from `ref.refresh`. -- ProviderContainer no-longer throws when disposed if it has an undisposed child ProviderContainer. +- ProviderContainer no-longer throws when disposed if it has an undisposed child + ProviderContainer. - Fixes a stackoverflow on Web caused by Dart (thanks to @leehack) -- Fixes a bug when the root ProviderContainer is not associated with a ProviderScope. -- Fixes a case where a circular dependency between providers was incorrectly allowed (#1766) +- Fixes a bug when the root ProviderContainer is not associated with a + ProviderScope. +- Fixes a case where a circular dependency between providers was incorrectly + allowed (#1766) ## 2.0.2 -- **FIX**: Fixed an assert error if a `family` depends on itself while specifying `dependencies`. (#1721). +- **FIX**: Fixed an assert error if a `family` depends on itself while + specifying `dependencies`. (#1721). ## 2.0.2 -Fixed an assert error if a `family` depends on itself while specifying `dependencies`. +Fixed an assert error if a `family` depends on itself while specifying +`dependencies`. ## 2.0.1 @@ -234,81 +217,94 @@ Updated changelog (see 2.0.0) ## 2.0.0 -Here is the changelog for all the changes in the 2.0 version. -An article is in progress and will be linked here once available. +Here is the changelog for all the changes in the 2.0 version. An article is in +progress and will be linked here once available. **Breaking changes**: - `FutureProvider.stream` is removed. - Using `overrideWithProvider`, it is no-longer possible to override a provider - with a different type of provider (such as overriding `FutureProvider` with a `StreamProvider`). -- `AsyncError.stackTrace` is now a required positional parameter and non-nullable -- All `overrideWithValue` methods are removed, besides `Provider.overrideWithValue`. - This change is temporary, and these methods will be reintroduced in a later version. - In the meantime, you can use `overrideWithProvider`. -- Modifiers (`provider.future`, `provider.state`, ...) no-longer are providers, and therefore no-longer - appear inside `ProviderObserver`. + with a different type of provider (such as overriding `FutureProvider` with a + `StreamProvider`). +- `AsyncError.stackTrace` is now a required positional parameter and + non-nullable +- All `overrideWithValue` methods are removed, besides + `Provider.overrideWithValue`. This change is temporary, and these methods will + be reintroduced in a later version. In the meantime, you can use + `overrideWithProvider`. +- Modifiers (`provider.future`, `provider.state`, ...) no-longer are providers, + and therefore no-longer appear inside `ProviderObserver`. - The `Reader` typedef is removed. Use `Ref` instead. - `ProviderListener` is removed. Used `ref.listen` instead. - Removed the deprecated `ProviderReference`. -- Providers no-longer throw a `ProviderException` if an exception was thrown while building their value. - Instead, they will rethrow the thrown exception and its stacktrace. -- It is no longer possible to pass `provider.future/.notifier/...` to the parameter `dependencies` of provider. - Passing the provider directly is enough. +- Providers no-longer throw a `ProviderException` if an exception was thrown + while building their value. Instead, they will rethrow the thrown exception + and its stacktrace. +- It is no longer possible to pass `provider.future/.notifier/...` to the + parameter `dependencies` of provider. Passing the provider directly is enough. - The `Family` type now has a single generic parameter instead of 3. Non-breaking changes: - Upgrade minimum required Dart SDK version to 2.17.0 -- Added `provider.selectAsync`, which allows to both await an async value - while also filtering rebuilds. -- Added `ref.listenSelf`, for subscribing to changes of a provider within - that provider. - That can be useful for logging purposes or storing the state of a provider - in a DB. -- Added `container.invalidate(provider)`/`ref.invalidate(provider)` and `ref.invalidateSelf()`. - These are similar to `ref.refresh` methods, but do not immediately rebuild the provider. +- Added `provider.selectAsync`, which allows to both await an async value while + also filtering rebuilds. +- Added `ref.listenSelf`, for subscribing to changes of a provider within that + provider. That can be useful for logging purposes or storing the state of a + provider in a DB. +- Added `container.invalidate(provider)`/`ref.invalidate(provider)` and + `ref.invalidateSelf()`. These are similar to `ref.refresh` methods, but do not + immediately rebuild the provider. These methods are safer than `ref.refresh` as they can avoid a provider rebuilding twice in a quick succession. - Added `ref.onAddListener`, `ref.onRemoveListener`, `ref.onCancel` and - `ref.onResume`. All of which allow performing side-effects when providers - are listened or stop being listened. + `ref.onResume`. All of which allow performing side-effects when providers are + listened or stop being listened. - A new `AutoDisposeRef.keepAlive()` function is added. It is meant to replace - `AutoDisposeRef.maintainState` to make logic for preventing the disposal of a provider more reusable. -- feat; `AutoDisposeRef.maintainState` is deprecated. Use the new `AutoDisposeRef.keepAlive()` instead. + `AutoDisposeRef.maintainState` to make logic for preventing the disposal of a + provider more reusable. +- feat; `AutoDisposeRef.maintainState` is deprecated. Use the new + `AutoDisposeRef.keepAlive()` instead. - Add support for `ref.invalidate(family)` to recompute an entire family (#1517) -- Added `AsyncValue.valueOrNull` to obtain the value while ignoring potential errors. -- Added new functionalities to `AsyncValue`: `hasError`, `hasData`, `asError`, `isLoading` - , `copyWithPrevious` and `unwrapPrevious`. +- Added `AsyncValue.valueOrNull` to obtain the value while ignoring potential + errors. +- Added new functionalities to `AsyncValue`: `hasError`, `hasData`, `asError`, + `isLoading` , `copyWithPrevious` and `unwrapPrevious`. Fixes: -- fixed a bug where `AsyncValue.whenData` did not preserve `AsyncValue.isLoading/isRefreshing` +- fixed a bug where `AsyncValue.whenData` did not preserve + `AsyncValue.isLoading/isRefreshing` - `StateProvider` and `StateNotifierProvider` no longer notify their listeners on `ref.refresh` if the new result is identical to the old one. - fixed potential null exception when using `autoDispose` -- fixed a bug where unmounting a nested ProviderScope could cause an exception (#1400) -- Fixed an issue where providers were incorrectly allowed to depend on themselves, - breaking `autoDispose` in the process. +- fixed a bug where unmounting a nested ProviderScope could cause an exception + (#1400) +- Fixed an issue where providers were incorrectly allowed to depend on + themselves, breaking `autoDispose` in the process. - Fixed a memory leak when using `StateProvider.autoDispose`'s `.state` - Fix `ProviderObserver.didDisposeProvider` not executing on provider refresh. - Fixed an issue where `AsyncValue.value` did not throw if there is an error. -- Fixed a cast error when overriding a provider with a more specific provider type (#1100) +- Fixed a cast error when overriding a provider with a more specific provider + type (#1100) - Fixed a bug where `onDispose` listeners could be executed twice under certain conditions when using `autoDispose`. -- Fixed an issue where refreshing a `provider.future`/`provider.stream` did work properly +- Fixed an issue where refreshing a `provider.future`/`provider.stream` did work + properly - Fixed false positive with `ref.watch` asserts ## 2.0.0-dev.9 -Fix Timer leak when using `cacheTime`/`disposeDelay` and disposing a `ProviderContainer` +Fix Timer leak when using `cacheTime`/`disposeDelay` and disposing a +`ProviderContainer` ## 2.0.0-dev.8 -fix: a bug where unmounting a nested ProviderScope could cause an exception (#1400) +fix: a bug where unmounting a nested ProviderScope could cause an exception +(#1400) ## 2.0.0-dev.7 @@ -316,7 +312,8 @@ Upgrade minimum required Dart SDK version to 2.17.0 ## 2.0.0-dev.6 -- Added `AsyncValue.valueOrNull` to obtain the value while ignoring potential errors. +- Added `AsyncValue.valueOrNull` to obtain the value while ignoring potential + errors. - Fixed an issue where `AsyncValue.value` did not throw if there is an error. - Fix families not applying cacheTime/disposeDelay - Fixed a bug where an exception may be thrown asynchronously after a @@ -324,30 +321,31 @@ Upgrade minimum required Dart SDK version to 2.17.0 ## 2.0.0-dev.5 -- Fixed a bug where emitting an `AsyncData` after an `AsyncError` leads to `AsyncValue.hasError` to be true +- Fixed a bug where emitting an `AsyncData` after an `AsyncError` leads to + `AsyncValue.hasError` to be true ## 2.0.0-dev.4 -- Added `ref.listenSelf`, for subscribing to changes of a provider within - that provider. - That can be useful for logging purposes or storing the state of a provider - in a DB. -- Added `disposeDelay` to all `autoDispose` providers and to `ProviderContainer`/`ProviderScope`. - This configures the amount of time before a provider is disposed when it is - not listened. +- Added `ref.listenSelf`, for subscribing to changes of a provider within that + provider. That can be useful for logging purposes or storing the state of a + provider in a DB. +- Added `disposeDelay` to all `autoDispose` providers and to + `ProviderContainer`/`ProviderScope`. This configures the amount of time before + a provider is disposed when it is not listened. -- Added `container.invalidate(provider)`/`ref.invalidate(provider)` and `ref.invalidateSelf()`. - These are similar to `ref.refresh` methods, but do not immediately rebuild the provider. +- Added `container.invalidate(provider)`/`ref.invalidate(provider)` and + `ref.invalidateSelf()`. These are similar to `ref.refresh` methods, but do not + immediately rebuild the provider. These methods are safer than `ref.refresh` as they can avoid a provider rebuilding twice in a quick succession. -- The duration passed to `cacheTime` now represents the minimum amount of - time after the latest change of a provider, instead of the first time - a provider built. +- The duration passed to `cacheTime` now represents the minimum amount of time + after the latest change of a provider, instead of the first time a provider + built. -- Fixed an issue where providers were incorrectly allowed to depend on themselves, - breaking `autoDispose` in the process. +- Fixed an issue where providers were incorrectly allowed to depend on + themselves, breaking `autoDispose` in the process. - Fixed a memory leak when using `StateProvider.autoDispose`'s `.state` @@ -355,64 +353,69 @@ Upgrade minimum required Dart SDK version to 2.17.0 ## 2.0.0-dev.3 -When calling `ref.listen` on a provider, this provider will now properly -rebuild if one of its dependency had changed. +When calling `ref.listen` on a provider, this provider will now properly rebuild +if one of its dependency had changed. ## 2.0.0-dev.2 - Deprecated `ref.maintainState=` in favor of a newly added `ref.keepAlive()`. - This new `ref.keepAlive()` function is similar to `maintainState` but - better handles cases where we have multiple logics that want to - keep the state of a provider alive for some period of time. + This new `ref.keepAlive()` function is similar to `maintainState` but better + handles cases where we have multiple logics that want to keep the state of a + provider alive for some period of time. - Removed the deprecated `ProviderReference`. -- Added `ProviderContainer.cacheTime` and `MyProvider.autoDispose(..., cacheTime: duration)`. - `cacheTime` is used to keep an `autoDispose` provider alive for at least - a minimum amount of time before it gets disposed if not listened. +- Added `ProviderContainer.cacheTime` and + `MyProvider.autoDispose(..., cacheTime: duration)`. `cacheTime` is used to + keep an `autoDispose` provider alive for at least a minimum amount of time + before it gets disposed if not listened. - Added `ref.onAddListener`, `ref.onRemoveListener`, `ref.onCancel` and - `ref.onResume`. All of which allow performing side-effects when providers - are listened or stop being listened. + `ref.onResume`. All of which allow performing side-effects when providers are + listened or stop being listened. ## 2.0.0-dev.1 - Now requires Dart 2.16 -- **Breaking** Providers no-longer throw a `ProviderException` if an exception was thrown while building their value. - Instead, they will rethrow the thrown exception and its stacktrace. +- **Breaking** Providers no-longer throw a `ProviderException` if an exception + was thrown while building their value. Instead, they will rethrow the thrown + exception and its stacktrace. - Removed `AsyncValue`'s `isError`/`isData` -- Added new functionalities to `AsyncValue`: `hasError`, `hasData`, `copyWithPrevious` -- Added `provider.selectAsync`, which allows to both await an async value - while also filtering rebuilds. -- When a provider emits an `AsyncError` followed by an `AsyncData`, - the `AsyncData` emitted will now contain the latest error/stackTrace too. +- Added new functionalities to `AsyncValue`: `hasError`, `hasData`, + `copyWithPrevious` +- Added `provider.selectAsync`, which allows to both await an async value while + also filtering rebuilds. +- When a provider emits an `AsyncError` followed by an `AsyncData`, the + `AsyncData` emitted will now contain the latest error/stackTrace too. -- Fixed a cast error when overriding a provider with a more specific provider type (#1100) +- Fixed a cast error when overriding a provider with a more specific provider + type (#1100) - Fixed a bug where `onDispose` listeners could be executed twice under certain conditions when using `autoDispose`. ## 2.0.0-dev.0 -- **Breaking** After a provider has emitted an `AsyncValue.data` or `AsyncValue.error`, - that provider will no longer emit an `AsyncValue.loading`. +- **Breaking** After a provider has emitted an `AsyncValue.data` or + `AsyncValue.error`, that provider will no longer emit an `AsyncValue.loading`. Instead, it will re-emit the latest value, but with the property `AsyncValue.isRefreshing` to true. - This allows the UI to keep showing the previous data/error when a provider - is being refreshed. + This allows the UI to keep showing the previous data/error when a provider is + being refreshed. -- Adding `isLoading`, `isError`, `isData` and `asError` to `AsyncValue`. - Those getters allow interacting with `AsyncValue` without having to rely on - pattern matching. -- Fixed an issue where refreshing a `provider.future`/`provider.stream` did work properly +- Adding `isLoading`, `isError`, `isData` and `asError` to `AsyncValue`. Those + getters allow interacting with `AsyncValue` without having to rely on pattern + matching. +- Fixed an issue where refreshing a `provider.future`/`provider.stream` did work + properly - Fixed false positive with `ref.watch` asserts ## 1.0.3 -Removed an assert preventing from overriding the same provider/family -multiple times on a `ProviderScope`/`ProviderContainer`. +Removed an assert preventing from overriding the same provider/family multiple +times on a `ProviderScope`/`ProviderContainer`. ## 1.0.2 @@ -429,20 +432,23 @@ Riverpod is now stable! ### General changes -- **Breaking**: `ProviderContainer.debugProviderValues` and `ProviderContainer.debugProviderElements` are removed. - You can now instead use `ProviderContainer.getAllProviderElements`. +- **Breaking**: `ProviderContainer.debugProviderValues` and + `ProviderContainer.debugProviderElements` are removed. You can now instead use + `ProviderContainer.getAllProviderElements`. - Increased minimum SDK version to 2.14.0 - **Breaking** The return value when reading a `StateProvider` changed. Before, doing `ref.read(someStateProvider)` would return the `StateController` instance. Now, this will only return the state of the `StateController`. - This new behavior matches `StateNotifierProvider`. + This new behaviour matches `StateNotifierProvider`. For a simple migration, the old behavior is available by writing `ref.read(someStateProvider.state)`. -- Added `ref.listen` for triggering actions inside providers/widgets when a provider changes. +- Added `ref.listen` for triggering actions inside providers/widgets when a + provider changes. - It can be used to listen to another provider without recreating the provider state: + It can be used to listen to another provider without recreating the provider + state: ```dart final counterProvider = StateNotifierProvider(...); @@ -469,8 +475,9 @@ Riverpod is now stable! } ``` -- It is now possible to "await" all providers that emit an `AsyncValue` (previously limited to `FutureProvider`/`StreamProvider`). - This includes cases where a `StateNotifierProvider` exposes an `AsyncValue`: +- It is now possible to "await" all providers that emit an `AsyncValue` + (previously limited to `FutureProvider`/`StreamProvider`). This includes cases + where a `StateNotifierProvider` exposes an `AsyncValue`: ```dart class MyAsyncStateNotifier extends StateNotifier> { @@ -490,30 +497,34 @@ Riverpod is now stable! - Deprecated `StreamProvider.last` in favor of `StreamProvider.future`. -- `StreamProvider.future`, `StreamProvider.stream` and `FutureProvider.future` now - expose a future/stream that is independent from how many times the associated provider "rebuilt": +- `StreamProvider.future`, `StreamProvider.stream` and `FutureProvider.future` + now expose a future/stream that is independent from how many times the + associated provider "rebuilt": - if a `StreamProvider` rebuild before its stream emitted any value, - `StreamProvider.future` will resolve with the first value of the new stream instead. + `StreamProvider.future` will resolve with the first value of the new stream + instead. - if a `FutureProvider` rebuild before its future completes, - `FutureProvider.future` will resolve with the result of the new future instead. -- You can now override any provider with any other provider, as long as the value - that they expose matches. For example, it is possible to override a `StreamProvider` - with a `Provider>`. -- `ref.onDispose` now calls the dispose function as soon as one of the provider's - dependencies is known to have changed + `FutureProvider.future` will resolve with the result of the new future + instead. +- You can now override any provider with any other provider, as long as the + value that they expose matches. For example, it is possible to override a + `StreamProvider` with a `Provider>`. +- `ref.onDispose` now calls the dispose function as soon as one of the + provider's dependencies is known to have changed - Providers can now call `ref.refresh` to refresh a provider, instead of having to do `ref.container.refresh`. - Providers no longer wait until their next read to recompute their state if one of their dependencies changed and they have listeners. - Added `ProviderContainer.pump`, a utility to easily "await" until providers notify their listeners or are disposed. -- fixed an issue when using both `family` and `autoDispose` that could lead to an inconsistent state +- fixed an issue when using both `family` and `autoDispose` that could lead to + an inconsistent state ### Unified the syntax for interacting with providers - `ProviderReference` is deprecated in favor of `Ref`. -- `ref.watch` now supports `myProvider.select((value) => ...)`. - This allows filtering rebuilds: +- `ref.watch` now supports `myProvider.select((value) => ...)`. This allows + filtering rebuilds: ```dart final userProvider = StateNotifierProvider(...); @@ -525,7 +536,8 @@ Riverpod is now stable! }); ``` -- **Breaking**: `ProviderObserver.didUpdateProvider` now receives both the previous and new value. +- **Breaking**: `ProviderObserver.didUpdateProvider` now receives both the + previous and new value. - **Breaking**: `ProviderObserver.mayHaveChanged` is removed. - **Breaking**: `Family.overrideWithProvider` now must create a provider: @@ -538,7 +550,8 @@ Riverpod is now stable! ); ``` -- All providers now receive a custom subclass of `ProviderRefBase` as a parameter: +- All providers now receive a custom subclass of `ProviderRefBase` as a + parameter: ```dart Provider((ProviderRef ref) {...}); @@ -546,10 +559,12 @@ Riverpod is now stable! StateProvider((StateProviderRef ref) {...}); ``` - That allows providers to implement features that is not shared with other providers. + That allows providers to implement features that is not shared with other + providers. - - `Provider`, `FutureProvider` and `StreamProvider`'s `ref` now have a `state` property, - which represents the currently exposed value. Modifying it will notify the listeners: + - `Provider`, `FutureProvider` and `StreamProvider`'s `ref` now have a `state` + property, which represents the currently exposed value. Modifying it will + notify the listeners: ```dart Provider((ref) { @@ -564,7 +579,8 @@ Riverpod is now stable! - `StateProvider`'s `ref` now has a `controller` property, which allows the provider to access the `StateController` exposed. -- **Breaking**: `ProviderReference.mounted` is removed. You can implement something similar using `onDispose`: +- **Breaking**: `ProviderReference.mounted` is removed. You can implement + something similar using `onDispose`: ```dart Provider((ref) { var mounted = true; @@ -574,12 +590,12 @@ Riverpod is now stable! ### All providers can now be scoped. -- **Breaking**: `ScopedProvider` is removed. - To migrate, change `ScopedProvider`s to `Provider`s. +- **Breaking**: `ScopedProvider` is removed. To migrate, change + `ScopedProvider`s to `Provider`s. - All providers now come with an extra named parameter called `dependencies`. - This parameter optionally allows defining the list of providers/families that this - new provider depends on: + This parameter optionally allows defining the list of providers/families that + this new provider depends on: ```dart final a = Provider(...); @@ -587,12 +603,14 @@ Riverpod is now stable! final b = Provider((ref) => ref.watch(a), dependencies: [a]); ``` - By doing so, this will tell Riverpod to automatically override `b` if `a` gets overridden. + By doing so, this will tell Riverpod to automatically override `b` if `a` gets + overridden. ### Updated `AsyncValue`: - **Breaking** `AsyncValue.copyWith` is removed -- **Breaking** `AsyncValue.error(..., stacktrace)` is now a named parameter instead of positional parameter. +- **Breaking** `AsyncValue.error(..., stacktrace)` is now a named parameter + instead of positional parameter. - Deprecated `AsyncValue.data` in favor of `AsyncValue.value` - Allowed `AsyncData`, `AsyncError` and `AsyncLoading` to be extended - Added `AsyncValue.whenOrNull`, similar to `whenOrElse` but instead of an @@ -601,7 +619,8 @@ Riverpod is now stable! loading/error states. - `AsyncError` can now be instantiated with `const`. -- Added `StateController.update`, to simplify updating the state from the previous state: +- Added `StateController.update`, to simplify updating the state from the + previous state: ```dart final provider = StateController((ref) => 0); ... @@ -613,15 +632,18 @@ Riverpod is now stable! provider.select((value) => ref.watch(something)); // KO, cannot use ref.watch inside selectors ``` -- FutureProvider now creates a `FutureOr` instead of a `Future`. - That allows bypassing the loading state in the event where a value was synchronously available. +- FutureProvider now creates a `FutureOr` instead of a `Future`. That + allows bypassing the loading state in the event where a value was + synchronously available. ### Bug fixes -- Fixed a bug where widgets were not rebuilding in release mode under certain conditions -- **FIX**: StreamProvider.last no longer throws a StateError when no value were emitted (#296). -- fixed an issue where when chaining providers, widgets may re-render - a frame late, potentially causing a flicker. (see #648) +- Fixed a bug where widgets were not rebuilding in release mode under certain + conditions +- **FIX**: StreamProvider.last no longer throws a StateError when no value were + emitted (#296). +- fixed an issue where when chaining providers, widgets may re-render a frame + late, potentially causing a flicker. (see #648) ## 1.0.0-dev.10 @@ -636,13 +658,13 @@ Fix an issue where `*Provider.autoDispose` were not able to specify the ### Future/StreamProvider -- FutureProvider now creates a `FutureOr` instead of a `Future` - That allows bypassing the loading state in the event where a value was synchronously available. +- FutureProvider now creates a `FutureOr` instead of a `Future` That + allows bypassing the loading state in the event where a value was + synchronously available. -- During loading and error states, `FutureProvider` and `StreamProvider` now expose the - latest value through `AsyncValue`. - That allows UI to show the previous data while some new data is loading, - instead of showing a spinner: +- During loading and error states, `FutureProvider` and `StreamProvider` now + expose the latest value through `AsyncValue`. That allows UI to show the + previous data while some new data is loading, instead of showing a spinner: ```dart final provider = FutureProvider((ref) async { @@ -671,9 +693,10 @@ Fix an issue where `*Provider.autoDispose` were not able to specify the ### AsyncValue - **Breaking** `AsyncValue.copyWith` is removed -- **Breaking** `AsyncValue.error(..., stacktrace)` is now a named parameter instead of positional parameter. -- **Breaking** `AsyncValue.when(loading: )` and `AsyncValue.when(error: )` (and `when` variants) - now receive an extra "previous" parameter. +- **Breaking** `AsyncValue.error(..., stacktrace)` is now a named parameter + instead of positional parameter. +- **Breaking** `AsyncValue.when(loading: )` and `AsyncValue.when(error: )` (and + `when` variants) now receive an extra "previous" parameter. - Deprecated `AsyncValue.data` in favor of `AsyncValue.value` - Allowed `AsyncData`, `AsyncError` and `AsyncLoading` to be extended - Added `AsyncValue.whenOrNull`, similar to `whenOrElse` but instead of an @@ -685,11 +708,11 @@ Fix an issue where `*Provider.autoDispose` were not able to specify the ### General -- **Breaking** All `overrideWithProvider` methods are removed. - To migrate, instead use `overrideWithValue`. +- **Breaking** All `overrideWithProvider` methods are removed. To migrate, + instead use `overrideWithValue`. - All providers now come with an extra named parameter called `dependencies`. - This parameter optionally allows defining the list of providers/families that this - new provider depends on: + This parameter optionally allows defining the list of providers/families that + this new provider depends on: ```dart final a = Provider(...); @@ -697,9 +720,11 @@ Fix an issue where `*Provider.autoDispose` were not able to specify the final b = Provider((ref) => ref.watch(a), dependencies: [a]); ``` - By doing so, this will tell Riverpod to automatically override `b` if `a` gets overridden. + By doing so, this will tell Riverpod to automatically override `b` if `a` gets + overridden. -- Added `StateController.update`, to simplify updating the state from the previous state: +- Added `StateController.update`, to simplify updating the state from the + previous state: ```dart final provider = StateController((ref) => 0); ... @@ -716,21 +741,24 @@ Fix an issue where `*Provider.autoDispose` were not able to specify the - fixed `ref.listen` now working when downcasing the value of a provider. - fixed a bug where disposing a scoped `ProviderContainer` could cause other `ProviderContainer`s to stop working. -- fixed an issue where conditionally depending on an "autoDispose" provider - may not properly dispose of it (see #712) -- fixed an issue where when chaining providers, widgets may re-render - a frame late, potentially causing a flicker. (see #648) +- fixed an issue where conditionally depending on an "autoDispose" provider may + not properly dispose of it (see #712) +- fixed an issue where when chaining providers, widgets may re-render a frame + late, potentially causing a flicker. (see #648) ## 1.0.0-dev.7 - Fixed `ProviderObserver` not working when modifying a `StateProvider`. - Fixed a bug where scoped provider were potentially not disposed -- Fixed a bug where widgets were not rebuilding in release mode under certain conditions +- Fixed a bug where widgets were not rebuilding in release mode under certain + conditions ## 1.0.0-dev.6 -- **FIX**: StreamProvider.last no longer throws a StateError when no value were emitted (#296). -- Re-enabled debug assertions that were temporarily disabled by previous dev versions. +- **FIX**: StreamProvider.last no longer throws a StateError when no value were + emitted (#296). +- Re-enabled debug assertions that were temporarily disabled by previous dev + versions. - Allows families to be scoped/overridden - Fixed bugs with `ref.refresh` not working on some providers - renamed `ProviderBase.recreateShouldNotify` to `updateShouldNotify` @@ -746,7 +774,8 @@ Fixed various issues related to scoped providers. ## 1.0.0-dev.2 - All providers can now be scoped. -- **breaking**: `ScopedProvider` is removed. To migrate, change `ScopedProvider`s to `Provider`s. +- **breaking**: `ScopedProvider` is removed. To migrate, change + `ScopedProvider`s to `Provider`s. ## 1.0.0-dev.1 @@ -754,8 +783,8 @@ Fixed various issues related to scoped providers. ## 1.0.0-dev.0 -- `ref.watch` now support `myProvider.select((value) => ...)`. - This allows filtering rebuilds: +- `ref.watch` now support `myProvider.select((value) => ...)`. This allows + filtering rebuilds: ```dart final userProvider = StateNotifierProvider(...); @@ -777,9 +806,11 @@ Fixed various issues related to scoped providers. ); ``` -- **Breaking**: `ProviderObserver.didUpdateProvider` now receives both the previous and new value. +- **Breaking**: `ProviderObserver.didUpdateProvider` now receives both the + previous and new value. - **Breaking**: `ProviderObserver.mayHaveChanged` is removed. -- Added `ref.listen`, used to listen to another provider without recreating the provider state: +- Added `ref.listen`, used to listen to another provider without recreating the + provider state: ```dart final counter = StateNotifierProvider(...); @@ -792,7 +823,8 @@ Fixed various issues related to scoped providers. ``` - `ProviderReference` is deprecated in favor of `ProviderRefBase`. -- All providers now receive a custom subclass of `ProviderRefBase` as a parameter: +- All providers now receive a custom subclass of `ProviderRefBase` as a + parameter: ```dart Provider((ProviderRef ref) {...}); @@ -800,10 +832,12 @@ Fixed various issues related to scoped providers. StateProvider((StateProviderRef ref) {...}); ``` - That allows providers to implement features that is not shared with other providers. + That allows providers to implement features that is not shared with other + providers. - - `Provider`, `FutureProvider` and `StreamProvider`'s `ref` now have a `state` property, - which represents the currently exposed value. Modifying it will notify the listeners: + - `Provider`, `FutureProvider` and `StreamProvider`'s `ref` now have a `state` + property, which represents the currently exposed value. Modifying it will + notify the listeners: ```dart Provider((ref) { @@ -818,46 +852,54 @@ Fixed various issues related to scoped providers. - `StateProvider`'s `ref` now has a `controller` property, which allows the provider to access the `StateController` exposed. -- **Breaking**: `ProviderReference.mounted` is removed. You can implement something similar using `onDispose`: +- **Breaking**: `ProviderReference.mounted` is removed. You can implement + something similar using `onDispose`: ```dart Provider((ref) { var mounted = true; ref.onDispose(() => mounted = false); }); ``` -- **Breaking**: `ProviderContainer.debugProviderValues` and `ProviderContainer.debugProviderElements` are removed. - You can now instead use `ProviderContainer.getAllProviderElements`. +- **Breaking**: `ProviderContainer.debugProviderValues` and + `ProviderContainer.debugProviderElements` are removed. You can now instead use + `ProviderContainer.getAllProviderElements`. - `StreamProvider.last`, `StreamProvider.stream` and `FutureProvider.future` now - expose a future/stream that is independent from how many times the associated provider "rebuilt": + expose a future/stream that is independent from how many times the associated + provider "rebuilt": - if a `StreamProvider` rebuild before its stream emitted any value, - `StreamProvider.last` will resolve with the first value of the new stream instead. + `StreamProvider.last` will resolve with the first value of the new stream + instead. - if a `FutureProvider` rebuild before its future completes, - `FutureProvider.future` will resolve with the result of the new future instead. -- You can now override any provider with any other provider, as long as the value - that they expose matches. For example, it is possible to override a `StreamProvider` - with a `Provider>`. -- `ref.onDispose` now calls the dispose function as soon as one of the provider's - dependency is known to have changed + `FutureProvider.future` will resolve with the result of the new future + instead. +- You can now override any provider with any other provider, as long as the + value that they expose matches. For example, it is possible to override a + `StreamProvider` with a `Provider>`. +- `ref.onDispose` now calls the dispose function as soon as one of the + provider's dependency is known to have changed - Providers can now call `ref.refresh` to refresh a provider, instead of having to do `ref.container.refresh`. - Providers no longer wait until their next read to recompute their state if one of their dependencies changed and they have listeners. - Added `ProviderContainer.pump`, a utility to easily "await" until providers notify their listeners or are disposed. -- fixed an issue when using both `family` and `autoDispose` that could lead to an inconsistent state +- fixed an issue when using both `family` and `autoDispose` that could lead to + an inconsistent state ## 0.14.0+3 -Removed an assert that could cause issues when an application is partially migrated to null safety. +Removed an assert that could cause issues when an application is partially +migrated to null safety. ## 0.14.0+1 -- Re-added `StateProvider.overrideWithValue`/`StateProvider.overrideWithProvider` that were involuntarily removed. +- Re-added `StateProvider.overrideWithValue`/`StateProvider.overrideWithProvider` that were unvoluntarily removed. ## 0.14.0 -- **BREAKING CHANGE** The `Listener`/`LocatorMixin` typedefs are removed as the former could cause a name - conflict with the widget named `Listener` and the latter is not supported when using Riverpod. +- **BREAKING CHANGE** The `Listener`/`LocatorMixin` typedefs are removed as the + former could cause a name conflict with the widget named `Listener` and the + latter is not supported when using Riverpod. - **BREAKING CHANGE** The syntax for using `StateNotifierProvider` was updated. Before: @@ -887,18 +929,23 @@ Removed an assert that could cause issues when an application is partially migra } ``` - See also https://github.com/rrousselGit/riverpod/issues/341 for more information. + See also https://github.com/rrousselGit/riverpod/issues/341 for more + information. -- **BREAKING CHANGE** It is no longer possible to override `StreamProvider.stream/last` and `FutureProvider.future`. -- feat: Calling `ProviderContainer.dispose` multiple time no longer throws. - This simplifies the tear-off logic of tests. +- **BREAKING CHANGE** It is no longer possible to override + `StreamProvider.stream/last` and `FutureProvider.future`. +- feat: Calling `ProviderContainer.dispose` multiple time no longer throws. This + simplifies the tear-off logic of tests. - feat: Added `ChangeNotifierProvider.notifier` and `StateProvider.notifier` - They allow obtaining the notifier associated with the provider, without causing widgets/providers to rebuild when the state updates. -- fix: overriding a `StateNotifierProvider`/`ChangeNotifierProvider` with `overrideWithValue` now correctly listens to the notifier. + They allow obtaining the notifier associated with the provider, without + causing widgets/providers to rebuild when the state updates. +- fix: overriding a `StateNotifierProvider`/`ChangeNotifierProvider` with + `overrideWithValue` now correctly listens to the notifier. ## 0.13.1 -- Fixed a bug where overriding a `FutureProvider` with an error value could cause tests to fail (see https://github.com/rrousselGit/riverpod/issues/355) +- Fixed a bug where overriding a `FutureProvider` with an error value could + cause tests to fail (see https://github.com/rrousselGit/riverpod/issues/355) ## 0.13.0 @@ -906,16 +953,20 @@ Removed an assert that could cause issues when an application is partially migra - `ProviderObserver` can now have a const constructor - Added the mechanism for state-inspection using the Flutter devtool - loosened the version constraints of `freezed_annotation` -- deprecated `import 'riverpod/all.dart'`. Now everything is available with `riverpod/riverpod.dart`. -- Fixed a but where listening to `StreamProvider.last` could result in a `StateError` (#217) +- deprecated `import 'riverpod/all.dart'`. Now everything is available with + `riverpod/riverpod.dart`. +- Fixed a but where listening to `StreamProvider.last` could result in a + `StateError` (#217) ## 0.13.0-nullsafety.3 -- deprecated `import 'riverpod/all.dart'`. Now everything is available with `riverpod/riverpod.dart`. +- deprecated `import 'riverpod/all.dart'`. Now everything is available with + `riverpod/riverpod.dart`. ## 0.13.0-nullsafety.1 -- Fixed a but where listening to `StreamProvider.last` could result in a `StateError` (#217) +- Fixed a but where listening to `StreamProvider.last` could result in a + `StateError` (#217) ## 0.13.0-nullsafety.0 @@ -927,17 +978,20 @@ Migrated to null-safety ## 0.12.1 -- Fixed an remaining memory leak related to StreamProvider (see also https://github.com/rrousselGit/riverpod/issues/193) +- Fixed an remaining memory leak related to StreamProvider (see also + https://github.com/rrousselGit/riverpod/issues/193) ## 0.12.0 -- **Breaking** FutureProvider and StreamProvider no longer supports `null` as a valid value. -- Fixed a memory leak with StreamProvider (see also https://github.com/rrousselGit/riverpod/issues/193) +- **Breaking** FutureProvider and StreamProvider no longer supports `null` as a + valid value. +- Fixed a memory leak with StreamProvider (see also + https://github.com/rrousselGit/riverpod/issues/193) ## 0.11.2 -- Fixed a bug where providers (usually ScopedProviders) did not dispose correctly - (see also https://github.com/rrousselGit/riverpod/issues/154). +- Fixed a bug where providers (usually ScopedProviders) did not dispose + correctly (see also https://github.com/rrousselGit/riverpod/issues/154). ## 0.11.0 @@ -949,12 +1003,14 @@ Migrated to null-safety ## 0.10.0 -- Fixed a bug where the state of a provider may be disposed when it shouldn't be disposed. +- Fixed a bug where the state of a provider may be disposed when it shouldn't be + disposed. - Added a way to import the implementation class of providers with modifiers, such as `AutoDisposeProvider`. - This is useful if you want to use Riverpod with the lint `always_specify_types`: + This is useful if you want to use Riverpod with the lint + `always_specify_types`: ```dart import 'package:riverpod/all.dart'; @@ -969,7 +1025,8 @@ Migrated to null-safety ## 0.8.0 -- Renamed `ProviderContainer.debugProviderStates` to `ProviderContainer.debugProviderElements` +- Renamed `ProviderContainer.debugProviderStates` to + `ProviderContainer.debugProviderElements` - Fixed a bug where updating `ProviderScope.overrides` may cause an exception for no reason (see https://github.com/rrousselGit/riverpod/issues/107) @@ -978,7 +1035,8 @@ Migrated to null-safety - `ref.watch` on non ".autoDispose" providers can no longer read ".autoDispose" providers. - For more info, see http://riverpod.dev/docs/concepts/modifiers/auto_dispose#the-argument-type-autodisposeprovider-cant-be-assigned-to-the-parameter-type-alwaysaliveproviderbase + For more info, see + http://riverpod.dev/docs/concepts/modifiers/auto_dispose#the-argument-type-autodisposeprovider-cant-be-assigned-to-the-parameter-type-alwaysaliveproviderbase - `ScopedProvider` now accepts `null` as a function: @@ -997,14 +1055,14 @@ Migrated to null-safety ## 0.6.1 -- Fixed a bug where when disposing `ProviderContainer`, providers may be disposed - in an incorrect order. +- Fixed a bug where when disposing `ProviderContainer`, providers may be + disposed in an incorrect order. - Improved the performances of reading providers by 25% ## 0.6.0 -- Merged `Computed` and `Provider`. Now, all providers have the ability to rebuild - their state when one of the object they listen changed. +- Merged `Computed` and `Provider`. Now, all providers have the ability to + rebuild their state when one of the object they listen changed. To migrate, change: @@ -1026,9 +1084,9 @@ Migrated to null-safety }); ``` -- `Computed` (now `Provider`) no longer deeply compare collections to avoid rebuilds. - Comparing the content of lists is quite expensive and actually rarely useful. - Now, a simple `==` comparison is used. +- `Computed` (now `Provider`) no longer deeply compare collections to avoid + rebuilds. Comparing the content of lists is quite expensive and actually + rarely useful. Now, a simple `==` comparison is used. - Renamed `ProviderStateOwner` to `ProviderContainer` - Renamed `ProviderStateOwnerObserver` to `ProviderObserver` @@ -1036,19 +1094,19 @@ Migrated to null-safety - It is no longer possible to override a provider anywhere in the widget tree. Providers can only be overridden in the top-most `ProviderContainer`. -- Providers can now read values which may change over time using `ref.read` and `ref.watch`. - When using `ref.watch`, if the value obtained changes, this will cause the provider - to re-create its state. +- Providers can now read values which may change over time using `ref.read` and + `ref.watch`. When using `ref.watch`, if the value obtained changes, this will + cause the provider to re-create its state. -- It is no longer possible to add `ProviderObserver` anywhere in the widget tree. - They can be added only on the top-most `ProviderContainer`. +- It is no longer possible to add `ProviderObserver` anywhere in the widget + tree. They can be added only on the top-most `ProviderContainer`. -- Added `ProviderContainer.refresh(provider)`. - This method allows forcing the refresh of a provider, which can be useful - for things like "retry on error" or "pull to refresh". +- Added `ProviderContainer.refresh(provider)`. This method allows forcing the + refresh of a provider, which can be useful for things like "retry on error" or + "pull to refresh". -* `ref.read(StreamProvider)` no longer returns a `Stream` but an `AsyncValue` - Before: +* `ref.read(StreamProvider)` no longer returns a `Stream` but an + `AsyncValue` Before: ```dart final streamProvider = StreamProvider(...); @@ -1066,7 +1124,8 @@ Migrated to null-safety }); ``` -* `ref.read(FutureProvider)` no longer returns a `Future` but an `AsyncValue` +* `ref.read(FutureProvider)` no longer returns a `Future` but an + `AsyncValue` Before: @@ -1086,8 +1145,8 @@ Migrated to null-safety }); ``` -* Removed `ref.dependOn`. - You can now use `ref.read`/`ref.watch` to achieve the same effect. +* Removed `ref.dependOn`. You can now use `ref.read`/`ref.watch` to achieve the + same effect. Before: @@ -1107,7 +1166,8 @@ Migrated to null-safety }); ``` -* `Provider.readOwner(ProviderStateOwner)` is changed into `ProviderContainer.read(Provider)` +* `Provider.readOwner(ProviderStateOwner)` is changed into + `ProviderContainer.read(Provider)` * `Provider.watchOwner(ProviderStateOwner, (value) {})` is changed into: @@ -1124,12 +1184,11 @@ Migrated to null-safety subscription.close(); ``` -* `MyProvider.family.autoDispose` now correctly free both the arguments and the associated - providers from memory when the provider is no longer listened to. +* `MyProvider.family.autoDispose` now correctly free both the arguments and the + associated providers from memory when the provider is no longer listened to. - Added `ScopedProvider`, a new kind of provider that can be overridden anywhere - in the widget tree. - Normal providers cannot read a `ScopedProvider`. + in the widget tree. Normal providers cannot read a `ScopedProvider`. ## 0.5.1 @@ -1140,7 +1199,9 @@ Migrated to null-safety ## 0.5.0 - Changed `ComputedFamily` into `Computed.family` -- Added [AsyncValue.guard](https://pub.dev/documentation/riverpod/latest/riverpod/AsyncValue/guard.html to simplify transforming a Future into an AsyncValue. +- Added + [AsyncValue.guard](https://pub.dev/documentation/riverpod/latest/riverpod/AsyncValue/guard.html + to simplify transforming a Future into an AsyncValue. - Improved the documentation of the different providers ## 0.4.0 @@ -1168,22 +1229,23 @@ The behavior is the same. Only the syntax changed. ## 0.3.0 -- Added `AsyncValue.whenData`, syntax sugar for `AsyncValue.when` to handle - only the `data` case and do nothing for the error/loading cases. +- Added `AsyncValue.whenData`, syntax sugar for `AsyncValue.when` to handle only + the `data` case and do nothing for the error/loading cases. - Fixed a bug that caused [Computed] to crash if it stopped being listened to then was listened to again. ## 0.2.1 -- `Computed` now correctly unsubscribe to a provider when their - function stops using a provider. +- `Computed` now correctly unsubscribe to a provider when their function stops + using a provider. ## 0.2.0 - `ref.read` is renamed as `ref.dependOn` -- Deprecated `ref.dependOn(streamProvider).stream` and `ref.dependOn(futureProvider).future` - in favor of a universal `ref.dependOn(provider).value`. +- Deprecated `ref.dependOn(streamProvider).stream` and + `ref.dependOn(futureProvider).future` in favor of a universal + `ref.dependOn(provider).value`. - added `ref.read(provider)`, syntax sugar for `ref.dependOn(provider).value`. ## 0.1.0 diff --git a/packages/riverpod/example/lib/models.freezed.dart b/packages/riverpod/example/lib/models.freezed.dart index dcea04b78..64e8bd61f 100644 --- a/packages/riverpod/example/lib/models.freezed.dart +++ b/packages/riverpod/example/lib/models.freezed.dart @@ -12,7 +12,7 @@ part of 'models.dart'; T _$identity(T value) => value; final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); Configuration _$ConfigurationFromJson(Map json) { return _Configuration.fromJson(json); @@ -125,7 +125,7 @@ class _$ConfigurationImpl implements _Configuration { } @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && other is _$ConfigurationImpl && @@ -281,7 +281,7 @@ class _$MarvelResponseImpl implements _MarvelResponse { } @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && other is _$MarvelResponseImpl && @@ -424,7 +424,7 @@ class _$MarvelDataImpl implements _MarvelData { } @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && other is _$MarvelDataImpl && @@ -572,7 +572,7 @@ class _$ComicImpl implements _Comic { } @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && other is _$ComicImpl && diff --git a/packages/riverpod/lib/src/common.dart b/packages/riverpod/lib/src/common.dart index 1138940ae..94966f907 100644 --- a/packages/riverpod/lib/src/common.dart +++ b/packages/riverpod/lib/src/common.dart @@ -157,12 +157,33 @@ sealed class AsyncValue { /// }); /// } /// } + /// + /// An optional callback can be specified to catch errors only under a certain condition. + /// In the following example, we catch all exceptions beside FormatExceptions. + /// + /// ```dart + /// AsyncValue.guard( + /// () async { /* ... */ }, + /// // Catch all errors beside [FormatException]s. + /// (err) => err is! FormatException, + /// ); + /// } /// ``` - static Future> guard(Future Function() future) async { + static Future> guard( + Future Function() future, [ + bool Function(Object)? test, + ]) async { try { return AsyncValue.data(await future()); } catch (err, stack) { - return AsyncValue.error(err, stack); + if (test == null) { + return AsyncValue.error(err, stack); + } + if (test(err)) { + return AsyncValue.error(err, stack); + } + + Error.throwWithStackTrace(err, stack); } } diff --git a/packages/riverpod/lib/src/framework/foundation.dart b/packages/riverpod/lib/src/framework/foundation.dart index cf6190e68..d15697e0f 100644 --- a/packages/riverpod/lib/src/framework/foundation.dart +++ b/packages/riverpod/lib/src/framework/foundation.dart @@ -75,6 +75,11 @@ abstract class ProviderOrFamily implements ProviderListenableOrFamily { /// /// In that scenario, the `dependencies` parameter is required and it must /// include `scopedProvider`. + /// + /// See also: + /// - [provider_dependencies](https://github.com/rrousselGit/riverpod/tree/master/packages/riverpod_lint#provider_dependencies-riverpod_generator-only) + /// and [scoped_providers_should_specify_dependencies](https://github.com/rrousselGit/riverpod/tree/master/packages/riverpod_lint#scoped_providers_should_specify_dependencies-generator-only).\ + /// These are lint rules that will warn about incorrect `dependencies` usages. final Iterable? dependencies; /// All the dependencies of a provider and their dependencies too. diff --git a/packages/riverpod/lib/src/result.dart b/packages/riverpod/lib/src/result.dart index 807117ac1..fba7523f4 100644 --- a/packages/riverpod/lib/src/result.dart +++ b/packages/riverpod/lib/src/result.dart @@ -83,7 +83,7 @@ class ResultData implements Result { } @override - bool operator ==(Object? other) => + bool operator ==(Object other) => other is ResultData && other.runtimeType == runtimeType && other.state == state; @@ -130,7 +130,7 @@ class ResultError implements Result { } @override - bool operator ==(Object? other) => + bool operator ==(Object other) => other is ResultError && other.runtimeType == runtimeType && other.stackTrace == stackTrace && diff --git a/packages/riverpod/pubspec.yaml b/packages/riverpod/pubspec.yaml index e8cad4e8e..ad07fe65e 100644 --- a/packages/riverpod/pubspec.yaml +++ b/packages/riverpod/pubspec.yaml @@ -1,7 +1,7 @@ name: riverpod description: > - A simple way to access state from anywhere in your application while robust - and testable. + A reactive caching and data-binding framework. + Riverpod makes working with asynchronous code a breeze. version: 3.0.0-dev.3 homepage: https://riverpod.dev repository: https://github.com/rrousselGit/riverpod diff --git a/packages/riverpod/test/framework/async_value_test.dart b/packages/riverpod/test/framework/async_value_test.dart index 4710bfb9a..d00ac3d35 100644 --- a/packages/riverpod/test/framework/async_value_test.dart +++ b/packages/riverpod/test/framework/async_value_test.dart @@ -1559,4 +1559,45 @@ void main() { completion(AsyncError(42, stack)), ); }); + + test( + 'AsyncValue.guard emits the error when the created future fails and predicate is null', + () async { + final stack = StackTrace.current; + + await expectLater( + AsyncValue.guard( + () => Future.error(42, stack), + null, + ), + completion(AsyncError(42, stack)), + ); + }); + + test( + 'AsyncValue.guard emits the error when the created future fails and predicate is true', + () async { + final stack = StackTrace.current; + bool isInt(Object error) => error is int; + + await expectLater( + AsyncValue.guard( + () => Future.error(42, stack), + isInt, + ), + completion(AsyncError(42, stack)), + ); + }); + + test('AsyncValue.guard rethrows exception if predicate is false,', () async { + bool isInt(Object error) => error is int; + + await expectLater( + AsyncValue.guard( + () => throw const FormatException(), + isInt, + ), + throwsA(isA()), + ); + }); } diff --git a/packages/riverpod_analyzer_utils/CHANGELOG.md b/packages/riverpod_analyzer_utils/CHANGELOG.md index e1f565942..a127ba0d2 100644 --- a/packages/riverpod_analyzer_utils/CHANGELOG.md +++ b/packages/riverpod_analyzer_utils/CHANGELOG.md @@ -7,11 +7,15 @@ - Added `GeneratorProviderDeclarationElement.isFamily` +## 0.5.1 - 2024-02-04 + +- Bumped `custom_lint` version + ## 0.5.0 - 2023-11-20 - **Breaking** `LegacyProviderDeclarationElement.providerType` is now nullable. - Fix crash when parsing classes with a `ProviderBase` field. - + ## 0.4.3 - 2023-10-28 - Added `GeneratorProviderDeclarationElement.isFamily` diff --git a/packages/riverpod_analyzer_utils/pubspec.yaml b/packages/riverpod_analyzer_utils/pubspec.yaml index 254146a86..680ba9200 100644 --- a/packages/riverpod_analyzer_utils/pubspec.yaml +++ b/packages/riverpod_analyzer_utils/pubspec.yaml @@ -13,7 +13,7 @@ dependencies: analyzer: ">=5.12.0 <7.0.0" collection: ^1.16.0 crypto: ^3.0.2 - custom_lint_core: ^0.5.2 + custom_lint_core: ^0.6.0 freezed_annotation: ^2.2.0 meta: ^1.7.0 path: ^1.8.0 diff --git a/packages/riverpod_analyzer_utils_tests/test/generator_provider_declaration_test.dart b/packages/riverpod_analyzer_utils_tests/test/generator_provider_declaration_test.dart index 40ab0a738..c3513773a 100644 --- a/packages/riverpod_analyzer_utils_tests/test/generator_provider_declaration_test.dart +++ b/packages/riverpod_analyzer_utils_tests/test/generator_provider_declaration_test.dart @@ -214,7 +214,7 @@ int sixth(SixthRef ref) => 0; expect( errors[5].message, - 'Failed to parse dependency Type (int*)', + 'Failed to parse dependency Type (int)', ); expect(errors[5].targetElement?.toString(), 'int sixth(InvalidType ref)'); }); diff --git a/packages/riverpod_annotation/CHANGELOG.md b/packages/riverpod_annotation/CHANGELOG.md index e6c7f2efb..7d1e9f47b 100644 --- a/packages/riverpod_annotation/CHANGELOG.md +++ b/packages/riverpod_annotation/CHANGELOG.md @@ -14,6 +14,10 @@ - `riverpod` upgraded to `3.0.0-dev.0` +## 2.3.4 - 2024-02-03 + +- Improved `@Riverpod(dependencies: [...])` documentation. + ## 2.3.3 - 2023-11-27 - `riverpod` upgraded to `2.4.9` diff --git a/packages/riverpod_annotation/lib/src/riverpod_annotation.dart b/packages/riverpod_annotation/lib/src/riverpod_annotation.dart index 4086d1242..6d94a6938 100644 --- a/packages/riverpod_annotation/lib/src/riverpod_annotation.dart +++ b/packages/riverpod_annotation/lib/src/riverpod_annotation.dart @@ -29,10 +29,75 @@ class Riverpod { /// Defaults to false. final bool keepAlive; - /// The list of dependencies of a provider. + /// The list of providers that this provider potentially depends on. /// - /// Values passed to the list of dependency should be the classes/functions - /// annotated with `@riverpod`; not the provider. + /// This list must contains the classes/functions annotated with `@riverpod`, + /// not the generated providers themselves. + /// + /// Specifying this list is strictly equivalent to saying "This provider may + /// be scoped". If a provider is scoped, it should specify [dependencies]. + /// If it is never scoped, it should not specify [dependencies]. + /// + /// The content of [dependencies] should be a list of all the providers that + /// this provider may depend on which can be scoped. + /// + /// For example, consider the following providers: + /// ```dart + /// // By not specifying "dependencies", we are saying that this provider is never scoped + /// @riverpod + /// Foo root(RootRef ref) => Foo(); + /// // By specifying "dependencies" (even if the list is empty), + /// // we are saying that this provider is potentially scoped + /// @Riverpod(dependencies: []) + /// Foo scoped(ScopedRef ref) => Foo(); + /// ``` + /// + /// Then if we were to depend on `rootProvider` in a scoped provider, we + /// could write any of: + /// + /// ```dart + /// @riverpod + /// Object? dependent(DependentRef ref) { + /// ref.watch(rootProvider); + /// // This provider does not depend on any scoped provider, + /// // as such "dependencies" is optional + /// } + /// + /// @Riverpod(dependencies: []) + /// Object? dependent(DependentRef ref) { + /// ref.watch(rootProvider); + /// // This provider decided to specify "dependencies" anyway, marking + /// // "dependentProvider" as possibly scoped. + /// // Since "rootProvider" is never scoped, it doesn't need to be included + /// // in "dependencies". + /// } + /// + /// @Riverpod(dependencies: [root]) + /// Object? dependent(DependentRef ref) { + /// ref.watch(rootProvider); + /// // Including "rootProvider" in "dependencies" is fine too, even though + /// // it is not required. It is a no-op. + /// } + /// ``` + /// + /// However, if we were to depend on `scopedProvider` then our only choice is: + /// + /// ```dart + /// @Riverpod(dependencies: [scoped]) + /// Object? dependent(DependentRef ref) { + /// ref.watch(scopedProvider); + /// // Since "scopedProvider" specifies "dependencies", any provider that + /// // depends on it must also specify "dependencies" and include "scopedProvider". + /// } + /// ``` + /// + /// In that scenario, the `dependencies` parameter is required and it must + /// include `scopedProvider`. + /// + /// See also: + /// - [provider_dependencies](https://github.com/rrousselGit/riverpod/tree/master/packages/riverpod_lint#provider_dependencies-riverpod_generator-only) + /// and [scoped_providers_should_specify_dependencies](https://github.com/rrousselGit/riverpod/tree/master/packages/riverpod_lint#scoped_providers_should_specify_dependencies-generator-only).\ + /// These are lint rules that will warn about incorrect `dependencies` usages. final List? dependencies; } diff --git a/packages/riverpod_generator/CHANGELOG.md b/packages/riverpod_generator/CHANGELOG.md index 19db2a2da..c4e1e8484 100644 --- a/packages/riverpod_generator/CHANGELOG.md +++ b/packages/riverpod_generator/CHANGELOG.md @@ -81,6 +81,17 @@ This comes with a few minor restrictions: - **Breaking**: Arguments of the form `fn(void myParameter())` are no-longer supported. Instead use `fn(void Function() myParameter)`. +## 2.3.11 - 2024-02-04 + +- `riverpod_analyzer_utils` upgraded to `0.5.1` + +## 2.3.10 - 2024-02-03 + +- Improved error handling if: + - a Notifier has no default constructor + - a Notifier has has a default constructor but with required parameters + - a Notifier is abstract + ## 2.3.9 - 2023-11-27 - `riverpod_annotation` upgraded to `2.3.3` diff --git a/packages/riverpod_generator/lib/src/templates/class_based_provider.dart b/packages/riverpod_generator/lib/src/templates/class_based_provider.dart index 1dda0cf4d..0b788b073 100644 --- a/packages/riverpod_generator/lib/src/templates/class_based_provider.dart +++ b/packages/riverpod_generator/lib/src/templates/class_based_provider.dart @@ -1,6 +1,7 @@ import 'package:riverpod_analyzer_utils/riverpod_analyzer_utils.dart'; import '../models.dart'; import '../riverpod_generator.dart'; +import '../validation.dart'; import 'template.dart'; String providerNameFor( @@ -81,7 +82,10 @@ class ClassBasedProviderTemplate extends Template { 'Expected a non-family provider', ); } + + validateClassBasedProvider(provider); } + final ClassBasedProviderDeclaration provider; final String notifierTypedefName; final String hashFn; diff --git a/packages/riverpod_generator/lib/src/templates/family.dart b/packages/riverpod_generator/lib/src/templates/family.dart index 34f97ddae..7d99d5d24 100644 --- a/packages/riverpod_generator/lib/src/templates/family.dart +++ b/packages/riverpod_generator/lib/src/templates/family.dart @@ -4,6 +4,7 @@ import 'package:riverpod_analyzer_utils/riverpod_analyzer_utils.dart'; import '../models.dart'; import '../riverpod_generator.dart'; +import '../validation.dart'; import 'class_based_provider.dart'; import 'parameters.dart'; import 'template.dart'; @@ -151,6 +152,8 @@ ${parameters.map((e) => ' ${e.name}: ${e.name},\n').join()} required String hashFn, required BuildYamlOptions options, }) { + validateClassBasedProvider(provider); + var leading = ''; if (!provider.annotation.element.keepAlive) { leading = 'AutoDispose'; diff --git a/packages/riverpod_generator/lib/src/validation.dart b/packages/riverpod_generator/lib/src/validation.dart new file mode 100644 index 000000000..a3c4287dc --- /dev/null +++ b/packages/riverpod_generator/lib/src/validation.dart @@ -0,0 +1,31 @@ +import 'package:collection/collection.dart'; +import 'package:riverpod_analyzer_utils/riverpod_analyzer_utils.dart'; +import 'package:source_gen/source_gen.dart'; + +void validateClassBasedProvider(ClassBasedProviderDeclaration provider) { + // Assert that the class is not abstract + if (provider.node.abstractKeyword != null) { + throw InvalidGenerationSourceError( + '`@riverpod` can only be used on concrete classes.', + element: provider.node.declaredElement, + ); + } + + // Assert that the provider has a default constructor + final constructor = provider.node.declaredElement!.constructors + .firstWhereOrNull((e) => e.isDefaultConstructor); + if (constructor == null) { + throw InvalidGenerationSourceError( + 'The class ${provider.node.name} must have a default constructor.', + element: provider.node.declaredElement, + ); + } + + // Assert that the default constructor can be called with no parameter + if (constructor.parameters.any((e) => e.isRequired)) { + throw InvalidGenerationSourceError( + 'The default constructor of ${provider.node.name} must have not have required parameters.', + element: constructor, + ); + } +} diff --git a/packages/riverpod_generator/test/error_test.dart b/packages/riverpod_generator/test/error_test.dart new file mode 100644 index 000000000..f1df3cfbe --- /dev/null +++ b/packages/riverpod_generator/test/error_test.dart @@ -0,0 +1,147 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:analyzer/dart/analysis/results.dart'; +import 'package:analyzer/dart/analysis/utilities.dart'; +import 'package:path/path.dart' as path; +import 'package:riverpod_generator/src/riverpod_generator.dart'; +import 'package:source_gen/source_gen.dart'; +import 'package:test/test.dart'; + +void main() { + group('Notifiers', () { + group('with arguments', () { + test('should throw if the class is abstract', () async { + const source = r''' +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +@riverpod +abstract class MyNotifier extends _$MyNotifier { + @override + int build(int a) => 0; +} +'''; + + await expectLater( + () => compile(source), + throwsA(isA()), + ); + }); + + test('should throw if there is no default constructor', () async { + const source = r''' +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +@riverpod +class MyNotifier extends _$MyNotifier { + MyNotifier._(); + + @override + int build(int a) => 0; +} +'''; + + await expectLater( + () => compile(source), + throwsA(isA()), + ); + }); + + test('should throw if the default constructor has required parameters', + () async { + const source = r''' +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +@riverpod +class MyNotifier extends _$MyNotifier { + MyNotifier(int a); + + @override + int build(int a) => 0; +} +'''; + + await expectLater( + () => compile(source), + throwsA(isA()), + ); + }); + }); + + group('without arguments', () { + test('should throw if the class is abstract', () async { + const source = r''' +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +@riverpod +abstract class MyNotifier extends _$MyNotifier { + @override + int build() => 0; +} +'''; + + await expectLater( + () => compile(source), + throwsA(isA()), + ); + }); + }); + }); +} + +void tearDownTmp() { + addTearDown(() { + final tmp = Directory('.dart_tool/test'); + if (tmp.existsSync()) { + tmp.deleteSync(recursive: true); + } + }); +} + +File createTmpFile(String filePath) { + tearDownTmp(); + + final file = File(path.join('.dart_tool', 'test', filePath)); + file.createSync(recursive: true); + + return file; +} + +Future compile(String source) async { + final generator = RiverpodGenerator(const {}); + + final main = createTmpFile('lib/main.dart')..writeAsStringSync(source); + final pubspec = createTmpFile('pubspec.yaml')..writeAsStringSync(''' +name: test_app + +environment: + sdk: ">=3.0.0 <4.0.0" + +dependencies: + riverpod_annotation: ^2.3.3 +'''); + + await runPubGet(pubspec.parent); + + final result = await resolveFile2(path: main.absolute.path); + + result as ResolvedUnitResult; + + return generator.generateForUnit([result.unit]); +} + +Future runPubGet(Directory parent) async { + final process = await Process.start( + 'dart', + ['pub', 'get'], + workingDirectory: parent.path, + ); + + final exitCode = await process.exitCode; + if (exitCode != 0) { + throw Exception( + 'flutter pub get failed with exit code $exitCode\n' + '${await process.stdout.transform(utf8.decoder).join()}', + ); + } +} diff --git a/packages/riverpod_lint/CHANGELOG.md b/packages/riverpod_lint/CHANGELOG.md index 30a352a82..973b13e5d 100644 --- a/packages/riverpod_lint/CHANGELOG.md +++ b/packages/riverpod_lint/CHANGELOG.md @@ -18,6 +18,17 @@ - `riverpod` upgraded to `3.0.0-dev.0` +## 2.3.9 - 2024-02-04 + +- Bumped `custom_lint` version + +## 2.3.8 - 2024-02-03 + +- Fix `async_value_nullable_pattern` false positive when used with generics + that have non-nullable type constrains. +- Add migration widget field when convert Stateless-based and + Stateful-based to each other (thanks to @Kurogoma4D) + ## 2.3.7 - 2023-11-27 - `riverpod` upgraded to `2.4.9` diff --git a/packages/riverpod_lint/lib/src/assists/convert_to_stateful_base_widget.dart b/packages/riverpod_lint/lib/src/assists/convert_to_stateful_base_widget.dart index 784349bbd..4d6149a24 100644 --- a/packages/riverpod_lint/lib/src/assists/convert_to_stateful_base_widget.dart +++ b/packages/riverpod_lint/lib/src/assists/convert_to_stateful_base_widget.dart @@ -1,7 +1,11 @@ import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/dart/ast/token.dart'; +import 'package:analyzer/dart/ast/visitor.dart'; +import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/source/source_range.dart'; // ignore: implementation_imports, somehow not exported by analyzer import 'package:analyzer/src/generated/source.dart' show Source; +import 'package:analyzer_plugin/utilities/change_builder/change_builder_dart.dart'; import 'package:collection/collection.dart'; import 'package:custom_lint_builder/custom_lint_builder.dart'; @@ -83,6 +87,51 @@ class ConvertToStatefulBaseWidget extends RiverpodAssist { final widgetClass = node.thisOrAncestorOfType(); if (widgetClass == null) return; + final nodesToMove = {}; + final elementsToMove = {}; + final visitor = _FieldFinder(); + for (final member in widgetClass.members) { + if (member is ConstructorDeclaration) { + member.accept(visitor); + } + } + final fieldsAssignedInConstructors = visitor.fieldsAssignedInConstructors; + + for (final member in widgetClass.members) { + if (member is FieldDeclaration && !member.isStatic) { + for (final fieldNode in member.fields.variables) { + final fieldElement = fieldNode.declaredElement as FieldElement?; + if (fieldElement == null) continue; + if (!fieldsAssignedInConstructors.contains(fieldElement)) { + nodesToMove.add(member); + elementsToMove.add(fieldElement); + + final getter = fieldElement.getter; + if (getter != null) { + elementsToMove.add(getter); + } + + final setter = fieldElement.setter; + if (setter != null) { + elementsToMove.add(setter); + } + } + } + } else if (member is MethodDeclaration && !member.isStatic) { + nodesToMove.add(member); + elementsToMove.add(member.declaredElement!); + } + } + + for (final node in nodesToMove) { + final visitor = _ReplacementEditBuilder( + widgetClass.declaredElement!, + elementsToMove, + builder, + ); + node.accept(visitor); + } + final buildMethod = node .thisOrAncestorOfType() ?.members @@ -182,3 +231,78 @@ class $createdStateClassName extends $baseStateName<${widgetClass.name}> { }); } } + +// Original implemenation in +// package:analysis_server/lib/src/services/correction/dart/flutter_convert_to_stateful_widget.dart +class _FieldFinder extends RecursiveAstVisitor { + Set fieldsAssignedInConstructors = {}; + + @override + void visitFieldFormalParameter(FieldFormalParameter node) { + final element = node.declaredElement; + if (element is FieldFormalParameterElement) { + final field = element.field; + if (field != null) { + fieldsAssignedInConstructors.add(field); + } + } + + super.visitFieldFormalParameter(node); + } + + @override + void visitSimpleIdentifier(SimpleIdentifier node) { + if (node.parent is ConstructorFieldInitializer) { + final element = node.staticElement; + if (element is FieldElement) { + fieldsAssignedInConstructors.add(element); + } + } + if (node.inSetterContext()) { + final element = node.writeOrReadElement; + if (element is PropertyAccessorElement) { + final field = element.variable; + if (field is FieldElement) { + fieldsAssignedInConstructors.add(field); + } + } + } + } +} + +class _ReplacementEditBuilder extends RecursiveAstVisitor { + _ReplacementEditBuilder( + this.widgetClassElement, + this.elementsToMove, + this.builder, + ); + + final ClassElement widgetClassElement; + final Set elementsToMove; + final DartFileEditBuilder builder; + + @override + void visitSimpleIdentifier(SimpleIdentifier node) { + if (node.inDeclarationContext()) { + return; + } + final element = node.staticElement; + if (element is ExecutableElement && + element.enclosingElement == widgetClassElement && + !elementsToMove.contains(element)) { + final offset = node.offset; + final qualifier = + element.isStatic ? widgetClassElement.displayName : 'widget'; + + final parent = node.parent; + if (parent is InterpolationExpression && + parent.leftBracket.type == + TokenType.STRING_INTERPOLATION_IDENTIFIER) { + builder.addSimpleInsertion(offset, '{$qualifier.'); + builder.addSimpleInsertion(offset + node.length, '}'); + } else { + builder.addSimpleInsertion(offset, '$qualifier.'); + } + } + } +} diff --git a/packages/riverpod_lint/lib/src/assists/convert_to_stateless_base_widget.dart b/packages/riverpod_lint/lib/src/assists/convert_to_stateless_base_widget.dart index 24555fa7f..859f168ef 100644 --- a/packages/riverpod_lint/lib/src/assists/convert_to_stateless_base_widget.dart +++ b/packages/riverpod_lint/lib/src/assists/convert_to_stateless_base_widget.dart @@ -1,4 +1,7 @@ import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/dart/ast/token.dart'; +import 'package:analyzer/dart/ast/visitor.dart'; +import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/source/source_range.dart'; // ignore: implementation_imports, somehow not exported by analyzer import 'package:analyzer/src/generated/source.dart' show Source; @@ -145,87 +148,250 @@ class ConvertToStatelessBaseWidget extends RiverpodAssist { .whereType() .firstWhereOrNull((element) => element.name.lexeme == 'createState'); if (createStateMethod != null) { - builder.addDeletion(createStateMethod.sourceRange); + builder.addDeletion(createStateMethod.sourceRange.getExpanded(1)); } // Search for the associated State class final stateClass = findStateClass(widgetClass); if (stateClass == null) return; + final fieldFinder = _FieldFinder(); + + for (final member in stateClass.members) { + if (member is ConstructorDeclaration) { + member.accept(fieldFinder); + } + } + + final fieldsAssignedInConstructors = + fieldFinder.fieldsAssignedInConstructors; + + // Prepare nodes to move. + final nodesToMove = []; + final elementsToMove = {}; + for (final member in stateClass.members) { + if (member is FieldDeclaration) { + if (member.isStatic) { + return; + } + for (final fieldNode in member.fields.variables) { + final fieldElement = fieldNode.declaredElement as FieldElement?; + if (fieldElement == null) continue; + if (!fieldsAssignedInConstructors.contains(fieldElement)) { + nodesToMove.add(member); + elementsToMove.add(fieldElement); + + final getter = fieldElement.getter; + if (getter != null) { + elementsToMove.add(getter); + } + + final setter = fieldElement.setter; + if (setter != null) { + elementsToMove.add(setter); + } + } + } + } else if (member is MethodDeclaration) { + if (member.isStatic) { + return; + } + if (!_isDefaultOverride(member)) { + nodesToMove.add(member); + elementsToMove.add(member.declaredElement!); + } + } + } + + final deleteRanges = []; + for (final node in nodesToMove) { + final visitor = _ReplacementEditBuilder( + widgetClass.declaredElement!, + elementsToMove, + ); + node.accept(visitor); + deleteRanges.addAll(visitor.deleteRanges); + } + // Move the build method to the widget class final buildMethod = stateClass.members .whereType() .firstWhereOrNull((element) => element.name.lexeme == 'build'); if (buildMethod == null) return; - final String? newBuildMethod; + final outsideRange = SourceRange( + widgetClass.sourceRange.end, + stateClass.sourceRange.offset - widgetClass.sourceRange.end, + ); + final outsideLines = source.contents.data.substring( + outsideRange.offset, + outsideRange.end, + ); + if (outsideLines.trim().isNotEmpty) { + builder.addSimpleInsertion( + stateClass.sourceRange.end, + '${outsideLines.trimRight()}\n', + ); + } + + // ignore: prefer_foreach + for (final range in deleteRanges) { + builder.addDeletion(range); + } + + builder.addDeletion( + SourceRange( + widgetClass.rightBracket.offset, + stateClass.leftBracket.end - widgetClass.rightBracket.offset, + ), + ); + + final parameterRange = _generateBuildMethodParameterRange(buildMethod); + if (parameterRange == SourceRange.EMPTY) { + return; + } switch (targetWidget) { case StatelessBaseWidgetType.consumerWidget: case StatelessBaseWidgetType.hookConsumerWidget: - newBuildMethod = _buildMethodWithRef(buildMethod, source); + builder.addSimpleReplacement( + parameterRange, + 'BuildContext context, WidgetRef ref', + ); case StatelessBaseWidgetType.hookWidget: case StatelessBaseWidgetType.statelessWidget: - newBuildMethod = _buildMethodWithoutRef(buildMethod, source); + builder.addSimpleReplacement( + parameterRange, + 'BuildContext context', + ); } - - if (newBuildMethod == null) return; - builder.addSimpleInsertion( - widgetClass.rightBracket.offset, - newBuildMethod, - ); - - // Delete the state class - builder.addDeletion(stateClass.sourceRange); }); } - String? _buildMethodWithRef(MethodDeclaration buildMethod, Source source) { - final parameters = buildMethod.parameters; - if (parameters == null) return null; + SourceRange _generateBuildMethodParameterRange( + MethodDeclaration buildMethod, + ) { + final offset = buildMethod.parameters?.leftParenthesis.end ?? 0; + final end = buildMethod.parameters?.rightParenthesis.offset ?? 0; + return SourceRange(offset, end - offset); + } +} + +// Original implemenation in +// package:analysis_server/lib/src/services/correction/dart/flutter_convert_to_stateless_widget.dart +class _FieldFinder extends RecursiveAstVisitor { + final fieldsAssignedInConstructors = {}; - if (parameters.parameters.length == 2) { - // The build method already has a ref parameter, nothing to change - return '${source.contents.data.substring(buildMethod.offset, buildMethod.end)}\n'; + @override + void visitSimpleIdentifier(SimpleIdentifier node) { + if (node.parent is FieldFormalParameter) { + final element = node.staticElement; + if (element is FieldFormalParameterElement) { + final field = element.field; + if (field != null) { + fieldsAssignedInConstructors.add(field); + } + } } - final buffer = StringBuffer(); - final refParamStartOffset = parameters.parameters.firstOrNull?.end ?? - parameters.leftParenthesis.offset + 1; - - buffer - ..write( - source.contents.data.substring(buildMethod.offset, refParamStartOffset), - ) - ..write(', WidgetRef ref') - ..writeln( - source.contents.data.substring(refParamStartOffset, buildMethod.end), - ); + if (node.parent is ConstructorFieldInitializer) { + final element = node.staticElement; + if (element is FieldElement) { + fieldsAssignedInConstructors.add(element); + } + } - return buffer.toString(); + if (node.inSetterContext()) { + final element = node.writeOrReadElement; + if (element is PropertyAccessorElement) { + final field = element.variable; + if (field is FieldElement) { + fieldsAssignedInConstructors.add(field); + } + } + } } +} + +class _ReplacementEditBuilder extends RecursiveAstVisitor { + _ReplacementEditBuilder( + this.widgetClassElement, + this.elementsToMove, + ); - String? _buildMethodWithoutRef(MethodDeclaration buildMethod, Source source) { - final parameters = buildMethod.parameters; - if (parameters == null) return null; + final ClassElement widgetClassElement; + final Set elementsToMove; - if (parameters.parameters.length == 1) { - // The build method already has not a ref parameter, nothing to change - return '${source.contents.data.substring(buildMethod.offset, buildMethod.end)}\n'; - } + List deleteRanges = []; - final buffer = StringBuffer(); - final contextEndOffset = parameters.parameters.firstOrNull?.end ?? - parameters.leftParenthesis.offset + 1; - final refParamStartOffset = parameters.parameters.last.offset; - - buffer - ..write( - source.contents.data.substring(buildMethod.offset, contextEndOffset), - ) - ..writeln( - source.contents.data.substring(refParamStartOffset, buildMethod.end), - ); + @override + void visitSimpleIdentifier(SimpleIdentifier node) { + if (node.inDeclarationContext()) { + return; + } + final element = node.staticElement; + if (element is ExecutableElement && + element.enclosingElement == widgetClassElement && + !elementsToMove.contains(element)) { + final parent = node.parent; + if (parent is PrefixedIdentifier) { + final grandParent = parent.parent; + + if (!node.name.contains(r'$') && + grandParent is InterpolationExpression && + grandParent.leftBracket.type == + TokenType.STRING_INTERPOLATION_EXPRESSION) { + final offset = grandParent.rightBracket?.offset; + + if (offset != null) { + deleteRanges.add(SourceRange(offset, 1)); + } + deleteRanges.add(SourceRange(grandParent.leftBracket.end - 1, 1)); + } + final offset = parent.prefix.offset; + final length = parent.period.end - offset; + deleteRanges.add(SourceRange(offset, length)); + } else if (parent is MethodInvocation) { + final target = parent.target; + final operator = parent.operator; + if (target != null && operator != null) { + final offset = target.offset; + final length = operator.end - offset; + deleteRanges.add(SourceRange(offset, length)); + } + } else if (parent is PropertyAccess) { + final target = parent.target; + final operator = parent.operator; + if (target != null) { + final offset = target.offset; + final length = operator.end - offset; + deleteRanges.add(SourceRange(offset, length)); + } + } + } + } +} - return buffer.toString(); +bool _isDefaultOverride(MethodDeclaration? methodDeclaration) { + final body = methodDeclaration?.body; + if (body != null) { + Expression expression; + if (body is BlockFunctionBody) { + final statements = body.block.statements; + if (statements.isEmpty) return true; + if (statements.length > 1) return false; + final first = statements.first; + if (first is! ExpressionStatement) return false; + expression = first.expression; + } else if (body is ExpressionFunctionBody) { + expression = body.expression; + } else { + return false; + } + if (expression is MethodInvocation && + expression.target is SuperExpression && + methodDeclaration!.name.lexeme == expression.methodName.name) { + return true; + } } + return false; } diff --git a/packages/riverpod_lint/lib/src/assists/convert_to_widget_utils.dart b/packages/riverpod_lint/lib/src/assists/convert_to_widget_utils.dart index ed5c43299..9f0093eb5 100644 --- a/packages/riverpod_lint/lib/src/assists/convert_to_widget_utils.dart +++ b/packages/riverpod_lint/lib/src/assists/convert_to_widget_utils.dart @@ -1,4 +1,5 @@ import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/dart/element/element.dart'; // ignore: implementation_imports, somehow not exported by analyzer import 'package:collection/collection.dart'; import 'package:custom_lint_builder/custom_lint_builder.dart'; @@ -149,3 +150,32 @@ ClassDeclaration? findStateClass(ClassDeclaration widgetClass) { return checker.isExactlyType(stateWidgetType); }); } + +// Original implemenation in package:analyzer/lib/src/dart/ast/extensions.dart +extension IdentifierExtension on Identifier { + Element? get writeOrReadElement { + return _writeElement(this) ?? staticElement; + } +} + +Element? _writeElement(AstNode node) { + final parent = node.parent; + + if (parent is AssignmentExpression && parent.leftHandSide == node) { + return parent.writeElement; + } + if (parent is PostfixExpression && parent.operand == node) { + return parent.writeElement; + } + if (parent is PrefixExpression && parent.operand == node) { + return parent.writeElement; + } + + if (parent is PrefixedIdentifier && parent.identifier == node) { + return _writeElement(parent); + } + if (parent is PropertyAccess && parent.propertyName == node) { + return _writeElement(parent); + } + return null; +} diff --git a/packages/riverpod_lint/lib/src/lints/async_value_nullable_pattern.dart b/packages/riverpod_lint/lib/src/lints/async_value_nullable_pattern.dart index f3a64ca53..1d1dfbc51 100644 --- a/packages/riverpod_lint/lib/src/lints/async_value_nullable_pattern.dart +++ b/packages/riverpod_lint/lib/src/lints/async_value_nullable_pattern.dart @@ -53,7 +53,15 @@ class AsyncValueNullablePattern extends RiverpodLintRule { } grandParentType as InterfaceType; - final genericType = grandParentType.typeArguments.first; + var genericType = grandParentType.typeArguments.first; + + // If the AsyncValue's type is a generic type, we check the generic's constraint + if (genericType is TypeParameterType) { + final unit = node.thisOrAncestorOfType()!; + + genericType = genericType.element.bound ?? + unit.declaredElement!.library.typeProvider.dynamicType; + } if (genericType is! DynamicType && genericType.nullabilitySuffix != NullabilitySuffix.question) { diff --git a/packages/riverpod_lint/pubspec.yaml b/packages/riverpod_lint/pubspec.yaml index 97d81cdfd..dfc3e1390 100644 --- a/packages/riverpod_lint/pubspec.yaml +++ b/packages/riverpod_lint/pubspec.yaml @@ -14,7 +14,7 @@ dependencies: analyzer: ">=6.0.0 <7.0.0" analyzer_plugin: ^0.11.2 collection: ^1.16.0 - custom_lint_builder: ^0.5.2 + custom_lint_builder: ^0.6.0 meta: ^1.7.0 path: ^1.8.1 riverpod: ^3.0.0-dev.3 diff --git a/packages/riverpod_lint_flutter_test/pubspec.yaml b/packages/riverpod_lint_flutter_test/pubspec.yaml index ee7b99ace..e3d37c6fb 100644 --- a/packages/riverpod_lint_flutter_test/pubspec.yaml +++ b/packages/riverpod_lint_flutter_test/pubspec.yaml @@ -15,7 +15,8 @@ dependencies: dev_dependencies: build_runner: ^2.4.6 - custom_lint: ^0.5.2 + custom_lint: ^0.6.0 + custom_lint_core: ^0.6.0 freezed: ^2.3.2 json_serializable: ^6.6.1 riverpod_lint: diff --git a/packages/riverpod_lint_flutter_test/pubspec_overrides.yaml b/packages/riverpod_lint_flutter_test/pubspec_overrides.yaml index aef4942a3..4f0b94b08 100644 --- a/packages/riverpod_lint_flutter_test/pubspec_overrides.yaml +++ b/packages/riverpod_lint_flutter_test/pubspec_overrides.yaml @@ -14,4 +14,6 @@ dependency_overrides: path: ../riverpod_annotation riverpod_lint: path: ../riverpod_lint - test_api: ^0.6.1 + + # hotreloader is out of date + vm_service: ^14.0.0 diff --git a/packages/riverpod_lint_flutter_test/test/assists/convert_class_based_provider_to_functional/convert_class_based_provider_to_functional.diff b/packages/riverpod_lint_flutter_test/test/assists/convert_class_based_provider_to_functional/convert_class_based_provider_to_functional.diff new file mode 100644 index 000000000..18766d6a1 --- /dev/null +++ b/packages/riverpod_lint_flutter_test/test/assists/convert_class_based_provider_to_functional/convert_class_based_provider_to_functional.diff @@ -0,0 +1,63 @@ +Message: `Convert to functional provider` +Priority: 100 +Diff for file `test/assists/convert_class_based_provider_to_functional/convert_class_based_provider_to_functional.dart:7`: +``` +/// Some comment +@riverpod +- class Example extends _$Example { +- @override +- int build() => 0; +- } ++ int example(ExampleRef ref) => 0; + +/// Some comment +``` +--- +Message: `Convert to functional provider` +Priority: 100 +Diff for file `test/assists/convert_class_based_provider_to_functional/convert_class_based_provider_to_functional.dart:7`: +``` +/// Some comment +@riverpod +- class Example extends _$Example { +- @override +- int build() => 0; +- } ++ int example(ExampleRef ref) => 0; + +/// Some comment +``` +--- +Message: `Convert to functional provider` +Priority: 100 +Diff for file `test/assists/convert_class_based_provider_to_functional/convert_class_based_provider_to_functional.dart:7`: +``` +/// Some comment +@riverpod +- class Example extends _$Example { +- @override +- int build() => 0; +- } ++ int example(ExampleRef ref) => 0; + +/// Some comment +``` +--- +Message: `Convert to functional provider` +Priority: 100 +Diff for file `test/assists/convert_class_based_provider_to_functional/convert_class_based_provider_to_functional.dart:14`: +``` +/// Some comment +@riverpod +- class ExampleFamily extends _$ExampleFamily { +- @override +- int build({required int a, String b = '42'}) { +- // Hello world +- return 0; +- } ++ int exampleFamily(ExampleFamilyRef ref, {required int a, String b = '42'}) { ++ // Hello world ++ return 0; ++ } +``` +--- diff --git a/packages/riverpod_lint_flutter_test/test/assists/convert_class_based_provider_to_functional/convert_class_based_provider_to_functional.json b/packages/riverpod_lint_flutter_test/test/assists/convert_class_based_provider_to_functional/convert_class_based_provider_to_functional.json deleted file mode 100644 index 437d0dda2..000000000 --- a/packages/riverpod_lint_flutter_test/test/assists/convert_class_based_provider_to_functional/convert_class_based_provider_to_functional.json +++ /dev/null @@ -1 +0,0 @@ -[{"priority":100,"change":{"message":"Convert to functional provider","edits":[{"fileStamp":0,"edits":[{"offset":215,"length":2,"replacement":""},{"offset":208,"length":0,"replacement":"ExampleRef ref"},{"offset":202,"length":5,"replacement":"example"},{"offset":150,"length":48,"replacement":""}]}],"linkedEditGroups":[]}},{"priority":100,"change":{"message":"Convert to functional provider","edits":[{"fileStamp":0,"edits":[{"offset":215,"length":2,"replacement":""},{"offset":208,"length":0,"replacement":"ExampleRef ref"},{"offset":202,"length":5,"replacement":"example"},{"offset":150,"length":48,"replacement":""}]}],"linkedEditGroups":[]}},{"priority":100,"change":{"message":"Convert to functional provider","edits":[{"fileStamp":0,"edits":[{"offset":215,"length":2,"replacement":""},{"offset":208,"length":0,"replacement":"ExampleRef ref"},{"offset":202,"length":5,"replacement":"example"},{"offset":150,"length":48,"replacement":""}]}],"linkedEditGroups":[]}},{"priority":100,"change":{"message":"Convert to functional provider","edits":[{"fileStamp":0,"edits":[{"offset":389,"length":2,"replacement":""},{"offset":316,"length":0,"replacement":"ExampleFamilyRef ref, "},{"offset":310,"length":5,"replacement":"exampleFamily"},{"offset":246,"length":60,"replacement":""}]}],"linkedEditGroups":[]}},{"priority":100,"change":{"message":"Convert to functional provider","edits":[{"fileStamp":0,"edits":[{"offset":494,"length":2,"replacement":""},{"offset":487,"length":0,"replacement":"GenericRef ref"},{"offset":486,"length":0,"replacement":""},{"offset":481,"length":5,"replacement":"generic"},{"offset":403,"length":74,"replacement":""}]}],"linkedEditGroups":[]}}] \ No newline at end of file diff --git a/packages/riverpod_lint_flutter_test/test/assists/convert_class_based_provider_to_functional/convert_class_based_provider_to_functional_test.dart b/packages/riverpod_lint_flutter_test/test/assists/convert_class_based_provider_to_functional/convert_class_based_provider_to_functional_test.dart index 707b65113..97154ba21 100644 --- a/packages/riverpod_lint_flutter_test/test/assists/convert_class_based_provider_to_functional/convert_class_based_provider_to_functional_test.dart +++ b/packages/riverpod_lint_flutter_test/test/assists/convert_class_based_provider_to_functional/convert_class_based_provider_to_functional_test.dart @@ -1,24 +1,16 @@ -import 'dart:io'; - import 'package:riverpod_lint/src/assists/class_based_to_functional_provider.dart'; import 'package:analyzer/source/source_range.dart'; -import 'package:analyzer/dart/analysis/results.dart'; -import 'package:analyzer/dart/analysis/utilities.dart'; import '../../golden.dart'; void main() { testGolden( 'Convert plain class provider to functional provider', - 'assists/convert_class_based_provider_to_functional/convert_class_based_provider_to_functional.json', - () async { - final assist = ClassBasedToFunctionalProvider(); - final file = File( + 'assists/convert_class_based_provider_to_functional/convert_class_based_provider_to_functional.diff', + sourcePath: 'test/assists/convert_class_based_provider_to_functional/convert_class_based_provider_to_functional.dart', - ).absolute; - - final result = await resolveFile2(path: file.path); - result as ResolvedUnitResult; + (result) async { + final assist = ClassBasedToFunctionalProvider(); return [ ...await assist.testRun(result, const SourceRange(145, 0)), diff --git a/packages/riverpod_lint_flutter_test/test/assists/convert_functional_provider_to_class_based/convert_functional_provider_to_class_based.diff b/packages/riverpod_lint_flutter_test/test/assists/convert_functional_provider_to_class_based/convert_functional_provider_to_class_based.diff new file mode 100644 index 000000000..47aeb2dbf --- /dev/null +++ b/packages/riverpod_lint_flutter_test/test/assists/convert_functional_provider_to_class_based/convert_functional_provider_to_class_based.diff @@ -0,0 +1,33 @@ +Message: `Convert to class-based provider` +Priority: 100 +Diff for file `test/assists/convert_functional_provider_to_class_based/convert_functional_provider_to_class_based.dart:7`: +``` +/// Some comment +@riverpod +- int example(ExampleRef ref) => 0; ++ class Example extends _$Example { ++ @override ++ int build() => 0; ++ } + +/// Some comment +``` +--- +Message: `Convert to class-based provider` +Priority: 100 +Diff for file `test/assists/convert_functional_provider_to_class_based/convert_functional_provider_to_class_based.dart:11`: +``` +/// Some comment +@riverpod +- int exampleFamily(ExampleFamilyRef ref, {required int a, String b = '42'}) { +- // Hello world +- return 0; ++ class ExampleFamily extends _$ExampleFamily { ++ @override ++ int build({required int a, String b = '42'}) { ++ // Hello world ++ return 0; ++ } +} +``` +--- diff --git a/packages/riverpod_lint_flutter_test/test/assists/convert_functional_provider_to_class_based/convert_functional_provider_to_class_based.json b/packages/riverpod_lint_flutter_test/test/assists/convert_functional_provider_to_class_based/convert_functional_provider_to_class_based.json deleted file mode 100644 index 1d84c5034..000000000 --- a/packages/riverpod_lint_flutter_test/test/assists/convert_functional_provider_to_class_based/convert_functional_provider_to_class_based.json +++ /dev/null @@ -1 +0,0 @@ -[{"priority":100,"change":{"message":"Convert to class-based provider","edits":[{"fileStamp":0,"edits":[{"offset":183,"length":0,"replacement":"\n}"},{"offset":162,"length":14,"replacement":""},{"offset":154,"length":7,"replacement":"build"},{"offset":150,"length":0,"replacement":"class Example extends _$Example {\n @override\n "}]}],"linkedEditGroups":[]}},{"priority":100,"change":{"message":"Convert to class-based provider","edits":[{"fileStamp":0,"edits":[{"offset":319,"length":0,"replacement":"\n}"},{"offset":230,"length":22,"replacement":""},{"offset":216,"length":13,"replacement":"build"},{"offset":212,"length":0,"replacement":"class ExampleFamily extends _$ExampleFamily {\n @override\n "}]}],"linkedEditGroups":[]}}] \ No newline at end of file diff --git a/packages/riverpod_lint_flutter_test/test/assists/convert_functional_provider_to_class_based/convert_functional_provider_to_class_based_test.dart b/packages/riverpod_lint_flutter_test/test/assists/convert_functional_provider_to_class_based/convert_functional_provider_to_class_based_test.dart index ad29e9122..6f15d46df 100644 --- a/packages/riverpod_lint_flutter_test/test/assists/convert_functional_provider_to_class_based/convert_functional_provider_to_class_based_test.dart +++ b/packages/riverpod_lint_flutter_test/test/assists/convert_functional_provider_to_class_based/convert_functional_provider_to_class_based_test.dart @@ -1,24 +1,16 @@ -import 'dart:io'; - import 'package:riverpod_lint/src/assists/functional_to_class_based_provider.dart'; import 'package:analyzer/source/source_range.dart'; -import 'package:analyzer/dart/analysis/results.dart'; -import 'package:analyzer/dart/analysis/utilities.dart'; import '../../golden.dart'; void main() { testGolden( 'Convert functional providers to class-based providers', - 'assists/convert_functional_provider_to_class_based/convert_functional_provider_to_class_based.json', - () async { - final assist = FunctionalToClassBasedProvider(); - final file = File( + 'assists/convert_functional_provider_to_class_based/convert_functional_provider_to_class_based.diff', + sourcePath: 'test/assists/convert_functional_provider_to_class_based/convert_functional_provider_to_class_based.dart', - ).absolute; - - final result = await resolveFile2(path: file.path); - result as ResolvedUnitResult; + (result) async { + final assist = FunctionalToClassBasedProvider(); return [ ...await assist.testRun(result, const SourceRange(145, 0)), diff --git a/packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_consumer_stateful_widget.diff b/packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_consumer_stateful_widget.diff new file mode 100644 index 000000000..08a54981b --- /dev/null +++ b/packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_consumer_stateful_widget.diff @@ -0,0 +1,308 @@ +Message: `Convert to ConsumerStatefulWidget` +Priority: 31 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:5`: +``` +import 'package:flutter_hooks/flutter_hooks.dart'; + +- class Stateless extends StatelessWidget { +- const Stateless({super.key}); +- +- @override ++ class Stateless extends ConsumerStatefulWidget { ++ const Stateless({super.key}); ++ ++ @override ++ ConsumerState createState() => _StatelessState(); ++ } ++ ++ class _StatelessState extends ConsumerState { +@override + Widget build(BuildContext context) { +``` +--- +Message: `Convert to ConsumerStatefulWidget` +Priority: 31 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:14`: +``` +} + +- class StatelessWithComma extends StatelessWidget { +- const StatelessWithComma({super.key}); +- +- @override ++ class StatelessWithComma extends ConsumerStatefulWidget { ++ const StatelessWithComma({super.key}); ++ ++ @override ++ ConsumerState createState() => _StatelessWithCommaState(); ++ } ++ ++ class _StatelessWithCommaState extends ConsumerState { +@override + Widget build( +``` +--- +Message: `Convert to ConsumerStatefulWidget` +Priority: 31 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:25`: +``` +} + +- class Hook extends HookWidget { +- const Hook({super.key}); +- +- @override ++ class Hook extends ConsumerStatefulWidget { ++ const Hook({super.key}); ++ ++ @override ++ ConsumerState createState() => _HookState(); ++ } ++ ++ class _HookState extends ConsumerState { +@override + Widget build(BuildContext context) { +``` +--- +Message: `Convert to ConsumerStatefulWidget` +Priority: 31 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:34`: +``` +} + +- class HookConsumer extends HookConsumerWidget { +- const HookConsumer({super.key}); +- +- @override +- Widget build( +- BuildContext context, +- WidgetRef ref, +- ) { ++ class HookConsumer extends ConsumerStatefulWidget { ++ const HookConsumer({super.key}); ++ ++ @override ++ ConsumerState createState() => _HookConsumerState(); ++ } ++ ++ class _HookConsumerState extends ConsumerState { ++ @override ++ Widget build( ++ BuildContext context) { + return const Placeholder(); + } +``` +--- +Message: `Convert to ConsumerStatefulWidget` +Priority: 27 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:46`: +``` +} + +- class Stateful extends StatefulWidget { +- const Stateful({super.key}); +- +- @override +- State createState() => _StatefulState(); +- } +- +- class _StatefulState extends State { ++ class Stateful extends ConsumerStatefulWidget { ++ const Stateful({super.key}); ++ ++ @override ++ ConsumerState createState() => _StatefulState(); ++ } ++ ++ class _StatefulState extends ConsumerState { + /// Hello world + @override +``` +--- +Message: `Convert to ConsumerStatefulWidget` +Priority: 27 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:62`: +``` +} + +- class ExplicitCreateState extends StatefulWidget { +- const ExplicitCreateState({super.key}); +- +- @override +- ExplicitCreateStateState createState() => ExplicitCreateStateState(); +- } +- +- class ExplicitCreateStateState extends State { ++ class ExplicitCreateState extends ConsumerStatefulWidget { ++ const ExplicitCreateState({super.key}); ++ ++ @override ++ ExplicitCreateStateState createState() => ExplicitCreateStateState(); ++ } ++ ++ class ExplicitCreateStateState extends ConsumerState { + @override + Widget build( +``` +--- +Message: `Convert to ConsumerStatefulWidget` +Priority: 31 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:78`: +``` +} + +- class HookStateful extends StatefulHookWidget { +- const HookStateful({super.key}); +- +- @override +- State createState() => HookStatefulState(); +- } +- +- class HookStatefulState extends State { ++ class HookStateful extends ConsumerStatefulWidget { ++ const HookStateful({super.key}); ++ ++ @override ++ ConsumerState createState() => HookStatefulState(); ++ } ++ ++ class HookStatefulState extends ConsumerState { + @override + Widget build(BuildContext context) { +``` +--- +Message: `Convert to ConsumerStatefulWidget` +Priority: 31 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:106`: +``` +} + +- class HookConsumerStateful extends StatefulHookConsumerWidget { ++ class HookConsumerStateful extends ConsumerStatefulWidget { + const HookConsumerStateful({super.key}); + +``` +--- +Message: `Convert to ConsumerStatefulWidget` +Priority: 31 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:121`: +``` +} + +- class Consumer extends ConsumerWidget { +- const Consumer({super.key}); +- +- @override +- Widget build( +- BuildContext context, +- WidgetRef ref, +- ) { ++ class Consumer extends ConsumerStatefulWidget { ++ const Consumer({super.key}); ++ ++ @override ++ ConsumerState createState() => _ConsumerState(); ++ } ++ ++ class _ConsumerState extends ConsumerState { ++ @override ++ Widget build( ++ BuildContext context) { + return const Placeholder(); + } +``` +--- +Message: `Convert to ConsumerStatefulWidget` +Priority: 31 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:133`: +``` +} + +- class StatelessWithField extends StatelessWidget { +- const StatelessWithField({ +- super.key, +- required this.field, +- }); +- +- final int field; +- static final int staticField = 42; +- +- @override +- Widget build(BuildContext context) { +- return Column( +- children: [ +- Text('$field'), +- Text('$staticField'), ++ class StatelessWithField extends ConsumerStatefulWidget { ++ const StatelessWithField({ ++ super.key, ++ required this.field, ++ }); ++ ++ final int field; ++ static final int staticField = 42; ++ ++ @override ++ ConsumerState createState() => _StatelessWithFieldState(); ++ } ++ ++ class _StatelessWithFieldState extends ConsumerState { ++ @override ++ Widget build(BuildContext context) { ++ return Column( ++ children: [ ++ Text('${widget.field}'), ++ Text('${StatelessWithField.staticField}'), + ], + ); +``` +--- +Message: `Convert to ConsumerStatefulWidget` +Priority: 31 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:153`: +``` +} + +- class HookConsumerWithField extends HookConsumerWidget { +- const HookConsumerWithField({ +- super.key, +- required this.field, +- }); +- +- final int field; +- static final int staticField = 42; +- +- @override +- Widget build( +- BuildContext context, +- WidgetRef ref, +- ) { +- return Column( +- children: [ +- Text('$field'), +- Text('$staticField'), ++ class HookConsumerWithField extends ConsumerStatefulWidget { ++ const HookConsumerWithField({ ++ super.key, ++ required this.field, ++ }); ++ ++ final int field; ++ static final int staticField = 42; ++ ++ @override ++ ConsumerState createState() => _HookConsumerWithFieldState(); ++ } ++ ++ class _HookConsumerWithFieldState extends ConsumerState { ++ @override ++ Widget build( ++ BuildContext context) { ++ return Column( ++ children: [ ++ Text('${widget.field}'), ++ Text('${HookConsumerWithField.staticField}'), + ], + ); +``` +--- diff --git a/packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_consumer_stateful_widget.json b/packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_consumer_stateful_widget.json deleted file mode 100644 index e019d34fb..000000000 --- a/packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_consumer_stateful_widget.json +++ /dev/null @@ -1 +0,0 @@ -[{"priority":31,"change":{"message":"Convert to ConsumerStatefulWidget","edits":[{"fileStamp":0,"edits":[{"offset":221,"length":0,"replacement":"@override\n ConsumerState createState() => _StatelessState();\n}\n\nclass _StatelessState extends ConsumerState {\n"},{"offset":168,"length":15,"replacement":"ConsumerStatefulWidget"}]}],"linkedEditGroups":[]}},{"priority":31,"change":{"message":"Convert to ConsumerStatefulWidget","edits":[{"fileStamp":0,"edits":[{"offset":404,"length":0,"replacement":"@override\n ConsumerState createState() => _StatelessWithCommaState();\n}\n\nclass _StatelessWithCommaState extends ConsumerState {\n"},{"offset":342,"length":15,"replacement":"ConsumerStatefulWidget"}]}],"linkedEditGroups":[]}},{"priority":31,"change":{"message":"Convert to ConsumerStatefulWidget","edits":[{"fileStamp":0,"edits":[{"offset":563,"length":0,"replacement":"@override\n ConsumerState createState() => _HookState();\n}\n\nclass _HookState extends ConsumerState {\n"},{"offset":520,"length":10,"replacement":"ConsumerStatefulWidget"}]}],"linkedEditGroups":[]}},{"priority":31,"change":{"message":"Convert to ConsumerStatefulWidget","edits":[{"fileStamp":0,"edits":[{"offset":787,"length":23,"replacement":""},{"offset":737,"length":0,"replacement":"@override\n ConsumerState createState() => _HookConsumerState();\n}\n\nclass _HookConsumerState extends ConsumerState {\n"},{"offset":678,"length":18,"replacement":"ConsumerStatefulWidget"}]}],"linkedEditGroups":[]}},{"priority":27,"change":{"message":"Convert to ConsumerStatefulWidget","edits":[{"fileStamp":0,"edits":[{"offset":1022,"length":15,"replacement":"ConsumerState"},{"offset":939,"length":15,"replacement":"ConsumerState"},{"offset":876,"length":14,"replacement":"ConsumerStatefulWidget"}]}],"linkedEditGroups":[]}},{"priority":27,"change":{"message":"Convert to ConsumerStatefulWidget","edits":[{"fileStamp":0,"edits":[{"offset":1389,"length":26,"replacement":"ConsumerState"},{"offset":1203,"length":14,"replacement":"ConsumerStatefulWidget"}]}],"linkedEditGroups":[]}},{"priority":31,"change":{"message":"Convert to ConsumerStatefulWidget","edits":[{"fileStamp":0,"edits":[{"offset":1708,"length":19,"replacement":"ConsumerState"},{"offset":1615,"length":19,"replacement":"ConsumerState"},{"offset":1544,"length":18,"replacement":"ConsumerStatefulWidget"}]}],"linkedEditGroups":[]}},{"priority":31,"change":{"message":"Convert to ConsumerStatefulWidget","edits":[{"fileStamp":0,"edits":[{"offset":2424,"length":35,"replacement":"ConsumerState"},{"offset":2291,"length":35,"replacement":"ConsumerState"},{"offset":2204,"length":26,"replacement":"ConsumerStatefulWidget"}]}],"linkedEditGroups":[]}},{"priority":31,"change":{"message":"Convert to ConsumerStatefulWidget","edits":[{"fileStamp":0,"edits":[{"offset":2676,"length":23,"replacement":""},{"offset":2626,"length":0,"replacement":"@override\n ConsumerState createState() => _ConsumerState();\n}\n\nclass _ConsumerState extends ConsumerState {\n"},{"offset":2575,"length":14,"replacement":"ConsumerStatefulWidget"}]}],"linkedEditGroups":[]}}] \ No newline at end of file diff --git a/packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_consumer_widget.diff b/packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_consumer_widget.diff new file mode 100644 index 000000000..ac6453b08 --- /dev/null +++ b/packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_consumer_widget.diff @@ -0,0 +1,330 @@ +Message: `Convert to ConsumerWidget` +Priority: 35 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:5`: +``` +import 'package:flutter_hooks/flutter_hooks.dart'; + +- class Stateless extends StatelessWidget { +- const Stateless({super.key}); +- +- @override +- Widget build(BuildContext context) { ++ class Stateless extends ConsumerWidget { ++ const Stateless({super.key}); ++ ++ @override ++ Widget build(BuildContext context, WidgetRef ref) { + return const Placeholder(); + } +``` +--- +Message: `Convert to ConsumerWidget` +Priority: 35 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:14`: +``` +} + +- class StatelessWithComma extends StatelessWidget { +- const StatelessWithComma({super.key}); +- +- @override +- Widget build( +- BuildContext context, ++ class StatelessWithComma extends ConsumerWidget { ++ const StatelessWithComma({super.key}); ++ ++ @override ++ Widget build( ++ BuildContext context, WidgetRef ref, + ) { + return const Placeholder(); +``` +--- +Message: `Convert to ConsumerWidget` +Priority: 35 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:25`: +``` +} + +- class Hook extends HookWidget { +- const Hook({super.key}); +- +- @override +- Widget build(BuildContext context) { ++ class Hook extends ConsumerWidget { ++ const Hook({super.key}); ++ ++ @override ++ Widget build(BuildContext context, WidgetRef ref) { + return const Placeholder(); + } +``` +--- +Message: `Convert to ConsumerWidget` +Priority: 35 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:34`: +``` +} + +- class HookConsumer extends HookConsumerWidget { ++ class HookConsumer extends ConsumerWidget { + const HookConsumer({super.key}); + +``` +--- +Message: `Convert to ConsumerWidget` +Priority: 31 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:46`: +``` +} + +- class Stateful extends StatefulWidget { +- const Stateful({super.key}); +- +- @override +- State createState() => _StatefulState(); +- } +- +- class _StatefulState extends State { +- /// Hello world +- @override +- Widget build(BuildContext context) { ++ class Stateful extends ConsumerWidget { ++ const Stateful({super.key}); ++ ++ ++ /// Hello world ++ @override ++ Widget build(BuildContext context, WidgetRef ref) { + // Some comments + return const Placeholder(); +``` +--- +Message: `Convert to ConsumerWidget` +Priority: 31 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:62`: +``` +} + +- class ExplicitCreateState extends StatefulWidget { +- const ExplicitCreateState({super.key}); +- +- @override +- ExplicitCreateStateState createState() => ExplicitCreateStateState(); +- } +- +- class ExplicitCreateStateState extends State { +- @override +- Widget build( +- BuildContext context, +- ) { ++ class ExplicitCreateState extends ConsumerWidget { ++ const ExplicitCreateState({super.key}); ++ ++ ++ @override ++ Widget build(BuildContext context, WidgetRef ref) { + return const Placeholder(); + } +``` +--- +Message: `Convert to ConsumerWidget` +Priority: 35 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:78`: +``` +} + +- class HookStateful extends StatefulHookWidget { +- const HookStateful({super.key}); +- +- @override +- State createState() => HookStatefulState(); +- } +- +- class HookStatefulState extends State { +- @override +- Widget build(BuildContext context) { ++ class HookStateful extends ConsumerWidget { ++ const HookStateful({super.key}); ++ ++ ++ @override ++ Widget build(BuildContext context, WidgetRef ref) { + return const Placeholder(); + } +``` +--- +Message: `Convert to ConsumerWidget` +Priority: 35 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:92`: +``` +} + +- class ConsumerStateful extends ConsumerStatefulWidget { +- const ConsumerStateful({super.key}); +- +- @override +- ConsumerState createState() => _ConsumerStatefulState(); +- } +- +- class _ConsumerStatefulState extends ConsumerState { +- @override +- Widget build(BuildContext context) { ++ class ConsumerStateful extends ConsumerWidget { ++ const ConsumerStateful({super.key}); ++ ++ ++ @override ++ Widget build(BuildContext context, WidgetRef ref) { + return const Placeholder(); + } +``` +--- +Message: `Convert to ConsumerWidget` +Priority: 35 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:106`: +``` +} + +- class HookConsumerStateful extends StatefulHookConsumerWidget { +- const HookConsumerStateful({super.key}); +- +- @override +- ConsumerState createState() => +- _HookConsumerStatefulState(); +- } +- +- class _HookConsumerStatefulState extends ConsumerState { +- @override +- Widget build(BuildContext context) { ++ class HookConsumerStateful extends ConsumerWidget { ++ const HookConsumerStateful({super.key}); ++ ++ ++ @override ++ Widget build(BuildContext context, WidgetRef ref) { + return const Placeholder(); + } +``` +--- +Message: `Convert to ConsumerWidget` +Priority: 35 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:133`: +``` +} + +- class StatelessWithField extends StatelessWidget { +- const StatelessWithField({ +- super.key, +- required this.field, +- }); +- +- final int field; +- static final int staticField = 42; +- +- @override +- Widget build(BuildContext context) { ++ class StatelessWithField extends ConsumerWidget { ++ const StatelessWithField({ ++ super.key, ++ required this.field, ++ }); ++ ++ final int field; ++ static final int staticField = 42; ++ ++ @override ++ Widget build(BuildContext context, WidgetRef ref) { + return Column( + children: [ +``` +--- +Message: `Convert to ConsumerWidget` +Priority: 35 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:153`: +``` +} + +- class HookConsumerWithField extends HookConsumerWidget { ++ class HookConsumerWithField extends ConsumerWidget { + const HookConsumerWithField({ + super.key, +``` +--- +Message: `Convert to ConsumerWidget` +Priority: 35 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:180`: +``` +} + +- class ConsumerStatefulWithField extends ConsumerStatefulWidget { +- const ConsumerStatefulWithField({ +- super.key, +- required this.field, +- required this.foo, +- }); +- +- final int field; +- final FooClass foo; +- static final int staticField = _constantNumber; +- +- @override +- ConsumerState createState() => +- _ConsumerStatefulWithFieldState(); +- } +- +- const _constantNumber = 42; +- +- class _ConsumerStatefulWithFieldState +- extends ConsumerState { +- void printFoo() { +- print(widget.foo); +- } +- +- @override +- Widget build(BuildContext context) { +- print(widget.field); +- printFoo(); +- return Column( +- children: [ +- Text('${widget.field}'), +- Text('${widget.foo.bar}'), +- Text('${ConsumerStatefulWithField.staticField}'), +- ], +- ); +- } +- } ++ class ConsumerStatefulWithField extends ConsumerWidget { ++ const ConsumerStatefulWithField({ ++ super.key, ++ required this.field, ++ required this.foo, ++ }); ++ ++ final int field; ++ final FooClass foo; ++ static final int staticField = _constantNumber; ++ ++ ++ void printFoo() { ++ print(foo); ++ } ++ ++ @override ++ Widget build(BuildContext context, WidgetRef ref) { ++ print(field); ++ printFoo(); ++ return Column( ++ children: [ ++ Text('$field'), ++ Text('${foo.bar}'), ++ Text('$staticField'), ++ ], ++ ); ++ } ++ } ++ ++ const _constantNumber = 42; + +``` +--- diff --git a/packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_consumer_widget.json b/packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_consumer_widget.json deleted file mode 100644 index 8e975d16f..000000000 --- a/packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_consumer_widget.json +++ /dev/null @@ -1 +0,0 @@ -[{"priority":35,"change":{"message":"Convert to ConsumerWidget","edits":[{"fileStamp":0,"edits":[{"offset":266,"length":0,"replacement":", WidgetRef ref"},{"offset":168,"length":15,"replacement":"ConsumerWidget"}]}],"linkedEditGroups":[]}},{"priority":35,"change":{"message":"Convert to ConsumerWidget","edits":[{"fileStamp":0,"edits":[{"offset":454,"length":0,"replacement":", WidgetRef ref"},{"offset":342,"length":15,"replacement":"ConsumerWidget"}]}],"linkedEditGroups":[]}},{"priority":35,"change":{"message":"Convert to ConsumerWidget","edits":[{"fileStamp":0,"edits":[{"offset":608,"length":0,"replacement":", WidgetRef ref"},{"offset":520,"length":10,"replacement":"ConsumerWidget"}]}],"linkedEditGroups":[]}},{"priority":35,"change":{"message":"Convert to ConsumerWidget","edits":[{"fileStamp":0,"edits":[{"offset":678,"length":18,"replacement":"ConsumerWidget"}]}],"linkedEditGroups":[]}},{"priority":31,"change":{"message":"Convert to ConsumerWidget","edits":[{"fileStamp":0,"edits":[{"offset":993,"length":174,"replacement":""},{"offset":990,"length":0,"replacement":"/// Hello world\n @override\n Widget build(BuildContext context, WidgetRef ref) {\n // Some comments\n return const Placeholder();\n }\n"},{"offset":927,"length":62,"replacement":""},{"offset":876,"length":14,"replacement":"ConsumerWidget"}]}],"linkedEditGroups":[]}},{"priority":31,"change":{"message":"Convert to ConsumerWidget","edits":[{"fileStamp":0,"edits":[{"offset":1350,"length":165,"replacement":""},{"offset":1347,"length":0,"replacement":"@override\n Widget build(\n BuildContext context, WidgetRef ref,\n ) {\n return const Placeholder();\n }\n"},{"offset":1265,"length":81,"replacement":""},{"offset":1203,"length":14,"replacement":"ConsumerWidget"}]}],"linkedEditGroups":[]}},{"priority":35,"change":{"message":"Convert to ConsumerWidget","edits":[{"fileStamp":0,"edits":[{"offset":1676,"length":142,"replacement":""},{"offset":1673,"length":0,"replacement":"@override\n Widget build(BuildContext context, WidgetRef ref) {\n return const Placeholder();\n }\n"},{"offset":1603,"length":69,"replacement":""},{"offset":1544,"length":18,"replacement":"ConsumerWidget"}]}],"linkedEditGroups":[]}},{"priority":35,"change":{"message":"Convert to ConsumerWidget","edits":[{"fileStamp":0,"edits":[{"offset":2008,"length":159,"replacement":""},{"offset":2005,"length":0,"replacement":"@override\n Widget build(BuildContext context, WidgetRef ref) {\n return const Placeholder();\n }\n"},{"offset":1918,"length":86,"replacement":""},{"offset":1851,"length":22,"replacement":"ConsumerWidget"}]}],"linkedEditGroups":[]}},{"priority":35,"change":{"message":"Convert to ConsumerWidget","edits":[{"fileStamp":0,"edits":[{"offset":2383,"length":167,"replacement":""},{"offset":2380,"length":0,"replacement":"@override\n Widget build(BuildContext context, WidgetRef ref) {\n return const Placeholder();\n }\n"},{"offset":2279,"length":100,"replacement":""},{"offset":2204,"length":26,"replacement":"ConsumerWidget"}]}],"linkedEditGroups":[]}}] \ No newline at end of file diff --git a/packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_hook_consumer_widget.diff b/packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_hook_consumer_widget.diff new file mode 100644 index 000000000..b694c875c --- /dev/null +++ b/packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_hook_consumer_widget.diff @@ -0,0 +1,318 @@ +Message: `Convert to HookConsumerWidget` +Priority: 37 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:5`: +``` +import 'package:flutter_hooks/flutter_hooks.dart'; + +- class Stateless extends StatelessWidget { +- const Stateless({super.key}); +- +- @override +- Widget build(BuildContext context) { ++ class Stateless extends HookConsumerWidget { ++ const Stateless({super.key}); ++ ++ @override ++ Widget build(BuildContext context, WidgetRef ref) { + return const Placeholder(); + } +``` +--- +Message: `Convert to HookConsumerWidget` +Priority: 37 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:14`: +``` +} + +- class StatelessWithComma extends StatelessWidget { +- const StatelessWithComma({super.key}); +- +- @override +- Widget build( +- BuildContext context, ++ class StatelessWithComma extends HookConsumerWidget { ++ const StatelessWithComma({super.key}); ++ ++ @override ++ Widget build( ++ BuildContext context, WidgetRef ref, + ) { + return const Placeholder(); +``` +--- +Message: `Convert to HookConsumerWidget` +Priority: 37 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:25`: +``` +} + +- class Hook extends HookWidget { +- const Hook({super.key}); +- +- @override +- Widget build(BuildContext context) { ++ class Hook extends HookConsumerWidget { ++ const Hook({super.key}); ++ ++ @override ++ Widget build(BuildContext context, WidgetRef ref) { + return const Placeholder(); + } +``` +--- +Message: `Convert to HookConsumerWidget` +Priority: 33 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:46`: +``` +} + +- class Stateful extends StatefulWidget { +- const Stateful({super.key}); +- +- @override +- State createState() => _StatefulState(); +- } +- +- class _StatefulState extends State { +- /// Hello world +- @override +- Widget build(BuildContext context) { ++ class Stateful extends HookConsumerWidget { ++ const Stateful({super.key}); ++ ++ ++ /// Hello world ++ @override ++ Widget build(BuildContext context, WidgetRef ref) { + // Some comments + return const Placeholder(); +``` +--- +Message: `Convert to HookConsumerWidget` +Priority: 33 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:62`: +``` +} + +- class ExplicitCreateState extends StatefulWidget { +- const ExplicitCreateState({super.key}); +- +- @override +- ExplicitCreateStateState createState() => ExplicitCreateStateState(); +- } +- +- class ExplicitCreateStateState extends State { +- @override +- Widget build( +- BuildContext context, +- ) { ++ class ExplicitCreateState extends HookConsumerWidget { ++ const ExplicitCreateState({super.key}); ++ ++ ++ @override ++ Widget build(BuildContext context, WidgetRef ref) { + return const Placeholder(); + } +``` +--- +Message: `Convert to HookConsumerWidget` +Priority: 37 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:78`: +``` +} + +- class HookStateful extends StatefulHookWidget { +- const HookStateful({super.key}); +- +- @override +- State createState() => HookStatefulState(); +- } +- +- class HookStatefulState extends State { +- @override +- Widget build(BuildContext context) { ++ class HookStateful extends HookConsumerWidget { ++ const HookStateful({super.key}); ++ ++ ++ @override ++ Widget build(BuildContext context, WidgetRef ref) { + return const Placeholder(); + } +``` +--- +Message: `Convert to HookConsumerWidget` +Priority: 37 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:92`: +``` +} + +- class ConsumerStateful extends ConsumerStatefulWidget { +- const ConsumerStateful({super.key}); +- +- @override +- ConsumerState createState() => _ConsumerStatefulState(); +- } +- +- class _ConsumerStatefulState extends ConsumerState { +- @override +- Widget build(BuildContext context) { ++ class ConsumerStateful extends HookConsumerWidget { ++ const ConsumerStateful({super.key}); ++ ++ ++ @override ++ Widget build(BuildContext context, WidgetRef ref) { + return const Placeholder(); + } +``` +--- +Message: `Convert to HookConsumerWidget` +Priority: 37 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:106`: +``` +} + +- class HookConsumerStateful extends StatefulHookConsumerWidget { +- const HookConsumerStateful({super.key}); +- +- @override +- ConsumerState createState() => +- _HookConsumerStatefulState(); +- } +- +- class _HookConsumerStatefulState extends ConsumerState { +- @override +- Widget build(BuildContext context) { ++ class HookConsumerStateful extends HookConsumerWidget { ++ const HookConsumerStateful({super.key}); ++ ++ ++ @override ++ Widget build(BuildContext context, WidgetRef ref) { + return const Placeholder(); + } +``` +--- +Message: `Convert to HookConsumerWidget` +Priority: 37 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:121`: +``` +} + +- class Consumer extends ConsumerWidget { ++ class Consumer extends HookConsumerWidget { + const Consumer({super.key}); + +``` +--- +Message: `Convert to HookConsumerWidget` +Priority: 37 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:133`: +``` +} + +- class StatelessWithField extends StatelessWidget { +- const StatelessWithField({ +- super.key, +- required this.field, +- }); +- +- final int field; +- static final int staticField = 42; +- +- @override +- Widget build(BuildContext context) { ++ class StatelessWithField extends HookConsumerWidget { ++ const StatelessWithField({ ++ super.key, ++ required this.field, ++ }); ++ ++ final int field; ++ static final int staticField = 42; ++ ++ @override ++ Widget build(BuildContext context, WidgetRef ref) { + return Column( + children: [ +``` +--- +Message: `Convert to HookConsumerWidget` +Priority: 37 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:180`: +``` +} + +- class ConsumerStatefulWithField extends ConsumerStatefulWidget { +- const ConsumerStatefulWithField({ +- super.key, +- required this.field, +- required this.foo, +- }); +- +- final int field; +- final FooClass foo; +- static final int staticField = _constantNumber; +- +- @override +- ConsumerState createState() => +- _ConsumerStatefulWithFieldState(); +- } +- +- const _constantNumber = 42; +- +- class _ConsumerStatefulWithFieldState +- extends ConsumerState { +- void printFoo() { +- print(widget.foo); +- } +- +- @override +- Widget build(BuildContext context) { +- print(widget.field); +- printFoo(); +- return Column( +- children: [ +- Text('${widget.field}'), +- Text('${widget.foo.bar}'), +- Text('${ConsumerStatefulWithField.staticField}'), +- ], +- ); +- } +- } ++ class ConsumerStatefulWithField extends HookConsumerWidget { ++ const ConsumerStatefulWithField({ ++ super.key, ++ required this.field, ++ required this.foo, ++ }); ++ ++ final int field; ++ final FooClass foo; ++ static final int staticField = _constantNumber; ++ ++ ++ void printFoo() { ++ print(foo); ++ } ++ ++ @override ++ Widget build(BuildContext context, WidgetRef ref) { ++ print(field); ++ printFoo(); ++ return Column( ++ children: [ ++ Text('$field'), ++ Text('${foo.bar}'), ++ Text('$staticField'), ++ ], ++ ); ++ } ++ } ++ ++ const _constantNumber = 42; + +``` +--- diff --git a/packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_hook_consumer_widget.json b/packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_hook_consumer_widget.json deleted file mode 100644 index 449c635fc..000000000 --- a/packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_hook_consumer_widget.json +++ /dev/null @@ -1 +0,0 @@ -[{"priority":37,"change":{"message":"Convert to HookConsumerWidget","edits":[{"fileStamp":0,"edits":[{"offset":266,"length":0,"replacement":", WidgetRef ref"},{"offset":168,"length":15,"replacement":"HookConsumerWidget"}]}],"linkedEditGroups":[]}},{"priority":37,"change":{"message":"Convert to HookConsumerWidget","edits":[{"fileStamp":0,"edits":[{"offset":454,"length":0,"replacement":", WidgetRef ref"},{"offset":342,"length":15,"replacement":"HookConsumerWidget"}]}],"linkedEditGroups":[]}},{"priority":37,"change":{"message":"Convert to HookConsumerWidget","edits":[{"fileStamp":0,"edits":[{"offset":608,"length":0,"replacement":", WidgetRef ref"},{"offset":520,"length":10,"replacement":"HookConsumerWidget"}]}],"linkedEditGroups":[]}},{"priority":33,"change":{"message":"Convert to HookConsumerWidget","edits":[{"fileStamp":0,"edits":[{"offset":993,"length":174,"replacement":""},{"offset":990,"length":0,"replacement":"/// Hello world\n @override\n Widget build(BuildContext context, WidgetRef ref) {\n // Some comments\n return const Placeholder();\n }\n"},{"offset":927,"length":62,"replacement":""},{"offset":876,"length":14,"replacement":"HookConsumerWidget"}]}],"linkedEditGroups":[]}},{"priority":33,"change":{"message":"Convert to HookConsumerWidget","edits":[{"fileStamp":0,"edits":[{"offset":1350,"length":165,"replacement":""},{"offset":1347,"length":0,"replacement":"@override\n Widget build(\n BuildContext context, WidgetRef ref,\n ) {\n return const Placeholder();\n }\n"},{"offset":1265,"length":81,"replacement":""},{"offset":1203,"length":14,"replacement":"HookConsumerWidget"}]}],"linkedEditGroups":[]}},{"priority":37,"change":{"message":"Convert to HookConsumerWidget","edits":[{"fileStamp":0,"edits":[{"offset":1676,"length":142,"replacement":""},{"offset":1673,"length":0,"replacement":"@override\n Widget build(BuildContext context, WidgetRef ref) {\n return const Placeholder();\n }\n"},{"offset":1603,"length":69,"replacement":""},{"offset":1544,"length":18,"replacement":"HookConsumerWidget"}]}],"linkedEditGroups":[]}},{"priority":37,"change":{"message":"Convert to HookConsumerWidget","edits":[{"fileStamp":0,"edits":[{"offset":2008,"length":159,"replacement":""},{"offset":2005,"length":0,"replacement":"@override\n Widget build(BuildContext context, WidgetRef ref) {\n return const Placeholder();\n }\n"},{"offset":1918,"length":86,"replacement":""},{"offset":1851,"length":22,"replacement":"HookConsumerWidget"}]}],"linkedEditGroups":[]}},{"priority":37,"change":{"message":"Convert to HookConsumerWidget","edits":[{"fileStamp":0,"edits":[{"offset":2383,"length":167,"replacement":""},{"offset":2380,"length":0,"replacement":"@override\n Widget build(BuildContext context, WidgetRef ref) {\n return const Placeholder();\n }\n"},{"offset":2279,"length":100,"replacement":""},{"offset":2204,"length":26,"replacement":"HookConsumerWidget"}]}],"linkedEditGroups":[]}},{"priority":37,"change":{"message":"Convert to HookConsumerWidget","edits":[{"fileStamp":0,"edits":[{"offset":2575,"length":14,"replacement":"HookConsumerWidget"}]}],"linkedEditGroups":[]}}] \ No newline at end of file diff --git a/packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_hook_widget.diff b/packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_hook_widget.diff new file mode 100644 index 000000000..0d1a73ee1 --- /dev/null +++ b/packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_hook_widget.diff @@ -0,0 +1,314 @@ +Message: `Convert to HookWidget` +Priority: 36 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:5`: +``` +import 'package:flutter_hooks/flutter_hooks.dart'; + +- class Stateless extends StatelessWidget { ++ class Stateless extends HookWidget { + const Stateless({super.key}); + +``` +--- +Message: `Convert to HookWidget` +Priority: 36 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:14`: +``` +} + +- class StatelessWithComma extends StatelessWidget { ++ class StatelessWithComma extends HookWidget { + const StatelessWithComma({super.key}); + +``` +--- +Message: `Convert to HookWidget` +Priority: 36 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:34`: +``` +} + +- class HookConsumer extends HookConsumerWidget { +- const HookConsumer({super.key}); +- +- @override +- Widget build( +- BuildContext context, +- WidgetRef ref, +- ) { ++ class HookConsumer extends HookWidget { ++ const HookConsumer({super.key}); ++ ++ @override ++ Widget build( ++ BuildContext context) { + return const Placeholder(); + } +``` +--- +Message: `Convert to HookWidget` +Priority: 32 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:46`: +``` +} + +- class Stateful extends StatefulWidget { +- const Stateful({super.key}); +- +- @override +- State createState() => _StatefulState(); +- } +- +- class _StatefulState extends State { ++ class Stateful extends HookWidget { ++ const Stateful({super.key}); ++ ++ + /// Hello world + @override +``` +--- +Message: `Convert to HookWidget` +Priority: 32 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:62`: +``` +} + +- class ExplicitCreateState extends StatefulWidget { +- const ExplicitCreateState({super.key}); +- +- @override +- ExplicitCreateStateState createState() => ExplicitCreateStateState(); +- } +- +- class ExplicitCreateStateState extends State { +- @override +- Widget build( +- BuildContext context, +- ) { ++ class ExplicitCreateState extends HookWidget { ++ const ExplicitCreateState({super.key}); ++ ++ ++ @override ++ Widget build(BuildContext context) { + return const Placeholder(); + } +``` +--- +Message: `Convert to HookWidget` +Priority: 36 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:78`: +``` +} + +- class HookStateful extends StatefulHookWidget { +- const HookStateful({super.key}); +- +- @override +- State createState() => HookStatefulState(); +- } +- +- class HookStatefulState extends State { ++ class HookStateful extends HookWidget { ++ const HookStateful({super.key}); ++ ++ + @override + Widget build(BuildContext context) { +``` +--- +Message: `Convert to HookWidget` +Priority: 36 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:92`: +``` +} + +- class ConsumerStateful extends ConsumerStatefulWidget { +- const ConsumerStateful({super.key}); +- +- @override +- ConsumerState createState() => _ConsumerStatefulState(); +- } +- +- class _ConsumerStatefulState extends ConsumerState { ++ class ConsumerStateful extends HookWidget { ++ const ConsumerStateful({super.key}); ++ ++ + @override + Widget build(BuildContext context) { +``` +--- +Message: `Convert to HookWidget` +Priority: 36 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:106`: +``` +} + +- class HookConsumerStateful extends StatefulHookConsumerWidget { +- const HookConsumerStateful({super.key}); +- +- @override +- ConsumerState createState() => +- _HookConsumerStatefulState(); +- } +- +- class _HookConsumerStatefulState extends ConsumerState { ++ class HookConsumerStateful extends HookWidget { ++ const HookConsumerStateful({super.key}); ++ ++ + @override + Widget build(BuildContext context) { +``` +--- +Message: `Convert to HookWidget` +Priority: 36 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:121`: +``` +} + +- class Consumer extends ConsumerWidget { +- const Consumer({super.key}); +- +- @override +- Widget build( +- BuildContext context, +- WidgetRef ref, +- ) { ++ class Consumer extends HookWidget { ++ const Consumer({super.key}); ++ ++ @override ++ Widget build( ++ BuildContext context) { + return const Placeholder(); + } +``` +--- +Message: `Convert to HookWidget` +Priority: 36 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:133`: +``` +} + +- class StatelessWithField extends StatelessWidget { ++ class StatelessWithField extends HookWidget { + const StatelessWithField({ + super.key, +``` +--- +Message: `Convert to HookWidget` +Priority: 36 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:153`: +``` +} + +- class HookConsumerWithField extends HookConsumerWidget { +- const HookConsumerWithField({ +- super.key, +- required this.field, +- }); +- +- final int field; +- static final int staticField = 42; +- +- @override +- Widget build( +- BuildContext context, +- WidgetRef ref, +- ) { ++ class HookConsumerWithField extends HookWidget { ++ const HookConsumerWithField({ ++ super.key, ++ required this.field, ++ }); ++ ++ final int field; ++ static final int staticField = 42; ++ ++ @override ++ Widget build( ++ BuildContext context) { + return Column( + children: [ +``` +--- +Message: `Convert to HookWidget` +Priority: 36 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:180`: +``` +} + +- class ConsumerStatefulWithField extends ConsumerStatefulWidget { +- const ConsumerStatefulWithField({ +- super.key, +- required this.field, +- required this.foo, +- }); +- +- final int field; +- final FooClass foo; +- static final int staticField = _constantNumber; +- +- @override +- ConsumerState createState() => +- _ConsumerStatefulWithFieldState(); +- } +- +- const _constantNumber = 42; +- +- class _ConsumerStatefulWithFieldState +- extends ConsumerState { +- void printFoo() { +- print(widget.foo); +- } +- +- @override +- Widget build(BuildContext context) { +- print(widget.field); +- printFoo(); +- return Column( +- children: [ +- Text('${widget.field}'), +- Text('${widget.foo.bar}'), +- Text('${ConsumerStatefulWithField.staticField}'), +- ], +- ); +- } +- } ++ class ConsumerStatefulWithField extends HookWidget { ++ const ConsumerStatefulWithField({ ++ super.key, ++ required this.field, ++ required this.foo, ++ }); ++ ++ final int field; ++ final FooClass foo; ++ static final int staticField = _constantNumber; ++ ++ ++ void printFoo() { ++ print(foo); ++ } ++ ++ @override ++ Widget build(BuildContext context) { ++ print(field); ++ printFoo(); ++ return Column( ++ children: [ ++ Text('$field'), ++ Text('${foo.bar}'), ++ Text('$staticField'), ++ ], ++ ); ++ } ++ } ++ ++ const _constantNumber = 42; + +``` +--- diff --git a/packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_hook_widget.json b/packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_hook_widget.json deleted file mode 100644 index 2698c3ded..000000000 --- a/packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_hook_widget.json +++ /dev/null @@ -1 +0,0 @@ -[{"priority":36,"change":{"message":"Convert to HookWidget","edits":[{"fileStamp":0,"edits":[{"offset":168,"length":15,"replacement":"HookWidget"}]}],"linkedEditGroups":[]}},{"priority":36,"change":{"message":"Convert to HookWidget","edits":[{"fileStamp":0,"edits":[{"offset":342,"length":15,"replacement":"HookWidget"}]}],"linkedEditGroups":[]}},{"priority":36,"change":{"message":"Convert to HookWidget","edits":[{"fileStamp":0,"edits":[{"offset":787,"length":23,"replacement":""},{"offset":678,"length":18,"replacement":"HookWidget"}]}],"linkedEditGroups":[]}},{"priority":32,"change":{"message":"Convert to HookWidget","edits":[{"fileStamp":0,"edits":[{"offset":993,"length":174,"replacement":""},{"offset":990,"length":0,"replacement":"/// Hello world\n @override\n Widget build(BuildContext context) {\n // Some comments\n return const Placeholder();\n }\n"},{"offset":927,"length":62,"replacement":""},{"offset":876,"length":14,"replacement":"HookWidget"}]}],"linkedEditGroups":[]}},{"priority":32,"change":{"message":"Convert to HookWidget","edits":[{"fileStamp":0,"edits":[{"offset":1350,"length":165,"replacement":""},{"offset":1347,"length":0,"replacement":"@override\n Widget build(\n BuildContext context,\n ) {\n return const Placeholder();\n }\n"},{"offset":1265,"length":81,"replacement":""},{"offset":1203,"length":14,"replacement":"HookWidget"}]}],"linkedEditGroups":[]}},{"priority":36,"change":{"message":"Convert to HookWidget","edits":[{"fileStamp":0,"edits":[{"offset":1676,"length":142,"replacement":""},{"offset":1673,"length":0,"replacement":"@override\n Widget build(BuildContext context) {\n return const Placeholder();\n }\n"},{"offset":1603,"length":69,"replacement":""},{"offset":1544,"length":18,"replacement":"HookWidget"}]}],"linkedEditGroups":[]}},{"priority":36,"change":{"message":"Convert to HookWidget","edits":[{"fileStamp":0,"edits":[{"offset":2008,"length":159,"replacement":""},{"offset":2005,"length":0,"replacement":"@override\n Widget build(BuildContext context) {\n return const Placeholder();\n }\n"},{"offset":1918,"length":86,"replacement":""},{"offset":1851,"length":22,"replacement":"HookWidget"}]}],"linkedEditGroups":[]}},{"priority":36,"change":{"message":"Convert to HookWidget","edits":[{"fileStamp":0,"edits":[{"offset":2383,"length":167,"replacement":""},{"offset":2380,"length":0,"replacement":"@override\n Widget build(BuildContext context) {\n return const Placeholder();\n }\n"},{"offset":2279,"length":100,"replacement":""},{"offset":2204,"length":26,"replacement":"HookWidget"}]}],"linkedEditGroups":[]}},{"priority":36,"change":{"message":"Convert to HookWidget","edits":[{"fileStamp":0,"edits":[{"offset":2676,"length":23,"replacement":""},{"offset":2575,"length":14,"replacement":"HookWidget"}]}],"linkedEditGroups":[]}}] \ No newline at end of file diff --git a/packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_stateful_hook_consumer_widget.diff b/packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_stateful_hook_consumer_widget.diff new file mode 100644 index 000000000..103fb34a1 --- /dev/null +++ b/packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_stateful_hook_consumer_widget.diff @@ -0,0 +1,320 @@ +Message: `Convert to StatefulHookConsumerWidget` +Priority: 33 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:5`: +``` +import 'package:flutter_hooks/flutter_hooks.dart'; + +- class Stateless extends StatelessWidget { +- const Stateless({super.key}); +- +- @override ++ class Stateless extends StatefulHookConsumerWidget { ++ const Stateless({super.key}); ++ ++ @override ++ ConsumerState createState() => _StatelessState(); ++ } ++ ++ class _StatelessState extends ConsumerState { +@override + Widget build(BuildContext context) { +``` +--- +Message: `Convert to StatefulHookConsumerWidget` +Priority: 33 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:14`: +``` +} + +- class StatelessWithComma extends StatelessWidget { +- const StatelessWithComma({super.key}); +- +- @override ++ class StatelessWithComma extends StatefulHookConsumerWidget { ++ const StatelessWithComma({super.key}); ++ ++ @override ++ ConsumerState createState() => _StatelessWithCommaState(); ++ } ++ ++ class _StatelessWithCommaState extends ConsumerState { +@override + Widget build( +``` +--- +Message: `Convert to StatefulHookConsumerWidget` +Priority: 33 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:25`: +``` +} + +- class Hook extends HookWidget { +- const Hook({super.key}); +- +- @override ++ class Hook extends StatefulHookConsumerWidget { ++ const Hook({super.key}); ++ ++ @override ++ ConsumerState createState() => _HookState(); ++ } ++ ++ class _HookState extends ConsumerState { +@override + Widget build(BuildContext context) { +``` +--- +Message: `Convert to StatefulHookConsumerWidget` +Priority: 33 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:34`: +``` +} + +- class HookConsumer extends HookConsumerWidget { +- const HookConsumer({super.key}); +- +- @override +- Widget build( +- BuildContext context, +- WidgetRef ref, +- ) { ++ class HookConsumer extends StatefulHookConsumerWidget { ++ const HookConsumer({super.key}); ++ ++ @override ++ ConsumerState createState() => _HookConsumerState(); ++ } ++ ++ class _HookConsumerState extends ConsumerState { ++ @override ++ Widget build( ++ BuildContext context) { + return const Placeholder(); + } +``` +--- +Message: `Convert to StatefulHookConsumerWidget` +Priority: 29 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:46`: +``` +} + +- class Stateful extends StatefulWidget { +- const Stateful({super.key}); +- +- @override +- State createState() => _StatefulState(); +- } +- +- class _StatefulState extends State { ++ class Stateful extends StatefulHookConsumerWidget { ++ const Stateful({super.key}); ++ ++ @override ++ ConsumerState createState() => _StatefulState(); ++ } ++ ++ class _StatefulState extends ConsumerState { + /// Hello world + @override +``` +--- +Message: `Convert to StatefulHookConsumerWidget` +Priority: 29 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:62`: +``` +} + +- class ExplicitCreateState extends StatefulWidget { +- const ExplicitCreateState({super.key}); +- +- @override +- ExplicitCreateStateState createState() => ExplicitCreateStateState(); +- } +- +- class ExplicitCreateStateState extends State { ++ class ExplicitCreateState extends StatefulHookConsumerWidget { ++ const ExplicitCreateState({super.key}); ++ ++ @override ++ ExplicitCreateStateState createState() => ExplicitCreateStateState(); ++ } ++ ++ class ExplicitCreateStateState extends ConsumerState { + @override + Widget build( +``` +--- +Message: `Convert to StatefulHookConsumerWidget` +Priority: 33 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:78`: +``` +} + +- class HookStateful extends StatefulHookWidget { +- const HookStateful({super.key}); +- +- @override +- State createState() => HookStatefulState(); +- } +- +- class HookStatefulState extends State { ++ class HookStateful extends StatefulHookConsumerWidget { ++ const HookStateful({super.key}); ++ ++ @override ++ ConsumerState createState() => HookStatefulState(); ++ } ++ ++ class HookStatefulState extends ConsumerState { + @override + Widget build(BuildContext context) { +``` +--- +Message: `Convert to StatefulHookConsumerWidget` +Priority: 33 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:92`: +``` +} + +- class ConsumerStateful extends ConsumerStatefulWidget { ++ class ConsumerStateful extends StatefulHookConsumerWidget { + const ConsumerStateful({super.key}); + +``` +--- +Message: `Convert to StatefulHookConsumerWidget` +Priority: 33 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:121`: +``` +} + +- class Consumer extends ConsumerWidget { +- const Consumer({super.key}); +- +- @override +- Widget build( +- BuildContext context, +- WidgetRef ref, +- ) { ++ class Consumer extends StatefulHookConsumerWidget { ++ const Consumer({super.key}); ++ ++ @override ++ ConsumerState createState() => _ConsumerState(); ++ } ++ ++ class _ConsumerState extends ConsumerState { ++ @override ++ Widget build( ++ BuildContext context) { + return const Placeholder(); + } +``` +--- +Message: `Convert to StatefulHookConsumerWidget` +Priority: 33 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:133`: +``` +} + +- class StatelessWithField extends StatelessWidget { +- const StatelessWithField({ +- super.key, +- required this.field, +- }); +- +- final int field; +- static final int staticField = 42; +- +- @override +- Widget build(BuildContext context) { +- return Column( +- children: [ +- Text('$field'), +- Text('$staticField'), ++ class StatelessWithField extends StatefulHookConsumerWidget { ++ const StatelessWithField({ ++ super.key, ++ required this.field, ++ }); ++ ++ final int field; ++ static final int staticField = 42; ++ ++ @override ++ ConsumerState createState() => _StatelessWithFieldState(); ++ } ++ ++ class _StatelessWithFieldState extends ConsumerState { ++ @override ++ Widget build(BuildContext context) { ++ return Column( ++ children: [ ++ Text('${widget.field}'), ++ Text('${StatelessWithField.staticField}'), + ], + ); +``` +--- +Message: `Convert to StatefulHookConsumerWidget` +Priority: 33 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:153`: +``` +} + +- class HookConsumerWithField extends HookConsumerWidget { +- const HookConsumerWithField({ +- super.key, +- required this.field, +- }); +- +- final int field; +- static final int staticField = 42; +- +- @override +- Widget build( +- BuildContext context, +- WidgetRef ref, +- ) { +- return Column( +- children: [ +- Text('$field'), +- Text('$staticField'), ++ class HookConsumerWithField extends StatefulHookConsumerWidget { ++ const HookConsumerWithField({ ++ super.key, ++ required this.field, ++ }); ++ ++ final int field; ++ static final int staticField = 42; ++ ++ @override ++ ConsumerState createState() => _HookConsumerWithFieldState(); ++ } ++ ++ class _HookConsumerWithFieldState extends ConsumerState { ++ @override ++ Widget build( ++ BuildContext context) { ++ return Column( ++ children: [ ++ Text('${widget.field}'), ++ Text('${HookConsumerWithField.staticField}'), + ], + ); +``` +--- +Message: `Convert to StatefulHookConsumerWidget` +Priority: 33 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:180`: +``` +} + +- class ConsumerStatefulWithField extends ConsumerStatefulWidget { ++ class ConsumerStatefulWithField extends StatefulHookConsumerWidget { + const ConsumerStatefulWithField({ + super.key, +``` +--- diff --git a/packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_stateful_hook_consumer_widget.json b/packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_stateful_hook_consumer_widget.json deleted file mode 100644 index de88392fa..000000000 --- a/packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_stateful_hook_consumer_widget.json +++ /dev/null @@ -1 +0,0 @@ -[{"priority":33,"change":{"message":"Convert to StatefulHookConsumerWidget","edits":[{"fileStamp":0,"edits":[{"offset":221,"length":0,"replacement":"@override\n ConsumerState createState() => _StatelessState();\n}\n\nclass _StatelessState extends ConsumerState {\n"},{"offset":168,"length":15,"replacement":"StatefulHookConsumerWidget"}]}],"linkedEditGroups":[]}},{"priority":33,"change":{"message":"Convert to StatefulHookConsumerWidget","edits":[{"fileStamp":0,"edits":[{"offset":404,"length":0,"replacement":"@override\n ConsumerState createState() => _StatelessWithCommaState();\n}\n\nclass _StatelessWithCommaState extends ConsumerState {\n"},{"offset":342,"length":15,"replacement":"StatefulHookConsumerWidget"}]}],"linkedEditGroups":[]}},{"priority":33,"change":{"message":"Convert to StatefulHookConsumerWidget","edits":[{"fileStamp":0,"edits":[{"offset":563,"length":0,"replacement":"@override\n ConsumerState createState() => _HookState();\n}\n\nclass _HookState extends ConsumerState {\n"},{"offset":520,"length":10,"replacement":"StatefulHookConsumerWidget"}]}],"linkedEditGroups":[]}},{"priority":33,"change":{"message":"Convert to StatefulHookConsumerWidget","edits":[{"fileStamp":0,"edits":[{"offset":787,"length":23,"replacement":""},{"offset":737,"length":0,"replacement":"@override\n ConsumerState createState() => _HookConsumerState();\n}\n\nclass _HookConsumerState extends ConsumerState {\n"},{"offset":678,"length":18,"replacement":"StatefulHookConsumerWidget"}]}],"linkedEditGroups":[]}},{"priority":29,"change":{"message":"Convert to StatefulHookConsumerWidget","edits":[{"fileStamp":0,"edits":[{"offset":1022,"length":15,"replacement":"ConsumerState"},{"offset":939,"length":15,"replacement":"ConsumerState"},{"offset":876,"length":14,"replacement":"StatefulHookConsumerWidget"}]}],"linkedEditGroups":[]}},{"priority":29,"change":{"message":"Convert to StatefulHookConsumerWidget","edits":[{"fileStamp":0,"edits":[{"offset":1389,"length":26,"replacement":"ConsumerState"},{"offset":1203,"length":14,"replacement":"StatefulHookConsumerWidget"}]}],"linkedEditGroups":[]}},{"priority":33,"change":{"message":"Convert to StatefulHookConsumerWidget","edits":[{"fileStamp":0,"edits":[{"offset":1708,"length":19,"replacement":"ConsumerState"},{"offset":1615,"length":19,"replacement":"ConsumerState"},{"offset":1544,"length":18,"replacement":"StatefulHookConsumerWidget"}]}],"linkedEditGroups":[]}},{"priority":33,"change":{"message":"Convert to StatefulHookConsumerWidget","edits":[{"fileStamp":0,"edits":[{"offset":2045,"length":31,"replacement":"ConsumerState"},{"offset":1930,"length":31,"replacement":"ConsumerState"},{"offset":1851,"length":22,"replacement":"StatefulHookConsumerWidget"}]}],"linkedEditGroups":[]}},{"priority":33,"change":{"message":"Convert to StatefulHookConsumerWidget","edits":[{"fileStamp":0,"edits":[{"offset":2676,"length":23,"replacement":""},{"offset":2626,"length":0,"replacement":"@override\n ConsumerState createState() => _ConsumerState();\n}\n\nclass _ConsumerState extends ConsumerState {\n"},{"offset":2575,"length":14,"replacement":"StatefulHookConsumerWidget"}]}],"linkedEditGroups":[]}}] \ No newline at end of file diff --git a/packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_stateful_hook_widget.diff b/packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_stateful_hook_widget.diff new file mode 100644 index 000000000..b45a2a93b --- /dev/null +++ b/packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_stateful_hook_widget.diff @@ -0,0 +1,346 @@ +Message: `Convert to StatefulHookWidget` +Priority: 32 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:5`: +``` +import 'package:flutter_hooks/flutter_hooks.dart'; + +- class Stateless extends StatelessWidget { +- const Stateless({super.key}); +- +- @override ++ class Stateless extends StatefulHookWidget { ++ const Stateless({super.key}); ++ ++ @override ++ State createState() => _StatelessState(); ++ } ++ ++ class _StatelessState extends State { +@override + Widget build(BuildContext context) { +``` +--- +Message: `Convert to StatefulHookWidget` +Priority: 32 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:14`: +``` +} + +- class StatelessWithComma extends StatelessWidget { +- const StatelessWithComma({super.key}); +- +- @override ++ class StatelessWithComma extends StatefulHookWidget { ++ const StatelessWithComma({super.key}); ++ ++ @override ++ State createState() => _StatelessWithCommaState(); ++ } ++ ++ class _StatelessWithCommaState extends State { +@override + Widget build( +``` +--- +Message: `Convert to StatefulHookWidget` +Priority: 32 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:25`: +``` +} + +- class Hook extends HookWidget { +- const Hook({super.key}); +- +- @override ++ class Hook extends StatefulHookWidget { ++ const Hook({super.key}); ++ ++ @override ++ State createState() => _HookState(); ++ } ++ ++ class _HookState extends State { +@override + Widget build(BuildContext context) { +``` +--- +Message: `Convert to StatefulHookWidget` +Priority: 32 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:34`: +``` +} + +- class HookConsumer extends HookConsumerWidget { +- const HookConsumer({super.key}); +- +- @override +- Widget build( +- BuildContext context, +- WidgetRef ref, +- ) { ++ class HookConsumer extends StatefulHookWidget { ++ const HookConsumer({super.key}); ++ ++ @override ++ State createState() => _HookConsumerState(); ++ } ++ ++ class _HookConsumerState extends State { ++ @override ++ Widget build( ++ BuildContext context) { + return const Placeholder(); + } +``` +--- +Message: `Convert to StatefulHookWidget` +Priority: 28 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:46`: +``` +} + +- class Stateful extends StatefulWidget { ++ class Stateful extends StatefulHookWidget { + const Stateful({super.key}); + +``` +--- +Message: `Convert to StatefulHookWidget` +Priority: 28 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:62`: +``` +} + +- class ExplicitCreateState extends StatefulWidget { ++ class ExplicitCreateState extends StatefulHookWidget { + const ExplicitCreateState({super.key}); + +``` +--- +Message: `Convert to StatefulHookWidget` +Priority: 32 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:92`: +``` +} + +- class ConsumerStateful extends ConsumerStatefulWidget { +- const ConsumerStateful({super.key}); +- +- @override +- ConsumerState createState() => _ConsumerStatefulState(); +- } +- +- class _ConsumerStatefulState extends ConsumerState { ++ class ConsumerStateful extends StatefulHookWidget { ++ const ConsumerStateful({super.key}); ++ ++ @override ++ State createState() => _ConsumerStatefulState(); ++ } ++ ++ class _ConsumerStatefulState extends State { + @override + Widget build(BuildContext context) { +``` +--- +Message: `Convert to StatefulHookWidget` +Priority: 32 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:106`: +``` +} + +- class HookConsumerStateful extends StatefulHookConsumerWidget { +- const HookConsumerStateful({super.key}); +- +- @override +- ConsumerState createState() => +- _HookConsumerStatefulState(); +- } +- +- class _HookConsumerStatefulState extends ConsumerState { ++ class HookConsumerStateful extends StatefulHookWidget { ++ const HookConsumerStateful({super.key}); ++ ++ @override ++ State createState() => ++ _HookConsumerStatefulState(); ++ } ++ ++ class _HookConsumerStatefulState extends State { + @override + Widget build(BuildContext context) { +``` +--- +Message: `Convert to StatefulHookWidget` +Priority: 32 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:121`: +``` +} + +- class Consumer extends ConsumerWidget { +- const Consumer({super.key}); +- +- @override +- Widget build( +- BuildContext context, +- WidgetRef ref, +- ) { ++ class Consumer extends StatefulHookWidget { ++ const Consumer({super.key}); ++ ++ @override ++ State createState() => _ConsumerState(); ++ } ++ ++ class _ConsumerState extends State { ++ @override ++ Widget build( ++ BuildContext context) { + return const Placeholder(); + } +``` +--- +Message: `Convert to StatefulHookWidget` +Priority: 32 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:133`: +``` +} + +- class StatelessWithField extends StatelessWidget { +- const StatelessWithField({ +- super.key, +- required this.field, +- }); +- +- final int field; +- static final int staticField = 42; +- +- @override +- Widget build(BuildContext context) { +- return Column( +- children: [ +- Text('$field'), +- Text('$staticField'), ++ class StatelessWithField extends StatefulHookWidget { ++ const StatelessWithField({ ++ super.key, ++ required this.field, ++ }); ++ ++ final int field; ++ static final int staticField = 42; ++ ++ @override ++ State createState() => _StatelessWithFieldState(); ++ } ++ ++ class _StatelessWithFieldState extends State { ++ @override ++ Widget build(BuildContext context) { ++ return Column( ++ children: [ ++ Text('${widget.field}'), ++ Text('${StatelessWithField.staticField}'), + ], + ); +``` +--- +Message: `Convert to StatefulHookWidget` +Priority: 32 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:153`: +``` +} + +- class HookConsumerWithField extends HookConsumerWidget { +- const HookConsumerWithField({ +- super.key, +- required this.field, +- }); +- +- final int field; +- static final int staticField = 42; +- +- @override +- Widget build( +- BuildContext context, +- WidgetRef ref, +- ) { +- return Column( +- children: [ +- Text('$field'), +- Text('$staticField'), ++ class HookConsumerWithField extends StatefulHookWidget { ++ const HookConsumerWithField({ ++ super.key, ++ required this.field, ++ }); ++ ++ final int field; ++ static final int staticField = 42; ++ ++ @override ++ State createState() => _HookConsumerWithFieldState(); ++ } ++ ++ class _HookConsumerWithFieldState extends State { ++ @override ++ Widget build( ++ BuildContext context) { ++ return Column( ++ children: [ ++ Text('${widget.field}'), ++ Text('${HookConsumerWithField.staticField}'), + ], + ); +``` +--- +Message: `Convert to StatefulHookWidget` +Priority: 32 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:180`: +``` +} + +- class ConsumerStatefulWithField extends ConsumerStatefulWidget { +- const ConsumerStatefulWithField({ +- super.key, +- required this.field, +- required this.foo, +- }); +- +- final int field; +- final FooClass foo; +- static final int staticField = _constantNumber; +- +- @override +- ConsumerState createState() => +- _ConsumerStatefulWithFieldState(); +- } +- +- const _constantNumber = 42; +- +- class _ConsumerStatefulWithFieldState +- extends ConsumerState { ++ class ConsumerStatefulWithField extends StatefulHookWidget { ++ const ConsumerStatefulWithField({ ++ super.key, ++ required this.field, ++ required this.foo, ++ }); ++ ++ final int field; ++ final FooClass foo; ++ static final int staticField = _constantNumber; ++ ++ @override ++ State createState() => ++ _ConsumerStatefulWithFieldState(); ++ } ++ ++ const _constantNumber = 42; ++ ++ class _ConsumerStatefulWithFieldState ++ extends State { + void printFoo() { + print(widget.foo); +``` +--- diff --git a/packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_stateful_hook_widget.json b/packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_stateful_hook_widget.json deleted file mode 100644 index 8190dcdbc..000000000 --- a/packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_stateful_hook_widget.json +++ /dev/null @@ -1 +0,0 @@ -[{"priority":32,"change":{"message":"Convert to StatefulHookWidget","edits":[{"fileStamp":0,"edits":[{"offset":221,"length":0,"replacement":"@override\n State createState() => _StatelessState();\n}\n\nclass _StatelessState extends State {\n"},{"offset":168,"length":15,"replacement":"StatefulHookWidget"}]}],"linkedEditGroups":[]}},{"priority":32,"change":{"message":"Convert to StatefulHookWidget","edits":[{"fileStamp":0,"edits":[{"offset":404,"length":0,"replacement":"@override\n State createState() => _StatelessWithCommaState();\n}\n\nclass _StatelessWithCommaState extends State {\n"},{"offset":342,"length":15,"replacement":"StatefulHookWidget"}]}],"linkedEditGroups":[]}},{"priority":32,"change":{"message":"Convert to StatefulHookWidget","edits":[{"fileStamp":0,"edits":[{"offset":563,"length":0,"replacement":"@override\n State createState() => _HookState();\n}\n\nclass _HookState extends State {\n"},{"offset":520,"length":10,"replacement":"StatefulHookWidget"}]}],"linkedEditGroups":[]}},{"priority":32,"change":{"message":"Convert to StatefulHookWidget","edits":[{"fileStamp":0,"edits":[{"offset":787,"length":23,"replacement":""},{"offset":737,"length":0,"replacement":"@override\n State createState() => _HookConsumerState();\n}\n\nclass _HookConsumerState extends State {\n"},{"offset":678,"length":18,"replacement":"StatefulHookWidget"}]}],"linkedEditGroups":[]}},{"priority":28,"change":{"message":"Convert to StatefulHookWidget","edits":[{"fileStamp":0,"edits":[{"offset":1022,"length":15,"replacement":"State"},{"offset":939,"length":15,"replacement":"State"},{"offset":876,"length":14,"replacement":"StatefulHookWidget"}]}],"linkedEditGroups":[]}},{"priority":28,"change":{"message":"Convert to StatefulHookWidget","edits":[{"fileStamp":0,"edits":[{"offset":1389,"length":26,"replacement":"State"},{"offset":1203,"length":14,"replacement":"StatefulHookWidget"}]}],"linkedEditGroups":[]}},{"priority":32,"change":{"message":"Convert to StatefulHookWidget","edits":[{"fileStamp":0,"edits":[{"offset":2045,"length":31,"replacement":"State"},{"offset":1930,"length":31,"replacement":"State"},{"offset":1851,"length":22,"replacement":"StatefulHookWidget"}]}],"linkedEditGroups":[]}},{"priority":32,"change":{"message":"Convert to StatefulHookWidget","edits":[{"fileStamp":0,"edits":[{"offset":2424,"length":35,"replacement":"State"},{"offset":2291,"length":35,"replacement":"State"},{"offset":2204,"length":26,"replacement":"StatefulHookWidget"}]}],"linkedEditGroups":[]}},{"priority":32,"change":{"message":"Convert to StatefulHookWidget","edits":[{"fileStamp":0,"edits":[{"offset":2676,"length":23,"replacement":""},{"offset":2626,"length":0,"replacement":"@override\n State createState() => _ConsumerState();\n}\n\nclass _ConsumerState extends State {\n"},{"offset":2575,"length":14,"replacement":"StatefulHookWidget"}]}],"linkedEditGroups":[]}}] \ No newline at end of file diff --git a/packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_stateful_widget.diff b/packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_stateful_widget.diff new file mode 100644 index 000000000..86ce03af7 --- /dev/null +++ b/packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_stateful_widget.diff @@ -0,0 +1,245 @@ +Message: `Convert to StatefulWidget` +Priority: 30 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:25`: +``` +} + +- class Hook extends HookWidget { +- const Hook({super.key}); +- +- @override ++ class Hook extends StatefulWidget { ++ const Hook({super.key}); ++ ++ @override ++ State createState() => _HookState(); ++ } ++ ++ class _HookState extends State { +@override + Widget build(BuildContext context) { +``` +--- +Message: `Convert to StatefulWidget` +Priority: 30 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:34`: +``` +} + +- class HookConsumer extends HookConsumerWidget { +- const HookConsumer({super.key}); +- +- @override +- Widget build( +- BuildContext context, +- WidgetRef ref, +- ) { ++ class HookConsumer extends StatefulWidget { ++ const HookConsumer({super.key}); ++ ++ @override ++ State createState() => _HookConsumerState(); ++ } ++ ++ class _HookConsumerState extends State { ++ @override ++ Widget build( ++ BuildContext context) { + return const Placeholder(); + } +``` +--- +Message: `Convert to StatefulWidget` +Priority: 30 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:78`: +``` +} + +- class HookStateful extends StatefulHookWidget { ++ class HookStateful extends StatefulWidget { + const HookStateful({super.key}); + +``` +--- +Message: `Convert to StatefulWidget` +Priority: 30 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:92`: +``` +} + +- class ConsumerStateful extends ConsumerStatefulWidget { +- const ConsumerStateful({super.key}); +- +- @override +- ConsumerState createState() => _ConsumerStatefulState(); +- } +- +- class _ConsumerStatefulState extends ConsumerState { ++ class ConsumerStateful extends StatefulWidget { ++ const ConsumerStateful({super.key}); ++ ++ @override ++ State createState() => _ConsumerStatefulState(); ++ } ++ ++ class _ConsumerStatefulState extends State { + @override + Widget build(BuildContext context) { +``` +--- +Message: `Convert to StatefulWidget` +Priority: 30 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:106`: +``` +} + +- class HookConsumerStateful extends StatefulHookConsumerWidget { +- const HookConsumerStateful({super.key}); +- +- @override +- ConsumerState createState() => +- _HookConsumerStatefulState(); +- } +- +- class _HookConsumerStatefulState extends ConsumerState { ++ class HookConsumerStateful extends StatefulWidget { ++ const HookConsumerStateful({super.key}); ++ ++ @override ++ State createState() => ++ _HookConsumerStatefulState(); ++ } ++ ++ class _HookConsumerStatefulState extends State { + @override + Widget build(BuildContext context) { +``` +--- +Message: `Convert to StatefulWidget` +Priority: 30 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:121`: +``` +} + +- class Consumer extends ConsumerWidget { +- const Consumer({super.key}); +- +- @override +- Widget build( +- BuildContext context, +- WidgetRef ref, +- ) { ++ class Consumer extends StatefulWidget { ++ const Consumer({super.key}); ++ ++ @override ++ State createState() => _ConsumerState(); ++ } ++ ++ class _ConsumerState extends State { ++ @override ++ Widget build( ++ BuildContext context) { + return const Placeholder(); + } +``` +--- +Message: `Convert to StatefulWidget` +Priority: 30 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:153`: +``` +} + +- class HookConsumerWithField extends HookConsumerWidget { +- const HookConsumerWithField({ +- super.key, +- required this.field, +- }); +- +- final int field; +- static final int staticField = 42; +- +- @override +- Widget build( +- BuildContext context, +- WidgetRef ref, +- ) { +- return Column( +- children: [ +- Text('$field'), +- Text('$staticField'), ++ class HookConsumerWithField extends StatefulWidget { ++ const HookConsumerWithField({ ++ super.key, ++ required this.field, ++ }); ++ ++ final int field; ++ static final int staticField = 42; ++ ++ @override ++ State createState() => _HookConsumerWithFieldState(); ++ } ++ ++ class _HookConsumerWithFieldState extends State { ++ @override ++ Widget build( ++ BuildContext context) { ++ return Column( ++ children: [ ++ Text('${widget.field}'), ++ Text('${HookConsumerWithField.staticField}'), + ], + ); +``` +--- +Message: `Convert to StatefulWidget` +Priority: 30 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:180`: +``` +} + +- class ConsumerStatefulWithField extends ConsumerStatefulWidget { +- const ConsumerStatefulWithField({ +- super.key, +- required this.field, +- required this.foo, +- }); +- +- final int field; +- final FooClass foo; +- static final int staticField = _constantNumber; +- +- @override +- ConsumerState createState() => +- _ConsumerStatefulWithFieldState(); +- } +- +- const _constantNumber = 42; +- +- class _ConsumerStatefulWithFieldState +- extends ConsumerState { ++ class ConsumerStatefulWithField extends StatefulWidget { ++ const ConsumerStatefulWithField({ ++ super.key, ++ required this.field, ++ required this.foo, ++ }); ++ ++ final int field; ++ final FooClass foo; ++ static final int staticField = _constantNumber; ++ ++ @override ++ State createState() => ++ _ConsumerStatefulWithFieldState(); ++ } ++ ++ const _constantNumber = 42; ++ ++ class _ConsumerStatefulWithFieldState ++ extends State { + void printFoo() { + print(widget.foo); +``` +--- diff --git a/packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_stateful_widget.json b/packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_stateful_widget.json deleted file mode 100644 index 1b18dc0fd..000000000 --- a/packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_stateful_widget.json +++ /dev/null @@ -1 +0,0 @@ -[{"priority":30,"change":{"message":"Convert to StatefulWidget","edits":[{"fileStamp":0,"edits":[{"offset":563,"length":0,"replacement":"@override\n State createState() => _HookState();\n}\n\nclass _HookState extends State {\n"},{"offset":520,"length":10,"replacement":"StatefulWidget"}]}],"linkedEditGroups":[]}},{"priority":30,"change":{"message":"Convert to StatefulWidget","edits":[{"fileStamp":0,"edits":[{"offset":787,"length":23,"replacement":""},{"offset":737,"length":0,"replacement":"@override\n State createState() => _HookConsumerState();\n}\n\nclass _HookConsumerState extends State {\n"},{"offset":678,"length":18,"replacement":"StatefulWidget"}]}],"linkedEditGroups":[]}},{"priority":30,"change":{"message":"Convert to StatefulWidget","edits":[{"fileStamp":0,"edits":[{"offset":1708,"length":19,"replacement":"State"},{"offset":1615,"length":19,"replacement":"State"},{"offset":1544,"length":18,"replacement":"StatefulWidget"}]}],"linkedEditGroups":[]}},{"priority":30,"change":{"message":"Convert to StatefulWidget","edits":[{"fileStamp":0,"edits":[{"offset":2045,"length":31,"replacement":"State"},{"offset":1930,"length":31,"replacement":"State"},{"offset":1851,"length":22,"replacement":"StatefulWidget"}]}],"linkedEditGroups":[]}},{"priority":30,"change":{"message":"Convert to StatefulWidget","edits":[{"fileStamp":0,"edits":[{"offset":2424,"length":35,"replacement":"State"},{"offset":2291,"length":35,"replacement":"State"},{"offset":2204,"length":26,"replacement":"StatefulWidget"}]}],"linkedEditGroups":[]}},{"priority":30,"change":{"message":"Convert to StatefulWidget","edits":[{"fileStamp":0,"edits":[{"offset":2676,"length":23,"replacement":""},{"offset":2626,"length":0,"replacement":"@override\n State createState() => _ConsumerState();\n}\n\nclass _ConsumerState extends State {\n"},{"offset":2575,"length":14,"replacement":"StatefulWidget"}]}],"linkedEditGroups":[]}}] \ No newline at end of file diff --git a/packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_stateless_widget.diff b/packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_stateless_widget.diff new file mode 100644 index 000000000..fbf3ac38e --- /dev/null +++ b/packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_stateless_widget.diff @@ -0,0 +1,240 @@ +Message: `Convert to StatelessWidget` +Priority: 34 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:25`: +``` +} + +- class Hook extends HookWidget { ++ class Hook extends StatelessWidget { + const Hook({super.key}); + +``` +--- +Message: `Convert to StatelessWidget` +Priority: 34 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:34`: +``` +} + +- class HookConsumer extends HookConsumerWidget { +- const HookConsumer({super.key}); +- +- @override +- Widget build( +- BuildContext context, +- WidgetRef ref, +- ) { ++ class HookConsumer extends StatelessWidget { ++ const HookConsumer({super.key}); ++ ++ @override ++ Widget build( ++ BuildContext context) { + return const Placeholder(); + } +``` +--- +Message: `Convert to StatelessWidget` +Priority: 34 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:78`: +``` +} + +- class HookStateful extends StatefulHookWidget { +- const HookStateful({super.key}); +- +- @override +- State createState() => HookStatefulState(); +- } +- +- class HookStatefulState extends State { ++ class HookStateful extends StatelessWidget { ++ const HookStateful({super.key}); ++ ++ + @override + Widget build(BuildContext context) { +``` +--- +Message: `Convert to StatelessWidget` +Priority: 34 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:92`: +``` +} + +- class ConsumerStateful extends ConsumerStatefulWidget { +- const ConsumerStateful({super.key}); +- +- @override +- ConsumerState createState() => _ConsumerStatefulState(); +- } +- +- class _ConsumerStatefulState extends ConsumerState { ++ class ConsumerStateful extends StatelessWidget { ++ const ConsumerStateful({super.key}); ++ ++ + @override + Widget build(BuildContext context) { +``` +--- +Message: `Convert to StatelessWidget` +Priority: 34 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:106`: +``` +} + +- class HookConsumerStateful extends StatefulHookConsumerWidget { +- const HookConsumerStateful({super.key}); +- +- @override +- ConsumerState createState() => +- _HookConsumerStatefulState(); +- } +- +- class _HookConsumerStatefulState extends ConsumerState { ++ class HookConsumerStateful extends StatelessWidget { ++ const HookConsumerStateful({super.key}); ++ ++ + @override + Widget build(BuildContext context) { +``` +--- +Message: `Convert to StatelessWidget` +Priority: 34 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:121`: +``` +} + +- class Consumer extends ConsumerWidget { +- const Consumer({super.key}); +- +- @override +- Widget build( +- BuildContext context, +- WidgetRef ref, +- ) { ++ class Consumer extends StatelessWidget { ++ const Consumer({super.key}); ++ ++ @override ++ Widget build( ++ BuildContext context) { + return const Placeholder(); + } +``` +--- +Message: `Convert to StatelessWidget` +Priority: 34 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:153`: +``` +} + +- class HookConsumerWithField extends HookConsumerWidget { +- const HookConsumerWithField({ +- super.key, +- required this.field, +- }); +- +- final int field; +- static final int staticField = 42; +- +- @override +- Widget build( +- BuildContext context, +- WidgetRef ref, +- ) { ++ class HookConsumerWithField extends StatelessWidget { ++ const HookConsumerWithField({ ++ super.key, ++ required this.field, ++ }); ++ ++ final int field; ++ static final int staticField = 42; ++ ++ @override ++ Widget build( ++ BuildContext context) { + return Column( + children: [ +``` +--- +Message: `Convert to StatelessWidget` +Priority: 34 +Diff for file `test/assists/convert_to_widget/convert_to_widget.dart:180`: +``` +} + +- class ConsumerStatefulWithField extends ConsumerStatefulWidget { +- const ConsumerStatefulWithField({ +- super.key, +- required this.field, +- required this.foo, +- }); +- +- final int field; +- final FooClass foo; +- static final int staticField = _constantNumber; +- +- @override +- ConsumerState createState() => +- _ConsumerStatefulWithFieldState(); +- } +- +- const _constantNumber = 42; +- +- class _ConsumerStatefulWithFieldState +- extends ConsumerState { +- void printFoo() { +- print(widget.foo); +- } +- +- @override +- Widget build(BuildContext context) { +- print(widget.field); +- printFoo(); +- return Column( +- children: [ +- Text('${widget.field}'), +- Text('${widget.foo.bar}'), +- Text('${ConsumerStatefulWithField.staticField}'), +- ], +- ); +- } +- } ++ class ConsumerStatefulWithField extends StatelessWidget { ++ const ConsumerStatefulWithField({ ++ super.key, ++ required this.field, ++ required this.foo, ++ }); ++ ++ final int field; ++ final FooClass foo; ++ static final int staticField = _constantNumber; ++ ++ ++ void printFoo() { ++ print(foo); ++ } ++ ++ @override ++ Widget build(BuildContext context) { ++ print(field); ++ printFoo(); ++ return Column( ++ children: [ ++ Text('$field'), ++ Text('${foo.bar}'), ++ Text('$staticField'), ++ ], ++ ); ++ } ++ } ++ ++ const _constantNumber = 42; + +``` +--- diff --git a/packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_stateless_widget.json b/packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_stateless_widget.json deleted file mode 100644 index 71d0987a6..000000000 --- a/packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_stateless_widget.json +++ /dev/null @@ -1 +0,0 @@ -[{"priority":34,"change":{"message":"Convert to StatelessWidget","edits":[{"fileStamp":0,"edits":[{"offset":520,"length":10,"replacement":"StatelessWidget"}]}],"linkedEditGroups":[]}},{"priority":34,"change":{"message":"Convert to StatelessWidget","edits":[{"fileStamp":0,"edits":[{"offset":787,"length":23,"replacement":""},{"offset":678,"length":18,"replacement":"StatelessWidget"}]}],"linkedEditGroups":[]}},{"priority":34,"change":{"message":"Convert to StatelessWidget","edits":[{"fileStamp":0,"edits":[{"offset":1676,"length":142,"replacement":""},{"offset":1673,"length":0,"replacement":"@override\n Widget build(BuildContext context) {\n return const Placeholder();\n }\n"},{"offset":1603,"length":69,"replacement":""},{"offset":1544,"length":18,"replacement":"StatelessWidget"}]}],"linkedEditGroups":[]}},{"priority":34,"change":{"message":"Convert to StatelessWidget","edits":[{"fileStamp":0,"edits":[{"offset":2008,"length":159,"replacement":""},{"offset":2005,"length":0,"replacement":"@override\n Widget build(BuildContext context) {\n return const Placeholder();\n }\n"},{"offset":1918,"length":86,"replacement":""},{"offset":1851,"length":22,"replacement":"StatelessWidget"}]}],"linkedEditGroups":[]}},{"priority":34,"change":{"message":"Convert to StatelessWidget","edits":[{"fileStamp":0,"edits":[{"offset":2383,"length":167,"replacement":""},{"offset":2380,"length":0,"replacement":"@override\n Widget build(BuildContext context) {\n return const Placeholder();\n }\n"},{"offset":2279,"length":100,"replacement":""},{"offset":2204,"length":26,"replacement":"StatelessWidget"}]}],"linkedEditGroups":[]}},{"priority":34,"change":{"message":"Convert to StatelessWidget","edits":[{"fileStamp":0,"edits":[{"offset":2676,"length":23,"replacement":""},{"offset":2575,"length":14,"replacement":"StatelessWidget"}]}],"linkedEditGroups":[]}}] \ No newline at end of file diff --git a/packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_widget.dart b/packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_widget.dart index 04120bfc3..35ffffa14 100644 --- a/packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_widget.dart +++ b/packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_widget.dart @@ -129,3 +129,88 @@ class Consumer extends ConsumerWidget { return const Placeholder(); } } + +class StatelessWithField extends StatelessWidget { + const StatelessWithField({ + super.key, + required this.field, + }); + + final int field; + static final int staticField = 42; + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Text('$field'), + Text('$staticField'), + ], + ); + } +} + +class HookConsumerWithField extends HookConsumerWidget { + const HookConsumerWithField({ + super.key, + required this.field, + }); + + final int field; + static final int staticField = 42; + + @override + Widget build( + BuildContext context, + WidgetRef ref, + ) { + return Column( + children: [ + Text('$field'), + Text('$staticField'), + ], + ); + } +} + +class FooClass { + final bar = 42; +} + +class ConsumerStatefulWithField extends ConsumerStatefulWidget { + const ConsumerStatefulWithField({ + super.key, + required this.field, + required this.foo, + }); + + final int field; + final FooClass foo; + static final int staticField = _constantNumber; + + @override + ConsumerState createState() => + _ConsumerStatefulWithFieldState(); +} + +const _constantNumber = 42; + +class _ConsumerStatefulWithFieldState + extends ConsumerState { + void printFoo() { + print(widget.foo); + } + + @override + Widget build(BuildContext context) { + print(widget.field); + printFoo(); + return Column( + children: [ + Text('${widget.field}'), + Text('${widget.foo.bar}'), + Text('${ConsumerStatefulWithField.staticField}'), + ], + ); + } +} diff --git a/packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_widget_test.dart b/packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_widget_test.dart index b3b2eb9a8..664c6e71e 100644 --- a/packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_widget_test.dart +++ b/packages/riverpod_lint_flutter_test/test/assists/convert_to_widget/convert_to_widget_test.dart @@ -1,7 +1,3 @@ -import 'dart:io'; - -import 'package:analyzer/dart/analysis/results.dart'; -import 'package:analyzer/dart/analysis/utilities.dart'; import 'package:analyzer/source/source_range.dart'; import 'package:pubspec_parse/pubspec_parse.dart'; import 'package:riverpod_lint/src/assists/convert_to_stateful_base_widget.dart'; @@ -23,28 +19,54 @@ void main() { StatelessBaseWidgetType.values.forEach( (targetWidget) { + final int expectedChangeCount; + switch (targetWidget) { + case StatelessBaseWidgetType.hookConsumerWidget: + expectedChangeCount = 11; + break; + case StatelessBaseWidgetType.hookWidget: + case StatelessBaseWidgetType.consumerWidget: + expectedChangeCount = 12; + break; + case StatelessBaseWidgetType.statelessWidget: + expectedChangeCount = 8; + break; + } _runGoldenTest( ConvertToStatelessBaseWidget( targetWidget: targetWidget, ), 'Convert widgets to ${targetWidget.name}s with hooks_riverpod and flutter_hooks dependency', - 'assists/convert_to_widget/convert_to_${targetWidget.name.toSnakeCase()}.json', + 'assists/convert_to_widget/convert_to_${targetWidget.name.toSnakeCase()}.diff', pubspecWithDependencies, - targetWidget == StatelessBaseWidgetType.statelessWidget ? 6 : 9, + expectedChangeCount, ); }, ); StatefulBaseWidgetType.values.forEach( (targetWidget) { + final int expectedChangeCount; + switch (targetWidget) { + case StatefulBaseWidgetType.statefulHookConsumerWidget: + case StatefulBaseWidgetType.statefulHookWidget: + expectedChangeCount = 12; + break; + case StatefulBaseWidgetType.consumerStatefulWidget: + expectedChangeCount = 11; + break; + case StatefulBaseWidgetType.statefulWidget: + expectedChangeCount = 8; + break; + } _runGoldenTest( ConvertToStatefulBaseWidget( targetWidget: targetWidget, ), 'Convert widgets to ${targetWidget.name}s with hooks_riverpod and flutter_hooks dependency', - 'assists/convert_to_widget/convert_to_${targetWidget.name.toSnakeCase()}.json', + 'assists/convert_to_widget/convert_to_${targetWidget.name.toSnakeCase()}.diff', pubspecWithDependencies, - targetWidget == StatefulBaseWidgetType.statefulWidget ? 6 : 9, + expectedChangeCount, ); }, ); @@ -58,26 +80,26 @@ void main() { final int expectedChangeCount; switch (targetWidget) { case StatelessBaseWidgetType.consumerWidget: - expectedChangeCount = 9; + expectedChangeCount = 12; break; case StatelessBaseWidgetType.hookWidget: case StatelessBaseWidgetType.hookConsumerWidget: expectedChangeCount = 0; break; case StatelessBaseWidgetType.statelessWidget: - expectedChangeCount = 6; + expectedChangeCount = 8; break; } final String goldenFilePath; switch (targetWidget) { case StatelessBaseWidgetType.hookWidget: case StatelessBaseWidgetType.hookConsumerWidget: - goldenFilePath = 'assists/empty.json'; + goldenFilePath = 'assists/empty.diff'; break; case StatelessBaseWidgetType.consumerWidget: case StatelessBaseWidgetType.statelessWidget: goldenFilePath = - 'assists/convert_to_widget/convert_to_${targetWidget.name.toSnakeCase()}.json'; + 'assists/convert_to_widget/convert_to_${targetWidget.name.toSnakeCase()}.diff'; break; } @@ -98,26 +120,26 @@ void main() { final int expectedChangeCount; switch (targetWidget) { case StatefulBaseWidgetType.consumerStatefulWidget: - expectedChangeCount = 9; + expectedChangeCount = 11; break; case StatefulBaseWidgetType.statefulHookWidget: case StatefulBaseWidgetType.statefulHookConsumerWidget: expectedChangeCount = 0; break; case StatefulBaseWidgetType.statefulWidget: - expectedChangeCount = 6; + expectedChangeCount = 8; break; } final String goldenFilePath; switch (targetWidget) { case StatefulBaseWidgetType.statefulHookWidget: case StatefulBaseWidgetType.statefulHookConsumerWidget: - goldenFilePath = 'assists/empty.json'; + goldenFilePath = 'assists/empty.diff'; break; case StatefulBaseWidgetType.consumerStatefulWidget: case StatefulBaseWidgetType.statefulWidget: goldenFilePath = - 'assists/convert_to_widget/convert_to_${targetWidget.name.toSnakeCase()}.json'; + 'assists/convert_to_widget/convert_to_${targetWidget.name.toSnakeCase()}.diff'; break; } @@ -153,14 +175,8 @@ void _runGoldenTest( testGolden( description, goldenFilePath, - () async { - final file = File( - 'test/assists/convert_to_widget/convert_to_widget.dart', - ).absolute; - - final result = await resolveFile2(path: file.path); - result as ResolvedUnitResult; - + sourcePath: 'test/assists/convert_to_widget/convert_to_widget.dart', + (result) async { final changes = [ // Stateless ...await assist.testRun(result, const SourceRange(163, 0), @@ -205,6 +221,18 @@ void _runGoldenTest( // ConsumerWidget ...await assist.testRun(result, const SourceRange(2582, 0), pubspec: pubspec), + + // StatelessWithField + ...await assist.testRun(result, const SourceRange(2784, 0), + pubspec: pubspec), + + // HookConsumerWithField + ...await assist.testRun(result, const SourceRange(3139, 0), + pubspec: pubspec), + + // ConsumerStatefulWithField + ...await assist.testRun(result, const SourceRange(3571, 0), + pubspec: pubspec), ]; expect(changes, hasLength(expectedChangeCount)); diff --git a/packages/riverpod_lint_flutter_test/test/assists/empty.diff b/packages/riverpod_lint_flutter_test/test/assists/empty.diff new file mode 100644 index 000000000..e69de29bb diff --git a/packages/riverpod_lint_flutter_test/test/assists/empty.json b/packages/riverpod_lint_flutter_test/test/assists/empty.json deleted file mode 100644 index 0637a088a..000000000 --- a/packages/riverpod_lint_flutter_test/test/assists/empty.json +++ /dev/null @@ -1 +0,0 @@ -[] \ No newline at end of file diff --git a/packages/riverpod_lint_flutter_test/test/assists/wrap_widget/wrap_widget_test.dart b/packages/riverpod_lint_flutter_test/test/assists/wrap_widget/wrap_widget_test.dart index b1d9a00c4..e45b6cc7b 100644 --- a/packages/riverpod_lint_flutter_test/test/assists/wrap_widget/wrap_widget_test.dart +++ b/packages/riverpod_lint_flutter_test/test/assists/wrap_widget/wrap_widget_test.dart @@ -1,24 +1,17 @@ -import 'dart:io'; - import 'package:test/test.dart'; import 'package:riverpod_lint/src/assists/wrap_with_consumer.dart'; import 'package:riverpod_lint/src/assists/wrap_with_provider_scope.dart'; import 'package:analyzer/source/source_range.dart'; -import 'package:analyzer/dart/analysis/results.dart'; -import 'package:analyzer/dart/analysis/utilities.dart'; import '../../golden.dart'; void main() { testGolden( 'Wrap with consumer', - 'assists/wrap_widget/wrap_with_consumer.json', - () async { + 'assists/wrap_widget/wrap_with_consumer.diff', + sourcePath: 'test/assists/wrap_widget/wrap_widget.dart', + (result) async { final assist = WrapWithConsumer(); - final file = File('test/assists/wrap_widget/wrap_widget.dart').absolute; - - final result = await resolveFile2(path: file.path); - result as ResolvedUnitResult; var changes = [ // Map @@ -42,13 +35,10 @@ void main() { testGolden( 'Wrap with ProviderScope', - 'assists/wrap_widget/wrap_with_provider_scope.json', - () async { + 'assists/wrap_widget/wrap_with_provider_scope.diff', + sourcePath: 'test/assists/wrap_widget/wrap_widget.dart', + (result) async { final assist = WrapWithProviderScope(); - final file = File('test/assists/wrap_widget/wrap_widget.dart').absolute; - - final result = await resolveFile2(path: file.path); - result as ResolvedUnitResult; final changes = [ // Map diff --git a/packages/riverpod_lint_flutter_test/test/assists/wrap_widget/wrap_with_consumer.diff b/packages/riverpod_lint_flutter_test/test/assists/wrap_widget/wrap_with_consumer.diff new file mode 100644 index 000000000..acf024446 --- /dev/null +++ b/packages/riverpod_lint_flutter_test/test/assists/wrap_widget/wrap_with_consumer.diff @@ -0,0 +1,28 @@ +Message: `Wrap with Consumer` +Priority: 28 +Diff for file `test/assists/wrap_widget/wrap_widget.dart:11`: +``` + Map(); + +- return Scaffold( +- body: Container(), +- ); ++ return Consumer(builder: (context, ref, child) { return Scaffold( ++ body: Container(), ++ ); },); + } +} +``` +--- +Message: `Wrap with Consumer` +Priority: 28 +Diff for file `test/assists/wrap_widget/wrap_widget.dart:12`: +``` + + return Scaffold( +- body: Container(), ++ body: Consumer(builder: (context, ref, child) { return Container(); },), + ); + } +``` +--- diff --git a/packages/riverpod_lint_flutter_test/test/assists/wrap_widget/wrap_with_consumer.json b/packages/riverpod_lint_flutter_test/test/assists/wrap_widget/wrap_with_consumer.json deleted file mode 100644 index 7fdca8789..000000000 --- a/packages/riverpod_lint_flutter_test/test/assists/wrap_widget/wrap_with_consumer.json +++ /dev/null @@ -1 +0,0 @@ -[{"priority":28,"change":{"message":"Wrap with Consumer","edits":[{"fileStamp":0,"edits":[{"offset":281,"length":0,"replacement":"; },)"},{"offset":241,"length":0,"replacement":"Consumer(builder: (context, ref, child) { return "}]}],"linkedEditGroups":[]}},{"priority":28,"change":{"message":"Wrap with Consumer","edits":[{"fileStamp":0,"edits":[{"offset":274,"length":0,"replacement":"; },)"},{"offset":263,"length":0,"replacement":"Consumer(builder: (context, ref, child) { return "}]}],"linkedEditGroups":[]}}] \ No newline at end of file diff --git a/packages/riverpod_lint_flutter_test/test/assists/wrap_widget/wrap_with_provider_scope.diff b/packages/riverpod_lint_flutter_test/test/assists/wrap_widget/wrap_with_provider_scope.diff new file mode 100644 index 000000000..96714d099 --- /dev/null +++ b/packages/riverpod_lint_flutter_test/test/assists/wrap_widget/wrap_with_provider_scope.diff @@ -0,0 +1,28 @@ +Message: `Wrap with ProviderScope` +Priority: 27 +Diff for file `test/assists/wrap_widget/wrap_widget.dart:11`: +``` + Map(); + +- return Scaffold( +- body: Container(), +- ); ++ return ProviderScope(child: Scaffold( ++ body: Container(), ++ ),); + } +} +``` +--- +Message: `Wrap with ProviderScope` +Priority: 27 +Diff for file `test/assists/wrap_widget/wrap_widget.dart:12`: +``` + + return Scaffold( +- body: Container(), ++ body: ProviderScope(child: Container(),), + ); + } +``` +--- diff --git a/packages/riverpod_lint_flutter_test/test/assists/wrap_widget/wrap_with_provider_scope.json b/packages/riverpod_lint_flutter_test/test/assists/wrap_widget/wrap_with_provider_scope.json deleted file mode 100644 index 6170c06a6..000000000 --- a/packages/riverpod_lint_flutter_test/test/assists/wrap_widget/wrap_with_provider_scope.json +++ /dev/null @@ -1 +0,0 @@ -[{"priority":27,"change":{"message":"Wrap with ProviderScope","edits":[{"fileStamp":0,"edits":[{"offset":281,"length":0,"replacement":",)"},{"offset":241,"length":0,"replacement":"ProviderScope(child: "}]}],"linkedEditGroups":[]}},{"priority":27,"change":{"message":"Wrap with ProviderScope","edits":[{"fileStamp":0,"edits":[{"offset":274,"length":0,"replacement":",)"},{"offset":263,"length":0,"replacement":"ProviderScope(child: "}]}],"linkedEditGroups":[]}}] \ No newline at end of file diff --git a/packages/riverpod_lint_flutter_test/test/golden.dart b/packages/riverpod_lint_flutter_test/test/golden.dart index 646a103f8..54158a692 100644 --- a/packages/riverpod_lint_flutter_test/test/golden.dart +++ b/packages/riverpod_lint_flutter_test/test/golden.dart @@ -1,4 +1,3 @@ -import 'dart:convert'; import 'dart:io'; import 'package:freezed_annotation/freezed_annotation.dart'; @@ -6,6 +5,8 @@ import 'package:test/test.dart'; import 'package:path/path.dart'; import 'package:custom_lint_core/custom_lint_core.dart'; import 'package:analyzer_plugin/protocol/protocol_generated.dart'; +import 'package:analyzer/dart/analysis/results.dart'; +import 'package:analyzer/dart/analysis/utilities.dart'; @Deprecated('Do not commit') var goldenWrite = false; @@ -25,35 +26,43 @@ File writeToTemporaryFile(String content) { void testGolden( String description, String fileName, - Future> Function() body, -) { + Future> Function(ResolvedUnitResult unit) + body, { + required String sourcePath, +}) { test(description, () async { - final changes = await body().then((value) => value.toList()); + final file = File(sourcePath).absolute; + + final result = await resolveFile2(path: file.path); + result as ResolvedUnitResult; + + final changes = await body(result).then((value) => value.toList()); + final source = file.readAsStringSync(); try { expect( changes, - matcherNormalizedPrioritizedSourceChangeSnapshot(fileName), + matcherNormalizedPrioritizedSourceChangeSnapshot( + fileName, + sources: {'**': source}, + relativePath: Directory.current.path, + ), ); } on TestFailure { // ignore: deprecated_member_use_from_same_package if (!goldenWrite) rethrow; - final file = File('test/$fileName'); - - final changesJson = changes.map((e) => e.toJson()).toList(); - // Remove all "file" references from the json. - for (final change in changesJson) { - final changeMap = change['change']! as Map; - final edits = changeMap['edits']! as List; - for (final edit in edits.cast>()) { - edit.remove('file'); - } - } + final source = File(sourcePath).readAsStringSync(); + final result = encodePrioritizedSourceChanges( + changes, + sources: {'**': source}, + relativePath: Directory.current.path, + ); - file + final golden = File('test/$fileName'); + golden ..createSync(recursive: true) - ..writeAsStringSync(jsonEncode(changesJson)); + ..writeAsStringSync(result); return; } }); diff --git a/packages/riverpod_lint_flutter_test/test/lints/async_value_nullable_pattern/async_value_nullable_pattern.dart b/packages/riverpod_lint_flutter_test/test/lints/async_value_nullable_pattern/async_value_nullable_pattern.dart index 875b15fbe..5980e8489 100644 --- a/packages/riverpod_lint_flutter_test/test/lints/async_value_nullable_pattern/async_value_nullable_pattern.dart +++ b/packages/riverpod_lint_flutter_test/test/lints/async_value_nullable_pattern/async_value_nullable_pattern.dart @@ -55,3 +55,26 @@ void main() { print(value); } } + +void fn(T obj) { + switch (obj) { + // expect_lint: async_value_nullable_pattern + case AsyncValue(:final value?): + print(value); + } +} + +void fn2(T obj) { + switch (obj) { + case AsyncValue(:final value?): + print(value); + } +} + +void fn3(T obj) { + switch (obj) { + // expect_lint: async_value_nullable_pattern + case AsyncValue(:final value?): + print(value); + } +} diff --git a/packages/riverpod_lint_flutter_test/test/lints/async_value_nullable_pattern/fix/async_value_nullable_pattern.diff b/packages/riverpod_lint_flutter_test/test/lints/async_value_nullable_pattern/fix/async_value_nullable_pattern.diff new file mode 100644 index 000000000..77c08f60a --- /dev/null +++ b/packages/riverpod_lint_flutter_test/test/lints/async_value_nullable_pattern/fix/async_value_nullable_pattern.diff @@ -0,0 +1,24 @@ +Message: `Use "hasValue: true" instead` +Priority: 100 +Diff for file `test/lints/async_value_nullable_pattern/fix/async_value_nullable_pattern.dart:8`: +``` + case AsyncValue( + // expect_lint: async_value_nullable_pattern +- :final value?, ++ :final value, hasValue: true, + ): + print(value); +``` +--- +Message: `Use "hasValue: true" instead` +Priority: 100 +Diff for file `test/lints/async_value_nullable_pattern/fix/async_value_nullable_pattern.dart:17`: +``` + case AsyncValue( + // expect_lint: async_value_nullable_pattern +- :final value? ++ :final value, hasValue: true + ): + print(value); +``` +--- diff --git a/packages/riverpod_lint_flutter_test/test/lints/async_value_nullable_pattern/fix/async_value_nullable_pattern.json b/packages/riverpod_lint_flutter_test/test/lints/async_value_nullable_pattern/fix/async_value_nullable_pattern.json deleted file mode 100644 index 93bdc91dd..000000000 --- a/packages/riverpod_lint_flutter_test/test/lints/async_value_nullable_pattern/fix/async_value_nullable_pattern.json +++ /dev/null @@ -1 +0,0 @@ -[{"priority":100,"change":{"message":"Use \"hasValue: true\" instead","edits":[{"fileStamp":0,"edits":[{"offset":257,"length":0,"replacement":", hasValue: true"},{"offset":256,"length":1,"replacement":""}]}],"linkedEditGroups":[]}},{"priority":100,"change":{"message":"Use \"hasValue: true\" instead","edits":[{"fileStamp":0,"edits":[{"offset":472,"length":0,"replacement":", hasValue: true"},{"offset":471,"length":1,"replacement":""}]}],"linkedEditGroups":[]}}] \ No newline at end of file diff --git a/packages/riverpod_lint_flutter_test/test/lints/async_value_nullable_pattern/fix/async_value_nullable_pattern_test.dart b/packages/riverpod_lint_flutter_test/test/lints/async_value_nullable_pattern/fix/async_value_nullable_pattern_test.dart index 3883c5e86..04739c72a 100644 --- a/packages/riverpod_lint_flutter_test/test/lints/async_value_nullable_pattern/fix/async_value_nullable_pattern_test.dart +++ b/packages/riverpod_lint_flutter_test/test/lints/async_value_nullable_pattern/fix/async_value_nullable_pattern_test.dart @@ -1,25 +1,17 @@ -import 'dart:io'; - import 'package:collection/collection.dart'; import 'package:riverpod_lint/src/lints/async_value_nullable_pattern.dart'; -import 'package:analyzer/dart/analysis/results.dart'; -import 'package:analyzer/dart/analysis/utilities.dart'; import '../../../golden.dart'; void main() { testGolden( 'Verify that @riverpod classes has the build method', - 'lints/async_value_nullable_pattern/fix/async_value_nullable_pattern.json', - () async { + 'lints/async_value_nullable_pattern/fix/async_value_nullable_pattern.diff', + sourcePath: + 'test/lints/async_value_nullable_pattern/fix/async_value_nullable_pattern.dart', + (result) async { const lint = AsyncValueNullablePattern(); final fix = lint.getFixes().single; - final file = File( - 'test/lints/async_value_nullable_pattern/fix/async_value_nullable_pattern.dart', - ).absolute; - - final result = await resolveFile2(path: file.path); - result as ResolvedUnitResult; final errors = await lint.testRun(result); diff --git a/packages/riverpod_lint_flutter_test/test/lints/notifier_build/fix/notifier_build.diff b/packages/riverpod_lint_flutter_test/test/lints/notifier_build/fix/notifier_build.diff new file mode 100644 index 000000000..6e6005b7a --- /dev/null +++ b/packages/riverpod_lint_flutter_test/test/lints/notifier_build/fix/notifier_build.diff @@ -0,0 +1,16 @@ +Message: `Add build method` +Priority: 80 +Diff for file `test/lints/notifier_build/fix/notifier_build.dart:8`: +``` +@riverpod +// expect_lint: notifier_build +- class ExampleProvider1 extends _$ExampleProvider1 {} ++ class ExampleProvider1 extends _$ExampleProvider1 { ++ @override ++ dynamic build() { ++ // TODO: implement build ++ throw UnimplementedError(); ++ } ++ } +``` +--- diff --git a/packages/riverpod_lint_flutter_test/test/lints/notifier_build/fix/notifier_build.json b/packages/riverpod_lint_flutter_test/test/lints/notifier_build/fix/notifier_build.json deleted file mode 100644 index 978f6cc17..000000000 --- a/packages/riverpod_lint_flutter_test/test/lints/notifier_build/fix/notifier_build.json +++ /dev/null @@ -1 +0,0 @@ -[{"priority":80,"change":{"message":"Add build method","edits":[{"fileStamp":0,"edits":[{"offset":212,"length":0,"replacement":"\n @override\n dynamic build() {\n // TODO: implement build\n throw UnimplementedError();\n }\n"}]}],"linkedEditGroups":[]}}] \ No newline at end of file diff --git a/packages/riverpod_lint_flutter_test/test/lints/notifier_build/fix/notifier_build_test.dart b/packages/riverpod_lint_flutter_test/test/lints/notifier_build/fix/notifier_build_test.dart index 40bf330c7..c0ddf8731 100644 --- a/packages/riverpod_lint_flutter_test/test/lints/notifier_build/fix/notifier_build_test.dart +++ b/packages/riverpod_lint_flutter_test/test/lints/notifier_build/fix/notifier_build_test.dart @@ -1,25 +1,16 @@ -import 'dart:io'; - import 'package:collection/collection.dart'; import 'package:riverpod_lint/src/lints/notifier_build.dart'; -import 'package:analyzer/dart/analysis/results.dart'; -import 'package:analyzer/dart/analysis/utilities.dart'; import '../../../golden.dart'; void main() { testGolden( 'Verify that @riverpod classes has the build method', - 'lints/notifier_build/fix/notifier_build.json', - () async { + 'lints/notifier_build/fix/notifier_build.diff', + sourcePath: 'test/lints/notifier_build/fix/notifier_build.dart', + (result) async { const lint = NotifierBuild(); final fix = lint.getFixes().single; - final file = File( - 'test/lints/notifier_build/fix/notifier_build.dart', - ).absolute; - - final result = await resolveFile2(path: file.path); - result as ResolvedUnitResult; final errors = await lint.testRun(result); diff --git a/packages/riverpod_lint_flutter_test/test/lints/notifier_extends/notifier_extends.diff b/packages/riverpod_lint_flutter_test/test/lints/notifier_extends/notifier_extends.diff new file mode 100644 index 000000000..985408240 --- /dev/null +++ b/packages/riverpod_lint_flutter_test/test/lints/notifier_extends/notifier_extends.diff @@ -0,0 +1,24 @@ +Message: `Extend _$NoExtends` +Priority: 90 +Diff for file `test/lints/notifier_extends/notifier_extends.dart:12`: +``` +@riverpod +// expect_lint: notifier_extends +- class NoExtends { ++ class NoExtends extends _$NoExtends { + int build() => 0; +} +``` +--- +Message: `Extend _$WrongExtends` +Priority: 90 +Diff for file `test/lints/notifier_extends/notifier_extends.dart:18`: +``` +@riverpod +// expect_lint: notifier_extends +- class WrongExtends extends AsyncNotifier { ++ class WrongExtends extends _$WrongExtends { + int build() => 0; +} +``` +--- diff --git a/packages/riverpod_lint_flutter_test/test/lints/notifier_extends/notifier_extends.json b/packages/riverpod_lint_flutter_test/test/lints/notifier_extends/notifier_extends.json deleted file mode 100644 index e76303fd3..000000000 --- a/packages/riverpod_lint_flutter_test/test/lints/notifier_extends/notifier_extends.json +++ /dev/null @@ -1 +0,0 @@ -[{"priority":90,"change":{"message":"Extend _$NoExtends","edits":[{"fileStamp":0,"edits":[{"offset":280,"length":0,"replacement":" extends _$NoExtends"}]}],"linkedEditGroups":[]}},{"priority":90,"change":{"message":"Extend _$WrongExtends","edits":[{"fileStamp":0,"edits":[{"offset":376,"length":18,"replacement":"_$WrongExtends"}]}],"linkedEditGroups":[]}},{"priority":90,"change":{"message":"Extend _$NoGenerics","edits":[{"fileStamp":0,"edits":[{"offset":780,"length":12,"replacement":"_$NoGenerics"}]}],"linkedEditGroups":[]}},{"priority":90,"change":{"message":"Extend _$MissingGenerics","edits":[{"fileStamp":0,"edits":[{"offset":897,"length":20,"replacement":"_$MissingGenerics"}]}],"linkedEditGroups":[]}},{"priority":90,"change":{"message":"Extend _$WrongOrder","edits":[{"fileStamp":0,"edits":[{"offset":1017,"length":18,"replacement":"_$WrongOrder"}]}],"linkedEditGroups":[]}}] \ No newline at end of file diff --git a/packages/riverpod_lint_flutter_test/test/lints/notifier_extends/notifier_extends_test.dart b/packages/riverpod_lint_flutter_test/test/lints/notifier_extends/notifier_extends_test.dart index 41e55a5ce..aa435bd7f 100644 --- a/packages/riverpod_lint_flutter_test/test/lints/notifier_extends/notifier_extends_test.dart +++ b/packages/riverpod_lint_flutter_test/test/lints/notifier_extends/notifier_extends_test.dart @@ -1,10 +1,6 @@ -import 'dart:io'; - import 'package:custom_lint_builder/custom_lint_builder.dart'; import 'package:collection/collection.dart'; import 'package:riverpod_lint/src/lints/notifier_extends.dart'; -import 'package:analyzer/dart/analysis/results.dart'; -import 'package:analyzer/dart/analysis/utilities.dart'; import 'package:test/test.dart'; import '../../golden.dart'; @@ -12,16 +8,11 @@ import '../../golden.dart'; void main() { testGolden( 'Verify that @riverpod classes extend the generated typedef', - 'lints/notifier_extends/notifier_extends.json', - () async { + 'lints/notifier_extends/notifier_extends.diff', + sourcePath: 'test/lints/notifier_extends/notifier_extends.dart', + (result) async { final lint = NotifierExtends(); final fix = lint.getFixes().single as DartFix; - final file = File( - 'test/lints/notifier_extends/notifier_extends.dart', - ).absolute; - - final result = await resolveFile2(path: file.path); - result as ResolvedUnitResult; final errors = await lint.testRun(result); expect(errors, hasLength(5)); diff --git a/packages/riverpod_lint_flutter_test/test/lints/provider_dependencies/provider_dependencies.diff b/packages/riverpod_lint_flutter_test/test/lints/provider_dependencies/provider_dependencies.diff new file mode 100644 index 000000000..fddcc1f43 --- /dev/null +++ b/packages/riverpod_lint_flutter_test/test/lints/provider_dependencies/provider_dependencies.diff @@ -0,0 +1,137 @@ +Message: `Specify "dependencies"` +Priority: 100 +Diff for file `test/lints/provider_dependencies/provider_dependencies.dart:14`: +``` + +// expect_lint: provider_dependencies +- @riverpod ++ @Riverpod(dependencies: [dep]) +int plainAnnotation(PlainAnnotationRef ref) { + ref.watch(depProvider); +``` +--- +Message: `Specify "dependencies"` +Priority: 100 +Diff for file `test/lints/provider_dependencies/provider_dependencies.dart:21`: +``` + +// expect_lint: provider_dependencies +- @Riverpod(keepAlive: false) ++ @Riverpod(keepAlive: false, dependencies: [dep]) +int customAnnotation(CustomAnnotationRef ref) { + ref.watch(depProvider); +``` +--- +Message: `Specify "dependencies"` +Priority: 100 +Diff for file `test/lints/provider_dependencies/provider_dependencies.dart:29`: +``` +// expect_lint: provider_dependencies +@Riverpod( +- keepAlive: false, ++ keepAlive: false, dependencies: [dep], +) +int customAnnotationWithTrailingComma( +``` +--- +Message: `Update "dependencies"` +Priority: 100 +Diff for file `test/lints/provider_dependencies/provider_dependencies.dart:41`: +``` + keepAlive: false, + // expect_lint: provider_dependencies +- dependencies: [], ++ dependencies: [dep], +) +int existingDep(ExistingDepRef ref) { +``` +--- +Message: `Update "dependencies"` +Priority: 100 +Diff for file `test/lints/provider_dependencies/provider_dependencies.dart:51`: +``` + keepAlive: false, + // expect_lint: provider_dependencies +- dependencies: [], ++ dependencies: [dep, dep2], +) +int multipleDeps(MultipleDepsRef ref) { +``` +--- +Message: `Update "dependencies"` +Priority: 100 +Diff for file `test/lints/provider_dependencies/provider_dependencies.dart:61`: +``` +@Riverpod( + keepAlive: false, +- dependencies: [ +- // expect_lint: provider_dependencies +- dep, +- dep2, +- ], ++ dependencies: [dep2], +) +int extraDep(ExtraDepRef ref) { +``` +--- +Message: `Remove "dependencies"` +Priority: 100 +Diff for file `test/lints/provider_dependencies/provider_dependencies.dart:74`: +``` +@Riverpod( + keepAlive: false, +- dependencies: [ +- // expect_lint: provider_dependencies +- dep, +- ], +- ) ++ ) +int noDep(NoDepRef ref) { + return 0; +``` +--- +Message: `Remove "dependencies"` +Priority: 100 +Diff for file `test/lints/provider_dependencies/provider_dependencies.dart:84`: +``` + +@Riverpod( +- dependencies: [ +- // expect_lint: provider_dependencies +- dep, +- ], +- keepAlive: false, ++ keepAlive: false, +) +int dependenciesFirstThenKeepAlive(DependenciesFirstThenKeepAliveRef ref) { +``` +--- +Message: `Remove "dependencies"` +Priority: 100 +Diff for file `test/lints/provider_dependencies/provider_dependencies.dart:94`: +``` +} + +- @Riverpod( +- dependencies: [ +- // expect_lint: provider_dependencies +- dep, +- ], +- ) ++ @riverpod +int noDepNoParam(NoDepNoParamRef ref) { + return 0; +``` +--- +Message: `Remove "dependencies"` +Priority: 100 +Diff for file `test/lints/provider_dependencies/provider_dependencies.dart:105`: +``` + +// expect_lint: provider_dependencies +- @Riverpod(keepAlive: false, dependencies: [dep]) ++ @Riverpod(keepAlive: false,) +int noDepWithoutComma(NoDepWithoutCommaRef ref) { + return 0; +``` +--- diff --git a/packages/riverpod_lint_flutter_test/test/lints/provider_dependencies/provider_dependencies.json b/packages/riverpod_lint_flutter_test/test/lints/provider_dependencies/provider_dependencies.json deleted file mode 100644 index 90d167dd6..000000000 --- a/packages/riverpod_lint_flutter_test/test/lints/provider_dependencies/provider_dependencies.json +++ /dev/null @@ -1 +0,0 @@ -[{"priority":100,"change":{"message":"Specify \"dependencies\"","edits":[{"fileStamp":0,"edits":[{"offset":266,"length":9,"replacement":"@Riverpod(dependencies: [dep])"}]}],"linkedEditGroups":[]}},{"priority":100,"change":{"message":"Specify \"dependencies\"","edits":[{"fileStamp":0,"edits":[{"offset":427,"length":0,"replacement":", dependencies: [dep]"}]}],"linkedEditGroups":[]}},{"priority":100,"change":{"message":"Specify \"dependencies\"","edits":[{"fileStamp":0,"edits":[{"offset":585,"length":0,"replacement":", dependencies: [dep]"}]}],"linkedEditGroups":[]}},{"priority":100,"change":{"message":"Update \"dependencies\"","edits":[{"fileStamp":0,"edits":[{"offset":804,"length":2,"replacement":"[dep]"}]}],"linkedEditGroups":[]}},{"priority":100,"change":{"message":"Update \"dependencies\"","edits":[{"fileStamp":0,"edits":[{"offset":976,"length":2,"replacement":"[dep, dep2]"}]}],"linkedEditGroups":[]}},{"priority":100,"change":{"message":"Update \"dependencies\"","edits":[{"fileStamp":0,"edits":[{"offset":1137,"length":66,"replacement":"[dep2]"}]}],"linkedEditGroups":[]}},{"priority":100,"change":{"message":"Remove \"dependencies\"","edits":[{"fileStamp":0,"edits":[{"offset":1311,"length":74,"replacement":""}]}],"linkedEditGroups":[]}},{"priority":100,"change":{"message":"Remove \"dependencies\"","edits":[{"fileStamp":0,"edits":[{"offset":1439,"length":74,"replacement":""}]}],"linkedEditGroups":[]}},{"priority":100,"change":{"message":"Remove \"dependencies\"","edits":[{"fileStamp":0,"edits":[{"offset":1627,"length":86,"replacement":"@riverpod"}]}],"linkedEditGroups":[]}},{"priority":100,"change":{"message":"Remove \"dependencies\"","edits":[{"fileStamp":0,"edits":[{"offset":1834,"length":20,"replacement":""}]}],"linkedEditGroups":[]}}] \ No newline at end of file diff --git a/packages/riverpod_lint_flutter_test/test/lints/provider_dependencies/provider_dependencies_test.dart b/packages/riverpod_lint_flutter_test/test/lints/provider_dependencies/provider_dependencies_test.dart index b9228ffe1..b12d6a212 100644 --- a/packages/riverpod_lint_flutter_test/test/lints/provider_dependencies/provider_dependencies_test.dart +++ b/packages/riverpod_lint_flutter_test/test/lints/provider_dependencies/provider_dependencies_test.dart @@ -1,9 +1,5 @@ -import 'dart:io'; - import 'package:collection/collection.dart'; import 'package:riverpod_lint/src/lints/provider_dependencies.dart'; -import 'package:analyzer/dart/analysis/results.dart'; -import 'package:analyzer/dart/analysis/utilities.dart'; import 'package:test/test.dart'; import '../../golden.dart'; @@ -11,16 +7,11 @@ import '../../golden.dart'; void main() { testGolden( 'Verify that @riverpod classes extend the generated typedef', - 'lints/provider_dependencies/provider_dependencies.json', - () async { + 'lints/provider_dependencies/provider_dependencies.diff', + sourcePath: 'test/lints/provider_dependencies/provider_dependencies.dart', + (result) async { const lint = ProviderDependencies(); final fix = lint.getFixes().single; - final file = File( - 'test/lints/provider_dependencies/provider_dependencies.dart', - ).absolute; - - final result = await resolveFile2(path: file.path); - result as ResolvedUnitResult; final errors = await lint.testRun(result); expect(errors, hasLength(10)); diff --git a/packages/riverpod_lint_flutter_test/test/lints/provider_parameters.freezed.dart b/packages/riverpod_lint_flutter_test/test/lints/provider_parameters.freezed.dart index 803df2d6d..d3b95eea0 100644 --- a/packages/riverpod_lint_flutter_test/test/lints/provider_parameters.freezed.dart +++ b/packages/riverpod_lint_flutter_test/test/lints/provider_parameters.freezed.dart @@ -12,7 +12,7 @@ part of 'provider_parameters.dart'; T _$identity(T value) => value; final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); /// @nodoc mixin _$FreezedExample {} @@ -62,7 +62,7 @@ class _$FreezedExampleImpl implements _FreezedExample { } @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && other is _$FreezedExampleImpl); } diff --git a/website/docs/case_studies/cancel.mdx b/website/docs/case_studies/cancel.mdx index ffd5f71f5..182265a5e 100644 --- a/website/docs/case_studies/cancel.mdx +++ b/website/docs/case_studies/cancel.mdx @@ -31,7 +31,7 @@ use: navigates away from the page before the request completes. This ensures that you don't waste time processing a response that the user will never see. -In Riverpod, both of these techniques are can be implemented in a similar way. +In Riverpod, both of these techniques can be implemented in a similar way. The key is to use `ref.onDispose` combined with "automatic disposal" or `ref.watch` to achieve the desired behavior. diff --git a/website/docs/case_studies/cancel/detail_screen/codegen.freezed.dart b/website/docs/case_studies/cancel/detail_screen/codegen.freezed.dart index f4f9398ea..1020def96 100644 --- a/website/docs/case_studies/cancel/detail_screen/codegen.freezed.dart +++ b/website/docs/case_studies/cancel/detail_screen/codegen.freezed.dart @@ -12,7 +12,7 @@ part of 'codegen.dart'; T _$identity(T value) => value; final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); Activity _$ActivityFromJson(Map json) { return _Activity.fromJson(json); @@ -153,7 +153,7 @@ class _$ActivityImpl implements _Activity { } @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && other is _$ActivityImpl && diff --git a/website/docs/case_studies/pull_to_refresh/activity/codegen.freezed.dart b/website/docs/case_studies/pull_to_refresh/activity/codegen.freezed.dart index f4f9398ea..1020def96 100644 --- a/website/docs/case_studies/pull_to_refresh/activity/codegen.freezed.dart +++ b/website/docs/case_studies/pull_to_refresh/activity/codegen.freezed.dart @@ -12,7 +12,7 @@ part of 'codegen.dart'; T _$identity(T value) => value; final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); Activity _$ActivityFromJson(Map json) { return _Activity.fromJson(json); @@ -153,7 +153,7 @@ class _$ActivityImpl implements _Activity { } @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && other is _$ActivityImpl && diff --git a/website/docs/case_studies/pull_to_refresh/full_app/codegen.freezed.dart b/website/docs/case_studies/pull_to_refresh/full_app/codegen.freezed.dart index f4f9398ea..1020def96 100644 --- a/website/docs/case_studies/pull_to_refresh/full_app/codegen.freezed.dart +++ b/website/docs/case_studies/pull_to_refresh/full_app/codegen.freezed.dart @@ -12,7 +12,7 @@ part of 'codegen.dart'; T _$identity(T value) => value; final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); Activity _$ActivityFromJson(Map json) { return _Activity.fromJson(json); @@ -153,7 +153,7 @@ class _$ActivityImpl implements _Activity { } @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && other is _$ActivityImpl && diff --git a/website/docs/concepts/combining_provider_states/todo_list_provider/codegen.freezed.dart b/website/docs/concepts/combining_provider_states/todo_list_provider/codegen.freezed.dart index a04416833..0b73d3548 100644 --- a/website/docs/concepts/combining_provider_states/todo_list_provider/codegen.freezed.dart +++ b/website/docs/concepts/combining_provider_states/todo_list_provider/codegen.freezed.dart @@ -12,7 +12,7 @@ part of 'codegen.dart'; T _$identity(T value) => value; final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); /// @nodoc mixin _$Todo { @@ -126,7 +126,7 @@ class _$TodoImpl implements _Todo { } @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && other is _$TodoImpl && diff --git a/website/docs/concepts/why_immutability/codegen.freezed.dart b/website/docs/concepts/why_immutability/codegen.freezed.dart index acb6d0e7f..6e8c8082b 100644 --- a/website/docs/concepts/why_immutability/codegen.freezed.dart +++ b/website/docs/concepts/why_immutability/codegen.freezed.dart @@ -12,7 +12,7 @@ part of 'codegen.dart'; T _$identity(T value) => value; final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); /// @nodoc mixin _$ThemeSettings { @@ -116,7 +116,7 @@ class _$ThemeSettingsImpl implements _ThemeSettings { } @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && other is _$ThemeSettingsImpl && diff --git a/website/docs/essentials/combining_requests.mdx b/website/docs/essentials/combining_requests.mdx index 7acf31d05..d887f68dc 100644 --- a/website/docs/essentials/combining_requests.mdx +++ b/website/docs/essentials/combining_requests.mdx @@ -12,20 +12,20 @@ import watchPlacement from "./combining_requests/watch_placement"; import listenExample from "./combining_requests/listen_example"; import readExample from './combining_requests/read_example' -Up till now, we've only seens cases were requests are independent from each +Up till now, we've only seen cases where requests are independent from each other. But a common use-case is to have to trigger a request based on the result of another request. We _could_ be using the mechanism to -do that, by passing the result of a provider as parameter to another a provider. +do that, by passing the result of a provider as a parameter to another provider. But this approach has a few downsides: - This leaks implementation details. Now, your UI needs to know about all the providers that are used - your other provider. -- Whenever the parameter change, a brand new state will be made. - By passing parameters, there are no way to keep the previous state + by your other provider. +- Whenever the parameter changes, a brand new state will be made. + By passing parameters, there is no way to keep the previous state when the parameter changes. - It makes combining requests harder. - This makes tooling less useful. A devtool wouldn't @@ -38,13 +38,13 @@ To improve this, Riverpod offers a different approach to combine requests. All possible ways of combining requests have one thing in common: They are all based on the `Ref` object. -The `Ref` object is an object which all providers have access to. +The `Ref` object is an object to which all providers have access. It grants them access to various life-cycle listeners, but also various methods to combine providers. The place where `Ref` can be obtained depends on the type of provider. -In functional providers, the `Ref` is passed as parameter to the +In functional providers, the `Ref` is passed as a parameter to the provider's function: @@ -77,12 +77,12 @@ Then, we could use this location to fetch the list of restaurants near the user. :::info -When the listened provider changes and our request recomputes, the previous -state is kept until the new request completes. +When the listened to provider changes and our request recomputes, the previous +state is kept until the new request is completed. At the same time, while the request is pending, the "isLoading" and "isReloading" flags will be set. -This enables UI to either show the previous state, or a loading indicator, +This enables UI to either show the previous state or a loading indicator, or even both. ::: @@ -93,7 +93,7 @@ await for an initial value to be available. If we omit that `.future`, we would receive an `AsyncValue`, which is a snapshot of the current state of the `locationProvider`. But if no location is available yet, -we wouldn't be able to do anything. +we won't be able to do anything. ::: :::caution diff --git a/website/docs/essentials/faq.mdx b/website/docs/essentials/faq.mdx index 1ddad482a..29f584183 100644 --- a/website/docs/essentials/faq.mdx +++ b/website/docs/essentials/faq.mdx @@ -48,7 +48,7 @@ This is done on purpose to avoid writing code which conditionally depends on one or the other. One issue is that `Ref` and `WidgetRef`, although similar looking, -have suble differences. +have subtle differences. Code relying on both would be unreliable in ways that are difficult to spot. At the same time, relying on `WidgetRef` is equivalent to relying on `BuildContext`. diff --git a/website/docs/essentials/first_request.mdx b/website/docs/essentials/first_request.mdx index 22902b9e1..620a3be52 100644 --- a/website/docs/essentials/first_request.mdx +++ b/website/docs/essentials/first_request.mdx @@ -243,7 +243,7 @@ Using the syntax defined previously, we can therefore define our provider as fol In this snippet, we've defined a provider named `activityProvider` which -our UI will be able to use to obtain a random activity. It is worth nothing +our UI will be able to use to obtain a random activity. It is worth noting that: - The network request will not be executed until the UI reads the provider diff --git a/website/docs/essentials/first_request/codegen/activity.freezed.dart b/website/docs/essentials/first_request/codegen/activity.freezed.dart index c10bc4253..6ffa343c1 100644 --- a/website/docs/essentials/first_request/codegen/activity.freezed.dart +++ b/website/docs/essentials/first_request/codegen/activity.freezed.dart @@ -12,7 +12,7 @@ part of 'activity.dart'; T _$identity(T value) => value; final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); Activity _$ActivityFromJson(Map json) { return _Activity.fromJson(json); @@ -177,7 +177,7 @@ class _$ActivityImpl implements _Activity { } @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && other is _$ActivityImpl && diff --git a/website/docs/essentials/side_effects.mdx b/website/docs/essentials/side_effects.mdx index 61059b609..9172ebbc3 100644 --- a/website/docs/essentials/side_effects.mdx +++ b/website/docs/essentials/side_effects.mdx @@ -427,7 +427,7 @@ performed, nor any information if failed. One way to do so is to store the Future returned by `addTodo` in the local widget state, and then listen to that future to show -a snipper or error message. +a spinner or error message. This is one scenario where [flutter_hooks](https://pub.dev/packages/flutter_hooks) comes in handy. But of course, you can also use a `StatefulWidget` instead. diff --git a/website/docs/essentials/side_effects/codegen/todo_list_notifier.freezed.dart b/website/docs/essentials/side_effects/codegen/todo_list_notifier.freezed.dart index 319351f78..3326f9ffa 100644 --- a/website/docs/essentials/side_effects/codegen/todo_list_notifier.freezed.dart +++ b/website/docs/essentials/side_effects/codegen/todo_list_notifier.freezed.dart @@ -12,7 +12,7 @@ part of 'todo_list_notifier.dart'; T _$identity(T value) => value; final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); Todo _$TodoFromJson(Map json) { return _Todo.fromJson(json); @@ -121,7 +121,7 @@ class _$TodoImpl implements _Todo { } @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && other is _$TodoImpl && diff --git a/website/docs/essentials/side_effects/codegen/todo_list_provider.freezed.dart b/website/docs/essentials/side_effects/codegen/todo_list_provider.freezed.dart index 95e5e1d15..26a2d640c 100644 --- a/website/docs/essentials/side_effects/codegen/todo_list_provider.freezed.dart +++ b/website/docs/essentials/side_effects/codegen/todo_list_provider.freezed.dart @@ -12,7 +12,7 @@ part of 'todo_list_provider.dart'; T _$identity(T value) => value; final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); /// @nodoc mixin _$Todo { @@ -113,7 +113,7 @@ class _$TodoImpl implements _Todo { } @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && other is _$TodoImpl && diff --git a/website/docs/essentials/testing.mdx b/website/docs/essentials/testing.mdx index 61b068f91..bb52e0906 100644 --- a/website/docs/essentials/testing.mdx +++ b/website/docs/essentials/testing.mdx @@ -45,7 +45,7 @@ The main difference with any other test is that we will want to create a `ProviderContainer` object. This object will enable our test to interact with providers. -It encouraged to make a testing utility for both creating and disposing +It is encouraged to make a testing utility for both creating and disposing of a `ProviderContainer` object: diff --git a/website/docs/from_provider/helpers/item.freezed.dart b/website/docs/from_provider/helpers/item.freezed.dart index 959fe13d2..e578c8154 100644 --- a/website/docs/from_provider/helpers/item.freezed.dart +++ b/website/docs/from_provider/helpers/item.freezed.dart @@ -12,7 +12,7 @@ part of 'item.dart'; T _$identity(T value) => value; final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); Item _$ItemFromJson(Map json) { return _Item.fromJson(json); @@ -107,7 +107,7 @@ class _$ItemImpl implements _Item { } @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && other is _$ItemImpl && diff --git a/website/docs/from_provider/quickstart.mdx b/website/docs/from_provider/quickstart.mdx index 316f27084..6ed213073 100644 --- a/website/docs/from_provider/quickstart.mdx +++ b/website/docs/from_provider/quickstart.mdx @@ -64,7 +64,7 @@ This should boost and simplify the migration process, while also minimizing / tr ## Riverpod and Provider can coexist *Keep in mind that it is entirely possible to use both Provider and Riverpod at the same time.* -Indeed, using import aliases, it is possible to use the two APIs altogheter. +Indeed, using import aliases, it is possible to use the two APIs altogether. This is also great for readibilty and it removes any ambiguous API usage. If you plan on doing this, consider using import aliases for each Provider import in your codebase. @@ -204,4 +204,5 @@ Following this guide, you *can* migrate towards codegen as a further step forwar [are composable by default]: /docs/from_provider/motivation#combining-providers-is-hard-and-error-prone [as mentioned above]: /docs/from_provider/quickstart#start-with-changenotifierprovider [Riverpod's `Consumer` APIs]: /docs/concepts/reading -[lazy by default]: /docs/concepts/provider_lifecycles \ No newline at end of file +[lazy by default]: /docs/concepts/provider_lifecycles +[is available here]: /docs/essentials/eager_initialization diff --git a/website/docs/providers/notifier_provider/remote_todos/codegen.freezed.dart b/website/docs/providers/notifier_provider/remote_todos/codegen.freezed.dart index 315bc924e..d1b8a01d5 100644 --- a/website/docs/providers/notifier_provider/remote_todos/codegen.freezed.dart +++ b/website/docs/providers/notifier_provider/remote_todos/codegen.freezed.dart @@ -12,7 +12,7 @@ part of 'codegen.dart'; T _$identity(T value) => value; final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); Todo _$TodoFromJson(Map json) { return _Todo.fromJson(json); @@ -134,7 +134,7 @@ class _$TodoImpl implements _Todo { } @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && other is _$TodoImpl && diff --git a/website/docs/providers/notifier_provider/todos/codegen.freezed.dart b/website/docs/providers/notifier_provider/todos/codegen.freezed.dart index a04416833..0b73d3548 100644 --- a/website/docs/providers/notifier_provider/todos/codegen.freezed.dart +++ b/website/docs/providers/notifier_provider/todos/codegen.freezed.dart @@ -12,7 +12,7 @@ part of 'codegen.dart'; T _$identity(T value) => value; final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); /// @nodoc mixin _$Todo { @@ -126,7 +126,7 @@ class _$TodoImpl implements _Todo { } @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && other is _$TodoImpl && diff --git a/website/i18n/fr/docusaurus-plugin-content-docs/current.json b/website/i18n/fr/docusaurus-plugin-content-docs/current.json index 994ac7a22..0669be5a8 100644 --- a/website/i18n/fr/docusaurus-plugin-content-docs/current.json +++ b/website/i18n/fr/docusaurus-plugin-content-docs/current.json @@ -91,9 +91,9 @@ "message": "Application Todo avec fonction de sauvegarde et de restauration", "description": "The label for link Todo App with Backup and Restore feature in sidebar Sidebar, linking to https://github.com/TheAlphaApp/flutter_riverpod_todo_app" }, - "sidebar.Sidebar.link.Integrating Hive database with Riverpod (simple example)": { - "message": "Integration d'une base de donnée Hive avec Riverpod (exemple simple)", - "description": "The label for link Integrating Hive database with Riverpod (simple example) in sidebar Sidebar, linking to https://github.com/GitGud31/theme_riverpod_hive" + "sidebar.Sidebar.link.Integrating Hive database with Riverpod": { + "message": "Integration d'une base de donnée Hive avec Riverpod", + "description": "The label for link Integrating Hive database with Riverpod in sidebar Sidebar, linking to https://github.com/GitGud31/flutter_theme_riverpod_hive" }, "sidebar.Sidebar.link.Browser App with Riverpod": { "message": "Application navigateur avec Riverpod", diff --git a/website/i18n/fr/docusaurus-plugin-content-docs/current/cookbooks/refresh.mdx b/website/i18n/fr/docusaurus-plugin-content-docs/current/cookbooks/refresh.mdx new file mode 100644 index 000000000..58a015b59 --- /dev/null +++ b/website/i18n/fr/docusaurus-plugin-content-docs/current/cookbooks/refresh.mdx @@ -0,0 +1,10 @@ +--- +title: Pull-to-refresh / Retry-on-error +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +Dans ce guide, nous verrons comment les Providers peuvent être utilisés pour mettre +en œuvre facilement une fonction de "pull-to-refresh" ou de "retry-on-error" +(tentative de réessai en cas d'erreur). \ No newline at end of file diff --git a/website/i18n/fr/docusaurus-plugin-content-docs/current/cookbooks/search_as_we_type.mdx b/website/i18n/fr/docusaurus-plugin-content-docs/current/cookbooks/search_as_we_type.mdx new file mode 100644 index 000000000..027aa921a --- /dev/null +++ b/website/i18n/fr/docusaurus-plugin-content-docs/current/cookbooks/search_as_we_type.mdx @@ -0,0 +1,133 @@ +--- +titre : Recherche au fur et à mesure de la frappe +--- +Un exemple concret pourrait être l'utilisation de `FutureProvider` pour implémenter une barre de recherche. + +## Exemple d'utilisation : Barre de recherche "Search as we type" (recherche au fur et à mesure de la frappe) + +La mise en œuvre d'une "recherche au fil de la frappe" peut sembler décourageante au début si l'on utilise des moyens conventionnels. +Il y a beaucoup d'éléments en jeu, tels que + +- le traitement des erreurs. +- le débouclage (debouncing) de l'entrée de l'utilisateur afin d'éviter de faire des demandes de réseau à chaque frappe. +- l'annulation des demandes de réseau en attente lorsque le champ de recherche change. + +Mais la combinaison de `FutureProvider` et de la puissance de [ref.watch] peut considérablement simplifier cette tâche. + +Un schéma courant pour effectuer des requêtes asynchrones est de la diviser en plusieurs providers : + +- un [StateNotifierProvider] ou `StateProvider` pour les paramètres de votre requête (ou alternativement utiliser [family]) +- un `FutureProvider`, qui effectuera la requête en lisant les paramètres des autres providers/[family]. + +La première étape consiste à stocker l'entrée de l'utilisateur quelque part. Pour cet exemple, nous utiliserons `StateProvider` (puisque l'état de la recherche n'est qu'une simple `chaîne`) : + +```dart +final searchInputProvider = StateProvider((ref) => ''); +``` + +Nous pouvons ensuite connecter ce provider à un [TextField] en faisant : + +```dart +Consumer( + builder: (context, ref, child) { + return TextField( + onChanged: (value) => ref.read(searchInputProvider.notifier).state = value, + ); + }, +) +``` + +Ensuite, nous pouvons créer notre `FutureProvider` qui s'occupera de la requête : + +```dart +final searchProvider = FutureProvider< + + + + + + + } + + +}`} + annotations={[ + { + offset: 0, + length: 9, + label: "L'annotazione", + description: <> + +Tutti i provider devono essere annotati con `@riverpod` o `@Riverpod()`. +Questa annotazione può essere posta su funzioni globali o classi. +Attraverso questa annotazione, è possibile configurare il provider. + +Per esempio, possiamo disabilitare "auto-dispose" (che vedremo più avanti) scrivendo `@Riverpod(keepAlive: true)`. + + + }, + { + offset: 10, + length: 16, + label: "Il Notifier", + description: <> + +Quando un'annotazione `@riverpod` è posta su una classe, quella classe viene chiamata "Notifier". +La classe deve estendere `_$NotifierName`, dove `NotifierName` è il nome della classe. + +I Notifiers sono responsabili di esporre modalità per modificare lo stato del provider. +I metodi pubblici di questa classe sono accessibili ai consumer usando `ref.read(yourProvider.notifier).tuoMetodo()`. + +:::note +I Notifiers non dovrebbero avere proprietà pubbliche oltre alla proprietà built-in `state`, poiché l'interfaccia utente +non avrebbe modo di sapere che lo stato è cambiato. +::: + + + }, + { + offset: 52, + length: 54, + label: "Il metodo build", + description: <> + +Tutti i notifiers devono sovrascrivere il metodo `build`. +Questo metodo è equivalente al punto in cui normalmente inseriresti la tua +logica in un provider non-notifier. + +Questo metodo non dovrebbe essere chiamato direttamente. + + + }, +]} +/> + + +Per ulteriori informazioni, potresti voler consultare +per confrontare questa nuova sintassi con quella vista in precedenza. + +:::info +Un Notifier senza metodi al di fuori di `build` è identico all'utilizzo della sintassi vista in precedenza. +La sintassi mostrata in può essere considerata +come una scorciatoia per i notifiers senza possibilità di essere modificati dall'interfaccia utente. +::: + +Ora che abbiamo visto la sintassi, vediamo come convertire il nostro provider precedentemente definito in un notifier: + + + +Notiamo che il modo di leggere il provider all'interno dei widget non è cambiato. +Puoi ancora usare `ref.watch(todoListProvider)` come nella sintassi precedente. + +:::caution +Non inserire la logica nel costruttore del tuo notifier. +I notifiers non dovrebbero avere un costruttore, poiché `ref` e altre proprietà +non sono ancora disponibili in quel momento. Invece, inserisci la tua logica nel metodo `build`. + +```dart +class MyNotifier extends ... { + MyNotifier() { + // ❌ Non fare questo + // Causerà un'eccezione + state = AsyncValue.data(42); + } + + @override + Result build() { + // ✅ Fare questo invece + state = AsyncValue.data(42); + } +} +``` + +::: + +## Esporre un metodo per effettuare una richiesta _POST_ + +Ora che abbiamo un Notifier, possiamo iniziare ad aggiungere metodi per abilitare i side-effects. +Un side-effect del genere potrebbe essere quello di far eseguire al client una richiesta _POST_ +per aggiungere un nuovo elemento todo. +Possiamo farlo aggiungendo un metodo `addTodo` al nostro notifier: + + + +:::info +Nota come stiamo usando `ref.read` invece di `ref.watch` per invocare il nostro metodo. +Anche se `ref.watch` potrebbe funzionare tecnicamente, si consiglia di utilizzare `ref.read` +quando si esegue la logica negli event handlers come "onPressed". +::: + +Abbiamo ora un pulsante che effettua una richiesta _POST_ quando premuto. +Tuttavia, al momento, la nostra interfaccia utente non si aggiorna per riflettere la nuova to-do list. +Vogliamo che la nostra cache locale corrisponda allo stato del server. + +Esistono diversi modi per farlo, ognuno con i suoi vantaggi e svantaggi. + +### Aggiornare la cache locale per riflettere la risposta dell'API + +Una pratica comune lato server è fare sì che la richiesta _POST_ restituisca il nuovo stato della risorsa. +In particolare, la nostra API restituirebbe la nuova lista to-do dopo l'aggiunta di un nuovo to-do. +Un modo per farlo è scrivere `state = AsyncData(response)`: + + + +:::tip pro + +- L'interfaccia utente avrà lo stato più aggiornato possibile. + Se un altro utente ha aggiunto un to-do, la vedremo anche noi. +- Il server è la fonte di verità. Con questo approccio, + il client non ha bisogno di sapere dove il nuovo to-do deve essere inserito nella list to-do. +- È necessaria solo una singola richiesta di rete. + +::: + +:::danger contro + +- Questo approccio funzionerà solo se il server è implementato in un modo specifico. + Se il server non restituisce il nuovo stato, questo approccio non funzionerà. +- Potrebbe ancora non essere fattibile se la richiesta _GET_ associata è più complessa, + ad esempio se ha filtri/ordinamenti. + +::: + +### Usare `ref.invalidateSelf()` per ricaricare il provider. + +Un'opzione è far eseguire nuovamente la richiesta _GET_ al nostro provider. +Questo può essere fatto chiamando `ref.invalidateSelf()` dopo la richiesta _POST_: + + + +:::tip pro + +- L'interfaccia utente avrà lo stato più aggiornato possibile. + Se un altro utente ha aggiunto un nuovo to-do, lo vedremo anche noi. +- Il server è la fonte di verità. + Con questo approccio, il client non ha bisogno di sapere dove inserire il nuovo to-do + nella lista dei to-do. +- Questo approccio dovrebbe funzionare indipendentemente dall'implementazione del server. + Può essere particolarmente utile se la tua richiesta _GET_ è più complessa, + ad esempio se ha filtri/ordinamenti. + +::: + +:::danger contro + +- Questo approccio eseguirà una richiesta _GET_ aggiuntiva, il che potrebbe + essere inefficiente. + +::: + +### Aggiornare la cache locale manualmente + +Un'altra opzione è quella di aggiornare manualmente la cache locale. +Ciò implicherebbe cercare di imitare il comportamento del backend. +Ad esempio, dovremmo sapere se il backend inserisce nuovi elementi +all'inizio o alla fine. + + + +:::info +Questo esempio utilizza uno stato immutabile. Non è obbligatorio, ma consigliato. +Consulta per ulteriori dettagli. +Se desideri utilizzare uno stato mutabile, puoi fare in alternativa: + + + +::: + +:::tip pro + +- Questo approccio dovrebbe funzionare indipendentemente dall'implementazione del server. +- È necessaria solo una singola richiesta di rete. + +::: + +:::danger contro + +- La cache locale potrebbe non corrispondere allo stato del server. + Se un altro utente ha aggiunto un to-do, non lo vedremo. +- Questo approccio potrebbe essere più complesso da implementare ed in realtà + duplicare la logica del backend. + +::: + +## Andando oltre: Mostrare uno spinner e gestione dell'errore + +Con tutto ciò che abbiamo visto finora, abbiamo un pulsante che effettua una richiesta _POST_ +quando viene premuto; e quando la richiesta è completata, l'interfaccia utente si aggiorna per riflettere +le modifiche. Ma al momento, non c'è alcuna indicazione che la richiesta stia avvenendo, +né alcuna informazione in caso di fallimento. + +Un modo per farlo è memorizzare il Future restituito da `addTodo` +nello stato locale del widget e quindi ascoltare quel future per mostrare +un indicatore di caricamento o un messaggio di errore. Questo è uno scenario in cui +[flutter_hooks](https://pub.dev/packages/flutter_hooks) risulta utile. Ma naturalmente, +è possibile utilizzare anche un `StatefulWidget` al suo posto. + +Il seguente snippet mostra un indicatore di avanzamento mentre +un'operazione è in corso. Se fallisce, rende il pulsante di colore rosso: + +![Un bottone che diventa rosso quando l'operazione fallisce](/img/essentials/side_effects/spinner.gif) + + \ No newline at end of file diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier.dart new file mode 100644 index 000000000..23a30cb03 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier.dart @@ -0,0 +1,28 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'todo_list_notifier.freezed.dart'; +part 'todo_list_notifier.g.dart'; + +@freezed +class Todo with _$Todo { + factory Todo({ + required String description, + @Default(false) bool completed, + }) = _Todo; + + factory Todo.fromJson(Map json) => _$TodoFromJson(json); +} + +/* SNIPPET START */ +@riverpod +class TodoList extends _$TodoList { + @override + Future> build() async { + // La logica che precedentemente avevamo nel nostro FutureProvider è ora nel metodo 'build'. + return [ + Todo(description: 'Learn Flutter', completed: true), + Todo(description: 'Learn Riverpod'), + ]; + } +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier.freezed.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier.freezed.dart new file mode 100644 index 000000000..3326f9ffa --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier.freezed.dart @@ -0,0 +1,166 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'todo_list_notifier.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +Todo _$TodoFromJson(Map json) { + return _Todo.fromJson(json); +} + +/// @nodoc +mixin _$Todo { + String get description => throw _privateConstructorUsedError; + bool get completed => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $TodoCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $TodoCopyWith<$Res> { + factory $TodoCopyWith(Todo value, $Res Function(Todo) then) = + _$TodoCopyWithImpl<$Res, Todo>; + @useResult + $Res call({String description, bool completed}); +} + +/// @nodoc +class _$TodoCopyWithImpl<$Res, $Val extends Todo> + implements $TodoCopyWith<$Res> { + _$TodoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? description = null, + Object? completed = null, + }) { + return _then(_value.copyWith( + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + completed: null == completed + ? _value.completed + : completed // ignore: cast_nullable_to_non_nullable + as bool, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$TodoImplCopyWith<$Res> implements $TodoCopyWith<$Res> { + factory _$$TodoImplCopyWith( + _$TodoImpl value, $Res Function(_$TodoImpl) then) = + __$$TodoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String description, bool completed}); +} + +/// @nodoc +class __$$TodoImplCopyWithImpl<$Res> + extends _$TodoCopyWithImpl<$Res, _$TodoImpl> + implements _$$TodoImplCopyWith<$Res> { + __$$TodoImplCopyWithImpl(_$TodoImpl _value, $Res Function(_$TodoImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? description = null, + Object? completed = null, + }) { + return _then(_$TodoImpl( + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + completed: null == completed + ? _value.completed + : completed // ignore: cast_nullable_to_non_nullable + as bool, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$TodoImpl implements _Todo { + _$TodoImpl({required this.description, this.completed = false}); + + factory _$TodoImpl.fromJson(Map json) => + _$$TodoImplFromJson(json); + + @override + final String description; + @override + @JsonKey() + final bool completed; + + @override + String toString() { + return 'Todo(description: $description, completed: $completed)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$TodoImpl && + (identical(other.description, description) || + other.description == description) && + (identical(other.completed, completed) || + other.completed == completed)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, description, completed); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$TodoImplCopyWith<_$TodoImpl> get copyWith => + __$$TodoImplCopyWithImpl<_$TodoImpl>(this, _$identity); + + @override + Map toJson() { + return _$$TodoImplToJson( + this, + ); + } +} + +abstract class _Todo implements Todo { + factory _Todo({required final String description, final bool completed}) = + _$TodoImpl; + + factory _Todo.fromJson(Map json) = _$TodoImpl.fromJson; + + @override + String get description; + @override + bool get completed; + @override + @JsonKey(ignore: true) + _$$TodoImplCopyWith<_$TodoImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier.g.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier.g.dart new file mode 100644 index 000000000..9d12d50c6 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier.g.dart @@ -0,0 +1,42 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'todo_list_notifier.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$TodoImpl _$$TodoImplFromJson(Map json) => _$TodoImpl( + description: json['description'] as String, + completed: json['completed'] as bool? ?? false, + ); + +Map _$$TodoImplToJson(_$TodoImpl instance) => + { + 'description': instance.description, + 'completed': instance.completed, + }; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$todoListHash() => r'c939d438b07da6065dbbcfab86c27ef363bdb76c'; + +/// See also [TodoList]. +@ProviderFor(TodoList) +final todoListProvider = + AutoDisposeAsyncNotifierProvider>.internal( + TodoList.new, + name: r'todoListProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$todoListHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$TodoList = AutoDisposeAsyncNotifier>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier_add_todo.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier_add_todo.dart new file mode 100644 index 000000000..1b3803279 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier_add_todo.dart @@ -0,0 +1,26 @@ +// ignore_for_file: avoid_print + +import 'dart:convert'; + +import 'package:http/http.dart' as http; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import 'todo_list_notifier.dart'; + +part 'todo_list_notifier_add_todo.g.dart'; + +/* SNIPPET START */ +@riverpod +class TodoList extends _$TodoList { + @override + Future> build() async => [/* ... */]; + + Future addTodo(Todo todo) async { + await http.post( + Uri.https('your_api.com', '/todos'), + // Serializziamo il nostro oggetto Todo e lo salviamo tramite POST sul server. + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(todo.toJson()), + ); + } +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier_add_todo.g.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier_add_todo.g.dart new file mode 100644 index 000000000..a6c2ce847 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier_add_todo.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'todo_list_notifier_add_todo.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$todoListHash() => r'4008395aaca8f55312f668c0b2a32e7599f82349'; + +/// See also [TodoList]. +@ProviderFor(TodoList) +final todoListProvider = + AutoDisposeAsyncNotifierProvider>.internal( + TodoList.new, + name: r'todoListProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$todoListHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$TodoList = AutoDisposeAsyncNotifier>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_provider.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_provider.dart new file mode 100644 index 000000000..76ab146fd --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_provider.dart @@ -0,0 +1,23 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'todo_list_provider.freezed.dart'; +part 'todo_list_provider.g.dart'; + +@freezed +class Todo with _$Todo { + factory Todo({ + required String description, + @Default(false) bool completed, + }) = _Todo; +} + +/* SNIPPET START */ +@riverpod +Future> todoList(TodoListRef ref) async { + // Simula una richiesta di rete. Normalmente il risultato dovrebbe venire da una API reale + return [ + Todo(description: 'Learn Flutter', completed: true), + Todo(description: 'Learn Riverpod'), + ]; +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_provider.freezed.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_provider.freezed.dart new file mode 100644 index 000000000..26a2d640c --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_provider.freezed.dart @@ -0,0 +1,148 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'todo_list_provider.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +/// @nodoc +mixin _$Todo { + String get description => throw _privateConstructorUsedError; + bool get completed => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $TodoCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $TodoCopyWith<$Res> { + factory $TodoCopyWith(Todo value, $Res Function(Todo) then) = + _$TodoCopyWithImpl<$Res, Todo>; + @useResult + $Res call({String description, bool completed}); +} + +/// @nodoc +class _$TodoCopyWithImpl<$Res, $Val extends Todo> + implements $TodoCopyWith<$Res> { + _$TodoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? description = null, + Object? completed = null, + }) { + return _then(_value.copyWith( + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + completed: null == completed + ? _value.completed + : completed // ignore: cast_nullable_to_non_nullable + as bool, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$TodoImplCopyWith<$Res> implements $TodoCopyWith<$Res> { + factory _$$TodoImplCopyWith( + _$TodoImpl value, $Res Function(_$TodoImpl) then) = + __$$TodoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String description, bool completed}); +} + +/// @nodoc +class __$$TodoImplCopyWithImpl<$Res> + extends _$TodoCopyWithImpl<$Res, _$TodoImpl> + implements _$$TodoImplCopyWith<$Res> { + __$$TodoImplCopyWithImpl(_$TodoImpl _value, $Res Function(_$TodoImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? description = null, + Object? completed = null, + }) { + return _then(_$TodoImpl( + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + completed: null == completed + ? _value.completed + : completed // ignore: cast_nullable_to_non_nullable + as bool, + )); + } +} + +/// @nodoc + +class _$TodoImpl implements _Todo { + _$TodoImpl({required this.description, this.completed = false}); + + @override + final String description; + @override + @JsonKey() + final bool completed; + + @override + String toString() { + return 'Todo(description: $description, completed: $completed)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$TodoImpl && + (identical(other.description, description) || + other.description == description) && + (identical(other.completed, completed) || + other.completed == completed)); + } + + @override + int get hashCode => Object.hash(runtimeType, description, completed); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$TodoImplCopyWith<_$TodoImpl> get copyWith => + __$$TodoImplCopyWithImpl<_$TodoImpl>(this, _$identity); +} + +abstract class _Todo implements Todo { + factory _Todo({required final String description, final bool completed}) = + _$TodoImpl; + + @override + String get description; + @override + bool get completed; + @override + @JsonKey(ignore: true) + _$$TodoImplCopyWith<_$TodoImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_provider.g.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_provider.g.dart new file mode 100644 index 000000000..455d9a64b --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_provider.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'todo_list_provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$todoListHash() => r'26b30307668c8feefa7cbe3c400b73e6edccbc39'; + +/// See also [todoList]. +@ProviderFor(todoList) +final todoListProvider = AutoDisposeFutureProvider>.internal( + todoList, + name: r'todoListProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$todoListHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef TodoListRef = AutoDisposeFutureProviderRef>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/consumer_add_todo_call.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/consumer_add_todo_call.dart new file mode 100644 index 000000000..b65d27945 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/consumer_add_todo_call.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../raw/todo_list_notifier.dart' show Todo; +import '../raw/todo_list_notifier_add_todo.dart'; + +/* SNIPPET START */ +class Example extends ConsumerWidget { + const Example({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return ElevatedButton( + onPressed: () { + // Usando "ref.read" combinato con "myProvider.notifier" possiamo + // ottenere l'istanza della classe del nostro notifier. Ciò ci permette di + // chiamare il metodo "addTodo". + ref.read(todoListProvider.notifier).addTodo(Todo(description: 'Questo è un nuovo todo')); + }, + child: const Text('Aggiungi todo'), + ); + } +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/invalidate_self_add_todo.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/invalidate_self_add_todo.dart new file mode 100644 index 000000000..9487699de --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/invalidate_self_add_todo.dart @@ -0,0 +1,37 @@ +// ignore_for_file: avoid_print, prefer_final_locals, omit_local_variable_types + +import 'dart:convert'; + +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:http/http.dart' as http; + +import 'todo_list_notifier.dart'; + +final todoListProvider = AsyncNotifierProvider.autoDispose>( + TodoList.new, +); + +class TodoList extends AutoDisposeAsyncNotifier> { + @override + Future> build() async => [/* ... */]; + + /* SNIPPET START */ + Future addTodo(Todo todo) async { + // Non ci importa della risposta dell'API + await http.post( + Uri.https('your_api.com', '/todos'), + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(todo.toJson()), + ); + + // Una volta che la richiesta è terminata, possiamo marcare la cache locale come sporca. + // Facendo ciò, il metodo "build" sul nostro notifier verrà chiamato asincronamente di nuovo, + // notificando i suoi listener. + ref.invalidateSelf(); + + // (Opzionale) Possiamo quindi aspettare che il nuovo stato venga computato. + // Questo assicura che "addTodo" non venga completato finchè il nuovo stato non è disponibile. + await future; + } +/* SNIPPET END */ +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/manual_add_todo.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/manual_add_todo.dart new file mode 100644 index 000000000..e99cff69d --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/manual_add_todo.dart @@ -0,0 +1,40 @@ +// ignore_for_file: avoid_print, prefer_final_locals, omit_local_variable_types + +import 'dart:convert'; + +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:http/http.dart' as http; + +import 'todo_list_notifier.dart'; + +final todoListProvider = AsyncNotifierProvider.autoDispose>( + TodoList.new, +); + +class TodoList extends AutoDisposeAsyncNotifier> { + @override + Future> build() async => [/* ... */]; + + /* SNIPPET START */ + Future addTodo(Todo todo) async { + // Non ci importa della risposta dell'API + await http.post( + Uri.https('your_api.com', '/todos'), + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(todo.toJson()), + ); + + // Possiamo quindi aggiornare manualmente la cache locale. Per fare ciò, avremo bisogno + // di ottenere lo stato precedente. + // Attenzione: lo stato precedente potrebbe essere anche in stato di loading o di errore. + // Un modo elegante di gestirlo sarebbe leggere `this.future` invece + // di `this.state`, il che consentirebbe di attendere lo stato di loading e + // generare un errore se lo stato è in uno stato di errore. + final previousState = await future; + + // Possiamo quindi aggiornare lo stato, creando un nuovo oggetto di stato. + // Ciò notificherà i suoi listener. + state = AsyncData([...previousState, todo]); + } +/* SNIPPET END */ +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/mutable_manual_add_todo.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/mutable_manual_add_todo.dart new file mode 100644 index 000000000..58e615287 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/mutable_manual_add_todo.dart @@ -0,0 +1,34 @@ +// ignore_for_file: avoid_print, prefer_final_locals, omit_local_variable_types + +import 'dart:convert'; + +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:http/http.dart' as http; + +import 'todo_list_notifier.dart'; + +final todoListProvider = AsyncNotifierProvider.autoDispose>( + TodoList.new, +); + +class TodoList extends AutoDisposeAsyncNotifier> { + @override + Future> build() async => [/* ... */]; + + Future addTodo(Todo todo) async { + // Non ci importa della risposta dell'API + await http.post( + Uri.https('your_api.com', '/todos'), + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(todo.toJson()), + ); + + /* SNIPPET START */ + final previousState = await future; + // Modifica la lista dei todo in modo mutabile. + previousState.add(todo); + // Notifica manualmente i listener. + ref.notifyListeners(); + /* SNIPPET END */ + } +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/render_add_todo.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/render_add_todo.dart new file mode 100644 index 000000000..720077e54 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/render_add_todo.dart @@ -0,0 +1,77 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +import 'rest_add_todo.dart'; +import 'todo_list_notifier.dart' show Todo; + +void main() { + runApp( + const ProviderScope(child: MyApp()), + ); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return const MaterialApp(home: Example()); + } +} + +/* SNIPPET START */ +class Example extends ConsumerStatefulWidget { + const Example({super.key}); + + @override + ConsumerState createState() => _ExampleState(); +} + +class _ExampleState extends ConsumerState { + // L'operazione addTodo in sospeso. O null se nessuna è in attesa. + Future? _pendingAddTodo; + + @override + Widget build(BuildContext context) { + return FutureBuilder( + // Ascoltiamo l'operazione in sospeso per aggiornare l'interfaccia utente di conseguenza. + future: _pendingAddTodo, + builder: (context, snapshot) { + // Calcoliamo se c'è uno stato di errore o meno. + // Controlliamo qui lo stato di ConnectionState per gestire quando l'operazione viene ripetuta. + final isErrored = snapshot.hasError && snapshot.connectionState != ConnectionState.waiting; + + return Row( + children: [ + ElevatedButton( + style: ButtonStyle( + // Se c'è stato un errore mostriamo il bottone in rosso + backgroundColor: MaterialStateProperty.all( + isErrored ? Colors.red : null, + ), + ), + onPressed: () { + // Assegniamo il future ritornato da 'addTodo' in una variabile + final future = ref + .read(todoListProvider.notifier) + .addTodo(Todo(description: 'This is a new todo')); + + // Immagazziniamo il future nello stato locale + setState(() { + _pendingAddTodo = future; + }); + }, + child: const Text('Add todo'), + ), + // L'operazione è in sospeso, mostriamo un indicatore di progresso + if (snapshot.connectionState == ConnectionState.waiting) ...[ + const SizedBox(width: 8), + const CircularProgressIndicator(), + ] + ], + ); + }, + ); + } +} +/* SNIPPET END */ \ No newline at end of file diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/render_add_todo_hooks.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/render_add_todo_hooks.dart new file mode 100644 index 000000000..0f4522d09 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/render_add_todo_hooks.dart @@ -0,0 +1,68 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +import 'rest_add_todo.dart'; +import 'todo_list_notifier.dart' show Todo; + +void main() { + runApp( + const ProviderScope(child: MyApp()), + ); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return const MaterialApp(home: Example()); + } +} + +/* SNIPPET START */ +class Example extends HookConsumerWidget { + const Example({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + // L'operazione addTodo in sospeso. O null se nessuna è in attesa. + final pendingAddTodo = useState?>(null); + + // Ascoltiamo l'operazione in sospeso per aggiornare l'interfaccia utente di conseguenza. + final snapshot = useFuture(pendingAddTodo.value); + + // Calcoliamo se c'è uno stato di errore o meno. + // Controlliamo qui lo stato di ConnectionState per gestire quando l'operazione viene ripetuta. + final isErrored = snapshot.hasError && snapshot.connectionState != ConnectionState.waiting; + + return Row( + children: [ + ElevatedButton( + style: ButtonStyle( + // Se c'è stato un errore mostriamo il bottone in rosso + backgroundColor: MaterialStateProperty.all( + isErrored ? Colors.red : null, + ), + ), + onPressed: () async { + // Assegniamo il future ritornato da 'addTodo' in una variabile + final future = ref + .read(todoListProvider.notifier) + .addTodo(Todo(description: 'This is a new todo')); + + // Immagazziniamo il future nello stato locale + pendingAddTodo.value = future; + }, + child: const Text('Add todo'), + ), + // L'operazione è in sospeso, mostriamo un indicatore di progresso + if (snapshot.connectionState == ConnectionState.waiting) ...[ + const SizedBox(width: 8), + const CircularProgressIndicator(), + ] + ], + ); + } +} +/* SNIPPET END */ \ No newline at end of file diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/rest_add_todo.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/rest_add_todo.dart new file mode 100644 index 000000000..2a70ea672 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/rest_add_todo.dart @@ -0,0 +1,38 @@ +// ignore_for_file: avoid_print, prefer_final_locals, omit_local_variable_types + +import 'dart:convert'; + +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:http/http.dart' as http; + +import 'todo_list_notifier.dart'; + +final todoListProvider = AsyncNotifierProvider.autoDispose>( + TodoList.new, +); + +class TodoList extends AutoDisposeAsyncNotifier> { + @override + Future> build() async => [/* ... */]; + + /* SNIPPET START */ + Future addTodo(Todo todo) async { + // La richiesta POST restituirà una List corrispondente al nuovo stato dell'applicazione + final response = await http.post( + Uri.https('your_api.com', '/todos'), + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(todo.toJson()), + ); + + // Decodifichiamo la risposta API e la convertiamo in una List + List newTodos = (jsonDecode(response.body) as List) + .cast>() + .map(Todo.fromJson) + .toList(); + + // Aggiorniamo la cache locale per riflettere il nuovo stato. + // Questo notificherà tutti i suoi ascoltatori. + state = AsyncData(newTodos); + } +/* SNIPPET END */ +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/todo_list_notifier.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/todo_list_notifier.dart new file mode 100644 index 000000000..d02e936d0 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/todo_list_notifier.dart @@ -0,0 +1,43 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +class Todo { + Todo({ + required this.description, + this.completed = false, + }); + + factory Todo.fromJson(Map json) { + return Todo( + description: json['description']! as String, + completed: json['completed']! as bool, + ); + } + + final String description; + final bool completed; + + Map toJson() => { + 'description': description, + 'completed': completed, + }; +} + +/* SNIPPET START */ +// Ora usiamo AsyncNotifierProvider invece di FutureProvider +final todoListProvider = AsyncNotifierProvider.autoDispose>( + TodoList.new, +); + +// Usiamo un AsyncNotifier perché la nostra logica è asincrona. +// Più nello specifico, avremo di AutoDisposeAsyncNotifier +// per fruire del modificatore "autoDispose". +class TodoList extends AutoDisposeAsyncNotifier> { + @override + Future> build() async { + // La logica che in precedenza avevamo nel nostro FutureProvider ora è nel metodo di build. + return [ + Todo(description: 'Learn Flutter', completed: true), + Todo(description: 'Learn Riverpod'), + ]; + } +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/todo_list_notifier_add_todo.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/todo_list_notifier_add_todo.dart new file mode 100644 index 000000000..581c9fc81 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/todo_list_notifier_add_todo.dart @@ -0,0 +1,27 @@ +// ignore_for_file: avoid_print + +import 'dart:convert'; + +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:http/http.dart' as http; + +import 'todo_list_notifier.dart'; + +final todoListProvider = AsyncNotifierProvider.autoDispose>( + TodoList.new, +); + +/* SNIPPET START */ +class TodoList extends AutoDisposeAsyncNotifier> { + @override + Future> build() async => [/* ... */]; + + Future addTodo(Todo todo) async { + await http.post( + Uri.https('your_api.com', '/todos'), + // Serializziamo il nostro oggetto Todo e lo inviamo al server. + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(todo.toJson()), + ); + } +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/todo_list_provider.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/todo_list_provider.dart new file mode 100644 index 000000000..327cc8918 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/todo_list_provider.dart @@ -0,0 +1,27 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +/* SNIPPET START */ +class Todo { + Todo({ + required this.description, + this.completed = false, + }); + + factory Todo.fromJson(Map json) { + return Todo( + description: json['description'] as String, + completed: json['completed'] as bool, + ); + } + + final String description; + final bool completed; +} + +final todoListProvider = FutureProvider.autoDispose>((ref) async { + // Simula una richiesta di rete. Normalmente il risultato dovrebbe venire da una API reale + return [ + Todo(description: 'Learn Flutter', completed: true), + Todo(description: 'Learn Riverpod'), + ]; +}); diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/render_add_todo.ts b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/render_add_todo.ts new file mode 100644 index 000000000..798feb107 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/render_add_todo.ts @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw/render_add_todo.dart"; +import hooks from "!!raw-loader!./raw/render_add_todo_hooks.dart"; + +export default { + raw: raw, + hooks: hooks, + codegen: raw, + hooksCodegen: hooks, +}; \ No newline at end of file diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/todo_list_notifier.ts b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/todo_list_notifier.ts new file mode 100644 index 000000000..b32ae3b9b --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/todo_list_notifier.ts @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw/todo_list_notifier.dart"; +import codegen from "!!raw-loader!./codegen/todo_list_notifier.dart"; + +export default { + raw: raw, + hooks: raw, + codegen: codegen, + hooksCodegen: codegen, +}; \ No newline at end of file diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/todo_list_notifier_add_todo.ts b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/todo_list_notifier_add_todo.ts new file mode 100644 index 000000000..62fb0e0fc --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/todo_list_notifier_add_todo.ts @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw/todo_list_notifier_add_todo.dart"; +import codegen from "!!raw-loader!./codegen/todo_list_notifier_add_todo.dart"; + +export default { + raw: raw, + hooks: raw, + codegen: codegen, + hooksCodegen: codegen, +}; \ No newline at end of file diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/todo_list_provider.ts b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/todo_list_provider.ts new file mode 100644 index 000000000..de7789c0b --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/side_effects/todo_list_provider.ts @@ -0,0 +1,7 @@ +import raw from "!!raw-loader!./raw/todo_list_provider.dart"; +import codegen from "!!raw-loader!./codegen/todo_list_provider.dart"; + +export default { + raw: raw, + codegen: codegen, +}; diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing.mdx b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing.mdx new file mode 100644 index 000000000..569e751a7 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing.mdx @@ -0,0 +1,160 @@ +--- +title: Testare i tuoi provider +--- + +import { AutoSnippet, When } from "../../../../../src/components/CodeSnippet"; +import createContainer from "!!raw-loader!./testing/create_container.dart"; +import unitTest from "!!raw-loader!./testing/unit_test.dart"; +import widgetTest from "!!raw-loader!./testing/widget_test.dart"; +import fullWidgetTest from "!!raw-loader!./testing/full_widget_test.dart"; +import widgetContainerOf from "!!raw-loader!./testing/widget_container_of.dart"; +import providerToMock from "./testing/provider_to_mock"; +import mockProvider from "!!raw-loader!./testing/mock_provider.dart"; +import autoDisposeListen from "!!raw-loader!./testing/auto_dispose_listen.dart"; +import listenProvider from "!!raw-loader!./testing/listen_provider.dart"; +import awaitFuture from "!!raw-loader!./testing/await_future.dart"; +import notifierMock from "./testing/notifier_mock"; + +Una parte fondamentale delle API di Riverpod è l'abilità di testare i tuoi provider in modo isolato. + +Per una suite di test adeguata, ci sono alcune sfide da superare: + +- I test non dovrebbero condividere lo stato. Ciò significa che nuovi test + non dovrebbero essere influenzati dai test precedenti. +- I test dovrebbero darci l'abilità di emulare certe funzionalità + per ottenere lo stato desiderato. +- L'ambiente di test dovrebbe essere il più vicino possibile all'ambiente reale. + +Fortunatamente, Riverpod semplifica il raggiungimento di tutti questi obiettivi. + +## Impostare un test + +Quando si definisce un test con Riverpod, ci sono due scenari principali: + +- Test unitari, di solito senza dipendenze di Flutter. + Possono essere utili per testare il comportamento di un provider isolamente. +- Test di widget, di solito con dipendenze di Flutter. + Possono essere utili per testare il comportamento di un widget che utilizza un provider. + +### Test unitari + +I test unitari sono definit usando la funzione `test` da [package:test](https://pub.dev/packages/test) + +La differenza principale con qualsiasi altro test è che creeremo un oggetto +`ProviderContainer`. Questo oggetto permetterà al nostro test di interagire con i provider + +Si consiglia di creare un'utilità di test sia per la creazione che per l'eliminazione +di un oggetto `ProviderContainer`: + + + +Successivamente, possiamo definire un `test` utilizzando questa utilità: + + + +Ora che abbiamo un ProviderContainer possiamo utilizzarlo per leggere i provider usando: + +- `container.read`, per leggere il valore corrente di un provider. +- `container.listen`, per restare in ascolto di un provider ed essere notificato dei suoi cambiamenti. + +:::caution +Fai attenzione quando usi `container.read` quando i provider sono distrutti automaticamente. +Se il tuo provider non è ascoltato, ci sono chances che il suo stato verrà distrutto nel mezzo +del nostro test. + +In quel caso, considera utilizzare `container.listen`. +Il suo valore di ritorno permette comunque di leggere il valore corrente del provider, +ma si assicurerà anche che il provider non venga distrutto nel mezzo del tuo test: + + +::: + +### Test di widget + +I test dei widget sono definiti usando la funzione `testWidgets` da [package:flutter_test](https://pub.dev/packages/flutter_test). + +In questo caso, la differenza principale con i normali test di widget è che dobbiamo +aggiungere un widget `ProviderScope` alla radice di `tester.pumpWidget`. + + + +Questo è simile a quello che facciamo quando abilitiamo Riverpod nella nostra app Flutter. + +Successivamente, possiamo usare `tester` per interagire col nostro widget. +In alternativa, se vuoi interagire coi tuoi provider, puoi ottenere +un `ProviderContainer`. +Un oggetto `ProviderContainer` può essere ottenuto usando `ProviderScope.containerOf(buildContext)`. +Usando `tester` possiamo quindi scrivere quanto segue: + + + +Possiamo quindi usarlo per leggere i provider. Di seguito un esempio completo: + + + +## Mock/Imitare provider + +Fino ad ora abbiamo visto come impostare un test ed interagire in modo semplice con i provider. +Tuttavia, in alcuni casi, potremmo voler imitare un provider. + +La parte interessante: tutti i provider possono essere imitati di default, senza nessun impostazione aggiuntiva. +Questo è possibile specificando il parametro `overrides` su `ProviderScope` o `ProviderContainer`. + +Consideriamo il provider seguente: + + + +Possiamo imitarlo usando: + + + +## Spiare i cambiamenti in un provider + +Dato che abbiamo ottenuto un `ProviderContainer` nei nostri test, è possibile +usarlo per "ascoltare" un provider: + + + +Puoi combinare questo con pacchetti come [mockito](https://pub.dev/packages/mockito) o +[mocktail](https://pub.dev/packages/mocktail) per usare la loro API `verify`. +O più semplicemente, puoi aggiungere tutti i cambiamenti in una lista e controllarli tramite 'assert'. + +## Aspettare provider asincroni + +In Riverpod è molto comune per i provider restituire un Future/Stream. +In questo caso, ci sono chances che i nostri test abbiano bisogno di aspettare che +quelle operazioni asincrone siano completate. + +Un modo per farlo è leggere il `.future` di un provider: + + + +## Imitare i Notifier + +È generalmente sconsigliato imitare i Notifier. +Invece, dovresti probabilmente introdurre un livello di astrazione nella logica del tuo +Notifier, in modo tale da poter imitare tale astrazione. +Per esempio, al posto di imitare un Notifier, potresti imitare un "repository" +che il Notifier usa per ottenere i dati. + +Se vuoi insistere nell'imitare un Notifier, esiste una considerazione speciale +per creare un mock di questo tipo: il tuo mock deve essere una subclass della classe +base del Notifier: non puoi implementare (via "implements") un Notifier, poichè +romperebbe l'interfaccia. + +Pertanto, quando si imita un Notifier, invece di scrivere il codice mockito seguente: + +```dart +class MyNotifierMock with Mock implements MyNotifier {} +``` + +Dovresti invece scrivere: + + + + + +Per far sì che funzioni, il tuo mock deve essere posto nello stesso file del Notifier +che stai imitando. Altrimenti, non avresti accesso alla classe `_$MyNotifier`. + + diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/auto_dispose_listen.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/auto_dispose_listen.dart new file mode 100644 index 000000000..6d901503b --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/auto_dispose_listen.dart @@ -0,0 +1,24 @@ +// ignore_for_file: unused_local_variable, avoid_print + +import 'package:flutter_test/flutter_test.dart'; +import 'package:riverpod/riverpod.dart'; + +import 'create_container.dart'; + +final provider = Provider((_) => 'Hello world'); + +void main() { + test('Some description', () { + final container = createContainer(); + /* SNIPPET START */ + final subscription = container.listen(provider, (_, __) {}); + + expect( + // Equivalente di `container.read(provider)` + // Ma il provider non verrà distrutto a meno che "subscription" non venga distrutta. + subscription.read(), + 'Some value', + ); + /* SNIPPET END */ + }); +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/await_future.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/await_future.dart new file mode 100644 index 000000000..060f4c592 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/await_future.dart @@ -0,0 +1,30 @@ +// ignore_for_file: unused_local_variable + +import 'package:flutter_test/flutter_test.dart'; +import 'package:riverpod/riverpod.dart'; + +import 'create_container.dart'; + +final provider = FutureProvider((_) async => 42); + +void main() { + test('Some description', () async { + // Crea un ProviderContainer per questo test. + // NON condivedere i ProviderContainer tra i vari test. + final container = createContainer(); + + /* SNIPPET START */ + // TODO: usa il container per testare la tua applicazione. + // Il valore atteso è asincrono, quindi dovremmo usare "expectLater" + await expectLater( + // Leggiamo "provider.future" invece di "provider". + // Questo è possibile su provider asincroni e restituisce un future + // che si risolverà con il valore del provider. + container.read(provider.future), + // Possiamo verificare che quel future si risolva con il valore atteso. + // In alternativa possiamo usare "throwsA" per gli errori. + completion('some value'), + ); + /* SNIPPET END */ + }); +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/create_container.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/create_container.dart new file mode 100644 index 000000000..e9441d3b8 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/create_container.dart @@ -0,0 +1,22 @@ +import 'package:riverpod/riverpod.dart'; +import 'package:test/test.dart'; + +/// Un'utilità di test che crea un [ProviderContainer] e lo distrugge automaticamente +/// alla fine del test +ProviderContainer createContainer({ + ProviderContainer? parent, + List overrides = const [], + List? observers, +}) { + // Crea un ProviderContainer, permettendo di specificare dei parametri. + final container = ProviderContainer( + parent: parent, + overrides: overrides, + observers: observers, + ); + + // Alla fine del test, distrugge il container + addTearDown(container.dispose); + + return container; +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/full_widget_test.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/full_widget_test.dart new file mode 100644 index 000000000..5b2ee2d94 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/full_widget_test.dart @@ -0,0 +1,33 @@ +// ignore_for_file: unused_local_variable + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; + +final provider = Provider((_) => 'some value'); + +class YourWidgetYouWantToTest extends StatelessWidget { + const YourWidgetYouWantToTest({super.key}); + + @override + Widget build(BuildContext context) => const Placeholder(); +} + +/* SNIPPET START */ +void main() { + testWidgets('Some description', (tester) async { + await tester.pumpWidget( + const ProviderScope(child: YourWidgetYouWantToTest()), + ); + + final element = tester.element(find.byType(YourWidgetYouWantToTest)); + final container = ProviderScope.containerOf(element); + + // TODO interagire con i tuoi provider + expect( + container.read(provider), + 'some value', + ); + }); +} +/* SNIPPET END */ diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/listen_provider.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/listen_provider.dart new file mode 100644 index 000000000..d2e840d9b --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/listen_provider.dart @@ -0,0 +1,22 @@ +// ignore_for_file: unused_local_variable, avoid_print + +import 'package:flutter_test/flutter_test.dart'; +import 'package:riverpod/riverpod.dart'; + +import 'create_container.dart'; + +final provider = Provider((_) => 'Hello world'); + +void main() { + test('Some description', () { + final container = createContainer(); + /* SNIPPET START */ + container.listen( + provider, + (previous, next) { + print('The provider changed from $previous to $next'); + }, + ); + /* SNIPPET END */ + }); +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/mock_provider.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/mock_provider.dart new file mode 100644 index 000000000..0e5b266d2 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/mock_provider.dart @@ -0,0 +1,45 @@ +// ignore_for_file: unused_local_variable + +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'create_container.dart'; +import 'full_widget_test.dart'; +import 'provider_to_mock/raw.dart'; + +void main() { + testWidgets('Some description', (tester) async { + await tester.pumpWidget( + const ProviderScope(child: YourWidgetYouWantToTest()), + ); + /* SNIPPET START */ + // Nei test unitari, riutilizzando la nostra precedente utilità "createContainer". + final container = createContainer( + // Possiamo specificare una lista di provider da emulare: + overrides: [ + // In questo caso, stiamo imitando "exampleProvider". + exampleProvider.overrideWith((ref) { + // Questa funzione è la tipica funzione di inizializzazione di un provider. + // Qui è dove normalmente chiamaresti "ref.watch" e restituiresti lo stato iniziale. + + // Sostituiamo il valore di default "Hello world" con un valore custom. + // Infine, quando interagiremo con `exampleProvider`, ci ritornerà questo valore. + return 'Hello from tests'; + }), + ], + ); + + // Possiamo anche fare lo stesso nei test di widget usando ProviderScope: + await tester.pumpWidget( + ProviderScope( + // I ProviderScope hanno lo stesso esatto parametro "overrides" + overrides: [ + // Uguale a prima + exampleProvider.overrideWith((ref) => 'Hello from tests'), + ], + child: const YourWidgetYouWantToTest(), + ), + ); + /* SNIPPET END */ + }); +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/notifier_mock/codegen.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/notifier_mock/codegen.dart new file mode 100644 index 000000000..84f0fff0b --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/notifier_mock/codegen.dart @@ -0,0 +1,17 @@ +// ignore_for_file: prefer_mixin + +import 'package:mockito/mockito.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +/* SNIPPET START */ +@riverpod +class MyNotifier extends _$MyNotifier { + @override + int build() => throw UnimplementedError(); +} + +// Il tuo mock necessita di subclassare la classe base del Notifier +class MyNotifierMock extends _$MyNotifier with Mock implements MyNotifier {} +/* SNIPPET END */ diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/notifier_mock/codegen.g.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/notifier_mock/codegen.g.dart new file mode 100644 index 000000000..7efc9fc64 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/notifier_mock/codegen.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$myNotifierHash() => r'912fa35c2296626fc0825bcbcfc6b6c85958be02'; + +/// See also [MyNotifier]. +@ProviderFor(MyNotifier) +final myNotifierProvider = + AutoDisposeNotifierProvider.internal( + MyNotifier.new, + name: r'myNotifierProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$myNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$MyNotifier = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/notifier_mock/index.ts b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/notifier_mock/index.ts new file mode 100644 index 000000000..9d50c6614 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/notifier_mock/index.ts @@ -0,0 +1,4 @@ +import raw from '!!raw-loader!./raw.dart'; +import codegen from '!!raw-loader!./codegen.dart'; + +export default { raw, codegen }; diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/notifier_mock/raw.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/notifier_mock/raw.dart new file mode 100644 index 000000000..d21cc3d0e --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/notifier_mock/raw.dart @@ -0,0 +1,14 @@ +// ignore_for_file: prefer_mixin + +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:mockito/mockito.dart'; + +/* SNIPPET START */ +class MyNotifier extends Notifier { + @override + int build() => throw UnimplementedError(); +} + +// Il tuo mock necessita di subclassare la classe base del Notifier +class MyNotifierMock extends Notifier with Mock implements MyNotifier {} +/* SNIPPET END */ diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/provider_to_mock/codegen.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/provider_to_mock/codegen.dart new file mode 100644 index 000000000..c03802466 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/provider_to_mock/codegen.dart @@ -0,0 +1,9 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +/* SNIPPET START */ +// Un provider inizializzato anticipatamente +@riverpod +Future example(ExampleRef ref) async => 'Hello world'; +/* SNIPPET END */ diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/provider_to_mock/codegen.g.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/provider_to_mock/codegen.g.dart new file mode 100644 index 000000000..739f3ea63 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/provider_to_mock/codegen.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$exampleHash() => r'd421d08db0ee9d10af5521159561135d8c5fa57c'; + +/// See also [example]. +@ProviderFor(example) +final exampleProvider = AutoDisposeFutureProvider.internal( + example, + name: r'exampleProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$exampleHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ExampleRef = AutoDisposeFutureProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/provider_to_mock/index.ts b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/provider_to_mock/index.ts new file mode 100644 index 000000000..9d50c6614 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/provider_to_mock/index.ts @@ -0,0 +1,4 @@ +import raw from '!!raw-loader!./raw.dart'; +import codegen from '!!raw-loader!./codegen.dart'; + +export default { raw, codegen }; diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/provider_to_mock/raw.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/provider_to_mock/raw.dart new file mode 100644 index 000000000..b4eb3aaf0 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/provider_to_mock/raw.dart @@ -0,0 +1,6 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +/* SNIPPET START */ +// Un provider inizializzato anticipatamente +final exampleProvider = FutureProvider((ref) async => 'Hello world'); +/* SNIPPET END */ diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/unit_test.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/unit_test.dart new file mode 100644 index 000000000..162c30a72 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/unit_test.dart @@ -0,0 +1,23 @@ +// ignore_for_file: unused_local_variable + +import 'package:flutter_test/flutter_test.dart'; +import 'package:riverpod/riverpod.dart'; + +import 'create_container.dart'; + +final provider = Provider((_) => 42); + +/* SNIPPET START */ +void main() { + test('Some description', () { + // Crea un ProviderContainer per questo test. + // NON condividere dei ProviderContainer tra i test. + final container = createContainer(); + + // TODO: usare il container per testare la tua applicazione. + expect( + container.read(provider), + equals('some value'), + ); + }); +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/widget_container_of.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/widget_container_of.dart new file mode 100644 index 000000000..61b2ca36b --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/widget_container_of.dart @@ -0,0 +1,15 @@ +// ignore_for_file: unused_local_variable + +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'widget_test.dart'; + +void main() { + testWidgets('Some description', (tester) async { + /* SNIPPET START */ + final element = tester.element(find.byType(YourWidgetYouWantToTest)); + final container = ProviderScope.containerOf(element); + /* SNIPPET END */ + }); +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/widget_test.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/widget_test.dart new file mode 100644 index 000000000..b4afc835c --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/testing/widget_test.dart @@ -0,0 +1,22 @@ +// ignore_for_file: unused_local_variable + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; + +class YourWidgetYouWantToTest extends StatelessWidget { + const YourWidgetYouWantToTest({super.key}); + + @override + Widget build(BuildContext context) => const Placeholder(); +} + +/* SNIPPET START */ +void main() { + testWidgets('Some description', (tester) async { + await tester.pumpWidget( + const ProviderScope(child: YourWidgetYouWantToTest()), + ); + }); +} +/* SNIPPET END */ diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync.mdx b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync.mdx new file mode 100644 index 000000000..9083ed859 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync.mdx @@ -0,0 +1,108 @@ +--- +title: Websocket ed esecuzione sincrona +--- + +import { + AutoSnippet, + When, +} from "../../../../../src/components/CodeSnippet"; +import syncDefinition from "./websockets_sync/sync_definition"; +import streamProvider from "./websockets_sync/stream_provider"; +import syncConsumer from "!!raw-loader!./websockets_sync/sync_consumer.dart"; +import rawUsage from "!!raw-loader!./websockets_sync/raw_usage.dart"; +import pipeChangeNotifier from "!!raw-loader!./websockets_sync/pipe_change_notifier.dart"; +import sharedPipeChangeNotifier from "!!raw-loader!./websockets_sync/shared_pipe_change_notifier.dart"; +import changeNotifierProvider from "!!raw-loader!./websockets_sync/change_notifier_provider.dart"; + +Fino ad ora abbiamo solo coperto come creare un `Future`. +Abbiamo fatto ciò apposta, dato che i `Future`s sono una parte fondamentale di come +le applicazioni che usano Riverpod dovrebbero essere costruite. +_Ma_, Riverpod supporta anche altri formati se necessario. + +In particolare, invece di un `Future`, i provider sono liberi di: + +- Ritornare in modo sincrono un oggetto, come creare un "Repository". +- Ritornare uno `Stream`, come ascoltare i websocket. + +Restituire un `Future` e restituire uno `Stream` o un oggetto è abbastanza simile nel complesso. +Considera questa pagina come una spiegazione delle sottili differenze e de vari +suggerimenti per questi casi d'uso. + +## Restituire un oggetto in modo sincrono + +Per creare un oggetto in modo sincrono, assicurati che il tuo provider non ritorni un Future: + + + +Quando un provider crea un oggetto in modo sincrono, questo impatta come quell'oggetto verrà consumato. +In particolare, i valori sincroni non sono contenuti in un "AsyncValue": + + + +La conseguenza di questa differenza è che se il tuo provider lancia un'eccezione, +provare a leggere il valore ricauserà l'errore. +In alternativa, quando si usa `ref.listen`, la callback "onError" sarà invocata. + +### Considerazioni sugli oggetti "Listenable" + + + +Gli oggetti Listenable come `ChangeNotifier` or `StateNotifier` non sono supportati. +Se, per motivi di compatibilità, è necessario interagire con uno di questi oggetti, +un workaround consiste nel collegare il loro meccanismo di notifica a Riverpod. + + + +:::info +Nel caso in cui tu abbia bisogno di questa logica molte volte, è importante notare che +la logica è condivisa! L'oggetto "ref" è progettato per essere componibile. +Ciò consente di estrarre la logica di dispose/ascolto dal provider: + + +::: + + + + + +Quando non si utilizza la generazione di codice, Riverpod offre provider "legacy" +per supportare `ChangeNotifier` e `StateNotifier` direttamente: `ChangeNotifierProvider` and `StateNotifierProvider`. +Usarli è simile ad usare altri tipi di provider. La differenza principale è +che entrambi ascolteranno e smaltiranno automaticamente dell'oggetto restituito. + +Questi provider non sono consigliati per nuova logica di business. +Ma possono essere d'aiuto quando si interagisce con codice vecchio, come +durante una migrazione da `pkg:provider` a Riverpod. + + + + + +## Ascoltare uno Stream + +Un caso d'uso comune delle moderne applicazioni è interagire con websockets, +come Firbase o GraphQL subscriptions. +Interagire con queste API è spesso fatto ascoltando uno `Stream`. + +Per aiutarci in tale scopo, Riverpod supporta di natura gli oggetti `Stream`. +Come con i `Future`, l'oggetto verrà convertito in un `AsyncValue`: + + + +:::info +Riverpod non è consapevole delle implementazioni personalizzate di `Stream`, +come ad esempio `BehaviorSubject` di RX. +Pertanto, restituire un `BehaviorSubject` non esporrà il valore +in modo sincrono ai widget, anche se è già disponibile alla creazione. +::: + +Di default, Riverpod convertirà `Stream` e `Future` in `AsyncValue`. +Anche se raramente necessario, è possibile disattivare questo comportamento +wrappando il tipo del valore di ritorno in un typedef `Raw`. + +:::caution +È generalmente sconsigliato disabilitare la conversione in `AsyncValue`. +Fallo solo se sai quello che stai facendo. +::: + + diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/change_notifier_provider.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/change_notifier_provider.dart new file mode 100644 index 000000000..ea3859a27 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/change_notifier_provider.dart @@ -0,0 +1,11 @@ +// ignore_for_file: omit_local_variable_types + +import 'package:flutter/widgets.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +/* SNIPPET START */ +final myProvider = ChangeNotifierProvider>((ref) { + // Ascolterà ed eliminerà ValueNotifier + // I widget possono quindi "ref.watch" questo provider per ascoltarne gli aggiornamenti. + return ValueNotifier(0); +}); diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/pipe_change_notifier.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/pipe_change_notifier.dart new file mode 100644 index 000000000..b030b5dd9 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/pipe_change_notifier.dart @@ -0,0 +1,22 @@ +// ignore_for_file: omit_local_variable_types + +import 'package:flutter/widgets.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'pipe_change_notifier.g.dart'; + +/* SNIPPET START */ +/// A provider which creates a ValueNotifier and update its listeners +/// whenever the value changes. +@riverpod +ValueNotifier myListenable(MyListenableRef ref) { + final notifier = ValueNotifier(0); + + // Smaltiamo il notifier quando il provider viene distrutto + ref.onDispose(notifier.dispose); + + // Notifica i listener di questo provider ogni volta che il ValueNotifier si aggiorna. + notifier.addListener(ref.notifyListeners); + + return notifier; +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/pipe_change_notifier.g.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/pipe_change_notifier.g.dart new file mode 100644 index 000000000..fb78ce8fc --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/pipe_change_notifier.g.dart @@ -0,0 +1,29 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'pipe_change_notifier.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$myListenableHash() => r'4cc07df2f47050c4aa761e5467f341ab6c312d09'; + +/// A provider which creates a ValueNotifier and update its listeners +/// whenever the value changes. +/// +/// Copied from [myListenable]. +@ProviderFor(myListenable) +final myListenableProvider = AutoDisposeProvider>.internal( + myListenable, + name: r'myListenableProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$myListenableHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef MyListenableRef = AutoDisposeProviderRef>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/raw_usage.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/raw_usage.dart new file mode 100644 index 000000000..6a25e47a5 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/raw_usage.dart @@ -0,0 +1,30 @@ +// ignore_for_file: omit_local_variable_types, prefer_final_locals, use_key_in_widget_constructors + +import 'package:flutter/widgets.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'raw_usage.g.dart'; + +/* SNIPPET START */ +@riverpod +Raw> rawStream(RawStreamRef ref) { + // "Raw" è un typedef. Non c'è bisogno di wrappare + // il valore di ritorno in un costruttore "Raw". + return const Stream.empty(); +} + +class Consumer extends ConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + // Il valore non è più convertito in AsyncValue + // e lo stream creato è ritornato come tale. + Stream stream = ref.watch(rawStreamProvider); + return StreamBuilder( + stream: stream, + builder: (context, snapshot) { + return Text('${snapshot.data}'); + }, + ); + } +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/raw_usage.g.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/raw_usage.g.dart new file mode 100644 index 000000000..5b898587c --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/raw_usage.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'raw_usage.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$rawStreamHash() => r'7e7c2e8f4f08d33a4d86d60449e143c419ca4822'; + +/// See also [rawStream]. +@ProviderFor(rawStream) +final rawStreamProvider = AutoDisposeProvider>>.internal( + rawStream, + name: r'rawStreamProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$rawStreamHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef RawStreamRef = AutoDisposeProviderRef>>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/shared_pipe_change_notifier.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/shared_pipe_change_notifier.dart new file mode 100644 index 000000000..fa41ad22e --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/shared_pipe_change_notifier.dart @@ -0,0 +1,29 @@ +// ignore_for_file: omit_local_variable_types + +import 'package:flutter/widgets.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'shared_pipe_change_notifier.g.dart'; + +/* SNIPPET START */ +extension on Ref { + // Possiamo spostare la logica precedente in una estensione Ref. + // Questo abilita il riutilizzo della logica + T disposeAndListenChangeNotifier(T notifier) { + onDispose(notifier.dispose); + notifier.addListener(notifyListeners); + // Restituiamo il notifier per facilitarne di un poco l'utilizzo + return notifier; + } +} + +@riverpod +ValueNotifier myListenable(MyListenableRef ref) { + return ref.disposeAndListenChangeNotifier(ValueNotifier(0)); +} + +@riverpod +ValueNotifier anotherListenable(AnotherListenableRef ref) { + return ref.disposeAndListenChangeNotifier(ValueNotifier(42)); +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/shared_pipe_change_notifier.g.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/shared_pipe_change_notifier.g.dart new file mode 100644 index 000000000..66cb3d39c --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/shared_pipe_change_notifier.g.dart @@ -0,0 +1,42 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'shared_pipe_change_notifier.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$myListenableHash() => r'7096094cd24ed50dbabb9fb9ab64b340176c04bf'; + +/// See also [myListenable]. +@ProviderFor(myListenable) +final myListenableProvider = AutoDisposeProvider>.internal( + myListenable, + name: r'myListenableProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$myListenableHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef MyListenableRef = AutoDisposeProviderRef>; +String _$anotherListenableHash() => r'38bfe5dbf5f148819b3671ad69d15c8e05264c23'; + +/// See also [anotherListenable]. +@ProviderFor(anotherListenable) +final anotherListenableProvider = + AutoDisposeProvider>.internal( + anotherListenable, + name: r'anotherListenableProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$anotherListenableHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef AnotherListenableRef = AutoDisposeProviderRef>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/stream_provider/codegen.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/stream_provider/codegen.dart new file mode 100644 index 000000000..0a1030247 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/stream_provider/codegen.dart @@ -0,0 +1,34 @@ +// ignore_for_file: omit_local_variable_types, prefer_final_locals, use_key_in_widget_constructors + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +/* SNIPPET START */ +@riverpod +Stream streamExample(StreamExampleRef ref) async* { + // Ogni secondo ritorna un numero da 0 a 41. + // Questo può essere sostituito con uno Stream da Firestore o GraphQL o altro. + for (var i = 0; i < 42; i++) { + yield i; + await Future.delayed(const Duration(seconds: 1)); + } +} + +class Consumer extends ConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + // Lo stream è ascoltato e convertito in un AsyncValue + AsyncValue value = ref.watch(streamExampleProvider); + + // Possiamo usare l'AsyncValue per gestire i stati di caricamento/errore e mostrare il dato. + return switch (value) { + AsyncValue(:final error?) => Text('Error: $error'), + AsyncValue(:final valueOrNull?) => Text('$valueOrNull'), + _ => const CircularProgressIndicator(), + }; + } +} +/* SNIPPET END */ \ No newline at end of file diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/stream_provider/codegen.g.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/stream_provider/codegen.g.dart new file mode 100644 index 000000000..fec43b43a --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/stream_provider/codegen.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$streamExampleHash() => r'ca9993b22f6d587b20c041133cacd28d01933074'; + +/// See also [streamExample]. +@ProviderFor(streamExample) +final streamExampleProvider = AutoDisposeStreamProvider.internal( + streamExample, + name: r'streamExampleProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$streamExampleHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef StreamExampleRef = AutoDisposeStreamProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/stream_provider/index.ts b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/stream_provider/index.ts new file mode 100644 index 000000000..4ee159de8 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/stream_provider/index.ts @@ -0,0 +1,4 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./codegen.dart"; + +export default { raw, codegen }; diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/stream_provider/raw.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/stream_provider/raw.dart new file mode 100644 index 000000000..07ee676d2 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/stream_provider/raw.dart @@ -0,0 +1,30 @@ +// ignore_for_file: omit_local_variable_types, prefer_final_locals, use_key_in_widget_constructors + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +/* SNIPPET START */ +final streamExampleProvider = StreamProvider.autoDispose((ref) async* { + // Ogni secondo ritorna un numero da 0 a 41. + // Questo può essere sostituito con uno Stream da Firestore o GraphQL o altro. + for (var i = 0; i < 42; i++) { + yield i; + await Future.delayed(const Duration(seconds: 1)); + } +}); + +class Consumer extends ConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + // Lo stream è ascoltato e convertito in un AsyncValue + AsyncValue value = ref.watch(streamExampleProvider); + + // Possiamo usare l'AsyncValue per gestire i stati di caricamento/errore e mostrare il dato. + return switch (value) { + AsyncValue(:final error?) => Text('Error: $error'), + AsyncValue(:final valueOrNull?) => Text('$valueOrNull'), + _ => const CircularProgressIndicator(), + }; + } +} +/* SNIPPET END */ \ No newline at end of file diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_consumer.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_consumer.dart new file mode 100644 index 000000000..bf4aa03f7 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_consumer.dart @@ -0,0 +1,19 @@ +// ignore_for_file: omit_local_variable_types, prefer_final_locals + +import 'package:flutter/widgets.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'sync_definition/raw.dart'; + +void main() { +/* SNIPPET START */ + Consumer( + builder: (context, ref, child) { + // Il valore non è contenuto in un "AsyncValue" + int value = ref.watch(synchronousExampleProvider); + + return Text('$value'); + }, + ); +/* SNIPPET END */ +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_definition/codegen.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_definition/codegen.dart new file mode 100644 index 000000000..b18b8f76e --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_definition/codegen.dart @@ -0,0 +1,10 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +/* SNIPPET START */ +@riverpod +int synchronousExample(SynchronousExampleRef ref) { + return 0; +} +/* SNIPPET END */ \ No newline at end of file diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_definition/codegen.g.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_definition/codegen.g.dart new file mode 100644 index 000000000..9d331d63e --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_definition/codegen.g.dart @@ -0,0 +1,28 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$synchronousExampleHash() => + r'98df96e07d554683041f668c06b36f183ff534c1'; + +/// See also [synchronousExample]. +@ProviderFor(synchronousExample) +final synchronousExampleProvider = AutoDisposeProvider.internal( + synchronousExample, + name: r'synchronousExampleProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$synchronousExampleHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef SynchronousExampleRef = AutoDisposeProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_definition/index.ts b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_definition/index.ts new file mode 100644 index 000000000..4ee159de8 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_definition/index.ts @@ -0,0 +1,4 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./codegen.dart"; + +export default { raw, codegen }; diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_definition/raw.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_definition/raw.dart new file mode 100644 index 000000000..9c64294a0 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_definition/raw.dart @@ -0,0 +1,7 @@ +import 'package:riverpod/riverpod.dart'; + +/* SNIPPET START */ +final synchronousExampleProvider = Provider.autoDispose((ref) { + return 0; +}); +/* SNIPPET END */ \ No newline at end of file diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/family/family.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/family/family.dart new file mode 100644 index 000000000..4d49c552a --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/family/family.dart @@ -0,0 +1,11 @@ +import 'dart:math'; + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'family.g.dart'; +/* SNIPPET START */ + +@riverpod +int random(RandomRef ref, {required int seed, required int max}) { + return Random(seed).nextInt(max); +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/family/family.g.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/family/family.g.dart new file mode 100644 index 000000000..528729ff0 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/family/family.g.dart @@ -0,0 +1,174 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'family.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$randomHash() => r'517b12aad4df7b31f8872b89af74e7880377b2ea'; + +/// Copied from Dart SDK +class _SystemHash { + _SystemHash._(); + + static int combine(int hash, int value) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + value); + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); + return hash ^ (hash >> 6); + } + + static int finish(int hash) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + // ignore: parameter_assignments + hash = hash ^ (hash >> 11); + return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); + } +} + +/// See also [random]. +@ProviderFor(random) +const randomProvider = RandomFamily(); + +/// See also [random]. +class RandomFamily extends Family { + /// See also [random]. + const RandomFamily(); + + /// See also [random]. + RandomProvider call({ + required int seed, + required int max, + }) { + return RandomProvider( + seed: seed, + max: max, + ); + } + + @override + RandomProvider getProviderOverride( + covariant RandomProvider provider, + ) { + return call( + seed: provider.seed, + max: provider.max, + ); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'randomProvider'; +} + +/// See also [random]. +class RandomProvider extends AutoDisposeProvider { + /// See also [random]. + RandomProvider({ + required int seed, + required int max, + }) : this._internal( + (ref) => random( + ref as RandomRef, + seed: seed, + max: max, + ), + from: randomProvider, + name: r'randomProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$randomHash, + dependencies: RandomFamily._dependencies, + allTransitiveDependencies: RandomFamily._allTransitiveDependencies, + seed: seed, + max: max, + ); + + RandomProvider._internal( + super._createNotifier, { + required super.name, + required super.dependencies, + required super.allTransitiveDependencies, + required super.debugGetCreateSourceHash, + required super.from, + required this.seed, + required this.max, + }) : super.internal(); + + final int seed; + final int max; + + @override + Override overrideWith( + int Function(RandomRef provider) create, + ) { + return ProviderOverride( + origin: this, + override: RandomProvider._internal( + (ref) => create(ref as RandomRef), + from: from, + name: null, + dependencies: null, + allTransitiveDependencies: null, + debugGetCreateSourceHash: null, + seed: seed, + max: max, + ), + ); + } + + @override + AutoDisposeProviderElement createElement() { + return _RandomProviderElement(this); + } + + @override + bool operator ==(Object other) { + return other is RandomProvider && other.seed == seed && other.max == max; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, seed.hashCode); + hash = _SystemHash.combine(hash, max.hashCode); + + return _SystemHash.finish(hash); + } +} + +mixin RandomRef on AutoDisposeProviderRef { + /// The parameter `seed` of this provider. + int get seed; + + /// The parameter `max` of this provider. + int get max; +} + +class _RandomProviderElement extends AutoDisposeProviderElement + with RandomRef { + _RandomProviderElement(super.provider); + + @override + int get seed => (origin as RandomProvider).seed; + @override + int get max => (origin as RandomProvider).max; +} +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/family/index.tsx b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/family/index.tsx new file mode 100644 index 000000000..fa391f61a --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/family/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./family.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/family/raw.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/family/raw.dart new file mode 100644 index 000000000..68b84d40d --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/family/raw.dart @@ -0,0 +1,27 @@ +import 'dart:math'; + +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +@immutable +abstract class Equatable { + const Equatable(); + + List get props; +} + +/* SNIPPET START */ +class ParamsType extends Equatable { + const ParamsType({required this.seed, required this.max}); + + final int seed; + final int max; + + @override + List get props => [seed, max]; +} + +final randomProvider = + Provider.family.autoDispose((ref, params) { + return Random(params.seed).nextInt(params.max); +}); diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/helpers/item.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/helpers/item.dart new file mode 100644 index 000000000..1082ef6f3 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/helpers/item.dart @@ -0,0 +1,12 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +import 'json.dart'; + +part 'item.freezed.dart'; +part 'item.g.dart'; + +@freezed +class Item with _$Item { + const factory Item({required int id}) = _Item; + factory Item.fromJson(Json json) => _$ItemFromJson(json); +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/helpers/item.freezed.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/helpers/item.freezed.dart new file mode 100644 index 000000000..e578c8154 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/helpers/item.freezed.dart @@ -0,0 +1,146 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'item.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +Item _$ItemFromJson(Map json) { + return _Item.fromJson(json); +} + +/// @nodoc +mixin _$Item { + int get id => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $ItemCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ItemCopyWith<$Res> { + factory $ItemCopyWith(Item value, $Res Function(Item) then) = + _$ItemCopyWithImpl<$Res, Item>; + @useResult + $Res call({int id}); +} + +/// @nodoc +class _$ItemCopyWithImpl<$Res, $Val extends Item> + implements $ItemCopyWith<$Res> { + _$ItemCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + }) { + return _then(_value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$ItemImplCopyWith<$Res> implements $ItemCopyWith<$Res> { + factory _$$ItemImplCopyWith( + _$ItemImpl value, $Res Function(_$ItemImpl) then) = + __$$ItemImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({int id}); +} + +/// @nodoc +class __$$ItemImplCopyWithImpl<$Res> + extends _$ItemCopyWithImpl<$Res, _$ItemImpl> + implements _$$ItemImplCopyWith<$Res> { + __$$ItemImplCopyWithImpl(_$ItemImpl _value, $Res Function(_$ItemImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + }) { + return _then(_$ItemImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$ItemImpl implements _Item { + const _$ItemImpl({required this.id}); + + factory _$ItemImpl.fromJson(Map json) => + _$$ItemImplFromJson(json); + + @override + final int id; + + @override + String toString() { + return 'Item(id: $id)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ItemImpl && + (identical(other.id, id) || other.id == id)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, id); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$ItemImplCopyWith<_$ItemImpl> get copyWith => + __$$ItemImplCopyWithImpl<_$ItemImpl>(this, _$identity); + + @override + Map toJson() { + return _$$ItemImplToJson( + this, + ); + } +} + +abstract class _Item implements Item { + const factory _Item({required final int id}) = _$ItemImpl; + + factory _Item.fromJson(Map json) = _$ItemImpl.fromJson; + + @override + int get id; + @override + @JsonKey(ignore: true) + _$$ItemImplCopyWith<_$ItemImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/helpers/item.g.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/helpers/item.g.dart new file mode 100644 index 000000000..3c653e18c --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/helpers/item.g.dart @@ -0,0 +1,18 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'item.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$ItemImpl _$$ItemImplFromJson(Map json) => _$ItemImpl( + id: json['id'] as int, + ); + +Map _$$ItemImplToJson(_$ItemImpl instance) => + { + 'id': instance.id, + }; diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/helpers/json.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/helpers/json.dart new file mode 100644 index 000000000..17cfb1c01 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/helpers/json.dart @@ -0,0 +1 @@ +typedef Json = Map; diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/async_values/async_values.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/async_values/async_values.dart new file mode 100644 index 000000000..802a23150 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/async_values/async_values.dart @@ -0,0 +1,29 @@ +import 'package:collection/collection.dart'; +import 'package:dio/dio.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../../helpers/item.dart'; +import '../../helpers/json.dart'; + +part 'async_values.g.dart'; + +/* SNIPPET START */ + +@riverpod +Future> itemsApi(ItemsApiRef ref) async { + final client = Dio(); + final result = await client.get>('your-favorite-api'); + final parsed = [...result.data!.map((e) => Item.fromJson(e as Json))]; + return parsed; +} + +@riverpod +List evenItems(EvenItemsRef ref) { + final asyncValue = ref.watch(itemsApiProvider); + if (asyncValue.isReloading) return []; + if (asyncValue.hasError) return const [Item(id: -1)]; + + final items = asyncValue.requireValue; + + return [...items.whereIndexed((index, element) => index.isEven)]; +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/async_values/async_values.g.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/async_values/async_values.g.dart new file mode 100644 index 000000000..09f07382c --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/async_values/async_values.g.dart @@ -0,0 +1,40 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'async_values.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$itemsApiHash() => r'b32ccb7b85305e361d8ed752cbe11d9524c96190'; + +/// See also [itemsApi]. +@ProviderFor(itemsApi) +final itemsApiProvider = AutoDisposeFutureProvider>.internal( + itemsApi, + name: r'itemsApiProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$itemsApiHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ItemsApiRef = AutoDisposeFutureProviderRef>; +String _$evenItemsHash() => r'55ae98f9b6108203dfc4a139f1ade9fbd8ba8ddd'; + +/// See also [evenItems]. +@ProviderFor(evenItems) +final evenItemsProvider = AutoDisposeProvider>.internal( + evenItems, + name: r'evenItemsProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$evenItemsHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef EvenItemsRef = AutoDisposeProviderRef>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/async_values/index.tsx b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/async_values/index.tsx new file mode 100644 index 000000000..526f2dffe --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/async_values/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./async_values.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/async_values/raw.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/async_values/raw.dart new file mode 100644 index 000000000..1ca8987ef --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/async_values/raw.dart @@ -0,0 +1,25 @@ +import 'package:collection/collection.dart'; +import 'package:dio/dio.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../../helpers/item.dart'; +import '../../helpers/json.dart'; + +/* SNIPPET START */ + +final itemsApiProvider = FutureProvider.autoDispose((ref) async { + final client = Dio(); + final result = await client.get>('your-favorite-api'); + final parsed = [...result.data!.map((e) => Item.fromJson(e as Json))]; + return parsed; +}); + +final evenItemsProvider = Provider.autoDispose((ref) { + final asyncValue = ref.watch(itemsApiProvider); + if (asyncValue.isLoading) return []; + if (asyncValue.hasError) return const [Item(id: -1)]; + + final items = asyncValue.requireValue; + + return [...items.whereIndexed((index, element) => index.isEven)]; +}); diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/auto_dispose/auto_dispose.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/auto_dispose/auto_dispose.dart new file mode 100644 index 000000000..079e29de2 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/auto_dispose/auto_dispose.dart @@ -0,0 +1,28 @@ +import 'dart:math'; + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'auto_dispose.g.dart'; + +/* SNIPPET START */ + +// Con la generazione di codice, un provider è .autoDispose di default +@riverpod +int diceRoll(DiceRollRef ref) { + // Poiché questo provider è .autoDispose, smettere di ascoltarlo ne disporrà lo stato esposto attuale. + // Quindi, ogni volta che questo provider viene ascoltato di nuovo, + // verrà tirato un nuovo dado e lo stato verrà esposto di nuovo. + final dice = Random().nextInt(10); + return dice; +} + +@riverpod +int cachedDiceRoll(CachedDiceRollRef ref) { + final coin = Random().nextInt(10); + if (coin > 5) throw Exception('Way too large.'); + // La condizione sopra potrebbe fallire; + // Se non lo fa, l'istruzione seguente dice al Provider + // di mantenere il suo stato in cache, *anche quando nessuno lo ascolta più*. + ref.keepAlive(); + return coin; +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/auto_dispose/auto_dispose.g.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/auto_dispose/auto_dispose.g.dart new file mode 100644 index 000000000..376279010 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/auto_dispose/auto_dispose.g.dart @@ -0,0 +1,41 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'auto_dispose.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$diceRollHash() => r'dfd5ac8b74351a0076da9d131c10277f53ff11b9'; + +/// See also [diceRoll]. +@ProviderFor(diceRoll) +final diceRollProvider = AutoDisposeProvider.internal( + diceRoll, + name: r'diceRollProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$diceRollHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef DiceRollRef = AutoDisposeProviderRef; +String _$cachedDiceRollHash() => r'fc31fcb804f10360d75362e56329976343ee7abb'; + +/// See also [cachedDiceRoll]. +@ProviderFor(cachedDiceRoll) +final cachedDiceRollProvider = AutoDisposeProvider.internal( + cachedDiceRoll, + name: r'cachedDiceRollProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$cachedDiceRollHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef CachedDiceRollRef = AutoDisposeProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/auto_dispose/index.tsx b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/auto_dispose/index.tsx new file mode 100644 index 000000000..6c57cfffd --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/auto_dispose/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./auto_dispose.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/auto_dispose/raw.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/auto_dispose/raw.dart new file mode 100644 index 000000000..4847d18d4 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/auto_dispose/raw.dart @@ -0,0 +1,23 @@ +import 'dart:math'; + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +/* SNIPPET START */ + +final diceRollProvider = Provider.autoDispose((ref) { + // Poiché questo provider è .autoDispose, smettere di ascoltarlo ne disporrà lo stato esposto attuale. + // Quindi, ogni volta che questo provider viene ascoltato di nuovo, + // verrà tirato un nuovo dado e lo stato verrà esposto di nuovo. + final dice = Random().nextInt(10); + return dice.isEven; +}); + +final cachedDiceRollProvider = Provider.autoDispose((ref) { + final coin = Random().nextInt(10); + if (coin > 5) throw Exception('Way too large.'); + // La condizione sopra potrebbe fallire; + // Se non lo fa, l'istruzione seguente dice al Provider + // di mantenere il suo stato in cache, *anche quando nessuno lo ascolta più*. + ref.keepAlive(); + return coin.isEven; +}); diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/combine/combine.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/combine/combine.dart new file mode 100644 index 000000000..ecd1915da --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/combine/combine.dart @@ -0,0 +1,19 @@ +import 'dart:math'; + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'combine.g.dart'; + +/* SNIPPET START */ + +@riverpod +int number(NumberRef ref) { + return Random().nextInt(10); +} + +@riverpod +int doubled(DoubledRef ref) { + final number = ref.watch(numberProvider); + + return number * 2; +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/combine/combine.g.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/combine/combine.g.dart new file mode 100644 index 000000000..7df3df2f7 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/combine/combine.g.dart @@ -0,0 +1,40 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'combine.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$numberHash() => r'725e25be57b9cc2bd914752f156e26a214596b63'; + +/// See also [number]. +@ProviderFor(number) +final numberProvider = AutoDisposeProvider.internal( + number, + name: r'numberProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$numberHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef NumberRef = AutoDisposeProviderRef; +String _$doubledHash() => r'ddc640c876bdbe49fe72fe1632b5ff48687c9279'; + +/// See also [doubled]. +@ProviderFor(doubled) +final doubledProvider = AutoDisposeProvider.internal( + doubled, + name: r'doubledProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$doubledHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef DoubledRef = AutoDisposeProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/combine/index.tsx b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/combine/index.tsx new file mode 100644 index 000000000..2ff7dfbaa --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/combine/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./combine.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/combine/raw.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/combine/raw.dart new file mode 100644 index 000000000..ad33636e7 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/combine/raw.dart @@ -0,0 +1,15 @@ +import 'dart:math'; + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +/* SNIPPET START */ + +final numberProvider = Provider.autoDispose((ref) { + return Random().nextInt(10); +}); + +final doubledProvider = Provider.autoDispose((ref) { + final number = ref.watch(numberProvider); + + return number * 2; +}); diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/motivation.mdx b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/motivation.mdx new file mode 100644 index 000000000..aadb58d49 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/motivation.mdx @@ -0,0 +1,189 @@ +--- +title: Motivazione +--- + +import sameType from "./same_type"; +import combine from "./combine"; +import asyncValues from "./async_values"; +import autoDispose from "./auto_dispose"; +import override from "./override"; +import sideEffects from "./side_effects"; +import { + AutoSnippet, +} from "../../../../../../src/components/CodeSnippet"; + +Questo articolo dettagliato è pensato per spiegare perché Riverpod esiste. + +In particolare, questa sezione dovrebbe rispondere alle seguenti domande: +- Dato che Provider è ampiamente popolare, perché si dovrebbe migrare a Riverpod? +- Quali vantaggi concreti si ottengono? +- Come posso migrare a Riverpod? +- Posso migrare in modo incrementale? +- ecc. + +Alla fine di questa sezione dovresti essere convinto che Riverpod è da preferire rispetto a Provider. + +**Riverpod è davvero un approccio più moderno, raccomandato e affidabile rispetto a Provider**. + +Riverpod offre migliori capacità di gestione dello stato, migliori strategie di caching e un modello di reattività semplificato. +Mentre Provider attualmente presenta molte carenze senza una via d'uscita. + +## Limitazioni di Provider +Provider ha problemi fondamentali dovuti alla restrizione dell'API di InheritedWidget. +Intrinsecamente, Provider è un "`InheritedWidget` più semplice"; +Provider è solo un wrapper di InheritedWidget, ed è quindi limitato da esso. + +Ecco un elenco di problemi noti di Provider. + +### Provider non può gestire due (o più) provider dello stesso "tipo". +Dichiarare due `Provider` porterà a un comportamento non affidabile: l'API di `InheritedWidget` +otterrà solo *uno dei due*: l'antenato `Provider` più vicino. +Sebbene [un workaround] sia spiegato nella documentazione di Provider, Riverpod semplicemente non presenta questo problema. + +Eliminando questa limitazione, possiamo suddividere liberamente la logica in piccole parti, come segue: + + + +### I provider emettono ragionevolmente solo un valore alla volta +Quando si legge un'API esterna RESTful, è abbastanza comune mostrare l'ultimo valore letto +mentre una nuova chiamata carica il successivo. +Riverpod consente questo comportamento emettendo due valori contemporaneamente (ossia un valore di dati precedenti +e un nuovo valore di caricamento in arrivo) tramite le API di `AsyncValue`: + + + +Nello snippet precedente, osservando `evenItemsProvider` si avranno i seguenti effetti: +1. Inizialmente, viene effettuata la richiesta. Otteniamo una lista vuota; +2. Poi, diciamo che si verifica un errore. Otteniamo `[Item(id: -1)]`; +3. Quindi, riproviamo la richiesta con una logica di pull-to-refresh (ad esempio tramite `ref.invalidate`); +4. Mentre ricarichiamo il primo provider, il secondo continua a esporre `[Item(id: -1)]`; +5. Questa volta, alcuni dati processati vengono ricevuti correttamente: i nostri elementi pari vengono restituiti correttamente. + +Con Provider, le caratteristiche sopra menzionate non sono lontanamente realizzabili, e ancor meno facili da aggirare. + +### Combinare i provider è difficile e soggetto a errori +Con Provider potremmo essere tentati di utilizzare `context.watch` all'interno del metodo `create` del provider. +Questo sarebbe inaffidabile, poiché `didChangeDependencies` potrebbe essere attivato anche se nessuna dipendenza +è cambiata (ad esempio quando è coinvolta una GlobalKey nell'albero dei widget). + +Tuttavia, Provider ha una soluzione ad hoc chiamata ProxyProvider, ma è considerata tediosa e soggetta a errori. + +La combinazione dello stato è un meccanismo fondamentale di Riverpod, poiché possiamo combinare e memorizzare +valori reattivamente senza alcun overhead con utilità semplici ma potenti come [ref.watch] e [ref.listen]: + + + +La combinazione dei valori viene naturale con Riverpod: le dipendenze sono leggibili e le API rimangono le stesse. + +### Mancanza di sicurezza +Con Provider, è comune trovarsi con un'eccezione in fase di esecuzione come `ProviderNotFoundException` durante +ristrutturazioni e/o durante modifiche importanti. +Questa eccezione in fase di esecuzione *era* una delle principali ragioni per cui Riverpod è stato creato in primo luogo. + +Anche se Riverpod offre molte altre funzionalità, semplicemente non può generare questa eccezione. + +### La distruzione dello stato è difficile +`InheritedWidget` [non può reagire quando un consumatore smette di ascoltarlo]. +Questo impedisce a Provider di distruggere automaticamente lo stato dei suoi provider quando non vengono più utilizzati. +Con Provider, [dobbiamo] fare affidamento sulla creazione di provider per eliminare lo stato quando +smette di essere utilizzato. +Ma questo non è facile, soprattutto quando lo stato è condiviso tra le pagine. + +Riverpod risolve questo problema con API facili da capire come [autodispose] e [keepAlive]. +Queste due API consentono strategie di caching flessibili e creative (ad esempio, caching basato sul tempo): + + + +Sfortunatamente, non c'è modo di implementare questo con un `InheritedWidget` grezzo e quindi con Provider. + +### Mancanza di un meccanismo di parametrizzazione affidabile +Riverpod consente all'utente di dichiarare provider "parametrizzati" con il [modificatore .family]. +Infatti, `.family` è una delle caratteristiche più potenti di Riverpod ed è fondamentale per le sue innovazioni, +ad esempio consente un'enorme [semplificazione della logica]. + +Se volessimo implementare qualcosa di simile utilizzando Provider, +dovremmo rinunciare alla facilità d'uso *e* alla sicurezza dei tipi su tali parametri. + +Inoltre, non poter implementare un meccanismo simile a `.autoDispose` con Provider +impedisce intrinsecamente la possibilità di una implementazione equivalente di `.family`, [poiché queste due funzionalità vanno di pari passo]. + +Infine, come mostrato in precedenza, [risulta che] i widget *non smettono mai* di ascoltare un `InheritedWidget`. +Ciò comporta gravi perdite di memoria se lo stato di alcuni provider viene "montato dinamicamente", +ossia quando si utilizzano parametri per creare un Provider, che è esattamente ciò che fa `.family`. +Pertanto, ottenere un equivalente di `.family` per Provider è fondamentalmente impossibile al momento. + +### Testare è tedioso +Per poter scrivere un test, *è necessario* ridefinire i provider all'interno di ogni test. + +Con Riverpod, i provider sono pronti per essere utilizzati all'interno dei test di default. +Inoltre, Riverpod espone una pratica collezione di utilità di "sovrascrittura" che sono cruciali quando si simulano i provider. + +Testare il codice dello stato combinato sarebbe semplice come segue: + + + +Per più informazioni riguardo ai test, vedere [Testing]. + +### Attivare effetti collaterali non è immediato +Poiché `InheritedWidget` non ha un callback `onChange`, Provider non può averne uno. +Questo è problematico per la navigazione, ad esempio per le snack bar, le modali, ecc. + +Invece, Riverpod offre semplicemente `ref.listen`, che [si integra bene con Flutter]. + + + +## Verso Riverpod +Dal punto di vista concettuale, Riverpod e Provider sono abbastanza simili. +Entrambi i pacchetti svolgono un ruolo simile. Entrambi cercano di: + +- memorizzare nella cache e smaltire oggetti con dello stato; +- offrire un modo per emulare tali oggetti durante i test; +- offrire un modo per i widget di ascoltare tali oggetti in modo semplice. + +Puoi pensare a Riverpod come a ciò che Provider avrebbe potuto diventare se fosse continuato a maturare per alcuni anni. + +### Perché un package separato? +Originariamente, era previsto un importante aggiornamento di Provider come soluzione ai problemi sopra menzionati. +Tuttavia, in seguito si è deciso di non farlo, poiché sarebbe stato "troppo incisivo" e persino controverso, +a causa della nuova API `ConsumerWidget`. +Poiché Provider è ancora uno dei pacchetti Flutter più utilizzati, è stato invece deciso +di creare un package separato, e così è nato Riverpod. + +La creazione di un package separato ha permesso: + - Una facilità di migrazione per chiunque voglia farlo, consentendo anche l'uso temporaneo di entrambi gli approcci, *nello stesso momento*; + - Di permettere alle persone di rimanere fedeli a Provider se non gradiscono Riverpod in principio o se non lo trovano ancora affidabile; + - Sperimentazione, consentendo a Riverpod di cercare soluzioni production-ready alle varie limitazioni tecniche di Provider. + +Infatti, Riverpod è progettato per essere il successore spirituale di Provider. Da qui il nome "Riverpod" (che è un anagramma di "Provider"). + +### La breaking change +L'unico vero svantaggio di Riverpod è che richiede la modifica del tipo di widget per funzionare: + +- Invece di estendere `StatelessWidget`, con Riverpod dovresti estendere `ConsumerWidget`. +- Invece di estendere `StatefulWidget`, con Riverpod dovresti estendere `ConsumerStatefulWidget`. + +Ma questa inconvenienza è piuttosto minore nel quadro generale. E questa richiesta potrebbe, un giorno, scomparire. + +### Scegliere la liberia giusta +Probabilmente ti stai chiedendo: +*"Quindi, come utente di Provider, dovrei usare Provider o Riverpod?"*. + +Vogliamo rispondere a questa domanda in modo molto chiaro: + + Probabilmente dovresti utilizzare Riverpod + +Riverpod è globalmente meglio progettato e potrebbe portare a semplificazioni drastiche della tua logica. + +[ref.watch]: /docs/concepts/reading#using-refwatch-to-observe-a-provider +[ref.listen]: /docs/concepts/reading#using-reflisten-to-react-to-a-provider-change +[autodispose]: /docs/concepts/modifiers/auto_dispose +[workaround]: https://pub.dev/packages/provider#can-i-obtain-two-different-providers-using-the-same-type +[modifier .family]: /docs/concepts/modifiers/family +[keepAlive]: /docs/concepts/modifiers/auto_dispose#refkeepalive +[poiché queste due funzionalità vanno di pari passo]: /docs/concepts/modifiers/family#prefer-using-autodispose-when-the-parameter-is-not-constant +[semplificazione della logica]: /docs/concepts/modifiers/family#usage +[dobbiamo]: https://github.com/flutter/flutter/issues/128432 +[risulta che]: https://github.com/flutter/flutter/issues/106549 +[non può reagire quando un consumatore smette di ascoltarlo]: https://github.com/flutter/flutter/issues/106546 +[Testing]: /docs/cookbooks/testing +[si integra bene con Flutter]: /docs/concepts/reading#using-reflisten-to-react-to-a-provider-change diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/override/index.tsx b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/override/index.tsx new file mode 100644 index 000000000..43ec56b51 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/override/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./override.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/override/override.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/override/override.dart new file mode 100644 index 000000000..860020903 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/override/override.dart @@ -0,0 +1,16 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +import '../combine/combine.dart'; + +/* SNIPPET START */ + +void main() { + test('it doubles the value correctly', () async { + final container = ProviderContainer( + overrides: [numberProvider.overrideWith((ref) => 9)], + ); + final doubled = container.read(doubledProvider); + expect(doubled, 9 * 2); + }); +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/override/raw.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/override/raw.dart new file mode 100644 index 000000000..860020903 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/override/raw.dart @@ -0,0 +1,16 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +import '../combine/combine.dart'; + +/* SNIPPET START */ + +void main() { + test('it doubles the value correctly', () async { + final container = ProviderContainer( + overrides: [numberProvider.overrideWith((ref) => 9)], + ); + final doubled = container.read(doubledProvider); + expect(doubled, 9 * 2); + }); +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/same_type/index.tsx b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/same_type/index.tsx new file mode 100644 index 000000000..8569e8316 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/same_type/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./same_type.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/same_type/raw.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/same_type/raw.dart new file mode 100644 index 000000000..dacfe9b9d --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/same_type/raw.dart @@ -0,0 +1,15 @@ +import 'package:collection/collection.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../../helpers/item.dart'; + +/* SNIPPET START */ + +final itemsProvider = Provider.autoDispose( + (ref) => [], // ... +); + +final evenItemsProvider = Provider.autoDispose((ref) { + final items = ref.watch(itemsProvider); + return [...items.whereIndexed((index, element) => index.isEven)]; +}); diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/same_type/same_type.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/same_type/same_type.dart new file mode 100644 index 000000000..94a4ab086 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/same_type/same_type.dart @@ -0,0 +1,19 @@ +import 'package:collection/collection.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../../helpers/item.dart'; + +part 'same_type.g.dart'; + +/* SNIPPET START */ + +@riverpod +List items(ItemsRef ref) { + return []; // ... +} + +@riverpod +List evenItems(EvenItemsRef ref) { + final items = ref.watch(itemsProvider); + return [...items.whereIndexed((index, element) => index.isEven)]; +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/same_type/same_type.g.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/same_type/same_type.g.dart new file mode 100644 index 000000000..db84c17ad --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/same_type/same_type.g.dart @@ -0,0 +1,40 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'same_type.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$itemsHash() => r'f0a8fa6874f4868db9ead31e82c75d976f9d2033'; + +/// See also [items]. +@ProviderFor(items) +final itemsProvider = AutoDisposeProvider>.internal( + items, + name: r'itemsProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$itemsHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ItemsRef = AutoDisposeProviderRef>; +String _$evenItemsHash() => r'82b4525e91604745f2b4664531b32d4aff5717d4'; + +/// See also [evenItems]. +@ProviderFor(evenItems) +final evenItemsProvider = AutoDisposeProvider>.internal( + evenItems, + name: r'evenItemsProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$evenItemsHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef EvenItemsRef = AutoDisposeProviderRef>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/side_effects/index.tsx b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/side_effects/index.tsx new file mode 100644 index 000000000..f4797a94f --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/side_effects/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./side_effects.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/side_effects/raw.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/side_effects/raw.dart new file mode 100644 index 000000000..61f016870 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/side_effects/raw.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +import '../auto_dispose/auto_dispose.dart'; + +/* SNIPPET START */ + +class DiceRollWidget extends ConsumerWidget { + const DiceRollWidget({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + ref.listen(diceRollProvider, (previous, next) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Dice roll! We got: $next')), + ); + }); + return TextButton.icon( + onPressed: () => ref.invalidate(diceRollProvider), + icon: const Icon(Icons.casino), + label: const Text('Roll a dice'), + ); + } +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/side_effects/side_effects.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/side_effects/side_effects.dart new file mode 100644 index 000000000..61f016870 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/motivation/side_effects/side_effects.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +import '../auto_dispose/auto_dispose.dart'; + +/* SNIPPET START */ + +class DiceRollWidget extends ConsumerWidget { + const DiceRollWidget({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + ref.listen(diceRollProvider, (previous, next) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Dice roll! We got: $next')), + ); + }); + return TextButton.icon( + onPressed: () => ref.invalidate(diceRollProvider), + icon: const Icon(Icons.casino), + label: const Text('Roll a dice'), + ); + } +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/provider_vs_riverpod.mdx b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/provider_vs_riverpod.mdx new file mode 100644 index 000000000..dff3aaa22 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/provider_vs_riverpod.mdx @@ -0,0 +1,401 @@ +--- +title: Provider vs Riverpod +--- + +import family from "./family"; +import { + AutoSnippet, +} from "../../../../../src/components/CodeSnippet"; + +Questo articolo riepiloga le differenze e le somiglianze tra Provider e Riverpod. + +## Definire i provider +La differenza principale tra entrambi i package riguarda come vengono definiti i "provider". + +Con [Provider], i provider sono widget e, come tali, vengono inseriti all'interno dell'albero dei widget, +di solito all'interno di un `MultiProvider`: + +```dart +class Counter extends ChangeNotifier { + ... +} + +void main() { + runApp( + MultiProvider( + providers: [ + ChangeNotifierProvider(create: (context) => Counter()), + ], + child: MyApp(), + ) + ); +} +``` + +Con Riverpod, i provider **non** sono widget. Invece, sono semplici oggetti Dart. +Allo stesso modo, i provider sono definiti al di fuori dell'albero dei widget e +vengono dichiarati come variabili finali globali. + +Inoltre, affinché Riverpod funzioni, è necessario aggiungere un widget `ProviderScope` sopra l'intera applicazione. +Di conseguenza, l'equivalente dell'esempio con Provider utilizzando Riverpod sarebbe: + +```dart +// I provider sono ora variabili top-level +final counterProvider = ChangeNotifierProvider((ref) => Counter()); + +void main() { + runApp( + // Questo widget attiva Riverpod sull'intero progetto + ProviderScope( + child: MyApp(), + ), + ); +} +``` + +Nota come la definizione del provider si sia semplicemente spostata di alcune righe. + +:::info +Poiché con Riverpod i provider sono semplici oggetti Dart, è possibile utilizzare Riverpod senza Flutter. +Ad esempio, Riverpod può essere utilizzato per scrivere applicazioni a riga di comando. +::: + +## Leggere i provider: BuildContext +Con Provider, un modo per leggere i provider è utilizzare il `BuildContext` di un widget. + +Ad esempio, se un provider è definito come: + +```dart +Provider(...); +``` + +il modo per leggerlo con [Provider] sarà scritto come + +```dart +class Example extends StatelessWidget { + @override + Widget build(BuildContext context) { + Model model = context.watch(); + + } +} +``` + +L'equivalente in Riverpod sarebbe: + +```dart +final modelProvider = Provider(...); + +class Example extends ConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + Model model = ref.watch(modelProvider); + + } +} +``` + +Nota come: + +- Lo snippet di Riverpod estende `ConsumerWidget` invece di `StatelessWidget`. + Questo tipo di widget aggiunge un parametro in più alla nostra funzione `build`: `WidgetRef`. + +- Invece di `BuildContext.watch`, in Riverpod scriveremo `WidgetRef.watch`, utilizzando il `WidgetRef` + che abbiamo ottenuto da `ConsumerWidget`. + +- Riverpod non si basa sui tipi generici. Si basa invece sulla variabile creata utilizzando la definizione del provider. + +Nota anche quanto simile sia la terminologia. Sia Provider che Riverpod utilizzano la parola chiave "watch" +per descrivere "questo widget dovrebbe essere ricostruito quando il valore cambia". + +:::info +Riverpod utilizza la stessa terminologia di Provider per la lettura dei provider. + +- `BuildContext.watch` -> `WidgetRef.watch` +- `BuildContext.read` -> `WidgetRef.read` +- `BuildContext.select` -> `WidgetRef.watch(myProvider.select)` + +Le regole per "context.watch" vs "context.read" si applicano anche a Riverpod: +All'interno del metodo `build`, utilizza "watch". Nei gestori di eventi come gli eventi di clic, utilizza "read". +Quando hai bisogno di filtrare i valori e ricreare, utilizza "select". +::: + +## Leggere i provider: Consumer +Provider include opzionalmente un widget chiamato `Consumer` (e varianti come `Consumer2`) per leggere i provider. + +`Consumer` è utile per l'ottimizzazione delle prestazioni, consentendo ricostruzioni più granulari dell'albero dei widget e +aggiornando solo i widget rilevanti quando lo stato cambia. + +Pertanto, se un provider è definito come: + +```dart +Provider(...); +``` + +Provider ci permette di leggere quel provider usando `Consumer` in questo modo: + +```dart +Consumer( + builder: (BuildContext context, Model model, Widget? child) { + + } +) +``` + +Riverpod segue lo stesso principio. Anch'esso, ha un widget chiamato `Consumer` con lo stesso scopo. + +Se definiamo un provider come: + +```dart +final modelProvider = Provider(...); +``` + +Possiamo poi utilizzare `Consumer` in questo modo: + +```dart +Consumer( + builder: (BuildContext context, WidgetRef ref, Widget? child) { + Model model = ref.watch(modelProvider); + + } +) +``` + +Nota come `Consumer` ci fornisce un oggetto `WidgetRef`. Si tratta dello stesso oggetto +che abbiamo visto nella parte precedente relativa a `ConsumerWidget`. + +### Non c'è nessun `ConsumerN` equivalente in Riverpod +Nota come `Consumer2`, `Consumer3` e simili di pkg:Provider non sono necessari in Riverpod. + +Con Riverpod, se desideri leggere valori da più provider, puoi semplicemente scrivere più istruzioni `ref.watch`, come segue: + +```dart +Consumer( + builder: (context, ref, child) { + Model1 model = ref.watch(model1Provider); + Model2 model = ref.watch(model2Provider); + Model3 model = ref.watch(model3Provider); + // ... + } +) +``` + +Rispetto alle API `ConsumerN` di pkg:Provider, la soluzione sopra sembra molto meno complessa ed è probabilmente più facile da comprendere. + +## Combinare provider: ProxyProvider con oggetti stateless +Quando si utilizza Provider, il modo ufficiale di combinare i provider è utilizzare il widget `ProxyProvider` +(o varianti come `ProxyProvider2`). + +Ad esempio, potremmo definire: + +```dart +class UserIdNotifier extends ChangeNotifier { + String? userId; +} + +// ... + +ChangeNotifierProvider(create: (context) => UserIdNotifier()), +``` + +Da qui abbiamo due opzioni. Possiamo combinare `UserIdNotifier` per creare un nuovo provider +"senza stato" (tipicamente un valore immutabile che eventualmente sovrascrive ==). +Ad esempio: + +```dart +ProxyProvider( + update: (context, userIdNotifier, _) { + return 'The user ID of the the user is ${userIdNotifier.userId}'; + } +) +``` + +Questo provider restituirebbe automaticamente una nuova `String` ogni volta che `UserIdNotifier.userId` cambia. + +Possiamo fare qualcosa di simile in Riverpod, ma la sintassi è diversa. +Innanzitutto, in Riverpod, la definizione del nostro `UserIdNotifier` sarebbe: + +```dart +class UserIdNotifier extends ChangeNotifier { + String? userId; +} + +// ... + +final userIdNotifierProvider = ChangeNotifierProvider( + (ref) => UserIdNotifier(), +); +``` + +Da qui, per generare la nostra `String` basata su `userId` potremmo fare: + +```dart +final labelProvider = Provider((ref) { + UserIdNotifier userIdNotifier = ref.watch(userIdNotifierProvider); + return 'The user ID of the the user is ${userIdNotifier.userId}'; +}); +``` + +Notare la riga che usa `ref.watch(userIdNotifierProvider)`. + +Questa riga di codice dice a Riverpod di ottenere il contenuto di `userIdNotifierProvider` e che +ogni volta che quel valore cambia, `labelProvider` verrà ricomputato. +Di conseguenza, la `String` emessa da `labelProvider` si aggiornerà automaticamente ogni volta che cambia `userId`. + +Questo schema è stato illustrato in precedenza spiegando [come leggere i provider all'interno dei widget](#lettura-dei-provider-buildcontext). +Infatti, i provider sono ora in grado di ascoltare altri provider allo stesso modo in cui lo fanno i widget. + +## Combinare provider: ProxyProvider con oggetti stateful +Quando si combinano i provider, un'altra possibile situazione è esporre oggetti con stato, +come un'istanza di `ChangeNotifier`. + +Per questo scopo, potremmo utilizzare `ChangeNotifierProxyProvider` (o varianti come `ChangeNotifierProxyProvider2`). +Ad esempio, potremmo definire: + +```dart +class UserIdNotifier extends ChangeNotifier { + String? userId; +} + +// ... + +ChangeNotifierProvider(create: (context) => UserIdNotifier()), +``` + +Successivamente, possiamo definire un nuovo `ChangeNotifier` che è basato su `UserIdNotifier.userId`. +Per esempio, potremmo fare: + +```dart +class UserNotifier extends ChangeNotifier { + String? _userId; + + void setUserId(String? userId) { + if (userId != _userId) { + print('The user ID changed from $_userId to $userId'); + _userId = userId; + } + } +} + +// ... + +ChangeNotifierProxyProvider( + create: (context) => UserNotifier(), + update: (context, userIdNotifier, userNotifier) { + return userNotifier! + ..setUserId(userIdNotifier.userId); + }, +); +``` + +Questo nuovo provider crea un'unica istanza di `UserNotifier` (che non viene mai ricostruita) +e stampa una stringa ogni volta che l'ID dell'utente cambia. + +Per fare la stessa cosa in Riverpod si procede in modo diverso. +Innanzitutto, in Riverpod, la definizione del nostro `UserIdNotifier` sarebbe: + +```dart +class UserIdNotifier extends ChangeNotifier { + String? userId; +} + +// ... + +final userIdNotifierProvider = ChangeNotifierProvider( + (ref) => UserIdNotifier(), +), +``` + +Da qui, l'equivalente del precedente `ChangeNotifierProxyProvider` sarebbe: + +```dart +class UserNotifier extends ChangeNotifier { + String? _userId; + + void setUserId(String? userId) { + if (userId != _userId) { + print('The user ID changed from $_userId to $userId'); + _userId = userId; + } + } +} + +// ... + +final userNotifierProvider = ChangeNotifierProvider((ref) { + final userNotifier = UserNotifier(); + ref.listen( + userIdNotifierProvider, + (previous, next) { + if (previous?.userId != next.userId) { + userNotifier.setUserId(next.userId); + } + }, + ); + + return userNotifier; +}); +``` + +La parte cruciale di questo snippet è la riga `ref.listen`. +Questa funzione `ref.listen` è un'utilità che consente di ascoltare un provider e +ogni qualvolta che il provider cambia, esegue una funzione. + +I parametri `previous` e `next` di quella funzione corrispondono all'ultimo valore prima che +il provider sia cambiato e al nuovo valore dopo il cambio. + +## Ambito (Scoping) dei provider vs `.family` + `.autoDispose` + +In pkg:Provider, lo scope veniva utilizzato per due scopi: +- distruggere lo stato quando si lascia una pagina +- avere uno stato personalizzato per pagina + +Utilizzare lo scoping solo per distruggere lo stato non è ideale. +Il problema è che lo scoping non funziona bene in applicazioni di grandi dimensioni. +Ad esempio, lo stato spesso viene creato in una pagina, ma distrutto in seguito in una pagina diversa dopo la navigazione. +Questo non consente di avere più cache attive su pagine diverse. + +Allo stesso modo, l'approccio "stato personalizzato per pagina" diventa rapidamente difficile da gestire +se lo stato deve essere condiviso con un'altra parte dell'albero dei widget, +come potrebbe essere necessario con modali o con un form a più passaggi. + +Riverpod adotta un approccio diverso: innanzitutto, lo scoping dei provider è in un certo senso scoraggiato; +in secondo luogo, `.family` e `.autoDispose` sono una soluzione di sostituzione completa per questo. + +All'interno di Riverpod, i provider contrassegnati come `.autoDispose` distruggono automaticamente il proprio stato quando non vengono più utilizzati. +Quando l'ultimo widget che rimuove un provider viene smontato, Riverpod lo rileverà e distruggerà il provider. +Prova a utilizzare questi due metodi del ciclo di vita in un provider per testare questo comportamento: + +```dart +ref.onCancel((){ + print("Nessuno sta più in ascolto di me!"); +}); +ref.onDispose((){ + print("Se sono stato definito come `.autoDispose`, sono stato appena distrutto!"); +}); +``` + +Ciò risolve intrinsecamente il problema della "distruzione dello stato". + +Inoltre, è possibile contrassegnare un Provider come `.family` (e, contemporaneamente, come `.autoDispose`). +Questo consente di passare parametri ai provider, il che fa sì che vengano creati e tracciati internamente più provider. +In altre parole, quando si passano parametri, *viene creato uno stato unico per ogni parametro univoco*. + + + +Ciò risolve il problema dello "stato personalizzato per pagina". +In realtà, c'è un altro vantaggio: uno stato del genere non è più vincolato a una pagina specifica. +Invece, se una diversa pagina cerca di accedere allo stesso stato, potrà farlo semplicemente riutilizzando i parametri. + +In molti modi, il passaggio di parametri ai provider è equivalente ad una Map key. +Se la chiave è la stessa, si otterrà lo stesso valore. Se si tratta di una chiave diversa, si otterrà uno stato diverso. + +[provider]: https://pub.dev/packages/provider +[ref.watch]: /docs/concepts/reading#using-refwatch-to-observe-a-provider +[ref.listen]: /docs/concepts/reading#using-reflisten-to-react-to-a-provider-change +[autodispose]: /docs/concepts/modifiers/auto_dispose +[workaround]: https://pub.dev/packages/provider#can-i-obtain-two-different-providers-using-the-same-type +[.family modifier]: /docs/concepts/modifiers/family +[keepAlive]: /docs/concepts/modifiers/auto_dispose#refkeepalive +[ChangeNotifierProvider]: /docs/providers/change_notifier_provider +[combining Providers]: /docs/concepts/combining_providers diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/quickstart.mdx b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/quickstart.mdx new file mode 100644 index 000000000..d47e57bde --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/from_provider/quickstart.mdx @@ -0,0 +1,197 @@ +--- +title: Quickstart +--- + +Questa sezione è progettata per le persone che hanno familiarità con il pacchetto [Provider] e +che desiderano imparare Riverpod. + +Prima di tutto, leggi il breve articolo [getting started] e prova il piccolo esempio [sandbox] +per testare le funzionalità di Riverpod. Se ti piace ciò che vedi in quell'esempio, +dovresti sicuramente considerare una migrazione. + +Infatti, la migrazione da Provider a Riverpod può essere molto semplice e diretta. + +La migrazione consiste principalmente in alcuni passaggi che possono essere eseguiti in modo *incrementale*. + +## Inizia con `ChangeNotifierProvider` +Durante la transizione verso Riverpod puoi benissimo continuare ad utilizzare `ChangeNotifier`. + +Quanto segue è perfettamente accettabile per iniziare: + +```dart +// Se hai questo... +class MyNotifier extends ChangeNotifier { + int state = 0; + + void increment() { + state++; + notifyListeners(); + } +} + +// ... basta che aggiungi questo! +final myNotifierProvider = ChangeNotifierProvider((ref) { + return MyNotifier(); +}); +``` +Come puoi vedere, Riverpod espone una classe [ChangeNotifierProvider], +che è lì appositamente per supportare le migrazioni da pkg:Provider. + +Tieni presente che questo provider non è raccomandato quando si scrive nuovo codice +e non è il modo migliore per utilizzare Riverpod, ma è un modo semplice e delicato per iniziare la tua migrazione. + +:::tip +Non c'è bisogno di cambiare immediatamente i tuoi `ChangeNotifier` nei [provider più moderni di Riverpod]. +Alcuni richiedono un certo cambio di paradigma, quindi potrebbe essere difficile farlo inizialmente. + +Prenditi il tuo tempo, poiché è importante prendere familiarità con Riverpod prima; +scoprirai rapidamente che *quasi* tutti i provider di pkg:provider hanno un equivalente stretto in pkg:riverpod. +::: + +## Inizia con le *foglie (leaves)* + +Inizia con i provider che non dipendono da nessun altro provider, cioè inizia con le *foglie* nel tuo albero delle dipendenze. +Una volta che hai migrato tutte le foglie, puoi passare ai provider che dipendono dalle foglie. + +In altre parole, evita di migrare ProxyProvider all'inizio; affrontali una volta che tutte le loro dipendenze sono state migrate. + +Questo dovrebbe accelerare e semplificare il processo di migrazione, mentre allo stesso tempo ridurre al minimo e tracciare eventuali errori. + +## Riverpod e Provider possono coesistere +*Tieni presente che è del tutto possibile utilizzare contemporaneamente sia Provider che Riverpod.* + +Infatti, utilizzando alias di importazione, è possibile utilizzare entrambe le API insieme. +Questo è anche ottimo per la leggibilità e rimuove qualsiasi uso ambiguo dell'API. + +Se hai intenzione di farlo, considera l'uso di alias di importazione per ciascuna importazione di Provider nel tuo codice. + +:::info +Una guida completa su come implementare efficacemente alias di importazione sarà presto disponibile. +::: + +## Non è *necessario* utilizzare `Consumer` immediatamente. +È importante tenere a mente che non c'è bisogno di utilizzare *immediatamente* le API di [Consumer di Riverpod]. +Se hai appena iniziato la migrazione, [come già menzionato], probabilmente dovresti iniziare con `ChangeNotifierProvider`. + +Considera `myNotifierProvider`, definito precedentemente. + +Dato che il tuo codice interno probabilmente dipende dalle API di pkg:Provider, usa quanto segue per iniziare a consumare `ChangeNotifier` con pkg:Riverpod. + +```dart +MultiProvider( + providers: [ + ChangeNotifierProvider.value(value: ref.watch(myNotifierProvider.notifier)), + ] +) +``` + +In questo modo, solo il Widget radice deve essere inizialmente convertito in un `ConsumerWidget`. +Ciò dovrebbe semplificare ulteriormente la migrazione verso pkg:Riverpod. + +## Migrare un Provider alla volta +Se hai già un'applicazione esistente, non cercare di migrare tutti i tuoi provider contemporaneamente! + +Anche se dovresti lavorare per spostare gradualmente tutta la tua applicazione su Riverpod nel lungo termine, **non esagerare**. +Fallo un provider alla volta. + +Prendi l'esempio precedente. Migrare completamente quel `myNotifierProvider` a Riverpod significa scrivere quanto segue: + +```dart +class MyNotifier extends Notifier { + @override + int build() => 0; + + void increment() => state++; +} + +final myNotifierProvider = NotifierProvider(MyNotifier.new); +``` + +.. ed è _anche_ necessario cambiare in che modo quel provider viene consumato, ovvero scrivere `ref.watch` al posto di `context.watch` per questo provider. + +Questa operazione potrebbe richiedere del tempo e potrebbe causare alcuni errori, quindi non affrettarti a farlo tutto in una volta. + +## Migrare i `ProxyProvider` +In pkg:Provider, `ProxyProvider` viene utilizzato per combinare i valori di altri Provider; +la sua costruzione dipende dal valore di altri provider in modo reattivo. + +Con Riverpod, invece, i provider [sono componibili di default]; pertanto, quando migri un `ProxyProvider`, +dovrai semplicemente scrivere `ref.watch` se desideri dichiarare una dipendenza diretta da un Provider ad un altro. + +Al contempo, combinare valori con Riverpod dovrebbe risultare più semplice e diretto; +di conseguenza, la migrazione dovrebbe semplificare notevolmente il tuo codice. + +Inoltre, non ci sono trucchi per combinare più di due provider insieme: +aggiungi semplicemente un altro `ref.watch` e sarai pronto a procedere. + +## Inizializzazione anticipata +Poiché i provider di Riverpod sono variabili globali *final*, sono [lazy di default]. + +Se hai bisogno di inizializzare alcuni dati o un servizio di utilità in fase di avvio, +il modo migliore per farlo è leggere il tuo provider nel punto in cui di solito inserivi `MultiProvider`. + +In altre parole, poiché Riverpod non può essere forzato a essere inizializzato anticipatamente, i provider +possono essere letti e memorizzati nella tua fase di avvio, in modo che siano pronti e caricati quando necessario nel resto della tua applicazione. + +Una guida completa sull'inizializzazione anticipata dei provider di Riverpod è [disponibile qui]. + +## Generazione di codice +La [generazione di codice] è consigliata per utilizzare Riverpod in modo *future-proof*. +A tal proposito, è probabile che quando la metaprogrammazione diventerà una cosa comune, la generazione di codice sarà predefinita per Riverpod. + +Sfortunatamente, `@riverpod` non può generare codice per `ChangeNotifierProvider`. +Per superare questo problema, puoi utilizzare il seguente metodo di estensione di utilità: +```dart +extension ChangeNotifierWithCodeGenExtension on Ref { + T listenAndDisposeChangeNotifier(T notifier) { + notifier.addListener(notifyListeners); + onDispose(() => notifier.removeListener(notifyListeners)); + onDispose(notifier.dispose); + return notifier; + } +} +``` + +Successivamente, puoi esporre il tuo `ChangeNotifier` con la seguente sintassi codegen: +```dart +// ignore_for_file: unsupported_provider_value +@riverpod +MyNotifier example(ExampleRef ref) { + return ref.listenAndDisposeChangeNotifier(MyNotifier()); +} +``` + +Una volta completata la migrazione "base", puoi cambiare il tuo `ChangeNotifier` in `Notifier`, +eliminando così la necessità di estensioni temporanee. +Riprendendo gli esempi precedenti, un `Notifier` "completamente migrato" diventa: + +```dart +@riverpod +class MyNotifier extends _$MyNotifier { + @override + int build() => 0; + + void increment() => state++; +} +``` + +Una volta fatto ciò, e sei sicuro che non ci siano più `ChangeNotifierProvider` nel tuo codice, +puoi eliminare definitivamente l'estensione temporanea. + +Tieni presente che, sebbene sia consigliato, la generazione di codice non è *obbligatoria*. +È importante ragionare sulle migrazioni in modo incrementale: +quindi, è normale sentire che sia troppo pesante implementare sia la migrazione che la transizione alla sintassi di codice generato. + +Seguendo questa guida, potrai migrare alla generazione di codice come ulteriore passo avanti in seguito. + +[getting started]: /docs/introduction/getting_started +[sandbox]: https://dartpad.dev/?null_safety=true&id=ef06ab3ce0b822e6cc5db0575248e6e2 +[provider]: https://pub.dev/packages/provider +[ChangeNotifierProvider]: /docs/providers/change_notifier_provider +[generazione di codice]: /docs/concepts/about_code_generation +[provider più moderni di Riverpod]: /docs/providers/notifier_provider +[sono componibili di default]: /docs/from_provider/motivation#combinare-i-provider-è-difficile-e-soggetto-a-errori +[come già menzionato]: /docs/from_provider/quickstart#start-with-changenotifierprovider +[Consumer di Riverpod]: /docs/concepts/reading +[lazy di default]: /docs/concepts/provider_lifecycles +[disponibile qui]: /docs/essentials/eager_initialization \ No newline at end of file diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/getting_started.mdx b/website/i18n/it/docusaurus-plugin-content-docs/current/getting_started.mdx deleted file mode 100644 index d9c2a8524..000000000 --- a/website/i18n/it/docusaurus-plugin-content-docs/current/getting_started.mdx +++ /dev/null @@ -1,237 +0,0 @@ ---- -title: Introduzione ---- - -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; - -Prima di addentrarci nei meccanismi interni di [Riverpod], iniziamo con le basi, ovvero -installare [Riverpod] e di seguito creare il classico "Hello World". - -## Quale pacchetto installare - -Prima di tutto, devi sapere che [Riverpod] è diviso in diversi pacchetti, ognuno per una propria esigenza. -La variante di [Riverpod] che vorrai installare dipenderà dal tipo di App che hai intenzione di creare. - -Fai riferimento alla seguente tabella per decidere quale pacchetto installare: - -| tipo app | nome pacchetto | descrizione | -| ------------------------- | ---------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | -| Flutter + [flutter_hooks] | [hooks_riverpod] | Permette l'utilizzo di entrambi [flutter_hooks] e [Riverpod] insieme. | -| Solo Flutter | [flutter_riverpod] | Per utilizzare [Riverpod] per App Flutter | -| Solo Dart (No Flutter) | [riverpod](https://github.com/rrousselGit/riverpod/tree/master/packages/riverpod) | Versione di [Riverpod] con tutte le classi collegate a Flutter rimosse | - -## Installare il pacchetto - -Una volta che sai quale pacchetto installare, procedi aggiungendo la versione dalla seguente tabella al file `pubspec.yaml`: - - - - -```yaml title="pubspec.yaml" -environment: - sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.0.0" - -dependencies: - flutter: - sdk: flutter - flutter_hooks: ^0.18.0 - hooks_riverpod: ^2.1.3 -``` - -Dopodichè esegui `flutter pub get`. - - - - -```yaml title="pubspec.yaml" -environment: - sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.0.0" - -dependencies: - flutter: - sdk: flutter - flutter_riverpod: ^2.1.3 -``` - -Dopodichè esegui `flutter pub get`. - - - - -```yaml title="pubspec.yaml" -environment: - sdk: ">=2.12.0-0 <3.0.0" - -dependencies: - riverpod: ^2.1.3 -``` - -Dopodichè esegui `dart pub get`. - - - - -Ottimo! Hai appena aggiunto [Riverpod] alla tua app! - -## Esempio di utilizzo: Hello world - -Ora che hai installato [Riverpod], possiamo iniziare ad utilizzarlo. - -I seguenti snippet mostrano come utilizzare la nostra nuova dipendenza per creare un "Hello world": - - - - -```dart title="lib/main.dart" -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; - -// Creiamo un "provider", che salverà un valore (Nel nostro caso "Hello world") -// Utilizzare un provider ci permetterà di imitare/sovrascrivere il valore esposto. -final helloWorldProvider = Provider((_) => 'Hello world'); - -void main() { - runApp( - // Per fare in modo che i widget leggano i provider, dobbiamo avvolgere - // l'intera applicazione in un widget "ProviderScope". - // Qui è dove sarà salvato lo stato dei nostri provider. - ProviderScope( - child: MyApp(), - ), - ); -} - -// Nota: MyApp è un HookConsumerWidget, da hooks_riverpod -class MyApp extends HookConsumerWidget { - @override - Widget build(BuildContext context, WidgetRef ref) { - final String value = ref.watch(helloWorldProvider); - - return MaterialApp( - home: Scaffold( - appBar: AppBar(title: Text('Example')), - body: Center( - child: Text(value), - ), - ), - ); - } -} -``` - -Puoi eseguire l'app con `flutter run`. -Questo mostrerà "Hello world" nel tuo device. - - - - -```dart title="lib/main.dart" -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; - -// Creiamo un "provider", che salverà un valore (Nel nostro caso "Hello world") -// Utilizzando provider ci permetterà di mock/override il valore esposto. -final helloWorldProvider = Provider((_) => 'Hello world'); - -void main() { - runApp( - // Per i widget, per avere la possibilità di leggere i provider, dobbiamo avvolgere - // l'intera applicazione con il Widget "ProviderScope". - // Qui è dove sarà salvato lo stato dei nostri provider. - ProviderScope( - child: MyApp(), - ), - ); -} - -// Estende ConsumerWidget (esposto da Riverpod) invece che StatelessWidget -class MyApp extends ConsumerWidget { - @override - Widget build(BuildContext context, WidgetRef ref) { - final String value = ref.watch(helloWorldProvider); - - return MaterialApp( - home: Scaffold( - appBar: AppBar(title: Text('Example')), - body: Center( - child: Text(value), - ), - ), - ); - } -} -``` - -Puoi eseguire con `flutter run`. -Questo mostrerà "Hello world" nel tuo device. - - - - -```dart title="lib/main.dart" -import 'package:riverpod/riverpod.dart'; - -// Creiamo un "provider", che salverà un valore(Nel nostro caso "Hello world") -// Utilizzando provider ci permetterà di mock/override il valore esposto. -final helloWorldProvider = Provider((_) => 'Hello world'); - -void main() { - - // In questo oggetto sarà salvato lo stato dei nostri provider. - final container = ProviderContainer(); - - // Grazie a "container", possiamo leggere il nostro provider. - final value = container.read(helloWorldProvider); - - print(value); // Hello world -} -``` - -Puoi eseguire con `dart lib/main.dart`. -Questo mostrerà "Hello world" nella console - - - - -## Andando oltre: Installare snippet - -Se stai utilizzando `Flutter` e `VS Code`, considera l'utilizzo di [Flutter Riverpod Snippets](https://marketplace.visualstudio.com/items?itemName=robert-brunhage.flutter-riverpod-snippets) - -Se stai utilizzando `Flutter` e `Android Studio` o `IntelliJ`, considera l'utilizzo di [Flutter Riverpod Snippets](https://plugins.jetbrains.com/plugin/14641-flutter-riverpod-snippets) - -![img](/img/snippets/greetingProvider.gif) - -## Scegli il prossimo passo - -Apprendi i concetti base: - -- [Impara di più riguardo i providers](/docs/concepts/providers) - -Segui un cookbook: - -- [Come testare i provider](/docs/cookbooks/testing) - -[riverpod]: https://github.com/rrousselgit/riverpod -[hooks_riverpod]: https://pub.dev/packages/hooks_riverpod -[flutter_riverpod]: https://pub.dev/packages/flutter_riverpod -[flutter_hooks]: https://github.com/rrousselGit/flutter_hooks diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/getting_started_hello_world.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/getting_started_hello_world.dart deleted file mode 100644 index a52fcebae..000000000 --- a/website/i18n/it/docusaurus-plugin-content-docs/current/getting_started_hello_world.dart +++ /dev/null @@ -1,20 +0,0 @@ -// ignore_for_file: avoid_print - -/* SNIPPET START */ - -import 'package:riverpod/riverpod.dart'; - -// Creiamo un "provider", il quale immagazzinerà un valore (qui "Hello world"). -// Usare un provider ci permetterà di simulare/sovrascrivere il valore esposto. - -final helloWorldProvider = Provider((_) => 'Hello world'); - -void main() { - // Questo è l'oggetto dove lo stato dei nostri provider sarà salvato. - final container = ProviderContainer(); - - // Grazie a "container", possiamo leggere il nostro provider. - final value = container.read(helloWorldProvider); - - print(value); // Hello world -} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/getting_started_hello_world_hooks.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/getting_started_hello_world_hooks.dart deleted file mode 100644 index c56c2929b..000000000 --- a/website/i18n/it/docusaurus-plugin-content-docs/current/getting_started_hello_world_hooks.dart +++ /dev/null @@ -1,38 +0,0 @@ -// ignore_for_file: use_key_in_widget_constructors, omit_local_variable_types - -/* SNIPPET START */ - -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; - -// Creiamo un "provider", il quale immagazzinerà un valore (qui "Hello world"). -// Usare un provider ci permetterà di simulare/sovrascrivere il valore esposto. -final helloWorldProvider = Provider((_) => 'Hello world'); - -void main() { - runApp( - // Per fare in modo che i widget leggano i provider, dobbiamo incapsulare - // l'intera applicazione in un widget "ProviderScope" - // Qui è dove lo stato dei nostri provider sarà salvato. - ProviderScope( - child: MyApp(), - ), - ); -} - -// Nota: MyApp è un HookConsumerWidget, da hooks_riverpod. -class MyApp extends HookConsumerWidget { - @override - Widget build(BuildContext context, WidgetRef ref) { - final String value = ref.watch(helloWorldProvider); - - return MaterialApp( - home: Scaffold( - appBar: AppBar(title: const Text('Example')), - body: Center( - child: Text(value), - ), - ), - ); - } -} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/introduction/getting_started.mdx b/website/i18n/it/docusaurus-plugin-content-docs/current/introduction/getting_started.mdx new file mode 100644 index 000000000..941063d06 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/introduction/getting_started.mdx @@ -0,0 +1,195 @@ +--- +title: Introduzione +pagination_next: essentials/first_request +version: 4 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; +import CodeBlock from "@theme/CodeBlock"; +import pubspec from "./getting_started/pubspec"; +import dartHelloWorld from "./getting_started/dart_hello_world"; +import pubadd from "./getting_started/pub_add"; +import helloWorld from "./getting_started/hello_world"; +import dartPubspec from "./getting_started/dart_pubspec"; +import dartPubadd from "./getting_started/dart_pub_add"; +import { + AutoSnippet, + When, +} from "../../../../../src/components/CodeSnippet"; +import { Link } from "../../../../../src/components/Link"; + +## Prova Riverpod online + +Per farti un'idea di Riverpod, prova a utilizzarlo online su [Dartpad](https://dartpad.dev/?null_safety=true&id=ef06ab3ce0b822e6cc5db0575248e6e2) +o su [Zapp](https://zapp.run/new): + + + +## Installazione del package + +Una volta che sai quale pacchetto desideri installare, procedi ad aggiungere la dipendenza alla tua app tramite riga di comando: + + + + + + + + + + + + + + + + +In alternativa, puoi aggiungere manualmente la dipendenza alla tua app all'interno del tuo file `pubspec.yaml`: + + + + + + +Infine, installa i package con `flutter pub get`. + + + Ora puoi eseguire il code-generator con{" "} + flutter pub run build_runner watch. + + + + + + + +Infine, installa i package con `dart pub get`. + + + Ora puoi eseguire il code-generator con{" "} + dart run build_runner watch. + + + + + +Ecco fatto. Hai aggiunto [Riverpod] alla tua app! + +## Abilitare riverpod_lint/custom_lint + +Riverpod include un pacchetto opzionale chiamato [riverpod_lint] +che fornisce regole di lint per aiutarti a scrivere codice migliore +e offre opzioni di refactoring personalizzate. + +Il pacchetto dovrebbe già essere installato se hai seguito i passaggi precedenti, +ma è necessario un passaggio separato per abilitarlo. + +Per abilitare [riverpod_lint], è necessario aggiungere un file `analysis_options.yaml` +posizionato sullo stesso livello del tuo `pubspec.yaml` e includere quanto segue: + + + {`analyzer: + plugins: + - custom_lint`} + + +Ora dovresti vedere dei warning nel tuo ambiente di sviluppo (IDE) se hai commesso errori +nell'utilizzo di Riverpod nel tuo codice. + +Per vedere l'elenco completo dei possibili warning e refactoring, vai alla pagina di [riverpod_lint]. + +:::note +Quei warning non verranno visualizzati nel comando `dart analyze`. +Se desideri verificare tali warning in CI/terminale, puoi eseguire quanto segue: + +```sh +dart run custom_lint +``` + +::: + +## Esempio di utilizzo: Hello world + +Ora che abbiamo installato [Riverpod], possiamo iniziare ad usarlo. + +Gli snippet seguenti mostrano come utilizzare la nostra nuova dipendenza per creare un "Hello world": + + + + + + +Successivamente, avvia l'applicazione con `flutter run`. +Questo renderizzerà "Hello world" sul tuo dispositivo. + + + + + + +Successivamente, avvia l'applicazione con `dart lib/main.dart`. +Vedrai stampato "Hello world" nella console. + + + + +## Andando oltre: Installazione di snippet di codice + +Se stai utilizzando `Flutter` e `VS Code`, considera l'utilizzo di [Flutter Riverpod Snippets](https://marketplace.visualstudio.com/items?itemName=robert-brunhage.flutter-riverpod-snippets) + +Se stai utilizzando `Flutter` e `Android Studio` o `IntelliJ`, considera l'utilizzo di [Flutter Riverpod Snippets](https://plugins.jetbrains.com/plugin/14641-flutter-riverpod-snippets) + +![img](/img/snippets/greetingProvider.gif) + +## Scegli il prossimo passo + +Apprendi i concetti base: + +- + +Segui un cookbook: + +- + +[riverpod]: https://github.com/rrousselgit/riverpod +[hooks_riverpod]: https://pub.dev/packages/hooks_riverpod +[flutter_riverpod]: https://pub.dev/packages/flutter_riverpod +[flutter_hooks]: https://github.com/rrousselGit/flutter_hooks +[riverpod_lint]: https://pub.dev/packages/riverpod_lint diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/getting_started/dart_hello_world/index.tsx b/website/i18n/it/docusaurus-plugin-content-docs/current/introduction/getting_started/dart_hello_world/index.tsx similarity index 100% rename from website/i18n/ko/docusaurus-plugin-content-docs/current/getting_started/dart_hello_world/index.tsx rename to website/i18n/it/docusaurus-plugin-content-docs/current/introduction/getting_started/dart_hello_world/index.tsx diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/getting_started/dart_hello_world/main.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/introduction/getting_started/dart_hello_world/main.dart similarity index 56% rename from website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/getting_started/dart_hello_world/main.dart rename to website/i18n/it/docusaurus-plugin-content-docs/current/introduction/getting_started/dart_hello_world/main.dart index 2b5fc5131..4394668b2 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/getting_started/dart_hello_world/main.dart +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/introduction/getting_started/dart_hello_world/main.dart @@ -7,18 +7,18 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'main.g.dart'; -// 我们创建一个 “provider”,它将用于保存一个值(这里是 “Hello world”)。 -// 通过使用一个 provider,我们能够模拟或覆盖被暴露的值。 +// Creiamo un "provider", che conterrà un valore (qui "Hello, world"). +// Utilizzando un provider, ciò ci consente di simulare/sostituire il valore esposto. @riverpod String helloWorld(HelloWorldRef ref) { return 'Hello world'; } void main() { - // 我们的 provider 将借这个对象存储状态。 + // Questo oggetto è dove lo stato dei nostri provider sarà salvato. final container = ProviderContainer(); - // 我们可以借助这个 “container” 对象读取 provider。 + // Grazie a "container", possiamo leggere il nostro provider. final value = container.read(helloWorldProvider); print(value); // Hello world diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/getting_started/dart_hello_world/main.g.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/introduction/getting_started/dart_hello_world/main.g.dart similarity index 100% rename from website/i18n/ko/docusaurus-plugin-content-docs/current/getting_started/dart_hello_world/main.g.dart rename to website/i18n/it/docusaurus-plugin-content-docs/current/introduction/getting_started/dart_hello_world/main.g.dart diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/getting_started/dart_hello_world/raw.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/introduction/getting_started/dart_hello_world/raw.dart similarity index 100% rename from website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/getting_started/dart_hello_world/raw.dart rename to website/i18n/it/docusaurus-plugin-content-docs/current/introduction/getting_started/dart_hello_world/raw.dart diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/introduction/getting_started/dart_pub_add.tsx b/website/i18n/it/docusaurus-plugin-content-docs/current/introduction/getting_started/dart_pub_add.tsx new file mode 100644 index 000000000..07c03cf6b --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/introduction/getting_started/dart_pub_add.tsx @@ -0,0 +1,32 @@ +export function buildDeps({ + deps = [], + devDeps = [], +}: { + deps?: string[]; + devDeps?: string[]; +}) { + var result = ""; + for (const dep of deps) { + result += `dart pub add ${dep}\n`; + } + + for (const dep of [...devDeps, "custom_lint", "riverpod_lint"]) { + result += `dart pub add dev:${dep}\n`; + } + + return result; +} + +const raw = buildDeps({ deps: ["riverpod"] }); + +const codegen = buildDeps({ + deps: ["riverpod", "riverpod_annotation"], + devDeps: ["riverpod_generator", "build_runner"], +}); + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/getting_started/dart_pubspec.tsx b/website/i18n/it/docusaurus-plugin-content-docs/current/introduction/getting_started/dart_pubspec.tsx similarity index 78% rename from website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/getting_started/dart_pubspec.tsx rename to website/i18n/it/docusaurus-plugin-content-docs/current/introduction/getting_started/dart_pubspec.tsx index 598ab76e8..d44367c30 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/getting_started/dart_pubspec.tsx +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/introduction/getting_started/dart_pubspec.tsx @@ -3,12 +3,11 @@ import { riverpodAnnotationVersion, riverpodGeneratorVersion, riverpodLintVersion, -} from "../../../../../src/versions"; +} from "../../../../../../src/versions"; -const codegen = ` -name: my_app_name +const codegen = `name: my_app_name environment: - sdk: ">=2.17.0 <3.0.0" + sdk: ">=3.0.0 <4.0.0" dependencies: riverpod: ^${riverpodVersion} @@ -21,10 +20,9 @@ dev_dependencies: riverpod_lint: ^${riverpodLintVersion} `; -const raw = ` -name: my_app_name +const raw = `name: my_app_name environment: - sdk: ">=2.17.0 <3.0.0" + sdk: ">=3.0.0 <4.0.0" dependencies: riverpod: ^${riverpodVersion} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/hooks_codegen/main.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/hooks_codegen/main.dart new file mode 100644 index 000000000..d2929e44b --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/hooks_codegen/main.dart @@ -0,0 +1,46 @@ +// ignore_for_file: use_key_in_widget_constructors, omit_local_variable_types + +/* SNIPPET START */ import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'main.g.dart'; + +// Creiamo un "provider", che conterrà un valore (qui "Hello, world"). +// Utilizzando un provider, ciò ci consente di simulare/sostituire il valore esposto. +@riverpod +String helloWorld(HelloWorldRef ref) { + return 'Hello world'; +} + +void main() { + runApp( + // Per consentire ai widget di leggere i provider, è necessario incapsulare l'intera + // applicazione in un widget "ProviderScope". + // Questo è il luogo in cui verrà memorizzato lo stato dei nostri provider. + ProviderScope( + child: MyApp(), + ), + ); +} + +// Estendiamo HookConsumerWidget invece di StatelessWidget, il quale è esposto da Riverpod +class MyApp extends HookConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + // Possiamo usare gli hooks all'interno di un HookConsumerWidget + final counter = useState(0); + + final String value = ref.watch(helloWorldProvider); + + return MaterialApp( + home: Scaffold( + appBar: AppBar(title: const Text('Example')), + body: Center( + child: Text('$value ${counter.value}'), + ), + ), + ); + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/getting_started/hello_world/main.g.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/hooks_codegen/main.g.dart similarity index 100% rename from website/i18n/ko/docusaurus-plugin-content-docs/current/getting_started/hello_world/main.g.dart rename to website/i18n/it/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/hooks_codegen/main.g.dart diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/getting_started/hello_world/index.tsx b/website/i18n/it/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/index.tsx similarity index 67% rename from website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/getting_started/hello_world/index.tsx rename to website/i18n/it/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/index.tsx index 9535552ac..3f10c2a94 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/getting_started/hello_world/index.tsx +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/index.tsx @@ -1,11 +1,11 @@ import raw from "!!raw-loader!./raw.dart"; import raw_hooks from "!!raw-loader!./raw_hooks.dart"; import codegen from "!!raw-loader!./main.dart"; -import codegen_hooks from "!!raw-loader!./main_hooks.dart"; +import hooksCodegen from "!!raw-loader!./hooks_codegen/main.dart"; export default { raw, hooks: raw_hooks, codegen, - hooksCodegen: codegen_hooks, + hooksCodegen: hooksCodegen, }; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/getting_started/hello_world/main.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/main.dart similarity index 57% rename from website/i18n/ko/docusaurus-plugin-content-docs/current/getting_started/hello_world/main.dart rename to website/i18n/it/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/main.dart index da5ffb095..535266fa1 100644 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/getting_started/hello_world/main.dart +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/main.dart @@ -1,15 +1,13 @@ // ignore_for_file: use_key_in_widget_constructors, omit_local_variable_types -/* SNIPPET START */ - -import 'package:flutter/material.dart'; +/* SNIPPET START */ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'main.g.dart'; -// We create a "provider", which will store a value (here "Hello world"). -// By using a provider, this allows us to mock/override the value exposed. +// Creiamo un "provider", che conterrà un valore (qui "Hello, world"). +// Utilizzando un provider, ciò ci consente di simulare/sostituire il valore esposto. @riverpod String helloWorld(HelloWorldRef ref) { return 'Hello world'; @@ -17,16 +15,16 @@ String helloWorld(HelloWorldRef ref) { void main() { runApp( - // For widgets to be able to read providers, we need to wrap the entire - // application in a "ProviderScope" widget. - // This is where the state of our providers will be stored. + // Per consentire ai widget di leggere i provider, è necessario incapsulare l'intera + // applicazione in un widget "ProviderScope". + // Questo è il luogo in cui verrà memorizzato lo stato dei nostri provider. ProviderScope( child: MyApp(), ), ); } -// Extend ConsumerWidget instead of StatelessWidget, which is exposed by Riverpod +// Estendiamo ConsumerWidget invece di StatelessWidget, il quale è esposto da Riverpod class MyApp extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/getting_started/dart_hello_world/main.g.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/main.g.dart similarity index 100% rename from website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/getting_started/dart_hello_world/main.g.dart rename to website/i18n/it/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/main.g.dart diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/getting_started_hello_world_flutter.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/raw.dart similarity index 54% rename from website/i18n/it/docusaurus-plugin-content-docs/current/getting_started_hello_world_flutter.dart rename to website/i18n/it/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/raw.dart index ae21ba688..7cbad6012 100644 --- a/website/i18n/it/docusaurus-plugin-content-docs/current/getting_started_hello_world_flutter.dart +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/raw.dart @@ -1,26 +1,24 @@ // ignore_for_file: use_key_in_widget_constructors, omit_local_variable_types -/* SNIPPET START */ - -import 'package:flutter/material.dart'; +/* SNIPPET START */ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -// Creiamo un "provider", il quale immagazzinerà un valore (qui "Hello world"). -// Usare un provider ci permetterà di simulare/sovrascrivere il valore esposto. +// Creiamo un "provider", che conterrà un valore (qui "Hello, world"). +// Utilizzando un provider, ciò ci consente di simulare/sostituire il valore esposto. final helloWorldProvider = Provider((_) => 'Hello world'); void main() { runApp( - // Per fare in modo che i widget leggano i provider, dobbiamo incapsulare - // l'intera applicazione in un widget "ProviderScope" - // Qui è dove lo stato dei nostri provider sarà salvato. + // Per consentire ai widget di leggere i provider, è necessario incapsulare l'intera + // applicazione in un widget "ProviderScope". + // Questo è il luogo in cui verrà memorizzato lo stato dei nostri provider. ProviderScope( child: MyApp(), ), ); } -// Estendere ConsumerWidget (esposto da Riverpod) invece di StatelessWidget +// Estendiamo ConsumerWidget invece di StatelessWidget, il quale è esposto da Riverpod class MyApp extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/raw_hooks.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/raw_hooks.dart new file mode 100644 index 000000000..57ddda404 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/raw_hooks.dart @@ -0,0 +1,40 @@ +// ignore_for_file: use_key_in_widget_constructors, omit_local_variable_types + +/* SNIPPET START */ import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +// Creiamo un "provider", che conterrà un valore (qui "Hello, world"). +// Utilizzando un provider, ciò ci consente di simulare/sostituire il valore esposto. +final helloWorldProvider = Provider((_) => 'Hello world'); + +void main() { + runApp( + // Per consentire ai widget di leggere i provider, è necessario incapsulare l'intera + // applicazione in un widget "ProviderScope". + // Questo è il luogo in cui verrà memorizzato lo stato dei nostri provider. + ProviderScope( + child: MyApp(), + ), + ); +} + +// Estendiamo HookConsumerWidget invece di StatelessWidget, il quale è esposto da Riverpod +class MyApp extends HookConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + // Possiamo usare gli hooks all'interno di un HookConsumerWidget + final counter = useState(0); + + final String value = ref.watch(helloWorldProvider); + + return MaterialApp( + home: Scaffold( + appBar: AppBar(title: const Text('Example')), + body: Center( + child: Text('$value ${counter.value}'), + ), + ), + ); + } +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/introduction/getting_started/pub_add.tsx b/website/i18n/it/docusaurus-plugin-content-docs/current/introduction/getting_started/pub_add.tsx new file mode 100644 index 000000000..65c7be1bd --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/introduction/getting_started/pub_add.tsx @@ -0,0 +1,39 @@ +export function buildDeps({ + deps = [], + devDeps = [], +}: { + deps?: string[]; + devDeps?: string[]; +}) { + var result = ''; + for (const dep of deps) { + result += `flutter pub add ${dep}\n`; + } + + for (const dep of [...devDeps, "custom_lint", "riverpod_lint"]) { + result += `flutter pub add dev:${dep}\n`; + } + + return result; +} + +const raw = buildDeps({ deps: ["flutter_riverpod"] }); + +const codegen = buildDeps({ + deps: ["flutter_riverpod", "riverpod_annotation"], + devDeps: ["riverpod_generator", "build_runner"], +}); + +const hooks = buildDeps({ deps: ["hooks_riverpod", "flutter_hooks"] }); + +const hooksCodegen = buildDeps({ + deps: ["hooks_riverpod", "flutter_hooks", "riverpod_annotation"], + devDeps: ["riverpod_generator", "build_runner"], +}); + +export default { + raw: raw, + hooks: hooks, + codegen: codegen, + hooksCodegen: hooksCodegen, +}; diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/introduction/getting_started/pubspec.tsx b/website/i18n/it/docusaurus-plugin-content-docs/current/introduction/getting_started/pubspec.tsx new file mode 100644 index 000000000..da974c2a1 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/introduction/getting_started/pubspec.tsx @@ -0,0 +1,51 @@ +import { + flutterRiverpodVersion, + hooksRiverpodVersion, + riverpodAnnotationVersion, + riverpodGeneratorVersion, + riverpodLintVersion, +} from "../../../../../../src/versions"; + +function plain(riverpod: string) { + return `name: my_app_name +environment: + sdk: ">=3.0.0 <4.0.0" + flutter: ">=3.0.0" + +dependencies: + flutter: + sdk: flutter + ${riverpod} + +dev_dependencies: + custom_lint: + riverpod_lint: ^${riverpodLintVersion} +`; +} + +function codegen(riverpod: string) { + return `name: my_app_name +environment: + sdk: ">=3.0.0 <4.0.0" + flutter: ">=3.0.0" + +dependencies: + flutter: + sdk: flutter + ${riverpod} + riverpod_annotation: ^${riverpodAnnotationVersion} + +dev_dependencies: + build_runner: + custom_lint: + riverpod_generator: ^${riverpodGeneratorVersion} + riverpod_lint: ^${riverpodLintVersion} +`; +} + +export default { + raw: plain(`flutter_riverpod: ^${flutterRiverpodVersion}`), + hooks: plain(`hooks_riverpod: ^${hooksRiverpodVersion}\n flutter_hooks:`), + codegen: codegen(`flutter_riverpod: ^${flutterRiverpodVersion}`), + hooksCodegen: codegen(`hooks_riverpod: ^${hooksRiverpodVersion}\n flutter_hooks:`), +}; diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/introduction/why_riverpod.mdx b/website/i18n/it/docusaurus-plugin-content-docs/current/introduction/why_riverpod.mdx new file mode 100644 index 000000000..c556de7cd --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/introduction/why_riverpod.mdx @@ -0,0 +1,60 @@ +--- +title: Perché Riverpod? +version: 1 +--- + +import whyRiverpod from "./why_riverpod"; +import { AutoSnippet } from "../../../../../src/components/CodeSnippet"; + +## Che cos'è Riverpod? + +Riverpod (un anagramma di [Provider](https://pub.dev/packages/provider)) è un framework reattivo con cache per Flutter/Dart. + +Utilizzando la programmazione dichiarativa e reattiva, Riverpod è in grado di gestire +una grande parte della logica della tua applicazione per te. +Può effettuare richieste di rete con gestione degli errori e caching integrati, mentre ri-scarica automaticamente i dati quando necessario. + +## Motivazione + +Le applicazioni moderne raramente dispongono subito di tutte le informazioni necessarie per renderizzare la loro interfaccia utente. +Al contrario, i dati vengono spesso recuperati in modo asincrono da un server. + +Il problema è che lavorare con il codice asincrono è complesso. Anche se Flutter fornisce alcuni modi +per creare variabili di stato e aggiornare l'interfaccia utente in caso di modifiche, +le possibilità rimangono limitate. Alcune sfide rimangono irrisolte: + +- Le richieste asincrone devono essere memorizzate in cache localmente, +poiché sarebbe irragionevole eseguirle nuovamente ogni volta che l'interfaccia utente viene aggiornata. +- Dato che disponiamo di una cache, la nostra cache potrebbe diventare obsoleta se non siamo attenti. +- È inoltre necessario gestire errori e stati di caricamento. + +Affrontare tali problemi su larga scala può essere complicato e possono essere influenzati da numerose caratteristiche, come ad esempio: + +- aggiornamento con trascinamento verso il basso (pull to refresh) +- liste infinite / recupero dati man mano che si scorre (infinite lists / fetch as we scroll) +- ricerca in tempo reale mentre si digita (search as we type) +- ritardo nella richiesta asincrona (debouncing asynchronous requests) +- annullamento delle richieste asincrone quando non sono più necessarie +- UI ottimistica +- modalità offline +- ... + +Queste funzionalità possono essere complesse da implementare, ma sono cruciali per una buona esperienza utente. +Tuttavia, pochi package cercano di affrontare direttamente questi problemi, e gran parte del lavoro +deve essere svolto manualmente. + +Ecco dove entra in gioco Riverpod. +Riverpod cerca di risolvere questi problemi offrendo un nuovo e unico modo di scrivere la business logic, +ispirato ai widget di Flutter. +In molti modi, Riverpod è paragonabile ai widget, ma per lo stato. + +Utilizzando questo nuovo approccio, queste funzionalità complesse sono per lo più gestite già di default. +Tutto ciò che rimane è concentrarsi sulla tua interfaccia utente. + +Se sei scettico, ecco un esempio. Il seguente snippet è una semplificazione dell'applicazione client +[Pub.dev](https://github.com/rrousselGit/riverpod/tree/master/examples/pub) implementata con Riverpod. + + + +Questo snippet contiene tutta la business logic necessaria per una funzionalità di "ricerca in tempo reale" + +"aggiornamento con trascinamento verso il basso" + "lista infinita", gestendo anche gli stati di errore e caricamento. diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/introduction/why_riverpod/codegen.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/introduction/why_riverpod/codegen.dart new file mode 100644 index 000000000..3e2022906 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/introduction/why_riverpod/codegen.dart @@ -0,0 +1,31 @@ +// ignore_for_file: use_key_in_widget_constructors, omit_local_variable_types + +import 'package:dio/dio.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +class Package { + static Package fromJson(dynamic json) { + throw UnimplementedError(); + } +} + +/* SNIPPET START */ + +// Recupera l'elenco dei package da pub.dev +@riverpod +Future> fetchPackages( + FetchPackagesRef ref, { + required int page, + String search = '', +}) async { + final dio = Dio(); + // Effettua una richiesta API. Qui stiamo utilizzando il package 'dio', ma potremmo usare qualsiasi altro package. + final response = await dio.get>( + 'https://pub.dartlang.org/api/search?page=$page&q=${Uri.encodeQueryComponent(search)}', + ); + + // Decodifica la risposta JSON in una classe Dart. + return response.data?.map(Package.fromJson).toList() ?? const []; +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/introduction/why_riverpod/codegen.g.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/introduction/why_riverpod/codegen.g.dart new file mode 100644 index 000000000..5c271e243 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/introduction/why_riverpod/codegen.g.dart @@ -0,0 +1,178 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$fetchPackagesHash() => r'eebf7d838a57f493fffebfd2c8d8ab76d3233165'; + +/// Copied from Dart SDK +class _SystemHash { + _SystemHash._(); + + static int combine(int hash, int value) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + value); + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); + return hash ^ (hash >> 6); + } + + static int finish(int hash) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + // ignore: parameter_assignments + hash = hash ^ (hash >> 11); + return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); + } +} + +/// See also [fetchPackages]. +@ProviderFor(fetchPackages) +const fetchPackagesProvider = FetchPackagesFamily(); + +/// See also [fetchPackages]. +class FetchPackagesFamily extends Family>> { + /// See also [fetchPackages]. + const FetchPackagesFamily(); + + /// See also [fetchPackages]. + FetchPackagesProvider call({ + required int page, + String search = '', + }) { + return FetchPackagesProvider( + page: page, + search: search, + ); + } + + @override + FetchPackagesProvider getProviderOverride( + covariant FetchPackagesProvider provider, + ) { + return call( + page: provider.page, + search: provider.search, + ); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'fetchPackagesProvider'; +} + +/// See also [fetchPackages]. +class FetchPackagesProvider extends AutoDisposeFutureProvider> { + /// See also [fetchPackages]. + FetchPackagesProvider({ + required int page, + String search = '', + }) : this._internal( + (ref) => fetchPackages( + ref as FetchPackagesRef, + page: page, + search: search, + ), + from: fetchPackagesProvider, + name: r'fetchPackagesProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$fetchPackagesHash, + dependencies: FetchPackagesFamily._dependencies, + allTransitiveDependencies: + FetchPackagesFamily._allTransitiveDependencies, + page: page, + search: search, + ); + + FetchPackagesProvider._internal( + super._createNotifier, { + required super.name, + required super.dependencies, + required super.allTransitiveDependencies, + required super.debugGetCreateSourceHash, + required super.from, + required this.page, + required this.search, + }) : super.internal(); + + final int page; + final String search; + + @override + Override overrideWith( + FutureOr> Function(FetchPackagesRef provider) create, + ) { + return ProviderOverride( + origin: this, + override: FetchPackagesProvider._internal( + (ref) => create(ref as FetchPackagesRef), + from: from, + name: null, + dependencies: null, + allTransitiveDependencies: null, + debugGetCreateSourceHash: null, + page: page, + search: search, + ), + ); + } + + @override + AutoDisposeFutureProviderElement> createElement() { + return _FetchPackagesProviderElement(this); + } + + @override + bool operator ==(Object other) { + return other is FetchPackagesProvider && + other.page == page && + other.search == search; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, page.hashCode); + hash = _SystemHash.combine(hash, search.hashCode); + + return _SystemHash.finish(hash); + } +} + +mixin FetchPackagesRef on AutoDisposeFutureProviderRef> { + /// The parameter `page` of this provider. + int get page; + + /// The parameter `search` of this provider. + String get search; +} + +class _FetchPackagesProviderElement + extends AutoDisposeFutureProviderElement> + with FetchPackagesRef { + _FetchPackagesProviderElement(super.provider); + + @override + int get page => (origin as FetchPackagesProvider).page; + @override + String get search => (origin as FetchPackagesProvider).search; +} +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/introduction/why_riverpod/index.tsx b/website/i18n/it/docusaurus-plugin-content-docs/current/introduction/why_riverpod/index.tsx new file mode 100644 index 000000000..339b89a56 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/introduction/why_riverpod/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./codegen.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/introduction/why_riverpod/raw.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/introduction/why_riverpod/raw.dart new file mode 100644 index 000000000..b79a3debd --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/introduction/why_riverpod/raw.dart @@ -0,0 +1,27 @@ +// ignore_for_file: use_key_in_widget_constructors, omit_local_variable_types + +import 'package:dio/dio.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +class Package { + static Package fromJson(dynamic json) { + throw UnimplementedError(); + } +} + +/* SNIPPET START */ + +// Recupera l'elenco dei package da pub.dev +final fetchPackagesProvider = FutureProvider.autoDispose + .family, ({int page, String? search})>((ref, params) async { + final page = params.page; + final search = params.search ?? ''; + final dio = Dio(); + // Effettua una richiesta API. Qui stiamo utilizzando il package 'dio', ma potremmo usare qualsiasi altro package. + final response = await dio.get>( + 'https://pub.dartlang.org/api/search?page=$page&q=${Uri.encodeQueryComponent(search)}', + ); + + // Decodifica la risposta JSON in una classe Dart. + return response.data?.map(Package.fromJson).toList() ?? const []; +}); diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/migration/0.13.0_to_0.14.0.mdx b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/0.13.0_to_0.14.0.mdx index aa8ba56f4..e577300ff 100644 --- a/website/i18n/it/docusaurus-plugin-content-docs/current/migration/0.13.0_to_0.14.0.mdx +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/0.13.0_to_0.14.0.mdx @@ -1,11 +1,11 @@ --- -title: da ^0.13.0 a ^0.14.0 +title: ^0.13.0 to ^0.14.0 --- -Con il rilascio della versione `0.14.0` di Riverpod, la sintassi per usare [StateNotifierProvider] è cambiata. -(vedere https://github.com/rrousselGit/riverpod/issues/341 per la spiegazione). +With the release of version `0.14.0` of Riverpod, the syntax for using [StateNotifierProvider] changed +(see https://github.com/rrousselGit/riverpod/issues/341 for the explanation). -Per l'intero articolo, considera leggere [StateNotifier]: +For the entire article, consider the following [StateNotifier]: ```dart class MyModel {} @@ -15,12 +15,12 @@ class MyStateNotifier extends StateNotifier { } ``` -## Cambiamenti +## The changes -- [StateNotifierProvider] accetta un generico parametro extra, che dovrebbe essere il tipo -dello stato del tuo [StateNotifier]. +- [StateNotifierProvider] takes an extra generic parameter, which should be + the type of the state of your [StateNotifier]. - Prima: + Before: ```dart final provider = StateNotifierProvider((ref) { @@ -28,7 +28,7 @@ dello stato del tuo [StateNotifier]. }); ``` - Dopo: + After: ```dart final provider = StateNotifierProvider((ref) { @@ -36,9 +36,9 @@ dello stato del tuo [StateNotifier]. }); ``` -- per ottenere lo [StateNotifier], ora dovresti leggere `myProvider.notifier` invece di solo `myProvider` : +- to obtain the [StateNotifier], you should now read `myProvider.notifier` instead of just `myProvider`: - Prima: + Before: ```dart Widget build(BuildContext context, ScopedReader watch) { @@ -46,7 +46,7 @@ dello stato del tuo [StateNotifier]. } ``` - Dopo: + After: ```dart Widget build(BuildContext context, ScopedReader watch) { @@ -54,9 +54,9 @@ dello stato del tuo [StateNotifier]. } ``` -- per stare in ascolto di un [StateNotifier], ora dovresti leggere `myProvider` invece di `myProvider.state` : +- to listen to the state of the [StateNotifier], you should now read `myProvider` instead of `myProvider.state`: - Prima: + Before: ```dart Widget build(BuildContext context, ScopedReader watch) { @@ -64,7 +64,7 @@ dello stato del tuo [StateNotifier]. } ``` - Dopo: + After: ```dart Widget build(BuildContext context, ScopedReader watch) { @@ -72,38 +72,39 @@ dello stato del tuo [StateNotifier]. } ``` -## Utilizzo dello strumento di migrazione per aggiornare i tuoi progetti alla nuova sintassi +## Using the migration tool to automatically upgrade your projects to the new syntax -Con la versione 0.14.0, viene introdotto uno strumento da riga di comando per Riverpod, il quale può aiutarti a migrare i progetti. +With version 0.14.0 came the release of a command line tool for Riverpod, +which can help you migrate your projects. -### Installazione dello strumento +### Installing the command line -Per installare lo strumento di migrazione, eseguire: +To install the migration tool, run: ```sh dart pub global activate riverpod_cli ``` -Dovresti poter eseguire ora: +You should now be able to run: ```sh riverpod --help ``` -### Uso +### Usage -Ora che lo strumento è installato, possiamo iniziare ad utilizzarlo. +Now that the command line is installed, we can start using it. -- Per prima cosa, apri il progetto che vuoi migrare nel tuo terminale. -- **NON** aggiornare Riverpod. - Lo strumento di migrazione aggiornerà la versione di Riverpod per te. -- Assicurati che il tuo progetto non contenga errori. -- Eseguire: +- First, open the project you want to migrate in your terminal. +- **Do not** upgrade Riverpod. + The migration tool will upgrade the version of Riverpod for you. +- Make sure that your project does not contain errors. +- Execute: ```sh riverpod migrate ``` -Lo strumento analizzerà il tuo progetto e ti suggerirà le modifiche. Per esempio potresti vedere: +The tool will then analyze your project and suggest changes. For example you may see: ```diff Widget build(BuildContext context, ScopedReader watch) { @@ -114,7 +115,8 @@ Widget build(BuildContext context, ScopedReader watch) { Accept change (y = yes, n = no [default], A = yes to all, q = quit)? ``` -Per accettare la modifica, premi semplicemente y. In caso contrario, premi n. +To accept the change, simply press y. Otherwise to reject it, press n. -[statenotifierprovider]: https://pub.dev/documentation/riverpod/latest/riverpod/StateNotifierProvider-class.html + +[statenotifierprovider]: ../providers/state_notifier_provider [statenotifier]: https://pub.dev/documentation/state_notifier/latest/state_notifier/StateNotifier-class.html diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/migration/0.14.0_to_1.0.0.mdx b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/0.14.0_to_1.0.0.mdx index 0bd679c83..91fcf0386 100644 --- a/website/i18n/it/docusaurus-plugin-content-docs/current/migration/0.14.0_to_1.0.0.mdx +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/0.14.0_to_1.0.0.mdx @@ -1,52 +1,54 @@ --- -title: da ^0.14.0 a ^1.0.0 +title: ^0.14.0 to ^1.0.0 --- -Dopo una lunga attesa, la prima versione stabile di Riverpod è stata finalmente rilasciata 👏 -Per vedere tutta la lista dei cambiamenti, consulta il [Changelog](https://pub.dev/packages/flutter_riverpod/changelog#100). -In questa pagina ci concentreremo su come migrare una applicazione Riverpod esistente -dalla versione 0.14.x alla versione 1.0.0. +After a long wait, the first stable version of Riverpod is finally released 👏 -## Utilizzo dello strumento di migrazione per aggiornare i tuoi progetti alla nuova sintassi +To see the full list of changes, consult the [Changelog](https://pub.dev/packages/flutter_riverpod/changelog#100). +In this page, we will focus on how to migrate an existing Riverpod application +from version 0.14.x to version 1.0.0. -Prima di spiegare i vari cambiamenti, vale la pena far notare che Riverpod ha -uno strumento da riga di comando per migrare automaticamente il tuo progetto. +## Using the migration tool to automatically upgrade your project to the new syntax -### Installazione dello strumento +Before explaining the various changes, it is worth noting that Riverpod comes with +a command-line tool to automatically migrate your project for you. -Per installare lo strumento di migrazione, eseguire: +### Installing the command line tool + +To install the migration tool, run: ```sh dart pub global activate riverpod_cli ``` -Dovresti poter eseguire ora: +You should now be able to run: ```sh riverpod --help ``` -### Uso +### Usage + +Now that the command line is installed, we can start using it. -Ora che lo strumento è installato, possiamo iniziare ad utilizzarlo. +- First, open the project you want to migrate in your terminal. +- **Do not** upgrade Riverpod. + The migration tool will upgrade the version of Riverpod for you. -- Per prima cosa, apri il progetto che vuoi migrare nel tuo terminale. -- **NON** aggiornare Riverpod. - Lo strumento di migrazione aggiornerà la versione di Riverpod per te. - :::danger PERICOLO - Non aggiornare Riverpod è importante. - Lo strumento non si eseguirà correttamente se hai già installato la versione 1.0.0. - Pertanto, assicurati di utilizzare correttamente una versione precedente prima di avviare lo strumento. + :::danger + Not upgrading Riverpod is important. + The tool will not execute properly if you have already installed version 1.0.0. + As such, make sure that you are properly using an older version before starting the tool. ::: -- Assicurati che il tuo progetto non contenga errori. -- Eseguire: +- Make sure that your project does not contain errors. +- Execute: ```sh riverpod migrate ``` -Lo strumento analizzerà il tuo progetto e ti suggerirà le modifiche. Per esempio potresti vedere: +The tool will then analyze your project and suggest changes. For example you may see: ```diff -Widget build(BuildContext context, ScopedReader watch) { @@ -58,25 +60,27 @@ Lo strumento analizzerà il tuo progetto e ti suggerirà le modifiche. Per esemp Accept change (y = yes, n = no [default], A = yes to all, q = quit)? ``` -Per accettare la modifica, premi semplicemente y. In caso contrario, premi n. +To accept the change, simply press y. Otherwise to reject it, press n. -## I cambiamenti +## The changes -Ora che abbiamo visto come utilizzare il terminale per aggiornare il nostro progetto, -vediamo nel dettaglio queste modifiche. +Now that we've seen how to use the CLI to automatically upgrade your project, +let's see in detail the changes necessary. -### Unificazione della sintassi +### Syntax unification -La versione 1.0.0 di Riverpod si concentra sull'unificazione della sintassi -per interagire con i provider. -Prima, Riverpod aveva molte sintassi simili ma diverse per leggere un provider, -come `ref.watch(provider)` vs `useProvider(provider)` vs `watch(provider)`. -Con la versione 1.0.0, rimane solo una sintassi: `ref.watch(provider)`. Le altre sono state rimosse. +Version 1.0.0 of Riverpod focused on the unification of the syntax for +interacting with providers. +Before, Riverpod had many similar yet different syntaxes for reading a provider, +such as `ref.watch(provider)` vs `useProvider(provider)` vs `watch(provider)`. +With version 1.0.0, only one syntax remains: `ref.watch(provider)`. The +others were removed. -Il che significa che: +As such: -- `useProvider` è stato rimosso in favore di `HookConsumerWidget`. - Prima: +- `useProvider` is removed in favor of `HookConsumerWidget`. + + Before: ```dart class Example extends HookWidget { @@ -89,7 +93,7 @@ Il che significa che: } ``` - Dopo: + After: ```dart class Example extends HookConsumerWidget { @@ -102,9 +106,9 @@ Il che significa che: } ``` -- Il prototipo del metodo `build` di `ConsumerWidget` e `Consumer` è stato cambiato. - - Prima: +- The prototype of `ConsumerWidget`'s `build` and `Consumer`'s `builder` changed. + + Before: ```dart class Example extends ConsumerWidget { @@ -123,7 +127,7 @@ Il che significa che: ) ``` - Dopo: + After: ```dart class Example extends ConsumerWidget { @@ -142,9 +146,9 @@ Il che significa che: ) ``` -- `context.read` è sostituito da `ref.read`. +- `context.read` is removed in favor of `ref.read`. - Prima: + Before: ```dart class Example extends StatelessWidget { @@ -157,7 +161,7 @@ Il che significa che: } ``` - Dopo: + After: ```dart class Example extends ConsumerWidget { @@ -170,15 +174,15 @@ Il che significa che: } ``` -### StateProvider +### StateProvider update -[StateProvider] è stato allineato con [StateNotifierProvider]. +[StateProvider] was updated to match [StateNotifierProvider]. -In precedenza, scrivere `ref.watch(StateProvider)` restituiva un'istanza `StateController`. -Ora restituisce solo lo stato di `StateController`. +Before, doing `ref.watch(StateProvider)` returned a `StateController` instance. +Now it only returns the state of the `StateController`. -Per migrare hai alcune soluzioni. -Se il tuo codice otteneva lo stato senza modificarlo, puoi cambiare da: +To migrate you have a few solutions. +If your code only obtained the state without modifying it, you can change from: ```dart final provider = StateProvider(...); @@ -192,7 +196,7 @@ Consumer( ) ``` -a: +to: ```dart final provider = StateProvider(...); @@ -206,8 +210,7 @@ Consumer( ) ``` -Alternativemente, puoi usare il nuovo `StateProvider.state` per mantenere -il vecchio comportamento. +Alternatively you can use the new `StateProvider.state` to keep the old behavior. ```dart final provider = StateProvider(...); @@ -221,6 +224,6 @@ Consumer( ) ``` -[statenotifierprovider]: https://pub.dev/documentation/riverpod/latest/riverpod/StateNotifierProvider-class.html -[stateprovider]: https://pub.dev/documentation/riverpod/latest/riverpod/StateProvider-class.html +[statenotifierprovider]: ../providers/state_notifier_provider +[stateprovider]: ../providers/state_provider [statenotifier]: https://pub.dev/documentation/state_notifier/latest/state_notifier/StateNotifier-class.html diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_change_notifier.mdx b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_change_notifier.mdx new file mode 100644 index 000000000..69d0f8ab0 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_change_notifier.mdx @@ -0,0 +1,132 @@ +--- +title: Da `ChangeNotifier` +--- + +import old from "!!raw-loader!./from_change_notifier/old.dart"; +import declaration from "./from_change_notifier/declaration"; +import initialization from "./from_change_notifier/initialization"; +import migrated from "./from_change_notifier/migrated"; + +import { Link } from "../../../../../src/components/Link"; +import { AutoSnippet } from "../../../../../src/components/CodeSnippet"; + +All'interno di Riverpod, `ChangeNotifierProvider` deve essere utilizzato per offrire una transizione +graduale da pkg:provider. + +Se hai appena iniziato una migrazione verso Riverpod, assicurati di leggere la guida dedicata +(consulta ). +Questo articolo è pensata per chi è già transizionato su Riverpod ma vuole distaccarsi dall'usare +`ChangeNotifier` in modo definitivo. + +Nel complesso, la migrazione da `ChangeNotifier` a `AsyncNotifer` richiede un cambio di paradigma, +ma comporta una grande semplificazione con il codice migrato risultante. +Consulta anche . + +Prendiamo in considerazione questo esempio (difettoso): + + +Questa implementazione mostra diverse lacune di progettazione come: +- L'utilizzo di `isLoading` e `hasError` per gestire differenti casi asincroni +- La necessita di gestire attentamente le richieste attraverso le espressioni `try`/`catch`/`finally` +- La necessita di invocare `notifyListeners` nei momenti giusti per far sì che questa implementazione funzioni +- La presenza di stato incoerenti o possibilmente indesiderabile, ad esempio l'inizializzazione con una lista vuota + +Si noti come questo esempio è stato realizzato per mostrare come `ChangeNotifier` possa portare a scelte +di design difettose per sviluppatori alle prime armi; inoltre, un'altra conclusione è che lo stato mutabile +potrebbe essere molto più difficile di quanto promesso inizialmente. + +`Notifier`/`AsyncNotifer`, in combinazione con lo stato immutabile, può portare a scelte di design migliori +e meno errori. + +Vediamo come migrare lo snippet precedente, un passo alla volta, verso le API più recenti + +## Inizia la tua migrazione + +Per prima cosa, dovremmo dichiarare il nuovo provider / notifier: questo richiede ci richiede una riflessione +su cosa dobbiamo fare che dipende dalla tua unica business logic. + +Riassumiamo le richieste: +- Lo stato è rappresentato con una `List`, che è ottenuta da una chiamata di rete, con nessun parametro +- Lo stato dovrebbe *anche* esporre informazioni sui suoi stati di `loading`, `error` e `data` +- Lo stato può essere mutato attraverso dei metodi esposti, quindi una funzione non è sufficiente + +:::tip +La riflessione appena fatta si riduce alla risposta alle seguenti domande: +1. Sono richiesti dei side effects? + - `y`: Utilizza le API di Riverpod basate su classi + - `n`: Utilizza le API di Riverpod basate sulle funzioni +2. Lo stato necessita di essere caricato in modo asincrono? + - `y`: Permettiamo a `build` di restituire `Future` + - `n`: Permettiamo a `build` di restituire semplicemente `T` +1. Sono richiesti dei parametri? + - `y`: Permettiamo a `build` (o alla tua funzione) di accettarli + - `n`: Non permettiamo a `build` (o alla tua funzione) di accettare parametri extra +::: + +:::info +Se stai utilizzando la generazione di codice, la riflessione precedente è abbastanza. +Non è necessario pensare ai nomi corretti delle classi e alle loro API *specifiche*. +`@riverpod` ti chiede solamente di scrivere una classe con il suo tipo da restituire, e questo basta. +::: + +Tecnicamente, la mossa migliore qui è definire un `AutoDisposeAsyncNotifier>`, +che coprirà tutti i requisiti richiesti. Scriviamo per prima del pseudocodice. + + + +:::tip +Ricorda: utilizza gli snippet nel tuo IDE per avere una sorta di guida, oppure per velocizzare la scrittura del codice. +Consulta . +::: + +Rispetto all'implementazione di `ChangeNotifier`, non abbiamo più bisogno di dichiarare `todos`; +tale variabile è contenuta in `state`, che è implicitamente caricato con `build`. + +Infatti, i notifier di Riverpod possono esporre *una* entità alla volta. + +:::tip +Le API di Riverpod sono pensate per essere granulari, tuttavia, durante la migrazione, puoi comunque definire +un'entità personalizzata per contenere più valori. Inizialmente, valuta l'utilizzo dei +[record di Dart 3](https://dart.dev/language/records) per semplificare la migrazione. +::: + +### Inizializzazione + +Inizializzare un notifier è facile: basta scrivere la logica di inizializzazione dentro il metodo `build`. +Possiamo ora sbarazzarci della vecchia funzione `_init`. + + + +Rispetto alla vecchia funzone `_init`, al nuovo metodo `build` non manca nulla: non c'è più bisogno di +inizializzare variabili come `isLoading` o `hasError`. + +Riverpod tradurrà automaticamente qualsiasi provider asincrono esponendo un `AsyncValue>` e +gestirà le complessità dello stato asincrono in modo decisamente migliore rispetto a quello che possono fare +due semplici variabili booleane. + +Infatti, qualsiasi `AsyncNotifier` rende effettivamente la scrittura aggiuntiva di `try`/`catch`/`finally` +un anti-pattern per la gestione dello stato asincrono. + +### Mutazioni e Side effects + +Proprio come l'inizializzazione, quando si eseguono dei side effects non c'è la necessità di manipolare +variabili booleani come `hasError`, o di scrivere blocchi `try`/`catch`/`finally` aggiuntivi. + +Di seguito, abbiamo tagliato tutto il codice boilerplate e migrato con successo l'esempio di sopra: + + +:::tip +La sintassi e le scelte di progettazione possono variare, ma alla fine, quello di cui abbiamo bisogno +è scrivere le nostre richieste ed aggiornare lo stato in seguito. +Consulta . +::: + +## Riassunto del processo di migrazione + +Rivediamo l'intero processo di migrazione applicato in questa pagina, da un punto di vista operazionale. + +1. Abbiamo spostato l'inizializzazione, da un metodo personalizzato invocato in un costruttore, al metodo `build` +2. Abbiamo rimosso le proprietà `todos`, `isLoading` e `hasError`: la proprietà interna `state` sarà sufficiente +3. Abbiamo rimosso qualsiasi tipo blocco `try`-`catch`-`finally`, restituire il future è sufficiente +4. Abbiamo applicato la stessa simplificazione sui side effects (`addTodo`) +5. Abbiamo applicato le mutazioni, semplicemente riassegnando `state` diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_change_notifier/declaration/declaration.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_change_notifier/declaration/declaration.dart new file mode 100644 index 000000000..b3ca06ef2 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_change_notifier/declaration/declaration.dart @@ -0,0 +1,33 @@ +// ignore_for_file: avoid_print, avoid_unused_constructor_parameters + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'declaration.g.dart'; + +class Todo { + const Todo(this.id); + Todo.fromJson(Object obj) : id = 0; + + final int id; +} + +class Http { + Future> get(String str) async => [str]; + Future> post(String str) async => [str]; +} + +final http = Http(); + +/* SNIPPET START */ +@riverpod +class MyNotifier extends _$MyNotifier { + @override + FutureOr> build() { + // TODO ... + return []; + } + + Future addTodo(Todo todo) async { + // TODO + } +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_change_notifier/declaration/declaration.g.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_change_notifier/declaration/declaration.g.dart new file mode 100644 index 000000000..6a0307fb2 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_change_notifier/declaration/declaration.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'declaration.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$myNotifierHash() => r'fc9a07f8ef9f792da2ac660d76ea0a809335ba18'; + +/// See also [MyNotifier]. +@ProviderFor(MyNotifier) +final myNotifierProvider = + AutoDisposeAsyncNotifierProvider>.internal( + MyNotifier.new, + name: r'myNotifierProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$myNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$MyNotifier = AutoDisposeAsyncNotifier>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_change_notifier/declaration/index.tsx b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_change_notifier/declaration/index.tsx new file mode 100644 index 000000000..1ad659c31 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_change_notifier/declaration/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./declaration.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_change_notifier/declaration/raw.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_change_notifier/declaration/raw.dart new file mode 100644 index 000000000..c7485bbba --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_change_notifier/declaration/raw.dart @@ -0,0 +1,33 @@ +// ignore_for_file: avoid_print, avoid_unused_constructor_parameters + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +class Todo { + const Todo(this.id); + Todo.fromJson(Object obj) : id = 0; + + final int id; +} + +class Http { + Future> get(String str) async => [str]; + Future> post(String str) async => [str]; +} + +final http = Http(); + +/* SNIPPET START */ +@riverpod +class MyNotifier extends AutoDisposeAsyncNotifier> { + @override + FutureOr> build() { + // TODO ... + return []; + } + + Future addTodo(Todo todo) async { + // TODO + } +} + +final myNotifierProvider = AsyncNotifierProvider.autoDispose(MyNotifier.new); diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_change_notifier/initialization/index.tsx b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_change_notifier/initialization/index.tsx new file mode 100644 index 000000000..3b3fbd2cb --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_change_notifier/initialization/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./initialization.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_change_notifier/initialization/initialization.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_change_notifier/initialization/initialization.dart new file mode 100644 index 000000000..4da805e13 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_change_notifier/initialization/initialization.dart @@ -0,0 +1,29 @@ +// ignore_for_file: avoid_print, avoid_unused_constructor_parameters + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'initialization.g.dart'; + +class Todo { + const Todo(this.id); + Todo.fromJson(Object obj) : id = 0; + + final int id; +} + +class Http { + Future> get(String str) async => [str]; + Future> post(String str) async => [str]; +} + +final http = Http(); + +/* SNIPPET START */ +@riverpod +class MyNotifier extends _$MyNotifier { + @override + FutureOr> build() async { + final json = await http.get('api/todos'); + return [...json.map(Todo.fromJson)]; + } +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_change_notifier/initialization/initialization.g.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_change_notifier/initialization/initialization.g.dart new file mode 100644 index 000000000..8fd8b75ce --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_change_notifier/initialization/initialization.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'initialization.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$myNotifierHash() => r'1c67c12443102cf8c43efbf6c630d3028d9847c3'; + +/// See also [MyNotifier]. +@ProviderFor(MyNotifier) +final myNotifierProvider = + AutoDisposeAsyncNotifierProvider>.internal( + MyNotifier.new, + name: r'myNotifierProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$myNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$MyNotifier = AutoDisposeAsyncNotifier>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_change_notifier/initialization/raw.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_change_notifier/initialization/raw.dart new file mode 100644 index 000000000..24ab265f7 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_change_notifier/initialization/raw.dart @@ -0,0 +1,29 @@ +// ignore_for_file: avoid_print, avoid_unused_constructor_parameters + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +class Todo { + const Todo(this.id); + Todo.fromJson(Object obj) : id = 0; + + final int id; +} + +class Http { + Future> get(String str) async => [str]; + Future> post(String str) async => [str]; +} + +final http = Http(); + +/* SNIPPET START */ +@riverpod +class MyNotifier extends AutoDisposeAsyncNotifier> { + @override + FutureOr> build() async { + final json = await http.get('api/todos'); + return [...json.map(Todo.fromJson)]; + } +} + +final myNotifierProvider = AsyncNotifierProvider.autoDispose(MyNotifier.new); diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_change_notifier/migrated/index.tsx b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_change_notifier/migrated/index.tsx new file mode 100644 index 000000000..075bfbdf5 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_change_notifier/migrated/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./migrated.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_change_notifier/migrated/migrated.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_change_notifier/migrated/migrated.dart new file mode 100644 index 000000000..b8f4ba829 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_change_notifier/migrated/migrated.dart @@ -0,0 +1,37 @@ +// ignore_for_file: avoid_print, avoid_unused_constructor_parameters + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'migrated.g.dart'; + +class Todo { + const Todo(this.id); + Todo.fromJson(Object obj) : id = 0; + + final int id; +} + +class Http { + Future> get(String str) async => [str]; + Future> post(String str) async => [str]; +} + +final http = Http(); + +/* SNIPPET START */ +@riverpod +class MyNotifier extends _$MyNotifier { + @override + FutureOr> build() async { + final json = await http.get('api/todos'); + + return [...json.map(Todo.fromJson)]; + } + + Future addTodo(Todo todo) async { + // optional: state = const AsyncLoading(); + final json = await http.post('api/todos'); + final newTodos = [...json.map(Todo.fromJson)]; + state = AsyncData(newTodos); + } +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_change_notifier/migrated/migrated.g.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_change_notifier/migrated/migrated.g.dart new file mode 100644 index 000000000..737a9ce7f --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_change_notifier/migrated/migrated.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'migrated.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$myNotifierHash() => r'bde95c56aa12eff7c8c01ede57ae4ad2b616c225'; + +/// See also [MyNotifier]. +@ProviderFor(MyNotifier) +final myNotifierProvider = + AutoDisposeAsyncNotifierProvider>.internal( + MyNotifier.new, + name: r'myNotifierProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$myNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$MyNotifier = AutoDisposeAsyncNotifier>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_change_notifier/migrated/raw.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_change_notifier/migrated/raw.dart new file mode 100644 index 000000000..8572db71d --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_change_notifier/migrated/raw.dart @@ -0,0 +1,37 @@ +// ignore_for_file: avoid_print, avoid_unused_constructor_parameters + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +class Todo { + const Todo(this.id); + Todo.fromJson(Object obj) : id = 0; + + final int id; +} + +class Http { + Future> get(String str) async => [str]; + Future> post(String str) async => [str]; +} + +final http = Http(); + +/* SNIPPET START */ +@riverpod +class MyNotifier extends AutoDisposeAsyncNotifier> { + @override + FutureOr> build() async { + final json = await http.get('api/todos'); + + return [...json.map(Todo.fromJson)]; + } + + Future addTodo(Todo todo) async { + // optional: state = const AsyncLoading(); + final json = await http.post('api/todos'); + final newTodos = [...json.map(Todo.fromJson)]; + state = AsyncData(newTodos); + } +} + +final myNotifierProvider = AsyncNotifierProvider.autoDispose(MyNotifier.new); diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_change_notifier/old.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_change_notifier/old.dart new file mode 100644 index 000000000..4e65e823d --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_change_notifier/old.dart @@ -0,0 +1,60 @@ +// ignore_for_file: avoid_print, avoid_unused_constructor_parameters + +import 'package:flutter/foundation.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +class Todo { + const Todo(this.id); + Todo.fromJson(Object obj) : id = 0; + + final int id; +} + +class Http { + Future> get(String str) async => [str]; + Future> post(String str) async => [str]; +} + +final http = Http(); + +/* SNIPPET START */ +class MyChangeNotifier extends ChangeNotifier { + MyChangeNotifier() { + _init(); + } + List todos = []; + bool isLoading = true; + bool hasError = false; + + Future _init() async { + try { + final json = await http.get('api/todos'); + todos = [...json.map(Todo.fromJson)]; + } on Exception { + hasError = true; + } finally { + isLoading = false; + notifyListeners(); + } + } + + Future addTodo(int id) async { + isLoading = true; + notifyListeners(); + + try { + final json = await http.post('api/todos'); + todos = [...json.map(Todo.fromJson)]; + hasError = false; + } on Exception { + hasError = true; + } finally { + isLoading = false; + notifyListeners(); + } + } +} + +final myChangeProvider = ChangeNotifierProvider((ref) { + return MyChangeNotifier(); +}); diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier.mdx b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier.mdx new file mode 100644 index 000000000..23f96416f --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier.mdx @@ -0,0 +1,264 @@ +--- +title: Da `StateNotifier` +--- + +import buildInit from "./from_state_notifier/build_init"; +import buildInitOld from "!!raw-loader!./from_state_notifier/build_init_old.dart"; +import consumersDontChange from "!!raw-loader!./from_state_notifier/consumers_dont_change.dart"; +import familyAndDispose from "./from_state_notifier/family_and_dispose"; +import familyAndDisposeOld from "!!raw-loader!./from_state_notifier/family_and_dispose_old.dart"; +import asyncNotifier from "./from_state_notifier/async_notifier"; +import asyncNotifierOld from "!!raw-loader!./from_state_notifier/async_notifier_old.dart"; +import addListener from "./from_state_notifier/add_listener"; +import addListenerOld from "!!raw-loader!./from_state_notifier/add_listener_old.dart"; +import fromStateProvider from "./from_state_notifier/from_state_provider"; +import fromStateProviderOld from "!!raw-loader!./from_state_notifier/from_state_provider_old.dart"; +import oldLifecycles from "./from_state_notifier/old_lifecycles"; +import oldLifecyclesOld from "!!raw-loader!./from_state_notifier/old_lifecycles_old.dart"; +import oldLifecyclesFinal from "./from_state_notifier/old_lifecycles_final"; +import obtainNotifierOnTests from "!!raw-loader!./from_state_notifier/obtain_notifier_on_tests.dart"; + +import { Link } from "../../../../../src/components/Link"; +import { AutoSnippet } from "../../../../../src/components/CodeSnippet"; + +Con [Riverpod 2.0](https://pub.dev/packages/flutter_riverpod/changelog#200), nuovi classi +sono state introdotte: `Notifier` / `AsyncNotifer`. +`StateNotifier` è ora sconsigliato a favore di queste nuove API. + +Questa pagina mostra come migrare dal deprecato `StateNotifier` a queste nuove API. + +Il beneficio principale introdotto da `AsyncNotifier` è un migliore supporto per operazioni `async`; +infatti, `AsyncNotifier` può essere pensato come un `FutureProvider` che espone dei modi per essere modificato dall'UI. + +Inoltre, il nuovo `(Async)Notifier`: + +- Espone un'oggetto `Ref` dentro la sua classe +- Offre una sintassi simile tra gli approcci con o senza generazione di codice +- Offre una sintassi simile tra le sue versioni sync e async. +- Sposta la logica dai Provider e la centralizza dentro gli stessi Notifier + +Vediamo come definire un `Notifier`, le differenze con `StateNotifier` e come migrare al nuovo +`AsyncNotifier` per lo stato asincrono. + +## Confronto della nuova sintassi + +Assicurati di sapere come definire un `Notifier` prima di tuffarti in questo confronto. +Consulta . + +Scriviamo un esempio, utilizzando la vecchia sintassi di `StateNotifier`: + + +Di seguito lo stesso esempio, costruito con le nuove API `Notifier` che si traduce approssimativamente in: + + +Confrontando `Notifier` e `StateNotifier`, si possono osservare queste differenze principali: + +- Le dipendenze reattive di `StateNotifier` sono dichiarate nel suo provider, mentre `Notifier` + centralizza questa logica nel suo metodo `build` +- L'intero processo di inizializzazione di `StateNotifier` è diviso tra il suo provider e il suo costruttore, + mentre `Notifier` riserva un unico posto in cui collocare tale logica +- Si noti come, a differenza di `StateNotifier`, nessuna logica viene mai scritta nel costruttore di un `Notifier` + +Conclusioni simili possono essere fatte con `AsyncNotifer`, ovvero l'equivalente `Notifier` per operazioni asincrone. + +## Migrare `StateNotifier` asincroni + +L'aspetto più interessante della nuova sintassi API è una DX migliorata sui dati asincroni. +Prendiamo il seguente esempio: + + + +Di seguito l'esempio precedente riscritto con le nuove API `AsyncNotifier`: + + + +`AsyncNotifier`, proprio come `Notifier`, introduce una più semplice ed uniforme API. +Qui è facile osservare come `AsyncNotifer` sia un `FutureProvider` con dei metodi. + +`AsyncNotifer` viene fornito con una serie di utilità e getters che `StateNotifier` non possiede, come +[`future`](https://pub.dev/documentation/riverpod/latest/riverpod/AutoDisposeAsyncNotifier/future.html) e +[`update`](https://pub.dev/documentation/riverpod/latest/riverpod/AutoDisposeAsyncNotifier/update.html). +Ciò ci permette di scrivere della logica più semplice quando gestiamo mutazioni asincrone e side-effects. +Vedere anche . + +:::tip +Migrare da `StateNotifier>` a `AsyncNotifer` si riduce a: + +- Mettere la logica di inizializzazione dentro `build` +- Rimuovere qualsiasi blocco `catch`/`try` in inizializzazione o nei metodi di side-effects +- Rimuovere qualsiasi `AsyncValue.guard` da `build`, poiché converte `Future` in `AsyncValue` +::: + +### Vantaggi + +Dopo questi esempi, evidenziamo ora i principali vantaggi di `Notifier` e `AsyncNotifier`: +- La nuova sintassi dovrebbe sembrare più semplice e più leggibile, specialmente per lo stato asincrono +- È probabile che le nuove API abbiano meno codice boilerplate in generale +- La sintassi ora è unificata, abilitando la generazione di codice; non importa la tipologia di provider che stai scrivendo +(consulta ) + +Addentriamoci ed evidenziamo più differenze e somiglianze. + +## Modifiche esplicite a `.family` e `.autoDispose` + +Un'altra importante differenza è come sono gestite le family e la funzionalità di auto dispose con le nuove API. + +`Notifier` ha le sue personali controparti di `.family` e `.autoDispose`, come `FamilyNotifier` +e `AutoDisposeNotifier`. +Come sempre, queste modifiche possono essere combinate (aka `AutoDisposeFamilyNotifier`). +`AsyncNotifer` ha le sue controparti asincrone equivalenti, (`AutoDisposeFamilyAsyncNotifier`). + +Questi modificatori sono esplicitamente dichiarati dentro la classe; qualsiasi parametro è iniettato direttamente +nel metodo `build` in modo tale da essere disponibile alla logica di inizializzazione. +Tutto questo dovrebbe portare una maggiore leggibilità, concisione e in generale meno errori. + +Prendiamo l'esempio seguente, dove un `StateNotifierProvider.family` viene definito. + + +`BugsEncounteredNotifier` sembra... pesante / difficile da leggere. +Diamo un'occhiata alla sua controparte `AsyncNotifier` migrata: + + + +La sua controparte migrata dovrebbe fornire una maggiore facilità di lettura. + +:::info +I parametri `.family` di `(Async)Notifier` sono disponibili tramite `this.arg` +(oppure tramite `this.paramName` quando si utilizza la generazione di codice) +::: + +## I cicli di vita hanno un comportamento differente + +I cicli di vita tra `Notifier`/`AsyncNotifier` e `StateNotifier` differiscono in modo sostanziale. + +Questo esempio mostra - di nuovo - come le vecchie API abbiano la logica sparsa: + + + +Qui, se `durationProvider` si aggiorna, `MyNotifier` _elimina_: la sua istanza è poi re-istanziata +e il suo stato interno è quindi re-inizializzato. +Inoltre, a differenza di tutti gli altri provider, la callback `dispose` deve essere definita +separatamente nella classe. +Infine, è ancora possibile scrivere `ref.onDispose` nel suo provider, mostrando ancora una volta come +possa essere sparsa la logica con quest'API; potenzialmente, lo sviluppatore potrebbe dover guardare otto (8!) +posti differenti per capire il comportamento di questo Notifier! + +Queste ambiguità sono risolte con `Riverpod 2.0`. + +### Vecchio `dispose` vs `ref.onDispose` +Il metodo `dispose` di `StateNotifier` si riferisce all'evento di distruzione del notificatore stesso, +ovvero è una callback che viena chiamata *prima di sbarazzarsi di se stesso*. + +Gli `(Async)Notifier` non hanno questa proprietà, dato che *non vengono distrutti durante la ricostruzione*; +solo il suo *stato interno* lo è. +Nei nuovi notifier, i cicli di vita di eliminazione sono gestiti in _un_ solo posto, tramite `ref.onDispose` +(e altri), proprio come qualsiasi altro provider. +Questo semplifica l'API, e, si spera la developer experience, in modo che ci sia solo _un_ posto a cui guardare +per comprendere gli effetti collaterali del ciclo di vita: il suo metodo `build`. + +In breve: per registrare una callback che si attiva prima che il suo *stato interno* venga ricostruito, possiamo usare +`ref.onDispose` come ogni altro provider. + +Puoi migrare lo snippet sopra in questo modo: + + + +In questo ultimo snippet abbiamo sicuramente introdotto della semplificazione, ma rimane ancora un problema: non +possiamo capire se i nostri notifier sono ancora attivi o no mentre eseguiamo `update`. +Ciò potrebbe verificare uno `StateError` indesiderato. + +### Non più `mounted` + +Questo accade perché `(Async)Notifier` manca della proprietà `mounted`, che era disponibile con `StateNotifier`. +Considerando la loro differenza nel ciclo di vita, questo ha perfettamente senso; seppur possibile, una +proprietà `mounted` sarebbe fuorviante sui nuovi notifier: `mounted` sarebbe *quasi sempre* `true`. + +Anche se sarebbe possibile creare una [soluzione alternativa personalizzata](https://github.com/rrousselGit/riverpod/issues/1879#issuecomment-1303189191), +si consiglia di aggirare questo problema annullando l'operazione asincrona. + +Annullare un'operazione può essere fatto con un [Completer](https://api.flutter.dev/flutter/dart-async/Completer-class.html) personalizzato, +o qualsiasi derivato personalizzato. + +Per esempio, se stai utilizzando `Dio` per eseguire richieste di rete, considera l'utilizzo di un [cancel token](https://pub.dev/documentation/dio/latest/dio/CancelToken-class.html) +(consulta anche ). + +Pertanto, l'esempio precedente viene migrato nel seguente: + + +## Le API di mutazione sono le stesse di prima + +Finora abbiamo mostrato le differenze tra `StateNotifier` e le nuove API. +Una cosa che invece `Notifier`, `AsyncNotifer` e `StateNotifier` condividono è il modo in cui i loro stati +possono essere consumati e mutati. + +I consumer possono ottenere dati da questi tre provider con la stessa sintassi, il che è ottimo nel caso +tu stai migrando da `StateNotifier`; questo si applica anche ai metodi dei notifier. + + + +## Altre migrazioni + +Esploriamo ora le differenze meno impattanti tra `StateNotifier` e `Notifier` (o `AsyncNotifier`) + +### Da `.addListener` e `.stream` + +`.addListener` e `.stream` di `StateNotifier` possono essere utilizzati per ascoltare i cambiamenti di stato. +Queste due API sono ormai da considerarsi superate. + +Ciò è intenzionale a causa del desiderio di raggiungere la completa uniformità dell'API con `Notifier`, `AsyncNotifier` e altri provider. +Infatti, l'utilizzo di un `Notifier` o di un `AsyncNotifier` non dovrebbe essere diverso da qualsiasi altro provider. + +Pertanto questo: + + +Diviene questo: + + +In poche parole: se vuoi ascoltare un `Notifier`/`AsyncNotifer`, usa semplicemente `ref.listen`. +Consulta . + +### Da `.debugState` nei test + +`StateNotifier` espone `.debugState`: questa proprietà è usata per gli utenti di [state_notifier](https://pub.dev/packages/state_notifier) +per abilitare l'accesso allo stato da fuori la classe quando si è in dev mode, per scopi di testing. + +Se stai utilizzando `.debugState` per accedere allo stato nei test, ci sono probabilità che tu debba abbandonare +questo approccio. + +`Notifier` / `AsyncNotifer` non hanno una prorpietà `.debugState`; invece, espongono direttamente `.state`, +che è `@visibleForTesting`. + +:::danger +EVITA di accedere a `.state` dai test; se devi farlo, fallo _solo e solo se_ hai già un +a `Notifier` / `AsyncNotifer` correttamente istanziato; solo dopo, puoi accedere a `.state` +all'interno dei test liberamente. + +Infatti, `Notifier` / `AsyncNotifier` _non dovrebbero_ essere istanziati a mano; al contrario, dovrebbero +essere interagiti utilizzando il relativo provider: non farlo *romperà* il notifier, a causa della mancata +inizializzazione di 'ref' e dei parametri family. +::: + +Non hai un'istanza `Notifier`? +Nessun problema, puoi ottenerne una con `ref.read`, proprio come leggeresti il suo stato esposto: + + + +Impara di più sui test nella loro guida dedicata. Consulta . + +### Da `StateProvider` + +`StateProvider` è stato esposto da Riverpod fin dalla sua release, ed era stato fatto per ridurre le +linee di codice per le versioni semplificate di `StateNotifierProvider`. +Dato che `StateNotifierProvider` è deprecato, anche `StateProvider` va evitato. +Inoltre, ad oggi, non c'è nessun equivalente di `StateProvider` per le nuove API. + +Ciò nonostante, migrare da `StateProvider` a `Notifier` è semplice. + +Questo: + + +Diventa: + + +Anche se ci costa qualche LoC in più, la migrazione da `StateProvider` +ci consente di archiviare definitivamente `StateNotifier`. diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener/add_listener.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener/add_listener.dart new file mode 100644 index 000000000..ead1c72d8 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener/add_listener.dart @@ -0,0 +1,18 @@ +// ignore_for_file: avoid_print + +import 'package:flutter/material.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'add_listener.g.dart'; + +/* SNIPPET START */ +@riverpod +class MyNotifier extends _$MyNotifier { + @override + int build() { + ref.listenSelf((_, next) => debugPrint('$next')); + return 0; + } + + void add() => state++; +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener/add_listener.g.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener/add_listener.g.dart new file mode 100644 index 000000000..3d55dd0bb --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener/add_listener.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'add_listener.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$myNotifierHash() => r'9acd382ed579c545ace755687b155e28eba01d22'; + +/// See also [MyNotifier]. +@ProviderFor(MyNotifier) +final myNotifierProvider = + AutoDisposeNotifierProvider.internal( + MyNotifier.new, + name: r'myNotifierProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$myNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$MyNotifier = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener/index.tsx b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener/index.tsx new file mode 100644 index 000000000..6d7ac6d37 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./add_listener.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener/raw.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener/raw.dart new file mode 100644 index 000000000..e25b4a181 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener/raw.dart @@ -0,0 +1,17 @@ +// ignore_for_file: avoid_print + +import 'package:flutter/material.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +/* SNIPPET START */ +class MyNotifier extends Notifier { + @override + int build() { + ref.listenSelf((_, next) => debugPrint('$next')); + return 0; + } + + void add() => state++; +} + +final myNotifierProvider = NotifierProvider(MyNotifier.new); diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener_old.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener_old.dart new file mode 100644 index 000000000..1f020617c --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener_old.dart @@ -0,0 +1,24 @@ +// ignore_for_file: avoid_print + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +/* SNIPPET START */ +class MyNotifier extends StateNotifier { + MyNotifier() : super(0); + + void add() => state++; +} + +final myNotifierProvider = StateNotifierProvider((ref) { + final notifier = MyNotifier(); + + final cleanup = notifier.addListener((state) => debugPrint('$state')); + ref.onDispose(cleanup); + + // O in modo equivalente: + // final listener = notifier.stream.listen((event) => debugPrint('$event')); + // ref.onDispose(listener.cancel); + + return notifier; +}); diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier/async_notifier.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier/async_notifier.dart new file mode 100644 index 000000000..5ead76378 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier/async_notifier.dart @@ -0,0 +1,28 @@ +// ignore_for_file: avoid_print, avoid_unused_constructor_parameters + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'async_notifier.g.dart'; + +class Todo { + Todo.fromJson(Object obj); +} + +class Http { + Future> get(String str) async => [str]; +} + +final http = Http(); + +/* SNIPPET START */ +@riverpod +class AsyncTodosNotifier extends _$AsyncTodosNotifier { + @override + FutureOr> build() async { + final json = await http.get('api/todos'); + + return [...json.map(Todo.fromJson)]; + } + + // ... +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier/async_notifier.g.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier/async_notifier.g.dart new file mode 100644 index 000000000..5f72d5d1c --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier/async_notifier.g.dart @@ -0,0 +1,29 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'async_notifier.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$asyncTodosNotifierHash() => + r'10207327c7dee180e9da8beece5bfffedcf86e98'; + +/// See also [AsyncTodosNotifier]. +@ProviderFor(AsyncTodosNotifier) +final asyncTodosNotifierProvider = + AutoDisposeAsyncNotifierProvider>.internal( + AsyncTodosNotifier.new, + name: r'asyncTodosNotifierProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$asyncTodosNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$AsyncTodosNotifier = AutoDisposeAsyncNotifier>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier/index.tsx b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier/index.tsx new file mode 100644 index 000000000..a0ff513c3 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./async_notifier.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier/raw.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier/raw.dart new file mode 100644 index 000000000..52da61d76 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier/raw.dart @@ -0,0 +1,29 @@ +// ignore_for_file: avoid_print, avoid_unused_constructor_parameters + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +class Todo { + Todo.fromJson(Object obj); +} + +class Http { + Future> get(String str) async => [str]; +} + +final http = Http(); + +/* SNIPPET START */ +class AsyncTodosNotifier extends AsyncNotifier> { + @override + FutureOr> build() async { + final json = await http.get('api/todos'); + + return [...json.map(Todo.fromJson)]; + } + + // ... +} + +final asyncTodosNotifier = AsyncNotifierProvider>( + AsyncTodosNotifier.new, +); diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier_old.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier_old.dart new file mode 100644 index 000000000..50d7f4aed --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier_old.dart @@ -0,0 +1,30 @@ +// ignore_for_file: avoid_print, avoid_unused_constructor_parameters + +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +class Todo { + Todo.fromJson(Object obj); +} + +class Http { + Future> get(String str) async => [str]; +} + +final http = Http(); + +/* SNIPPET START */ +class AsyncTodosNotifier extends StateNotifier>> { + AsyncTodosNotifier() : super(const AsyncLoading()) { + _postInit(); + } + + Future _postInit() async { + state = await AsyncValue.guard(() async { + final json = await http.get('api/todos'); + + return [...json.map(Todo.fromJson)]; + }); + } + + // ... +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init/build_init.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init/build_init.dart new file mode 100644 index 000000000..3e2303b7a --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init/build_init.dart @@ -0,0 +1,15 @@ +// ignore_for_file: avoid_print + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'build_init.g.dart'; + +/* SNIPPET START */ +@riverpod +class CounterNotifier extends _$CounterNotifier { + @override + int build() => 0; + + void increment() => state++; + void decrement() => state++; +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init/build_init.g.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init/build_init.g.dart new file mode 100644 index 000000000..9b4dc05a7 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init/build_init.g.dart @@ -0,0 +1,28 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'build_init.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$counterNotifierHash() => r'8d4e4011da15a0ef79af9622336839a0c9e406ab'; + +/// See also [CounterNotifier]. +@ProviderFor(CounterNotifier) +final counterNotifierProvider = + AutoDisposeNotifierProvider.internal( + CounterNotifier.new, + name: r'counterNotifierProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$counterNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$CounterNotifier = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init/index.tsx b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init/index.tsx new file mode 100644 index 000000000..276a143ac --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./build_init.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init/raw.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init/raw.dart new file mode 100644 index 000000000..0ba8eebed --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init/raw.dart @@ -0,0 +1,15 @@ +// ignore_for_file: avoid_print + +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +/* SNIPPET START */ +class CounterNotifier extends Notifier { + @override + int build() => 0; + + void increment() => state++; + void decrement() => state++; +} + +final counterNotifierProvider = NotifierProvider(CounterNotifier.new); diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init_old.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init_old.dart new file mode 100644 index 000000000..82a8c54bd --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init_old.dart @@ -0,0 +1,13 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +/* SNIPPET START */ +class CounterNotifier extends StateNotifier { + CounterNotifier() : super(0); + + void increment() => state++; + void decrement() => state++; +} + +final counterNotifierProvider = StateNotifierProvider((ref) { + return CounterNotifier(); +}); diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/consumers_dont_change.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/consumers_dont_change.dart new file mode 100644 index 000000000..6a964a5a6 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/consumers_dont_change.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +class CounterNotifier extends StateNotifier { + CounterNotifier() : super(0); + + void increment() => state++; + void decrement() => state++; +} + +final counterNotifierProvider = StateNotifierProvider((ref) { + return CounterNotifier(); +}); + +/* SNIPPET START */ +class SomeConsumer extends ConsumerWidget { + const SomeConsumer({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + /* highlight-start */ + final counter = ref.watch(counterNotifierProvider); + /* highlight-end */ + return Column( + children: [ + Text("You've counted up until $counter, good job!"), + TextButton( + /* highlight-start */ + onPressed: ref.read(counterNotifierProvider.notifier).increment, + /* highlight-end */ + child: const Text('Count even more!'), + ) + ], + ); + } +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose/family_and_dispose.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose/family_and_dispose.dart new file mode 100644 index 000000000..9a695614c --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose/family_and_dispose.dart @@ -0,0 +1,24 @@ +// ignore_for_file: unnecessary_this + +import 'dart:math'; + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../../utils.dart'; + +part 'family_and_dispose.g.dart'; + +/* SNIPPET START */ +@riverpod +class BugsEncounteredNotifier extends _$BugsEncounteredNotifier { + @override + FutureOr build(String featureId) { + return 99; + } + + Future fix(int amount) async { + final old = await future; + final result = await ref.read(taskTrackerProvider).fix(id: this.featureId, fixed: amount); + state = AsyncData(max(old - result, 0)); + } +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose/family_and_dispose.g.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose/family_and_dispose.g.dart new file mode 100644 index 000000000..36e1f1144 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose/family_and_dispose.g.dart @@ -0,0 +1,178 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'family_and_dispose.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$bugsEncounteredNotifierHash() => + r'c76e924f84db91c57d226896b062d9f4e8ab79e5'; + +/// Copied from Dart SDK +class _SystemHash { + _SystemHash._(); + + static int combine(int hash, int value) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + value); + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); + return hash ^ (hash >> 6); + } + + static int finish(int hash) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + // ignore: parameter_assignments + hash = hash ^ (hash >> 11); + return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); + } +} + +abstract class _$BugsEncounteredNotifier + extends BuildlessAutoDisposeAsyncNotifier { + late final String featureId; + + FutureOr build( + String featureId, + ); +} + +/// See also [BugsEncounteredNotifier]. +@ProviderFor(BugsEncounteredNotifier) +const bugsEncounteredNotifierProvider = BugsEncounteredNotifierFamily(); + +/// See also [BugsEncounteredNotifier]. +class BugsEncounteredNotifierFamily extends Family> { + /// See also [BugsEncounteredNotifier]. + const BugsEncounteredNotifierFamily(); + + /// See also [BugsEncounteredNotifier]. + BugsEncounteredNotifierProvider call( + String featureId, + ) { + return BugsEncounteredNotifierProvider( + featureId, + ); + } + + @override + BugsEncounteredNotifierProvider getProviderOverride( + covariant BugsEncounteredNotifierProvider provider, + ) { + return call( + provider.featureId, + ); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'bugsEncounteredNotifierProvider'; +} + +/// See also [BugsEncounteredNotifier]. +class BugsEncounteredNotifierProvider + extends AutoDisposeAsyncNotifierProviderImpl { + /// See also [BugsEncounteredNotifier]. + BugsEncounteredNotifierProvider( + String featureId, + ) : this._internal( + () => BugsEncounteredNotifier()..featureId = featureId, + from: bugsEncounteredNotifierProvider, + name: r'bugsEncounteredNotifierProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$bugsEncounteredNotifierHash, + dependencies: BugsEncounteredNotifierFamily._dependencies, + allTransitiveDependencies: + BugsEncounteredNotifierFamily._allTransitiveDependencies, + featureId: featureId, + ); + + BugsEncounteredNotifierProvider._internal( + super._createNotifier, { + required super.name, + required super.dependencies, + required super.allTransitiveDependencies, + required super.debugGetCreateSourceHash, + required super.from, + required this.featureId, + }) : super.internal(); + + final String featureId; + + @override + FutureOr runNotifierBuild( + covariant BugsEncounteredNotifier notifier, + ) { + return notifier.build( + featureId, + ); + } + + @override + Override overrideWith(BugsEncounteredNotifier Function() create) { + return ProviderOverride( + origin: this, + override: BugsEncounteredNotifierProvider._internal( + () => create()..featureId = featureId, + from: from, + name: null, + dependencies: null, + allTransitiveDependencies: null, + debugGetCreateSourceHash: null, + featureId: featureId, + ), + ); + } + + @override + AutoDisposeAsyncNotifierProviderElement + createElement() { + return _BugsEncounteredNotifierProviderElement(this); + } + + @override + bool operator ==(Object other) { + return other is BugsEncounteredNotifierProvider && + other.featureId == featureId; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, featureId.hashCode); + + return _SystemHash.finish(hash); + } +} + +mixin BugsEncounteredNotifierRef on AutoDisposeAsyncNotifierProviderRef { + /// The parameter `featureId` of this provider. + String get featureId; +} + +class _BugsEncounteredNotifierProviderElement + extends AutoDisposeAsyncNotifierProviderElement with BugsEncounteredNotifierRef { + _BugsEncounteredNotifierProviderElement(super.provider); + + @override + String get featureId => (origin as BugsEncounteredNotifierProvider).featureId; +} +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose/index.tsx b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose/index.tsx new file mode 100644 index 000000000..0780f2135 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./family_and_dispose.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose/raw.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose/raw.dart new file mode 100644 index 000000000..8dfce9447 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose/raw.dart @@ -0,0 +1,27 @@ +// ignore_for_file: unnecessary_this + +import 'dart:math'; + +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../../utils.dart'; + +/* SNIPPET START */ +class BugsEncounteredNotifier extends AutoDisposeFamilyAsyncNotifier { + @override + FutureOr build(String featureId) { + return 99; + } + + Future fix(int amount) async { + final old = await future; + final result = await ref.read(taskTrackerProvider).fix(id: this.arg, fixed: amount); + state = AsyncData(max(old - result, 0)); + } +} + +final bugsEncounteredNotifierProvider = + AsyncNotifierProvider.family.autoDispose( + BugsEncounteredNotifier.new, +); diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose_old.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose_old.dart new file mode 100644 index 000000000..28f93a65c --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose_old.dart @@ -0,0 +1,30 @@ +// ignore_for_file: unnecessary_this + +import 'dart:math'; + +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +import '../utils.dart'; + +/* SNIPPET START */ +class BugsEncounteredNotifier extends StateNotifier> { + BugsEncounteredNotifier({ + required this.ref, + required this.featureId, + }) : super(const AsyncData(99)); + final String featureId; + final Ref ref; + + Future fix(int amount) async { + state = await AsyncValue.guard(() async { + final old = state.requireValue; + final result = await ref.read(taskTrackerProvider).fix(id: featureId, fixed: amount); + return max(old - result, 0); + }); + } +} + +final bugsEncounteredNotifierProvider = + StateNotifierProvider.family.autoDispose((ref, id) { + return BugsEncounteredNotifier(ref: ref, featureId: id); +}); diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider/from_state_provider.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider/from_state_provider.dart new file mode 100644 index 000000000..2c71d6144 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider/from_state_provider.dart @@ -0,0 +1,14 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'from_state_provider.g.dart'; + +/* SNIPPET START */ +@riverpod +class CounterNotifier extends _$CounterNotifier { + @override + int build() => 0; + + @override + set state(int newState) => super.state = newState; + int update(int Function(int state) cb) => state = cb(state); +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider/from_state_provider.g.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider/from_state_provider.g.dart new file mode 100644 index 000000000..e9bbec271 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider/from_state_provider.g.dart @@ -0,0 +1,28 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'from_state_provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$counterNotifierHash() => r'b32033040f0fff627f1a6dfd9cfb4e93a842390b'; + +/// See also [CounterNotifier]. +@ProviderFor(CounterNotifier) +final counterNotifierProvider = + AutoDisposeNotifierProvider.internal( + CounterNotifier.new, + name: r'counterNotifierProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$counterNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$CounterNotifier = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider/index.tsx b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider/index.tsx new file mode 100644 index 000000000..f59794999 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./from_state_provider.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider/raw.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider/raw.dart new file mode 100644 index 000000000..97e4564f3 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider/raw.dart @@ -0,0 +1,13 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +/* SNIPPET START */ +class CounterNotifier extends Notifier { + @override + int build() => 0; + + @override + set state(int newState) => super.state = newState; + int update(int Function(int state) cb) => state = cb(state); +} + +final counterNotifierProvider = NotifierProvider(CounterNotifier.new); diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider_old.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider_old.dart new file mode 100644 index 000000000..246f44a0c --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider_old.dart @@ -0,0 +1,6 @@ +import 'package:riverpod/riverpod.dart'; + +/* SNIPPET START */ +final counterProvider = StateProvider((ref) { + return 0; +}); diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/obtain_notifier_on_tests.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/obtain_notifier_on_tests.dart new file mode 100644 index 000000000..4a0d4e07c --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/obtain_notifier_on_tests.dart @@ -0,0 +1,33 @@ +// ignore_for_file: unused_local_variable,omit_local_variable_types + +import 'package:flutter_test/flutter_test.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +class MyNotifier extends AutoDisposeNotifier { + @override + int build() { + return 0; + } +} + +final myNotifierProvider = NotifierProvider.autoDispose(MyNotifier.new); + +/* SNIPPET START */ +void main(List args) { + test('my test', () { + final container = ProviderContainer(); + addTearDown(container.dispose); + + // Ottenendo un notifier + /* highlight-start */ + final AutoDisposeNotifier notifier = container.read(myNotifierProvider.notifier); + /* highlight-end */ + + // Ottenendo il suo stato esposto + /* highlight-start */ + final int state = container.read(myNotifierProvider); + /* highlight-end */ + + // TODO scrivi i tuoi test + }); +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles/index.tsx b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles/index.tsx new file mode 100644 index 000000000..9b77f551a --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./old_lifecycles.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles/old_lifecycles.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles/old_lifecycles.dart new file mode 100644 index 000000000..7da7d4adc --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles/old_lifecycles.dart @@ -0,0 +1,36 @@ +import 'dart:async'; + +import 'package:dio/dio.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../../utils.dart'; + +part 'old_lifecycles.g.dart'; + +final repositoryProvider = Provider<_MyRepo>((ref) { + return _MyRepo(); +}); + +class _MyRepo { + Future update(int i, {CancelToken? token}) async {} +} + +/* SNIPPET START */ +@riverpod +class MyNotifier extends _$MyNotifier { + @override + int build() { + // Basta leggere/scrivere il codice qui, in un posto + final period = ref.watch(durationProvider); + final timer = Timer.periodic(period, (t) => update()); + ref.onDispose(timer.cancel); + + return 0; + } + + Future update() async { + await ref.read(repositoryProvider).update(state + 1); + // `mounted` non è più necessario! + state++; // This might throw. + } +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles/old_lifecycles.g.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles/old_lifecycles.g.dart new file mode 100644 index 000000000..9d1158e62 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles/old_lifecycles.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'old_lifecycles.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$myNotifierHash() => r'0495c52ce893ee0304d4d5ac5648c634ed4a241e'; + +/// See also [MyNotifier]. +@ProviderFor(MyNotifier) +final myNotifierProvider = + AutoDisposeNotifierProvider.internal( + MyNotifier.new, + name: r'myNotifierProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$myNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$MyNotifier = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles/raw.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles/raw.dart new file mode 100644 index 000000000..691d71af9 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles/raw.dart @@ -0,0 +1,35 @@ +import 'dart:async'; + +import 'package:dio/dio.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../../utils.dart'; + +final repositoryProvider = Provider<_MyRepo>((ref) { + return _MyRepo(); +}); + +class _MyRepo { + Future update(int i, {CancelToken? token}) async {} +} + +/* SNIPPET START */ +class MyNotifier extends Notifier { + @override + int build() { + // Just read/write the code here, in one place + final period = ref.watch(durationProvider); + final timer = Timer.periodic(period, (t) => update()); + ref.onDispose(timer.cancel); + + return 0; + } + + Future update() async { + await ref.read(repositoryProvider).update(state + 1); + // `mounted` is no more! + state++; // This might throw. + } +} + +final myNotifierProvider = NotifierProvider(MyNotifier.new); diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_final/index.tsx b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_final/index.tsx new file mode 100644 index 000000000..9823b1564 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_final/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./old_lifecycles_final.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_final/old_lifecycles_final.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_final/old_lifecycles_final.dart new file mode 100644 index 000000000..e343cdaa7 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_final/old_lifecycles_final.dart @@ -0,0 +1,38 @@ +import 'dart:async'; + +import 'package:dio/dio.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../../utils.dart'; + +part 'old_lifecycles_final.g.dart'; + +final repositoryProvider = Provider<_MyRepo>((ref) { + return _MyRepo(); +}); + +class _MyRepo { + Future update(int i, {CancelToken? token}) async {} +} + +/* SNIPPET START */ +@riverpod +class MyNotifier extends _$MyNotifier { + @override + int build() { + // Legge/scrive il codice solamente qui, in un posto unico + final period = ref.watch(durationProvider); + final timer = Timer.periodic(period, (t) => update()); + ref.onDispose(timer.cancel); + + return 0; + } + + Future update() async { + final cancelToken = CancelToken(); + ref.onDispose(cancelToken.cancel); + await ref.read(repositoryProvider).update(state + 1, token: cancelToken); + // Quando `cancelToken.cancel` è invocato, una Exception personalizzata viene generata + state++; + } +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_final/old_lifecycles_final.g.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_final/old_lifecycles_final.g.dart new file mode 100644 index 000000000..3600948dc --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_final/old_lifecycles_final.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'old_lifecycles_final.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$myNotifierHash() => r'8ea2586ea29d12306efd4b8b847142136dd20338'; + +/// See also [MyNotifier]. +@ProviderFor(MyNotifier) +final myNotifierProvider = + AutoDisposeNotifierProvider.internal( + MyNotifier.new, + name: r'myNotifierProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$myNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$MyNotifier = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_final/raw.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_final/raw.dart new file mode 100644 index 000000000..09aa16913 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_final/raw.dart @@ -0,0 +1,36 @@ +import 'dart:async'; + +import 'package:dio/dio.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../../utils.dart'; + +final repositoryProvider = Provider<_MyRepo>((ref) { + return _MyRepo(); +}); + +class _MyRepo { + Future update(int i, {CancelToken? token}) async {} +} + +/* SNIPPET START */ +class MyNotifier extends Notifier { + @override + int build() { + // Legge/scrive il codice solamente qui, in un posto unico + final period = ref.watch(durationProvider); + final timer = Timer.periodic(period, (t) => update()); + ref.onDispose(timer.cancel); + + return 0; + } + + Future update() async { + final cancelToken = CancelToken(); + ref.onDispose(cancelToken.cancel); + await ref.read(repositoryProvider).update(state + 1, token: cancelToken); + state++; + } +} + +final myNotifierProvider = NotifierProvider(MyNotifier.new); diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_old.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_old.dart new file mode 100644 index 000000000..8f6eaa485 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_old.dart @@ -0,0 +1,42 @@ +import 'dart:async'; + +import 'package:dio/dio.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../utils.dart'; + +final repositoryProvider = Provider<_MyRepo>((ref) { + return _MyRepo(); +}); + +class _MyRepo { + Future update(int i, {CancelToken? token}) async {} +} + +/* SNIPPET START */ +class MyNotifier extends StateNotifier { + MyNotifier(this.ref, this.period) : super(0) { + // 1 init logic + _timer = Timer.periodic(period, (t) => update()); // 2 side effect on init + } + final Duration period; + final Ref ref; + late final Timer _timer; + + Future update() async { + await ref.read(repositoryProvider).update(state + 1); // 3 mutation + if (mounted) state++; // 4 check for mounted props + } + + @override + void dispose() { + _timer.cancel(); // 5 custom dispose logic + super.dispose(); + } +} + +final myNotifierProvider = StateNotifierProvider((ref) { + // 6 provider definition + final period = ref.watch(durationProvider); // 7 reactive dependency logic + return MyNotifier(ref, period); // 8 pipe down `ref` +}); diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/migration/utils.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/utils.dart new file mode 100644 index 000000000..e515b6f94 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/migration/utils.dart @@ -0,0 +1,23 @@ +import 'dart:math' as math; + +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +final randomProvider = Provider((ref) { + return math.Random().nextInt(6); +}); + +final taskTrackerProvider = Provider((ref) { + return TaskTrackerRepo(); +}); + +class TaskTrackerRepo { + Future fix({required String id, required int fixed}) async => 0; +} + +final durationProvider = Provider((ref) { + return Duration.zero; +}); + +final availableWaterProvider = Provider((ref) { + return 40; +}); diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/change_notifier_provider.mdx b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/change_notifier_provider.mdx new file mode 100644 index 000000000..5143f6f51 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/change_notifier_provider.mdx @@ -0,0 +1,55 @@ +--- +title: ChangeNotifierProvider +--- + +import CodeBlock from "@theme/CodeBlock"; +import todos from "!!raw-loader!/docs/providers/change_notifier_provider/todos.dart"; +import todosConsumer from "!!raw-loader!/docs/providers/change_notifier_provider/todos_consumer.dart"; +import { trimSnippet } from "../../../../../src/components/CodeSnippet"; + +:::caution +The content of this page may be outdated. +It will be updated in the future, but for now you may want to refer to the content +in the top of the sidebar instead (introduction/essentials/case-studies/...) +::: + +`ChangeNotifierProvider` (flutter_riverpod/hooks_riverpod only) is a provider that +is used to listen to and expose a [ChangeNotifier] from Flutter itself. + +Using `ChangeNotifierProvider` is discouraged by Riverpod and exists primarily for: + +- an easy transition from `package:provider` when using its `ChangeNotifierProvider` +- supporting mutable state, even though immutable state is preferred + +:::info +Prefer using [NotifierProvider] instead. +Consider using `ChangeNotifierProvider` only if you are absolutely certain +that you want mutable state. +::: + +Using mutable state instead of immutable state can sometimes be more efficient. +The downside is, it can be harder to maintain and may break various features. +For example, using `provider.select` to optimize rebuilds of your widgets +may not work if your state is mutable, as `select` will think that the value +hasn't changed. +As such, using immutable data structures can sometimes be faster. Therefore +it is important to make benchmarks specific to your use-case, to make sure +that you are truly gaining performance by using `ChangeNotifierProvider`. + +As a usage example, we could use `ChangeNotifierProvider` to implement a todo-list. +Doing so would allow us to expose methods such as `addTodo` to let the UI +modify the list of todos on user interactions: + +{trimSnippet(todos)} + +Now that we have defined a `ChangeNotifierProvider`, we can use it to interact +with the list of todos in our UI: + +{trimSnippet(todosConsumer)} + +[state_notifier]: https://pub.dev/packages/state_notifier +[statenotifierprovider]: ./state_notifier_provider +[notifierprovider]: ./notifier_provider +[changenotifier]: https://api.flutter.dev/flutter/foundation/ChangeNotifier-class.html +[provider]: ./provider +[futureprovider]: ./future_provider diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/change_notifier_provider/todos.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/change_notifier_provider/todos.dart new file mode 100644 index 000000000..d766920b1 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/change_notifier_provider/todos.dart @@ -0,0 +1,45 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +/* SNIPPET START */ + +class Todo { + Todo({ + required this.id, + required this.description, + required this.completed, + }); + + String id; + String description; + bool completed; +} + +class TodosNotifier extends ChangeNotifier { + final todos = []; + + // Let's allow the UI to add todos. + void addTodo(Todo todo) { + todos.add(todo); + notifyListeners(); + } + + // Let's allow removing todos + void removeTodo(String todoId) { + todos.remove(todos.firstWhere((element) => element.id == todoId)); + notifyListeners(); + } + + // Let's mark a todo as completed + void toggle(String todoId) { + final todo = todos.firstWhere((todo) => todo.id == todoId); + todo.completed = !todo.completed; + notifyListeners(); + } +} + +// Finally, we are using ChangeNotifierProvider to allow the UI to interact with +// our TodosNotifier class. +final todosProvider = ChangeNotifierProvider((ref) { + return TodosNotifier(); +}); diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/change_notifier_provider/todos_consumer.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/change_notifier_provider/todos_consumer.dart new file mode 100644 index 000000000..beac13f4e --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/change_notifier_provider/todos_consumer.dart @@ -0,0 +1,32 @@ +// ignore_for_file: omit_local_variable_types, prefer_final_locals + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'todos.dart'; + +/* SNIPPET START */ + +class TodoListView extends ConsumerWidget { + const TodoListView({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + // rebuild the widget when the todo list changes + List todos = ref.watch(todosProvider).todos; + + // Let's render the todos in a scrollable list view + return ListView( + children: [ + for (final todo in todos) + CheckboxListTile( + value: todo.completed, + // When tapping on the todo, change its completed status + onChanged: (value) => + ref.read(todosProvider.notifier).toggle(todo.id), + title: Text(todo.description), + ), + ], + ); + } +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/future_provider.mdx b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/future_provider.mdx index ebe81b41f..6f114320a 100644 --- a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/future_provider.mdx +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/future_provider.mdx @@ -3,53 +3,60 @@ title: FutureProvider version: 1 --- -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; -import CodeBlock from "@theme/CodeBlock"; -import configProvider from "/docs/providers/future_provider/config_provider"; -import configConsumer from "/docs/providers/future_provider/config_consumer"; -import { trimSnippet,AutoSnippet} from "../../../../../src/components/CodeSnippet"; +import configProvider from "./future_provider/config_provider"; +import configConsumer from "./future_provider/config_consumer"; +import { AutoSnippet} from "../../../../../src/components/CodeSnippet"; + +:::caution +The content of this page may be outdated. +It will be updated in the future, but for now you may want to refer to the content +in the top of the sidebar instead (introduction/essentials/case-studies/...) +::: -`FutureProvider` è l'equivalente di [Provider] ma per il codice asincrono. +`FutureProvider` is the equivalent of [Provider] but for asynchronous code. -`FutureProvider` è tipicamente usato per: +`FutureProvider` is typically used for: -- eseguire e memorizzare nella cache operazioni asincrone (come le richieste di rete) -- gestire ottimamente gli stati di errore/caricamento delle operazioni asincrone -- combinare più valori asincroni in un altro valore +- performing and caching asynchronous operations (such as network requests) +- nicely handling error/loading states of asynchronous operations +- combining multiple asynchronous values into another value -`FutureProvider` è ottimo quando combinato con [ref.watch]. Tale combinazione consente il ri-ottenimento automatico -dei dati al variare di alcune variabili, assicurando che si abbia sempre il valore più aggiornato. +`FutureProvider` gains a lot when combined with [ref.watch]. This combination +allows automatic re-fetching of some data when some variables change, +ensuring that we always have the most up-to-date value. :::info -`FutureProvider` non offre un modo per modificare direttamente il calcolo (o computazione) dopo un'interazione dell'utente. -È invece progettato per risolvere semplici casi d'uso. -Per scenari più avanzati, considera usare [StateNotifierProvider]. +`FutureProvider` does not offer a way of directly modifying the computation after +a user interaction. It is designed to solve simple use-cases. +For more advanced scenarios, consider using [AsyncNotifierProvider]. ::: -## Esempio d'uso: leggere un file di configurazione +## Usage example: reading a configuration file -`FutureProvider` può essere utile per esporre un oggetto `Configuration` -creato leggendo un file JSON. +`FutureProvider` can be a convenient way to expose a `Configuration` object +created by reading a JSON file. -La creazione della configurazione verrebbe eseguita con la tipica sintassi async/await, ma all'interno del provider. -Usando gli asset di Flutter, sarebbe quindi: +Creating the configuration would be done with your typical async/await +syntax, but inside the provider. +Using Flutter's asset system, this would be: -Successivamente, la UI può stare in ascolto delle configurazioni in questo modo: + +Then, the UI can listen to configurations like so: -Ciò ricostruirà automaticamente l'interfaccia quando il [Future] finisce. -Allo stesso tempo, se più widget vogliono accedere alle configurazioni, l'asset verrà decodificato solo una volta. +This will automatically rebuild the UI when the [Future] completes. +At the same time, if multiple widgets want the configurations, +the asset will be decoded only once. -Come puoi vedere, ascoltare un `FutureProvider` dentro un widget restituisce un [AsyncValue], -il che permette di gestire gli stati di errore/caricamento. +As you can see, listening to a `FutureProvider` inside a widget returns +an [AsyncValue] – which allows handling the error/loading states. -[ref.watch]: ../concepts/reading#usare-ref.watch-per-osservare-un-provider -[statenotifierprovider]: ./state_notifier_provider +[ref.watch]: ../concepts/reading#using-refwatch-to-observe-a-provider +[asyncnotifierprovider]: ./notifier_provider [provider]: ./provider [asyncvalue]: https://pub.dev/documentation/riverpod/latest/riverpod/AsyncValue-class.html [future]: https://api.dart.dev/dart-async/Future-class.html -[family]: ../concepts/modifiers/family \ No newline at end of file +[family]: ../concepts/modifiers/family diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/future_provider/config_consumer/codegen.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/future_provider/config_consumer/codegen.dart new file mode 100644 index 000000000..a96f35e72 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/future_provider/config_consumer/codegen.dart @@ -0,0 +1,16 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +import '../config_provider/codegen.dart'; + +/* SNIPPET START */ + +Widget build(BuildContext context, WidgetRef ref) { + final config = ref.watch(fetchConfigurationProvider); + + return switch (config) { + AsyncError(:final error) => Text('Error: $error'), + AsyncData(:final value) => Text(value.host), + _ => const CircularProgressIndicator(), + }; +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/future_provider/config_consumer/hooks.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/future_provider/config_consumer/hooks.dart new file mode 100644 index 000000000..44d9b4df9 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/future_provider/config_consumer/hooks.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import '../config_provider/raw.dart'; + +/* SNIPPET START */ + +class MyConfiguration extends HookConsumerWidget { + const MyConfiguration({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final config = ref.watch(configProvider); + return Scaffold( + body: switch (config) { + AsyncError(:final error) => Center(child: Text('Error: $error')), + AsyncData(:final value) => Center(child: Text(value.host)), + _ => const Center(child: CircularProgressIndicator()), + }, + ); + } +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/future_provider/config_consumer/hooks_codegen.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/future_provider/config_consumer/hooks_codegen.dart new file mode 100644 index 000000000..ade0628fd --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/future_provider/config_consumer/hooks_codegen.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +import '../config_provider/codegen.dart'; + +/* SNIPPET START */ + +class MyConfiguration extends HookConsumerWidget { + const MyConfiguration({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final config = ref.watch(fetchConfigurationProvider); + return Scaffold( + body: switch (config) { + AsyncError(:final error) => Center(child: Text('Error: $error')), + AsyncData(:final value) => Center(child: Text(value.host)), + _ => const Center(child: CircularProgressIndicator()), + }, + ); + } +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/future_provider/config_consumer/index.tsx b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/future_provider/config_consumer/index.tsx new file mode 100644 index 000000000..8e7b2a443 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/future_provider/config_consumer/index.tsx @@ -0,0 +1,11 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./codegen.dart"; +import hooks from "!!raw-loader!./hooks.dart"; +import hooksCodegen from "!!raw-loader!./hooks_codegen.dart" + +export default { + raw, + hooks, + codegen, + hooksCodegen, +}; diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/future_provider/config_consumer/raw.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/future_provider/config_consumer/raw.dart new file mode 100644 index 000000000..9d39f3bcd --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/future_provider/config_consumer/raw.dart @@ -0,0 +1,18 @@ +// ignore_for_file: omit_local_variable_types, prefer_final_locals + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../config_provider/raw.dart'; + +/* SNIPPET START */ + +Widget build(BuildContext context, WidgetRef ref) { + AsyncValue config = ref.watch(configProvider); + + return switch (config) { + AsyncData(:final value) => Text(value.host), + AsyncError(:final error) => Text('Error: $error'), + _ => const CircularProgressIndicator(), + }; +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/future_provider/config_provider/codegen.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/future_provider/config_provider/codegen.dart new file mode 100644 index 000000000..2aa87a29e --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/future_provider/config_provider/codegen.dart @@ -0,0 +1,22 @@ +// ignore_for_file: avoid_unused_constructor_parameters + +import 'dart:convert'; + +import 'package:flutter/services.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; +part 'codegen.g.dart'; +class Configuration { + Configuration.fromJson(Map json); + final String host = ''; +} + +/* SNIPPET START */ + +@riverpod +Future fetchConfiguration(FetchConfigurationRef ref) async { + final content = json.decode( + await rootBundle.loadString('assets/configurations.json'), + ) as Map; + + return Configuration.fromJson(content); +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/future_provider/config_provider/codegen.g.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/future_provider/config_provider/codegen.g.dart new file mode 100644 index 000000000..08b9d838b --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/future_provider/config_provider/codegen.g.dart @@ -0,0 +1,29 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$fetchConfigurationHash() => + r'6c0f062e6f20baf883c4282856f1197fbe633d89'; + +/// See also [fetchConfiguration]. +@ProviderFor(fetchConfiguration) +final fetchConfigurationProvider = + AutoDisposeFutureProvider.internal( + fetchConfiguration, + name: r'fetchConfigurationProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$fetchConfigurationHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef FetchConfigurationRef = AutoDisposeFutureProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/future_provider/config_provider/index.tsx b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/future_provider/config_provider/index.tsx new file mode 100644 index 000000000..339b89a56 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/future_provider/config_provider/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./codegen.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/future_provider/config_provider/raw.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/future_provider/config_provider/raw.dart new file mode 100644 index 000000000..62d7926ca --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/future_provider/config_provider/raw.dart @@ -0,0 +1,22 @@ +// ignore_for_file: avoid_unused_constructor_parameters + +import 'dart:convert'; + +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +class Configuration { + Configuration.fromJson(Map json); + + final String host = ''; +} + +/* SNIPPET START */ + +final configProvider = FutureProvider((ref) async { + final content = json.decode( + await rootBundle.loadString('assets/configurations.json'), + ) as Map; + + return Configuration.fromJson(content); +}); diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/notifier_provider.mdx b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/notifier_provider.mdx new file mode 100644 index 000000000..4432f874f --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/notifier_provider.mdx @@ -0,0 +1,55 @@ +--- +title: (Async)NotifierProvider +--- + +import CodeBlock from "@theme/CodeBlock"; +import todos from "./notifier_provider/todos"; +import todosConsumer from "!!raw-loader!/docs/providers/notifier_provider/todos/todos_consumer.dart"; +import remoteTodos from "./notifier_provider/remote_todos"; +import remoteTodosConsumer from "!!raw-loader!/docs/providers/notifier_provider/remote_todos/todos_consumer.dart"; +import { trimSnippet, AutoSnippet } from "../../../../../src/components/CodeSnippet"; + +:::caution +The content of this page may be outdated. +It will be updated in the future, but for now you may want to refer to the content +in the top of the sidebar instead (introduction/essentials/case-studies/...) +::: + +[NotifierProvider] is a provider that is used to listen to and expose a [Notifier]. +[AsyncNotifierProvider] is a provider that is used to listen to and expose an [AsyncNotifier]. +[AsyncNotifier] is a [Notifier] that can be asynchronously initialized. +`(Async)NotifierProvider` along with `(Async)Notifier` is Riverpod's recommended solution +for managing state which may change in reaction to a user interaction. + +It is typically used for: + +- exposing a state which can change over time after reacting to custom events. +- centralizing the logic for modifying some state (aka "business logic") in a + single place, improving maintainability over time. + +As a usage example, we could use [NotifierProvider] to implement a todo-list. +Doing so would allow us to expose methods such as `addTodo` to let the UI +modify the list of todos on user interactions: + + + +Now that we have defined a [NotifierProvider], we can use it to interact +with the list of todos in our UI: + +{trimSnippet(todosConsumer)} + +As a usage example, we could use [AsyncNotifierProvider] to implement a remote todo-list. +Doing so would allow us to expose methods such as `addTodo` to let the UI +modify the list of todos on user interactions: + + + +Now that we have defined a [AsyncNotifierProvider], we can use it to interact +with the list of todos in our UI: + +{trimSnippet(remoteTodosConsumer)} + +[notifier]: https://pub.dev/documentation/riverpod/latest/riverpod/Notifier-class.html +[notifierprovider]: https://pub.dev/documentation/riverpod/latest/riverpod/NotifierProvider.html +[asyncnotifier]: https://pub.dev/documentation/riverpod/latest/riverpod/AsyncNotifier-class.html +[asyncnotifierprovider]: https://pub.dev/documentation/riverpod/latest/riverpod/AsyncNotifierProvider.html diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/codegen.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/codegen.dart new file mode 100644 index 000000000..e02195f87 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/codegen.dart @@ -0,0 +1,82 @@ +import 'dart:convert'; + +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.freezed.dart'; +part 'codegen.g.dart'; + +class Http { + Future get(String str) async => str; + Future delete(String str) async => str; + Future post(String str, Map body) async => str; + Future patch(String str, Map body) async => str; +} + +final http = Http(); + +/* SNIPPET START */ + +@freezed +class Todo with _$Todo { + factory Todo({ + required String id, + required String description, + required bool completed, + }) = _Todo; + + factory Todo.fromJson(Map json) => _$TodoFromJson(json); +} + +// This will generates a AsyncNotifier and AsyncNotifierProvider. +// The AsyncNotifier class that will be passed to our AsyncNotifierProvider. +// This class should not expose state outside of its "state" property, which means +// no public getters/properties! +// The public methods on this class will be what allow the UI to modify the state. +// Finally, we are using asyncTodosProvider(AsyncNotifierProvider) to allow the UI to +// interact with our Todos class. +@riverpod +class AsyncTodos extends _$AsyncTodos { + Future> _fetchTodo() async { + final json = await http.get('api/todos'); + final todos = jsonDecode(json) as List>; + return todos.map(Todo.fromJson).toList(); + } + + @override + FutureOr> build() async { + // Load initial todo list from the remote repository + return _fetchTodo(); + } + + Future addTodo(Todo todo) async { + // Set the state to loading + state = const AsyncValue.loading(); + // Add the new todo and reload the todo list from the remote repository + state = await AsyncValue.guard(() async { + await http.post('api/todos', todo.toJson()); + return _fetchTodo(); + }); + } + + // Let's allow removing todos + Future removeTodo(String todoId) async { + state = const AsyncValue.loading(); + state = await AsyncValue.guard(() async { + await http.delete('api/todos/$todoId'); + return _fetchTodo(); + }); + } + + // Let's mark a todo as completed + Future toggle(String todoId) async { + state = const AsyncValue.loading(); + state = await AsyncValue.guard(() async { + await http.patch( + 'api/todos/$todoId', + {'completed': true}, + ); + return _fetchTodo(); + }); + } +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/codegen.freezed.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/codegen.freezed.dart new file mode 100644 index 000000000..d1b8a01d5 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/codegen.freezed.dart @@ -0,0 +1,184 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'codegen.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +Todo _$TodoFromJson(Map json) { + return _Todo.fromJson(json); +} + +/// @nodoc +mixin _$Todo { + String get id => throw _privateConstructorUsedError; + String get description => throw _privateConstructorUsedError; + bool get completed => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $TodoCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $TodoCopyWith<$Res> { + factory $TodoCopyWith(Todo value, $Res Function(Todo) then) = + _$TodoCopyWithImpl<$Res, Todo>; + @useResult + $Res call({String id, String description, bool completed}); +} + +/// @nodoc +class _$TodoCopyWithImpl<$Res, $Val extends Todo> + implements $TodoCopyWith<$Res> { + _$TodoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? description = null, + Object? completed = null, + }) { + return _then(_value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + completed: null == completed + ? _value.completed + : completed // ignore: cast_nullable_to_non_nullable + as bool, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$TodoImplCopyWith<$Res> implements $TodoCopyWith<$Res> { + factory _$$TodoImplCopyWith( + _$TodoImpl value, $Res Function(_$TodoImpl) then) = + __$$TodoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String id, String description, bool completed}); +} + +/// @nodoc +class __$$TodoImplCopyWithImpl<$Res> + extends _$TodoCopyWithImpl<$Res, _$TodoImpl> + implements _$$TodoImplCopyWith<$Res> { + __$$TodoImplCopyWithImpl(_$TodoImpl _value, $Res Function(_$TodoImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? description = null, + Object? completed = null, + }) { + return _then(_$TodoImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + completed: null == completed + ? _value.completed + : completed // ignore: cast_nullable_to_non_nullable + as bool, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$TodoImpl implements _Todo { + _$TodoImpl( + {required this.id, required this.description, required this.completed}); + + factory _$TodoImpl.fromJson(Map json) => + _$$TodoImplFromJson(json); + + @override + final String id; + @override + final String description; + @override + final bool completed; + + @override + String toString() { + return 'Todo(id: $id, description: $description, completed: $completed)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$TodoImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.description, description) || + other.description == description) && + (identical(other.completed, completed) || + other.completed == completed)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, id, description, completed); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$TodoImplCopyWith<_$TodoImpl> get copyWith => + __$$TodoImplCopyWithImpl<_$TodoImpl>(this, _$identity); + + @override + Map toJson() { + return _$$TodoImplToJson( + this, + ); + } +} + +abstract class _Todo implements Todo { + factory _Todo( + {required final String id, + required final String description, + required final bool completed}) = _$TodoImpl; + + factory _Todo.fromJson(Map json) = _$TodoImpl.fromJson; + + @override + String get id; + @override + String get description; + @override + bool get completed; + @override + @JsonKey(ignore: true) + _$$TodoImplCopyWith<_$TodoImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/codegen.g.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/codegen.g.dart new file mode 100644 index 000000000..fb8e168d2 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/codegen.g.dart @@ -0,0 +1,44 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$TodoImpl _$$TodoImplFromJson(Map json) => _$TodoImpl( + id: json['id'] as String, + description: json['description'] as String, + completed: json['completed'] as bool, + ); + +Map _$$TodoImplToJson(_$TodoImpl instance) => + { + 'id': instance.id, + 'description': instance.description, + 'completed': instance.completed, + }; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$asyncTodosHash() => r'fd0d7502a1c17b7cedd2350519649dd680fc48cd'; + +/// See also [AsyncTodos]. +@ProviderFor(AsyncTodos) +final asyncTodosProvider = + AutoDisposeAsyncNotifierProvider>.internal( + AsyncTodos.new, + name: r'asyncTodosProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$asyncTodosHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$AsyncTodos = AutoDisposeAsyncNotifier>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/index.tsx b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/index.tsx new file mode 100644 index 000000000..339b89a56 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./codegen.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/raw.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/raw.dart new file mode 100644 index 000000000..f4455a338 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/raw.dart @@ -0,0 +1,101 @@ +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +class Http { + Future get(String str) async => str; + Future delete(String str) async => str; + Future post(String str, Map body) async => str; + Future patch(String str, Map body) async => str; +} + +final http = Http(); + +/* SNIPPET START */ + +// An immutable state is preferred. +// We could also use packages like Freezed to help with the implementation. +@immutable +class Todo { + const Todo({ + required this.id, + required this.description, + required this.completed, + }); + + factory Todo.fromJson(Map map) { + return Todo( + id: map['id'] as String, + description: map['description'] as String, + completed: map['completed'] as bool, + ); + } + + // All properties should be `final` on our class. + final String id; + final String description; + final bool completed; + + Map toJson() => { + 'id': id, + 'description': description, + 'completed': completed, + }; +} + +// The Notifier class that will be passed to our NotifierProvider. +// This class should not expose state outside of its "state" property, which means +// no public getters/properties! +// The public methods on this class will be what allow the UI to modify the state. +class AsyncTodosNotifier extends AsyncNotifier> { + Future> _fetchTodo() async { + final json = await http.get('api/todos'); + final todos = jsonDecode(json) as List>; + return todos.map(Todo.fromJson).toList(); + } + + @override + Future> build() async { + // Load initial todo list from the remote repository + return _fetchTodo(); + } + + Future addTodo(Todo todo) async { + // Set the state to loading + state = const AsyncValue.loading(); + // Add the new todo and reload the todo list from the remote repository + state = await AsyncValue.guard(() async { + await http.post('api/todos', todo.toJson()); + return _fetchTodo(); + }); + } + + // Let's allow removing todos + Future removeTodo(String todoId) async { + state = const AsyncValue.loading(); + state = await AsyncValue.guard(() async { + await http.delete('api/todos/$todoId'); + return _fetchTodo(); + }); + } + + // Let's mark a todo as completed + Future toggle(String todoId) async { + state = const AsyncValue.loading(); + state = await AsyncValue.guard(() async { + await http.patch( + 'api/todos/$todoId', + {'completed': true}, + ); + return _fetchTodo(); + }); + } +} + +// Finally, we are using NotifierProvider to allow the UI to interact with +// our TodosNotifier class. +final asyncTodosProvider = + AsyncNotifierProvider>(() { + return AsyncTodosNotifier(); +}); diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/todos_consumer.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/todos_consumer.dart new file mode 100644 index 000000000..5d00d05d9 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/todos_consumer.dart @@ -0,0 +1,37 @@ +// ignore_for_file: omit_local_variable_types, prefer_final_locals + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'codegen.dart'; + +/* SNIPPET START */ + +class TodoListView extends ConsumerWidget { + const TodoListView({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + // rebuild the widget when the todo list changes + final asyncTodos = ref.watch(asyncTodosProvider); + + // Let's render the todos in a scrollable list view + return switch (asyncTodos) { + AsyncData(:final value) => ListView( + children: [ + for (final todo in value) + CheckboxListTile( + value: todo.completed, + // When tapping on the todo, change its completed status + onChanged: (value) { + ref.read(asyncTodosProvider.notifier).toggle(todo.id); + }, + title: Text(todo.description), + ), + ], + ), + AsyncError(:final error) => Text('Error: $error'), + _ => const Center(child: CircularProgressIndicator()), + }; + } +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/codegen.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/codegen.dart new file mode 100644 index 000000000..866ed37bb --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/codegen.dart @@ -0,0 +1,68 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.freezed.dart'; +part 'codegen.g.dart'; + +/* SNIPPET START */ + +@freezed +class Todo with _$Todo { + factory Todo({ + required String id, + required String description, + required bool completed, + }) = _Todo; +} + +// This will generates a Notifier and NotifierProvider. +// The Notifier class that will be passed to our NotifierProvider. +// This class should not expose state outside of its "state" property, which means +// no public getters/properties! +// The public methods on this class will be what allow the UI to modify the state. +// Finally, we are using todosProvider(NotifierProvider) to allow the UI to +// interact with our Todos class. +@riverpod +class Todos extends _$Todos { + @override + List build() { + return []; + } + + // Let's allow the UI to add todos. + void addTodo(Todo todo) { + // Since our state is immutable, we are not allowed to do `state.add(todo)`. + // Instead, we should create a new list of todos which contains the previous + // items and the new one. + // Using Dart's spread operator here is helpful! + state = [...state, todo]; + // No need to call "notifyListeners" or anything similar. Calling "state =" + // will automatically rebuild the UI when necessary. + } + + // Let's allow removing todos + void removeTodo(String todoId) { + // Again, our state is immutable. So we're making a new list instead of + // changing the existing list. + state = [ + for (final todo in state) + if (todo.id != todoId) todo, + ]; + } + + // Let's mark a todo as completed + void toggle(String todoId) { + state = [ + for (final todo in state) + // we're marking only the matching todo as completed + if (todo.id == todoId) + // Once more, since our state is immutable, we need to make a copy + // of the todo. We're using our `copyWith` method implemented before + // to help with that. + todo.copyWith(completed: !todo.completed) + else + // other todos are not modified + todo, + ]; + } +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/codegen.freezed.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/codegen.freezed.dart new file mode 100644 index 000000000..0b73d3548 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/codegen.freezed.dart @@ -0,0 +1,166 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'codegen.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +/// @nodoc +mixin _$Todo { + String get id => throw _privateConstructorUsedError; + String get description => throw _privateConstructorUsedError; + bool get completed => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $TodoCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $TodoCopyWith<$Res> { + factory $TodoCopyWith(Todo value, $Res Function(Todo) then) = + _$TodoCopyWithImpl<$Res, Todo>; + @useResult + $Res call({String id, String description, bool completed}); +} + +/// @nodoc +class _$TodoCopyWithImpl<$Res, $Val extends Todo> + implements $TodoCopyWith<$Res> { + _$TodoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? description = null, + Object? completed = null, + }) { + return _then(_value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + completed: null == completed + ? _value.completed + : completed // ignore: cast_nullable_to_non_nullable + as bool, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$TodoImplCopyWith<$Res> implements $TodoCopyWith<$Res> { + factory _$$TodoImplCopyWith( + _$TodoImpl value, $Res Function(_$TodoImpl) then) = + __$$TodoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String id, String description, bool completed}); +} + +/// @nodoc +class __$$TodoImplCopyWithImpl<$Res> + extends _$TodoCopyWithImpl<$Res, _$TodoImpl> + implements _$$TodoImplCopyWith<$Res> { + __$$TodoImplCopyWithImpl(_$TodoImpl _value, $Res Function(_$TodoImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? description = null, + Object? completed = null, + }) { + return _then(_$TodoImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + completed: null == completed + ? _value.completed + : completed // ignore: cast_nullable_to_non_nullable + as bool, + )); + } +} + +/// @nodoc + +class _$TodoImpl implements _Todo { + _$TodoImpl( + {required this.id, required this.description, required this.completed}); + + @override + final String id; + @override + final String description; + @override + final bool completed; + + @override + String toString() { + return 'Todo(id: $id, description: $description, completed: $completed)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$TodoImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.description, description) || + other.description == description) && + (identical(other.completed, completed) || + other.completed == completed)); + } + + @override + int get hashCode => Object.hash(runtimeType, id, description, completed); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$TodoImplCopyWith<_$TodoImpl> get copyWith => + __$$TodoImplCopyWithImpl<_$TodoImpl>(this, _$identity); +} + +abstract class _Todo implements Todo { + factory _Todo( + {required final String id, + required final String description, + required final bool completed}) = _$TodoImpl; + + @override + String get id; + @override + String get description; + @override + bool get completed; + @override + @JsonKey(ignore: true) + _$$TodoImplCopyWith<_$TodoImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/codegen.g.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/codegen.g.dart new file mode 100644 index 000000000..d2f26ef1b --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/codegen.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$todosHash() => r'3485c14ec4db07efe5fe52243258a66e6f99b2b4'; + +/// See also [Todos]. +@ProviderFor(Todos) +final todosProvider = AutoDisposeNotifierProvider>.internal( + Todos.new, + name: r'todosProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$todosHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$Todos = AutoDisposeNotifier>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/index.tsx b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/index.tsx new file mode 100644 index 000000000..339b89a56 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./codegen.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/raw.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/raw.dart new file mode 100644 index 000000000..ec3e50308 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/raw.dart @@ -0,0 +1,85 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +/* SNIPPET START */ + +// An immutable state is preferred. +// We could also use packages like Freezed to help with the implementation. +@immutable +class Todo { + const Todo({ + required this.id, + required this.description, + required this.completed, + }); + + // All properties should be `final` on our class. + final String id; + final String description; + final bool completed; + + // Since Todo is immutable, we implement a method that allows cloning the + // Todo with slightly different content. + Todo copyWith({String? id, String? description, bool? completed}) { + return Todo( + id: id ?? this.id, + description: description ?? this.description, + completed: completed ?? this.completed, + ); + } +} + +// The Notifier class that will be passed to our NotifierProvider. +// This class should not expose state outside of its "state" property, which means +// no public getters/properties! +// The public methods on this class will be what allow the UI to modify the state. +class TodosNotifier extends Notifier> { + // We initialize the list of todos to an empty list + @override + List build() { + return []; + } + + // Let's allow the UI to add todos. + void addTodo(Todo todo) { + // Since our state is immutable, we are not allowed to do `state.add(todo)`. + // Instead, we should create a new list of todos which contains the previous + // items and the new one. + // Using Dart's spread operator here is helpful! + state = [...state, todo]; + // No need to call "notifyListeners" or anything similar. Calling "state =" + // will automatically rebuild the UI when necessary. + } + + // Let's allow removing todos + void removeTodo(String todoId) { + // Again, our state is immutable. So we're making a new list instead of + // changing the existing list. + state = [ + for (final todo in state) + if (todo.id != todoId) todo, + ]; + } + + // Let's mark a todo as completed + void toggle(String todoId) { + state = [ + for (final todo in state) + // we're marking only the matching todo as completed + if (todo.id == todoId) + // Once more, since our state is immutable, we need to make a copy + // of the todo. We're using our `copyWith` method implemented before + // to help with that. + todo.copyWith(completed: !todo.completed) + else + // other todos are not modified + todo, + ]; + } +} + +// Finally, we are using NotifierProvider to allow the UI to interact with +// our TodosNotifier class. +final todosProvider = NotifierProvider>(() { + return TodosNotifier(); +}); diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/todos_consumer.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/todos_consumer.dart new file mode 100644 index 000000000..192cb9f66 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/todos_consumer.dart @@ -0,0 +1,32 @@ +// ignore_for_file: omit_local_variable_types, prefer_final_locals + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'codegen.dart'; + +/* SNIPPET START */ + +class TodoListView extends ConsumerWidget { + const TodoListView({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + // rebuild the widget when the todo list changes + List todos = ref.watch(todosProvider); + + // Let's render the todos in a scrollable list view + return ListView( + children: [ + for (final todo in todos) + CheckboxListTile( + value: todo.completed, + // When tapping on the todo, change its completed status + onChanged: (value) => + ref.read(todosProvider.notifier).toggle(todo.id), + title: Text(todo.description), + ), + ], + ); + } +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider.mdx b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider.mdx index 4058bc3cc..136c8edbc 100644 --- a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider.mdx +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider.mdx @@ -2,96 +2,104 @@ title: Provider --- -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; import CodeBlock from "@theme/CodeBlock"; -import todo from "!!raw-loader!/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/todo.dart"; -import completedTodos from "!!raw-loader!/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/completed_todos.dart"; -import todosConsumer from "!!raw-loader!/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/todos_consumer.dart"; -import unoptimizedPreviousButton from "!!raw-loader!/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/unoptimized_previous_button.dart"; -import optimizedPreviousButton from "!!raw-loader!/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/optimized_previous_button.dart"; -import { trimSnippet } from "../../../../../src/components/CodeSnippet"; +import todo from "./provider/todo"; +import completedTodos from "./provider/completed_todos"; +import todosConsumer from "!!raw-loader!/docs/providers/provider/todos_consumer.dart"; +import unoptimizedPreviousButton from "./provider/unoptimized_previous_button"; +import optimizedPreviousButton from "./provider/optimized_previous_button"; +import { trimSnippet, AutoSnippet } from "../../../../../src/components/CodeSnippet"; -`Provider` è il provider più basico tra tutti. Crea un valore... e questo è tutto. +:::caution +The content of this page may be outdated. +It will be updated in the future, but for now you may want to refer to the content +in the top of the sidebar instead (introduction/essentials/case-studies/...) +::: -`Provider` è utilizzato tipicamente per: +`Provider` is the most basic of all providers. It creates a value... And that's about it. -- calcoli per memorizzazione in cache -- esporre un valore ad altri provider (come `Repository`/`HttpClient`). -- offrire un modo per test o widget di sovrascrivere un valore. -- ridurre il numero di rebuilds dei provider/widget senza dover usare `select`. +`Provider` is typically used for: -## Usare `Provider` per memorizzare calcoli/computazioni +- caching computations +- exposing a value to other providers (such as a `Repository`/`HttpClient`). +- offering a way for tests or widgets to override a value. +- reducing rebuilds of providers/widgets without having to use `select`. -`Provider` è un potente strumento per memorizzare operazioni sincrone -quando combinato con [ref.watch]. +## Using `Provider` to cache computations -Un esempio potrebbe essere filtrare una lista di todo. Dato che filtrare una lista -potrebbe risultare leggermente costoso, idealmente non vogliamo filtrare la nostra lista -di todo ogni volta che la nostra applicazione si renderizza. -In questa situazione possiamo usare `Provider` per fare filtrare al posto nostro. +`Provider` is a powerful tool for caching synchronous operations when combined +with [ref.watch]. -Per fare ciò, assumiamo che la nostra applicazione abbia un esistente [StateNotifierProvider] -che manipola una lista di todo: +An example would be filtering a list of todos. +Since filtering a list could be slightly expensive, we ideally do not want to +filter our list of todos whenever our application re-renders. +In this situation, we could use `Provider` to do the filtering for us. -{trimSnippet(todo)} +For that, assume that our application has an existing [NotifierProvider] +which manipulates a list of todos: -Da qui, possiamo usare `Provider` per esporre la lista dei todo filtrata, -mostrando solo i todo completati: + -{trimSnippet(completedTodos)} +From there, we can use `Provider` to expose the filtered list of todos, showing +only the completed todos: -Con questo codice, la nostra UI è ora in grado di mostrare la lista dei todo -completati stando in ascolto di `completedTodosProvider`: + + +With this code, our UI is now able to show the list of the completed todos +by listening to `completedTodosProvider`: {trimSnippet(todosConsumer)} -La parte interessante è che la lista filtrata ora è memorizzata in cache. +The interesting part is, the list filtering is now cached. -Ciò significa che la lista dei todo completati non sarà ricalcolata fino a quando -i todo verranno aggiunti/rimossi/aggiornati, anche se stiamo leggendo la lista dei todo completati più volte. +Meaning that the list of completed todos will not be recomputed until +todos are added/removed/updated, even if we are reading the list of completed +todos multiple times. -Tieni presente che non è necessario invalidare manualmente la cache quando la lista dei todo cambia. -`Provider` sà in modo autonomo quando il risultato deve essere ricalcolato grazie a [ref.watch]. +Note how we do not need to manually invalidate the cache when the list of todos +changes. `Provider` is automatically able to know when the result must be recomputed +thanks to [ref.watch]. -## Ridurre il numero di rebuilds dei provider/widget attraverso `Provider` +## Reducing provider/widget rebuilds by using `Provider` -Un aspetto unico di `Provider` è che anche quando `Provider` viene ricalcolato -(in genere quando si usa [ref.watch]), non aggiornerà i widget/provider che lo ascoltano -a meno che il valore non cambi. +A unique aspect of `Provider` is that even when `Provider` is recomputed +(typically when using [ref.watch]), it will not update the widgets/providers +that listen to it unless the value changed. -Un esempio reale potrebbe essere per abilitare/disabilitare i tasti previous/next -di una vista paginata: +A real world example would be for enabling/disabling previous/next buttons +of a paginated view: -![Tasto "Precedente/Successivo"](https://user-images.githubusercontent.com/134939/47580830-31263a00-d950-11e8-9b61-0eaddab2709e.png) +![stepper example](https://user-images.githubusercontent.com/134939/47580830-31263a00-d950-11e8-9b61-0eaddab2709e.png) -Nel nostro caso ci concentreremo specificamente sul tasto "Precedente" ("Previous"). -Un'implementazione ingenua di tale pulsante sarebbe un widget che ottiene l'indice della pagina corrente, -e se quell'indice è uguale a 0, disabiliteremmo il pulsante. +In our case, we will focus specifically on the "previous" button. +A naive implementation of such button would be a widget which obtains the +current page index, and if that index is equal to 0, we would disable the button. -Tale codice potrebbe essere: +This code could be: -{trimSnippet(unoptimizedPreviousButton)} + -Il problema con questo codice è che ogni volta che cambiamo la pagina corrente, il pulsante "Previous" verrà ricostruito. -Come funzionamento ideale, vorremmo che il pulsante si ricostruisse solo quando passa da attivato a disattivato. +The issue with this code is that whenever we change the current page, the "previous" +button will rebuild. +In the ideal world, we would want the button to rebuild only when changing between +activated and deactivated. -La radice del problema è che stiamo ricalcolando se l'utente è autorizzato ad andare -alla pagina precedente direttamente all'interno del pulsante "previous". +The root of the issue here is that we are computing whether the user is +allowed to go to the previous page directly within the "previous" button. -Un modo per risolvere questo problema è estrarre la logica al di fuori del widget -in un `Provider`: +A way to solve this is to extract this logic outside of the widget and into a `Provider`: -{trimSnippet(optimizedPreviousButton)} + -Facendo questa piccola modifica, il nostro widget `PreviousButton` non verrà più ricostruito -quando l'indice della pagina cambia grazie a `Provider`. +By doing this small refactoring, our `PreviousButton` widget will no longer +rebuild when the page index changes thanks to `Provider`. -D'ora in poi, quando l'indice della pagina cambierà, il provider `canGoToPreviousPageProvider` -sarà ricalcolato. Ma se il valore esposto dal provider non cambia, allora `PreviousButton` non verrà ricostruito. +From now on when the page index changes, our `canGoToPreviousPageProvider` provider +will be recomputed. But if the value exposed by the provider does not change, +then `PreviousButton` will not rebuild. -Questa modifica ha migliorato la performance del nostro bottone, e ha avuto -l'interessante beneficio di estrarre la logica al di fuori del nostro widget. +This change both improved the performance of our button and had the interesting +benefit of extracting the logic outside of our widget. -[ref.watch]: ../concepts/reading#usare-ref.watch-per-osservare-un-provider -[statenotifierprovider]: ./state_notifier_provider \ No newline at end of file +[ref.watch]: ../concepts/reading#using-refwatch-to-observe-a-provider +[notifierprovider]: ./notifier_provider diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/completed_todos/completed_todos.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/completed_todos/completed_todos.dart new file mode 100644 index 000000000..1a29d96f5 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/completed_todos/completed_todos.dart @@ -0,0 +1,15 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../todo/todo.dart'; + +part 'completed_todos.g.dart'; + +/* SNIPPET START */ + +@riverpod +List completedTodos(CompletedTodosRef ref) { + final todos = ref.watch(todosProvider); + + // we return only the completed todos + return todos.where((todo) => todo.isCompleted).toList(); +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/completed_todos/completed_todos.g.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/completed_todos/completed_todos.g.dart new file mode 100644 index 000000000..f7db84ee2 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/completed_todos/completed_todos.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'completed_todos.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$completedTodosHash() => r'855706c09268f428696b3b382ae1605818361b83'; + +/// See also [completedTodos]. +@ProviderFor(completedTodos) +final completedTodosProvider = AutoDisposeProvider>.internal( + completedTodos, + name: r'completedTodosProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$completedTodosHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef CompletedTodosRef = AutoDisposeProviderRef>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/completed_todos/index.tsx b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/completed_todos/index.tsx new file mode 100644 index 000000000..11451aa1c --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/completed_todos/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./completed_todos.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/completed_todos.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/completed_todos/raw.dart similarity index 65% rename from website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/completed_todos.dart rename to website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/completed_todos/raw.dart index 3783ddf9d..e24c48bd5 100644 --- a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/completed_todos.dart +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/completed_todos/raw.dart @@ -1,13 +1,13 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'todo.dart'; +import '../todo/raw.dart'; /* SNIPPET START */ final completedTodosProvider = Provider>((ref) { - // Otteniamo la lista di tutti i todo da todosProvider + // We obtain the list of all todos from the todosProvider final todos = ref.watch(todosProvider); - // restituiamo solo i todo completati + // we return only the completed todos return todos.where((todo) => todo.isCompleted).toList(); }); diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/optimized_previous_button/index.tsx b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/optimized_previous_button/index.tsx new file mode 100644 index 000000000..fb83c92f1 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/optimized_previous_button/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./optimized_previous_button.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/optimized_previous_button/optimized_previous_button.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/optimized_previous_button/optimized_previous_button.dart new file mode 100644 index 000000000..7d3b8a332 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/optimized_previous_button/optimized_previous_button.dart @@ -0,0 +1,50 @@ +// A provider that controls the current page +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'optimized_previous_button.g.dart'; + +/* SNIPPET START */ + +@riverpod +class PageIndex extends _$PageIndex { + @override + int build() { + return 0; + } + + void goToPreviousPage() { + state = state - 1; + } +} + +// A provider which computes whether the user is allowed to go to the previous page +@riverpod +/* highlight-start */ +bool canGoToPreviousPage(CanGoToPreviousPageRef ref) { +/* highlight-end */ + return ref.watch(pageIndexProvider) != 0; +} + +class PreviousButton extends ConsumerWidget { + const PreviousButton({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + // We are now watching our new Provider + // Our widget is no longer calculating whether we can go to the previous page. +/* highlight-start */ + final canGoToPreviousPage = ref.watch(canGoToPreviousPageProvider); +/* highlight-end */ + + void goToPreviousPage() { + ref.read(pageIndexProvider.notifier).goToPreviousPage(); + } + + return ElevatedButton( + onPressed: canGoToPreviousPage ? goToPreviousPage : null, + child: const Text('previous'), + ); + } +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/optimized_previous_button/optimized_previous_button.g.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/optimized_previous_button/optimized_previous_button.g.dart new file mode 100644 index 000000000..d71214f1d --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/optimized_previous_button/optimized_previous_button.g.dart @@ -0,0 +1,42 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'optimized_previous_button.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$canGoToPreviousPageHash() => + r'801fe8182a37cd21ae83bdfccbe36c125b4d14fb'; + +/// See also [canGoToPreviousPage]. +@ProviderFor(canGoToPreviousPage) +final canGoToPreviousPageProvider = AutoDisposeProvider.internal( + canGoToPreviousPage, + name: r'canGoToPreviousPageProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$canGoToPreviousPageHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef CanGoToPreviousPageRef = AutoDisposeProviderRef; +String _$pageIndexHash() => r'59307ecf23b5b2432833da5ad6b312bf36435d0e'; + +/// See also [PageIndex]. +@ProviderFor(PageIndex) +final pageIndexProvider = AutoDisposeNotifierProvider.internal( + PageIndex.new, + name: r'pageIndexProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$pageIndexHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$PageIndex = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/optimized_previous_button.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/optimized_previous_button/raw.dart similarity index 87% rename from website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/optimized_previous_button.dart rename to website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/optimized_previous_button/raw.dart index b7c497a43..30b1cc673 100644 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/optimized_previous_button.dart +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/optimized_previous_button/raw.dart @@ -6,11 +6,11 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; final pageIndexProvider = StateProvider((ref) => 0); -// 사용자(the user)가 이전 페이지로 돌아가는지 계산하기 위한 프로바이더 +// A provider which computes whether the user is allowed to go to the previous page /* highlight-start */ final canGoToPreviousPageProvider = Provider((ref) { /* highlight-end */ - return ref.watch(pageIndexProvider) == 0; + return ref.watch(pageIndexProvider) != 0; }); class PreviousButton extends ConsumerWidget { diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/todo/index.tsx b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/todo/index.tsx new file mode 100644 index 000000000..91d9cc4aa --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/todo/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./todo.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/todo.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/todo/raw.dart similarity index 59% rename from website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/todo.dart rename to website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/todo/raw.dart index 04fecf970..11e62bc3a 100644 --- a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/todo.dart +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/todo/raw.dart @@ -10,15 +10,18 @@ class Todo { final String description; } -class TodosNotifier extends StateNotifier> { - TodosNotifier() : super([]); +class TodosNotifier extends Notifier> { + @override + List build() { + return []; + } void addTodo(Todo todo) { state = [...state, todo]; } - // TODO aggiungere altri metodi, come "removeTodo", ... + // TODO add other methods, such as "removeTodo", ... } -final todosProvider = StateNotifierProvider>((ref) { +final todosProvider = NotifierProvider>(() { return TodosNotifier(); }); diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/todo/todo.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/todo/todo.dart new file mode 100644 index 000000000..7089bc962 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/todo/todo.dart @@ -0,0 +1,25 @@ +// ignore_for_file: avoid_positional_boolean_parameters +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'todo.g.dart'; + +/* SNIPPET START */ + +class Todo { + Todo(this.description, this.isCompleted); + final bool isCompleted; + final String description; +} + +@riverpod +class Todos extends _$Todos { + @override + List build() { + return []; + } + + void addTodo(Todo todo) { + state = [...state, todo]; + } + // TODO add other methods, such as "removeTodo", ... +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/todo/todo.g.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/todo/todo.g.dart new file mode 100644 index 000000000..a21239ae8 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/todo/todo.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'todo.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$todosHash() => r'4bd25c3c15bfff56ad6e733bd17ecb7284c4ceb2'; + +/// See also [Todos]. +@ProviderFor(Todos) +final todosProvider = AutoDisposeNotifierProvider>.internal( + Todos.new, + name: r'todosProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$todosHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$Todos = AutoDisposeNotifier>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/todos_consumer.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/todos_consumer.dart index 96801bb7c..9b93e9185 100644 --- a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/todos_consumer.dart +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/todos_consumer.dart @@ -3,16 +3,16 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'completed_todos.dart'; +import 'completed_todos/completed_todos.dart'; Widget build() { return /* SNIPPET START */ - Consumer(builder: (context, ref, child) { - final completedTodos = ref.watch(completedTodosProvider); - // TODO mostrare i todo usando ListView/GridView/.../* SKIP */ - return Container(); - /* SKIP END */ - }); +Consumer(builder: (context, ref, child) { + final completedTodos = ref.watch(completedTodosProvider); + // TODO show the todos using a ListView/GridView/.../* SKIP */ + return Container(); + /* SKIP END */ +}); /* SNIPPET END */ } diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/unoptimized_previous_button/index.tsx b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/unoptimized_previous_button/index.tsx new file mode 100644 index 000000000..d345d4f5d --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/unoptimized_previous_button/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./unoptimized_previous_button.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/unoptimized_previous_button.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/unoptimized_previous_button/raw.dart similarity index 76% rename from website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/unoptimized_previous_button.dart rename to website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/unoptimized_previous_button/raw.dart index f8a4596df..828d382d5 100644 --- a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/unoptimized_previous_button.dart +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/unoptimized_previous_button/raw.dart @@ -1,4 +1,4 @@ -// Un provider che controlla la pagina corrente +// A provider that controls the current page import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -11,8 +11,8 @@ class PreviousButton extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - // se non è la prima pagina, il pulsante "previous" è attivo - final canGoToPreviousPage = ref.watch(pageIndexProvider) == 0; + // if not on first page, the previous button is active + final canGoToPreviousPage = ref.watch(pageIndexProvider) != 0; void goToPreviousPage() { ref.read(pageIndexProvider.notifier).update((state) => state - 1); diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/unoptimized_previous_button/unoptimized_previous_button.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/unoptimized_previous_button/unoptimized_previous_button.dart new file mode 100644 index 000000000..f77de56c3 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/unoptimized_previous_button/unoptimized_previous_button.dart @@ -0,0 +1,39 @@ +// A provider that controls the current page +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'unoptimized_previous_button.g.dart'; + +/* SNIPPET START */ + +@riverpod +class PageIndex extends _$PageIndex { + @override + int build() { + return 0; + } + + void goToPreviousPage() { + state = state - 1; + } +} + +class PreviousButton extends ConsumerWidget { + const PreviousButton({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + // if not on first page, the previous button is active + final canGoToPreviousPage = ref.watch(pageIndexProvider) != 0; + + void goToPreviousPage() { + ref.read(pageIndexProvider.notifier).goToPreviousPage(); + } + + return ElevatedButton( + onPressed: canGoToPreviousPage ? goToPreviousPage : null, + child: const Text('previous'), + ); + } +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/unoptimized_previous_button/unoptimized_previous_button.g.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/unoptimized_previous_button/unoptimized_previous_button.g.dart new file mode 100644 index 000000000..2656fac9b --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/unoptimized_previous_button/unoptimized_previous_button.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'unoptimized_previous_button.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$pageIndexHash() => r'59307ecf23b5b2432833da5ad6b312bf36435d0e'; + +/// See also [PageIndex]. +@ProviderFor(PageIndex) +final pageIndexProvider = AutoDisposeNotifierProvider.internal( + PageIndex.new, + name: r'pageIndexProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$pageIndexHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$PageIndex = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/state_notifier_provider.mdx b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/state_notifier_provider.mdx index 8ea2992b0..ad8b55960 100644 --- a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/state_notifier_provider.mdx +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/state_notifier_provider.mdx @@ -2,30 +2,39 @@ title: StateNotifierProvider --- -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; import CodeBlock from "@theme/CodeBlock"; -import todos from "!!raw-loader!/i18n/it/docusaurus-plugin-content-docs/current/providers/state_notifier_provider/todos.dart"; -import todosConsumer from "!!raw-loader!/i18n/it/docusaurus-plugin-content-docs/current/providers/state_notifier_provider/todos_consumer.dart"; +import todos from "!!raw-loader!/docs/providers/state_notifier_provider/todos.dart"; +import todosConsumer from "!!raw-loader!/docs/providers/state_notifier_provider/todos_consumer.dart"; import { trimSnippet } from "../../../../../src/components/CodeSnippet"; -`StateNotifierProvider` è un provider usato per ascoltare ed esporre uno [StateNotifier] (dal package [state_notifier], che Riverpod ri-esporta). -`StateNotifierProvider` unito con [StateNotifier] è la soluzione consigliata da Riverpod per gestire lo stato in reazione all'interazione dell'utente. +:::caution +The content of this page may be outdated. +It will be updated in the future, but for now you may want to refer to the content +in the top of the sidebar instead (introduction/essentials/case-studies/...) +::: -Viene usato in genere per: +`StateNotifierProvider` is a provider that is used to listen to and expose a +[StateNotifier] (from the package [state_notifier], which Riverpod re-exports). -- esporre uno stato **immutabile** che può cambiare nel tempo dopo aver reagito ad eventi personalizzabili. -- centralizzare la logica per modificare lo stato (aka "business logic") in un singolo posto, - migliorando la mantenibilità nel tempo. +It is typically used for: -Come esempio d'uso, potremmo usare `StateNotifierProvider` per implementare una todo-list (lista di todo). -Fare ciò ci permette di esporre dei metodi come `addTodo` per lasciare che l'UI modifichi -la todo-list in base alle interazioni dell'utente: +- exposing an **immutable** state which can change over time after reacting to + custom events. +- centralizing the logic for modifying some state (aka "business logic") in a + single place, improving maintainability over time. + +:::info +Prefer using [NotifierProvider] instead. +::: + +As a usage example, we could use `StateNotifierProvider` to implement a todo-list. +Doing so would allow us to expose methods such as `addTodo` to let the UI +modify the list of todos on user interactions: {trimSnippet(todos)} -Ora che abbiamo definito uno `StateNotifierProvider`, -possiamo usarlo per interagire con la todo-list nella nostra interfaccia grafica: +Now that we have defined a `StateNotifierProvider`, we can use it to interact +with the list of todos in our UI: {trimSnippet(todosConsumer)} @@ -33,3 +42,4 @@ possiamo usarlo per interagire con la todo-list nella nostra interfaccia grafica [statenotifier]: https://pub.dev/documentation/state_notifier/latest/state_notifier/StateNotifier-class.html [provider]: ./provider [futureprovider]: ./future_provider +[notifierprovider]: ./notifier_provider diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/state_notifier_provider/todos.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/state_notifier_provider/todos.dart index 997d9d5f5..1318bbcbf 100644 --- a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/state_notifier_provider/todos.dart +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/state_notifier_provider/todos.dart @@ -3,21 +3,19 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; /* SNIPPET START */ -// Lo stato del nostro StateNotifier dovrebbe essere immutabile. -// Potremmo usare anche packages come Freezed per aiutarci con l'implementazione. - +// The state of our StateNotifier should be immutable. +// We could also use packages like Freezed to help with the implementation. @immutable class Todo { const Todo({required this.id, required this.description, required this.completed}); - // Tutte le proprietà dovrebbero essere `final` nella nostra classe. + // All properties should be `final` on our class. final String id; final String description; final bool completed; - // Dato che Todo è immutabile, implementiamo un metodo che ci permette di - // clonare l'oggetto Todo con un contenuto leggermente diverso. - + // Since Todo is immutable, we implement a method that allows cloning the + // Todo with slightly different content. Todo copyWith({String? id, String? description, bool? completed}) { return Todo( id: id ?? this.id, @@ -27,60 +25,54 @@ class Todo { } } -// La classe StateNotifier sarà passata al nostro StateNotifierProvider. -// Questa classe non dovrebbe esporre lo stato al di fuori della sua proprietà "state" -// il che significa nessuna proprietà o getter pubblico! - -// I metodi pubblici di questa classe sono quelli che consentiranno alla UI di modificare lo stato. - +// The StateNotifier class that will be passed to our StateNotifierProvider. +// This class should not expose state outside of its "state" property, which means +// no public getters/properties! +// The public methods on this class will be what allow the UI to modify the state. class TodosNotifier extends StateNotifier> { - // Inizializzamo la lista dei todo con una lista vuota - - TodosNotifier() : super([]); + // We initialize the list of todos to an empty list + TodosNotifier(): super([]); - // Consentiamo alla UI di aggiungere i todo + // Let's allow the UI to add todos. void addTodo(Todo todo) { - // Poichè il nostro stato è immutabile, non siamo autorizzati a fare `state.add(todo)`. - // Dovremmo invece creare una nuova lista di todo contenente - // gli elementi precedenti e il nuovo - - // Usare lo spread operator di Dart qui è d'aiuto! - + // Since our state is immutable, we are not allowed to do `state.add(todo)`. + // Instead, we should create a new list of todos which contains the previous + // items and the new one. + // Using Dart's spread operator here is helpful! state = [...state, todo]; - // Non c'è bisogno di chiamare "notifiyListeners" o qualcosa di simile. - // Chiamare "state =" ricostruirà automaticamente la UI quando necessario. + // No need to call "notifyListeners" or anything similar. Calling "state =" + // will automatically rebuild the UI when necessary. } - // Consentiamo di rimuovere i todo + // Let's allow removing todos void removeTodo(String todoId) { - // Di nuovo, il nostro stato è immutabile. Quindi facciamo una nuova lista - // invece di modificare la lista esistente. - + // Again, our state is immutable. So we're making a new list instead of + // changing the existing list. state = [ for (final todo in state) if (todo.id != todoId) todo, ]; } - // Contrassegniamo il todo come completato + // Let's mark a todo as completed void toggle(String todoId) { state = [ for (final todo in state) - // contrassegniamo solo il todo corrispondente come completato + // we're marking only the matching todo as completed if (todo.id == todoId) - // Usiamo il metodo `copyWith` implementato prima per aiutarci nel - // modificare lo stato - + // Once more, since our state is immutable, we need to make a copy + // of the todo. We're using our `copyWith` method implemented before + // to help with that. todo.copyWith(completed: !todo.completed) else - // gli altri todo non sono modificati + // other todos are not modified todo, ]; } } -// Infine, usiamo StateNotifierProvider per consentire all'UI di interagire con -// la classe TodosNotifier +// Finally, we are using StateNotifierProvider to allow the UI to interact with +// our TodosNotifier class. final todosProvider = StateNotifierProvider>((ref) { return TodosNotifier(); -}); +}); \ No newline at end of file diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/state_notifier_provider/todos_consumer.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/state_notifier_provider/todos_consumer.dart index 10628cc8b..8722a542f 100644 --- a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/state_notifier_provider/todos_consumer.dart +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/state_notifier_provider/todos_consumer.dart @@ -12,11 +12,10 @@ class TodoListView extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - // ricostruisce il widget quando la todo-list cambia - + // rebuild the widget when the todo list changes List todos = ref.watch(todosProvider); - // Renderizziamo i todo in una list view scrollabile + // Let's render the todos in a scrollable list view return ListView( children: [ for (final todo in todos) @@ -29,4 +28,4 @@ class TodoListView extends ConsumerWidget { ], ); } -} +} \ No newline at end of file diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/state_provider.mdx b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/state_provider.mdx index 3522af5c1..bb65359db 100644 --- a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/state_provider.mdx +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/state_provider.mdx @@ -2,125 +2,130 @@ title: StateProvider --- -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; import CodeBlock from "@theme/CodeBlock"; import product from "!!raw-loader!/docs/providers/state_provider/product.dart"; import productListView from "!!raw-loader!/docs/providers/state_provider/product_list_view.dart"; -import dropdown from "!!raw-loader!/i18n/it/docusaurus-plugin-content-docs/current/providers/state_provider/dropdown.dart"; -import sortProvider from "!!raw-loader!/i18n/it/docusaurus-plugin-content-docs/current/providers/state_provider/sort_provider.dart"; -import connectedDropdown from "!!raw-loader!/i18n/it/docusaurus-plugin-content-docs/current/providers/state_provider/connected_dropdown.dart"; +import dropdown from "!!raw-loader!/docs/providers/state_provider/dropdown.dart"; +import sortProvider from "!!raw-loader!/docs/providers/state_provider/sort_provider.dart"; +import connectedDropdown from "!!raw-loader!/docs/providers/state_provider/connected_dropdown.dart"; import sortedProductProvider from "!!raw-loader!/docs/providers/state_provider/sorted_product_provider.dart"; -import updateReadTwice from "!!raw-loader!/i18n/it/docusaurus-plugin-content-docs/current/providers/state_provider/update_read_twice.dart"; +import updateReadTwice from "!!raw-loader!/docs/providers/state_provider/update_read_twice.dart"; import updateReadOnce from "!!raw-loader!/docs/providers/state_provider/update_read_once.dart"; import { trimSnippet } from "../../../../../src/components/CodeSnippet"; -`StateProvider` è un provider che espone un modo per modificare il suo stato. -É una semplificazione di [StateNotifierProvider] e progettato per evitare di -dover scrivere una classe [StateNotifier] per casi d'uso molto semplici. +:::caution +The content of this page may be outdated. +It will be updated in the future, but for now you may want to refer to the content +in the top of the sidebar instead (introduction/essentials/case-studies/...) +::: -`StateProvider` esiste principalmente per consentire la modifica di variabili **semplici** -da parte dell'interfaccia utente. -Lo stato di uno `StateProvider` è generalmente: +`StateProvider` is a provider that exposes a way to modify its state. +It is a simplification of [NotifierProvider], designed to avoid +having to write a [Notifier] class for very simple use-cases. -- un enum, come può essere un tipo di filtro -- una String, tipicamente il contenuto grezzo di un campo di testo -- un booleano, per le checkbox -- un numero, per impaginazione e campi d'età +`StateProvider` exists primarily to allow the modification of +**simple** variables by the User Interface. +The state of a `StateProvider` is typically one of: -Non dovresti usare `StateProvider` se: +- an enum, such as a filter type +- a String, typically the raw content of a text field +- a boolean, for checkboxes +- a number, for pagination or age form fields -- il tuo stato necessita di logica di validazione -- il tuo stato è un oggetto complesso (come una classe personalizzata, una lista/map ecc) -- la logica per modificare il tuo stato è più avanzata di un semplice `count++`. +You should not use `StateProvider` if: -Per casi più avanzati, considera usare [StateNotifierProvider] e creare una classe [StateNotifier]. -Anche se il codice boilerplate inizialmente sarà più grande, avere una classe [StateNotifier] -personalizzata è fondamentale per la mantenibilità a lungo termine del tuo progetto - -poichè centralizza la logica del tuo stato in un'unica posizione. +- your state needs validation logic +- your state is a complex object (such as a custom class, a list/map, ...) +- the logic for modifying your state is more advanced than a simple `count++`. -## Esempio d'uso: cambiare il tipo di filtro usando una dropdown +For more advanced cases, consider using [NotifierProvider] instead and +create a [Notifier] class. +While the initial boilerplate will be a bit larger, having a custom +[Notifier] class is critical for the long-term maintainability of your +project – as it centralizes the business logic of your state in a single place. -Un caso d'uso reale di `StateProvider` sarebbe quello di gestire lo stato di -semplici componenti di form come dropdowns/campi di testo/checkboxes. +## Usage example: Changing the filter type using a dropdown -In particolare, vedremo come usare `StateProvider` per implementare una dropdown -che permetta di cambiare come una lista di prodotti è ordinata. +A real-world use-case of `StateProvider` would be to manage the state of +simple form components like dropdowns/text fields/checkboxes. +In particular, we will see how to use `StateProvider` to implement a dropdown +that allows changing how a list of products is sorted. -Per semplificare le cose, la lista dei prodotti che otterremo sarà costruita direttamente -nell'applicazione e sarà come di seguito: +For the sake of simplicity, the list of products that we will obtain +will be built directly in the application and will be as follows: {trimSnippet(product)} -In un'applicazione reale, questa lista sarebbe stata generalmente ottenuta -usando [FutureProvider] facendo una richiesta di rete. +In a real-world application, this list would typically be obtained using +[FutureProvider] by making a network request. -L'interfaccia utente può mostrare quindi la lista dei prodotti scrivendo: +The User Interface could then show the list of products by doing: {trimSnippet(productListView)} -Ora che abbiamo finito con la base, possiamo aggiungere una dropdown, la quale ci permetterà di filtrare i nostri prodotti sia per prezzo che per nome. -Per questo, useremo [DropDownButton](https://api.flutter.dev/flutter/material/DropdownButton-class.html). +Now that we're done with the base, we can add a dropdown, which will +allow filtering our products either by price or by name. +For that, we will use [DropDownButton](https://api.flutter.dev/flutter/material/DropdownButton-class.html). {trimSnippet(dropdown)} -Ora che abbiamo una dropdown, creiamo uno `StateProvider` e -sincronizziamo lo stato della dropdown con il nostro provider. +Now that we have a dropdown, let's create a `StateProvider` and +synchronize the state of the dropdown with our provider. -Per prima cosa, creiamo il provider con `StateProvider`: +First, let's create the `StateProvider`: {trimSnippet(sortProvider)} -Successivamente possiamo connettere questo provider con la nostra dropdown -scrivendo: +Then, we can connect this provider with our dropdown by doing: {trimSnippet(connectedDropdown)} -Con questo, ora dovremmo essere in grado di cambiare il tipo di ordinamento. -Tuttavia, non ha ancora alcun impatto sulla lista dei prodotti! É ora della parte finale: -aggiornare `productsProvider` per ordinare la lista dei prodotti. +With this, we should now be able to change the sort type. +It has no impact on the list of products yet though! It's now time for the +final part: Updating our `productsProvider` to sort the list of products. -Una compenente chiave di tale implementazione è di usare [ref.watch], per far sì che `productsProvider` -ottenga il tipo di ordinamento e ricalcoli la lista dei prodotti ogni volta -che il tipo di ordinamento cambia. +A key component of implementing this is to use [ref.watch], to have +our `productsProvider` obtain the sort type and recompute the list of +products whenever the sort type changes. -L'implementazione sarà: +The implementation would be: {trimSnippet(sortedProductProvider)} -Questo è quanto! Questa modifica basta per fare in modo che l'interfaccia utente -ri-renderizzi automaticamente la lista dei prodotti quando il tipo di ordinamento cambia. +That's all! This change is enough for the User Interface to automatically +re-render the list of products when the sort type changes. -Di seguito l'esempio completo su Dartpad: +Here is the complete example on Dartpad: -## Come aggiornare lo stato basandosi sul valore precedente senza leggere il provider due volte +## How to update the state based on the previous value without reading the provider twice -Delle volte, potresti voler aggiornare lo stato di uno `StateProvider` basandosi sul valore precedente. -Naturalmente, potresti finire per scrivere: +Sometimes, you want to update the state of a `StateProvider` based on the previous value. +Naturally, you may end-up writing: {trimSnippet(updateReadTwice)} -Anche se non c'è niente di particolarmente sbagliato in questo codice, -la sintassi è un po' scomoda. +While there's nothing particularly wrong with this snippet, the syntax is a bit inconvenient. -Per migliorare la sintassi, possiamo usare la funzione `update`. Tale funzione -prenderà una funzione callback che riceverà lo stato corrente e dovrebbe -restituire il nuovo stato. -Possiamo usarla per riscrivere il nostro codice precedente in: +To make the syntax a bit better, we can use the `update` function. +This function will take a callback that will receive the current state and is expected +to return the new state. +We can use it to refactor our previous code to: {trimSnippet(updateReadOnce)} -Questa modifica ottiene lo stesso effetto ma migliora leggermente la sintassi. +This change achieves the same effect while making the syntax a bit better. -[ref.watch]: ../concepts/reading#usare-ref.watch-per-osservare-un-provider -[ref.read]: ../concepts/reading#usare-refread-per-ottenere-lo-stato-di-un-provider +[ref.watch]: ../concepts/reading#using-refwatch-to-observe-a-provider +[ref.read]: ../concepts/reading#using-refread-to-obtain-the-state-of-a-provider-once [statenotifierprovider]: ./state_notifier_provider +[notifierprovider]: ./notifier_provider [futureprovider]: ./future_provider +[notifier]: https://pub.dev/documentation/riverpod/latest/riverpod/Notifier-class.html [statenotifier]: https://pub.dev/documentation/state_notifier/latest/state_notifier/StateNotifier-class.html [provider]: ./provider [asyncvalue]: https://pub.dev/documentation/riverpod/latest/riverpod/AsyncValue-class.html diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/state_provider/connected_dropdown.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/state_provider/connected_dropdown.dart index 163008a66..189ff3c6a 100644 --- a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/state_provider/connected_dropdown.dart +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/state_provider/connected_dropdown.dart @@ -9,16 +9,17 @@ import 'sort_provider.dart'; Widget build(BuildContext context, WidgetRef ref) { return AppBar(actions: [ /* SNIPPET START */ - DropdownButton( - // Quando il tipo di ordinamento cambia, ricostruirà la dropdown - // per aggiornare l'icona mostrata. - value: ref.watch(productSortTypeProvider), - // Quando l'utente interagisce con la dropdown aggiorniamo lo stato del provider. - onChanged: (value) => ref.read(productSortTypeProvider.notifier).state = value!, - items: [ - // ... - ], - ), +DropdownButton( + // When the sort type changes, this will rebuild the dropdown + // to update the icon shown. + value: ref.watch(productSortTypeProvider), + // When the user interacts with the dropdown, we update the provider state. + onChanged: (value) => + ref.read(productSortTypeProvider.notifier).state = value!, + items: [ + // ... + ], +), /* SNIPPET END */ ]); } diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/state_provider/dropdown.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/state_provider/dropdown.dart index 13c37b5a2..18cc6b211 100644 --- a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/state_provider/dropdown.dart +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/state_provider/dropdown.dart @@ -7,7 +7,7 @@ import 'full.dart'; /* SNIPPET START */ -// Un enum rappresentante il tipo di filtro +// An enum representing the filter type enum ProductSortType { name, price, diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/state_provider/full.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/state_provider/full.dart index 966f4dec3..4c598055f 100644 --- a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/state_provider/full.dart +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/state_provider/full.dart @@ -41,7 +41,7 @@ enum ProductSortType { } final productSortTypeProvider = StateProvider( - // Restituiamo il tipo di ordinamento di default, in questo caso 'name'. + // We return the default sort type, here name. (ref) => ProductSortType.name, ); @@ -66,12 +66,12 @@ class MyHomePage extends ConsumerWidget { title: const Text('Products'), actions: [ DropdownButton( - // Quando il tipo di ordinamento cambia, ricostruirà la dropdown - // per aggiornare l'icona mostrata. - + // When the sort type changes, this will rebuild the dropdown + // to update the icon shown. value: ref.watch(productSortTypeProvider), - // Quando l'utente interagisce con la dropdown aggiorniamo lo stato del provider. - onChanged: (value) => ref.read(productSortTypeProvider.notifier).state = value!, + // When the user interacts with the dropdown, we update the provider state. + onChanged: (value) => + ref.read(productSortTypeProvider.notifier).state = value!, items: const [ DropdownMenuItem( value: ProductSortType.name, diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/state_provider/product.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/state_provider/product.dart new file mode 100644 index 000000000..078def200 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/state_provider/product.dart @@ -0,0 +1,20 @@ +import 'package:riverpod/riverpod.dart'; + +/* SNIPPET START */ + +class Product { + Product({required this.name, required this.price}); + + final String name; + final double price; +} + +final _products = [ + Product(name: 'iPhone', price: 999), + Product(name: 'cookie', price: 2), + Product(name: 'ps5', price: 500), +]; + +final productsProvider = Provider>((ref) { + return _products; +}); diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/state_provider/product_list_view.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/state_provider/product_list_view.dart new file mode 100644 index 000000000..17765aea8 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/state_provider/product_list_view.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'product.dart'; + +/* SNIPPET START */ + +Widget build(BuildContext context, WidgetRef ref) { + final products = ref.watch(productsProvider); + return Scaffold( + body: ListView.builder( + itemCount: products.length, + itemBuilder: (context, index) { + final product = products[index]; + return ListTile( + title: Text(product.name), + subtitle: Text('${product.price} \$'), + ); + }, + ), + ); +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/state_provider/sort_provider.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/state_provider/sort_provider.dart index 259a89b1c..60a5f60c1 100644 --- a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/state_provider/sort_provider.dart +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/state_provider/sort_provider.dart @@ -5,6 +5,6 @@ import 'dropdown.dart'; /* SNIPPET START */ final productSortTypeProvider = StateProvider( - // Restituiamo il tipo di ordinamento di default, in questo caso 'name'. + // We return the default sort type, here name. (ref) => ProductSortType.name, ); diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/state_provider/sorted_product_provider.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/state_provider/sorted_product_provider.dart new file mode 100644 index 000000000..6725d0282 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/state_provider/sorted_product_provider.dart @@ -0,0 +1,24 @@ +import 'package:collection/collection.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'dropdown.dart'; +import 'product.dart'; +import 'sort_provider.dart'; + +final _products = [ + Product(name: 'iPhone', price: 999), + Product(name: 'cookie', price: 2), + Product(name: 'ps5', price: 500), +]; + +/* SNIPPET START */ + +final productsProvider = Provider>((ref) { + final sortType = ref.watch(productSortTypeProvider); + switch (sortType) { + case ProductSortType.name: + return _products.sorted((a, b) => a.name.compareTo(b.name)); + case ProductSortType.price: + return _products.sorted((a, b) => a.price.compareTo(b.price)); + } +}); \ No newline at end of file diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/state_provider/update_read_once.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/state_provider/update_read_once.dart new file mode 100644 index 000000000..825df6d0f --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/state_provider/update_read_once.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +/* SNIPPET START */ + +final counterProvider = StateProvider((ref) => 0); + +class HomeView extends ConsumerWidget { + const HomeView({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Scaffold( + floatingActionButton: FloatingActionButton( + onPressed: () { + /* highlight-start */ + ref.read(counterProvider.notifier).update((state) => state + 1); + /* highlight-end */ + }, + ), + ); + } +} \ No newline at end of file diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/state_provider/update_read_twice.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/state_provider/update_read_twice.dart index e3896c225..c029e0730 100644 --- a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/state_provider/update_read_twice.dart +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/state_provider/update_read_twice.dart @@ -15,11 +15,9 @@ class HomeView extends ConsumerWidget { onPressed: () { // We're updating the state from the previous value, we ended-up reading // the provider twice! - // Stiamo aggiornando lo stato dal valore precedente, siamo finiti per - // leggere il provider due volte! ref.read(counterProvider.notifier).state = ref.read(counterProvider.notifier).state + 1; }, ), ); } -} +} \ No newline at end of file diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/stream_provider.mdx b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/stream_provider.mdx index 8c39a2d30..bd40cfc5f 100644 --- a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/stream_provider.mdx +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/stream_provider.mdx @@ -2,35 +2,52 @@ title: StreamProvider --- -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; import CodeBlock from "@theme/CodeBlock"; -import { trimSnippet } from "../../../../../src/components/CodeSnippet"; +import { trimSnippet,AutoSnippet } from "../../../../../src/components/CodeSnippet"; +import streamProvider from "./stream_provider/live_stream_chat_provider"; +import streamConsumer from "!!raw-loader!/docs/providers/stream_provider/live_stream_chat_consumer.dart"; -`StreamProvider` è simile a [FutureProvider] ma finalizzato per gli [Stream]s -invece che per i [Future]s. +:::caution +The content of this page may be outdated. +It will be updated in the future, but for now you may want to refer to the content +in the top of the sidebar instead (introduction/essentials/case-studies/...) +::: -`StreamProvider` è solitamente usato per: +`StreamProvider` is similar to [FutureProvider] but for [Stream]s instead of +[Future]s. -- stare in ascolto di Firebase o web-sockets -- ricostruire un altro provider ogni pochi secondi +`StreamProvider` is usually used for: -Dato che gli [Stream]s espongono naturalmente un modo per stare in ascolto degli aggiornamenti, -alcuni potrebbero pensare che l'utilizzo di `StreamProvider` abbia poco valore. -In particolare, potresti credere che [StreamBuilder] di Flutter funzioni altrettanto bene per ascoltare uno [Stream], ma questo è un errore +- listening to Firebase or web-sockets +- rebuilding another provider every few seconds -Usare `StreamProvider` al posto di [StreamBuilder] ha numerosi vantaggi: +Since [Stream]s naturally expose a way for listening to updates, some may think +that using `StreamProvider` has a low value. In particular, you may believe that +Flutter's [StreamBuilder] would work just as well for listening to a [Stream], but +this is a mistake. -- permette ad altri provider di stare in ascolto dello stream usando [ref.watch]. -- assicura una corretta gestione dei casi di caricamento/errore, grazie ad [AsyncValue]. -- rimuove la necessità di differenziare gli streams di trasmissione (broadcast streams) - con gli stream normali. -- memorizza l'ultimo valore emesso dallo stream, assicurandosi che se un - listener viene aggiunto dopo l'emissione di un evento, il listener avrà comunque - accesso immediato all'evento più recente/aggiornato. -- facilita la finzione dello stream durante i test sovrascrivendo lo `StreamProvider`. +Using `StreamProvider` over [StreamBuilder] has numerous benefits: -[ref.watch]: ../concepts/reading#usare-refwatch-per-osservare-un-provider +- it allows other providers to listen to the stream using [ref.watch]. +- it ensures that loading and error cases are properly handled, thanks to [AsyncValue]. +- it removes the need for having to differentiate broadcast streams vs normal streams. +- it caches the latest value emitted by the stream, ensuring that if a + listener is added after an event is emitted, the listener will still have + immediate access to the most up-to-date event. +- it allows easily mocking the stream during tests by overriding the `StreamProvider`. + +## Usage example: live chat using sockets + +`StreamProvider` is used in when we handle stream of asynchronous data +such as Video Streaming, Weather broadcasting Apis or Live chat as follows: + + + +Then, the UI can listen to live streaming chats like so: + +{trimSnippet(streamConsumer)} + +[ref.watch]: ../concepts/reading#using-refwatch-to-observe-a-provider [statenotifierprovider]: ./state_notifier_provider [provider]: ./provider [futureprovider]: ./future_provider @@ -39,4 +56,4 @@ Usare `StreamProvider` al posto di [StreamBuilder] ha numerosi vantaggi: [stream]: https://api.dart.dev/dart-async/Stream-class.html [stream.periodic]: https://api.dart.dev/stable/2.15.1/dart-async/Stream/Stream.periodic.html [family]: ../concepts/modifiers/family -[streambuilder]: https://api.flutter.dev/flutter/widgets/StreamBuilder-class.html +[streambuilder]: https://api.flutter.dev/flutter/widgets/StreamBuilder-class.html \ No newline at end of file diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_consumer.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_consumer.dart new file mode 100644 index 000000000..383def216 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_consumer.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'live_stream_chat_provider.dart'; + +/* SNIPPET START */ +Widget build(BuildContext context, WidgetRef ref) { + final liveChats = ref.watch(chatProvider); + + // Like FutureProvider, it is possible to handle loading/error states using AsyncValue.when + return switch (liveChats) { + // Display all the messages in a scrollable list view. + AsyncData(:final value) => ListView.builder( + // Show messages from bottom to top + reverse: true, + itemCount: value.length, + itemBuilder: (context, index) { + final message = value[index]; + return Text(message); + }, + ), + AsyncError(:final error) => Text(error.toString()), + _ => const CircularProgressIndicator(), + }; +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_provider.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_provider.dart new file mode 100644 index 000000000..d2bb3ad2a --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_provider.dart @@ -0,0 +1,18 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +/* SNIPPET START */ +final chatProvider = StreamProvider>((ref) async* { + // Connect to an API using sockets, and decode the output + final socket = await Socket.connect('my-api', 4242); + ref.onDispose(socket.close); + + var allMessages = const []; + await for (final message in socket.map(utf8.decode)) { + // A new message has been received. Let's add it to the list of all messages. + allMessages = [...allMessages, message]; + yield allMessages; + } +}); diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_provider/codegen.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_provider/codegen.dart new file mode 100644 index 000000000..e2e34878c --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_provider/codegen.dart @@ -0,0 +1,23 @@ +// ignore_for_file: avoid_unused_constructor_parameters + +import 'dart:convert'; +import 'dart:io'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +/* SNIPPET START */ + +@riverpod +Stream> chat(ChatRef ref) async* { + // Connect to an API using sockets, and decode the output + final socket = await Socket.connect('my-api', 4242); + ref.onDispose(socket.close); + + var allMessages = const []; + await for (final message in socket.map(utf8.decode)) { + // A new message has been received. Let's add it to the list of all messages. + allMessages = [...allMessages, message]; + yield allMessages; + } +} diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_provider/codegen.g.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_provider/codegen.g.dart new file mode 100644 index 000000000..213c08a68 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_provider/codegen.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$chatHash() => r'db1302132f90e854fe2f5da9d97d89c9a3c8b858'; + +/// See also [chat]. +@ProviderFor(chat) +final chatProvider = AutoDisposeStreamProvider>.internal( + chat, + name: r'chatProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$chatHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ChatRef = AutoDisposeStreamProviderRef>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_provider/index.tsx b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_provider/index.tsx new file mode 100644 index 000000000..339b89a56 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_provider/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./codegen.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_provider/raw.dart b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_provider/raw.dart new file mode 100644 index 000000000..beb2bcd05 --- /dev/null +++ b/website/i18n/it/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_provider/raw.dart @@ -0,0 +1,18 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +/* SNIPPET START */ +final chatProvider = StreamProvider>((ref) async* { + // Connect to an API using sockets, and decode the output + final socket = await Socket.connect('my-api', 4242); + ref.onDispose(socket.close); + + var allMessages = const []; + await for (final message in socket.map(utf8.decode)) { + // A new message has been received. Let's add it to the list of all messages. + allMessages = [...allMessages, message]; + yield allMessages; + } +}); diff --git a/website/i18n/ko/NOTE.md b/website/i18n/ko/NOTE.md new file mode 100644 index 000000000..c5348e2f5 --- /dev/null +++ b/website/i18n/ko/NOTE.md @@ -0,0 +1,22 @@ +# 한국어 번역작업 (Korean Translation) + +## 작업하기 + +- [Initialize the i18n folder](https://docusaurus.io/docs/i18n/git#initialize-the-i18n-folder) + +```shell +# 처음에 한국어 i18n 생성하기 (docusaurus) +yarn run write-translations --locale ko + +# website/i18n/ko/ 폴더에서 문서수정 +# 한국어 문서 실행하고, 최초실행시 import 폴더 구조가 안맞는 것 맞추기 +yarn run dev --locale ko + +# 빌드 +yarn run build --locale ko +# 빌드된 파일 확인 +yarn run serve + +# 작업후 문서의 생성코드 업데이트 +dart run build_runner watch --delete-conflicting-outputs +``` diff --git a/website/i18n/ko/code.json b/website/i18n/ko/code.json index f161a7b30..aff98ff4c 100644 --- a/website/i18n/ko/code.json +++ b/website/i18n/ko/code.json @@ -1,165 +1,227 @@ { "home.shared_state_title": { - "message": "어디에서나 공유할 상태를 선언하세요." + "message": "어디서나 공유 상태 선언하기" }, "home.shared_state_body": { - "message": "{main}파일과 UI 파일간 이동할 필요가 없습니다.\n{br}\n테스트 가능성을 잃지 않고 공유 상태의 코드를 그것이 속한 곳에 별도의 패키지에 있거나 그것을 필요로 하는 위젯 바로 옆에 배치하십시오.", + "message": "더 이상 {main}과 UI 파일 사이를 오갈 필요가 없습니다.{br}\n공유 상태의 코드를 별도의 패키지에 넣든, 필요한 위젯 바로 옆에 넣든, 테스트 가능성을 잃지 않고 적절한 위치에 배치하세요.", "description": "The homepage input placeholder" }, "home.recompute_title": { - "message": "상태를 다시 계산하거나 필요할 때만 UI를 다시 빌드합니다." + "message": "필요할 때만 상태 재계산/UI 재빌드" }, "home.recompute_body": { - "message": "더 이상 {build} 메서드 내에서 목록을 정렬/필터링하거나 고급 캐시 메커니즘에 의존할 필요가 없습니다. {br} {br}\n{Provider} 및 {families}를 사용하여 목록을 정렬하거나 필요할 때만 HTTP\n요청을 수행하세요." + "message": "더 이상 {build} 메서드 내에서 목록을 정렬/필터링하거나 고급 캐시 메커니즘에 의존할 필요가 없습니다.{br} {br}\n{Provider}와 {families}를 사용하여 목록을 정렬하거나 {truly} 필요한 경우에만 HTTP 요청을 수행하세요." + }, + "home.refactors_title": { + "message": "리팩터로 일상적인 작업을 간소화하세요." + }, + "home.refactors_body": { + "message": "Riverpod은 \"Consumer로 위젯 감싸기\"등 다양한 리팩터(refactors)를 제공합니다..\n{warnings}를 살펴보세요." + }, + "home.refactors_list_link": { + "message": "리팩터링 목록" + }, + "home.lint_title": { + "message": "린트 규칙으로 코드의 유지보수성 유지" + }, + "home.lint_body": { + "message": "Riverpod에 특화된 새로운 린트 규칙이 구현되고 더 많은 규칙이 지속적으로 추가됩니다.\n 이렇게 하면 코드가 최상의 상태를 유지할 수 있습니다. {warnings}를 살펴보세요." + }, + "home.lint_rules_list_link": { + "message": "린트 규칙 목록" }, "home.safe_read_title": { - "message": "안전하게 Provider 읽기" + "message": "안전하게 provider 읽기" }, "home.safe_read_body": { - "message": "Provider를 읽는 중 더 이상 bad state가 되지 않습니다. 만약\nProvider를 읽기 위한 필요한 코드를 작성하면, 당신은 유효한 값을 얻을 수 있습니다.\n {br} {br}\n Provider는 비동기적으로 로드된 값에도 적용됩니다. 제공자와는 대조적으로 Riverpod는 로드/오류 사례를 깔끔하게 처리할 수 있습니다." + "message": "provider를 읽었을때 절대로 나쁜 상태(bad state)의 결과가 되지 않습니다. 공급자를 읽는 데 필요한 코드를 작성할 수 있다면 유효한 값(valid value)을 얻을 수 있습니다. {br} {br}이는 비동기적으로 로드된 값에도 적용됩니다. Provider와 달리 Riverpod를 사용하면 로딩/오류 사례를 깔끔하게 처리할 수 있습니다." }, "home.devtool_title": { - "message": "Devtool에서 상태를 관찰하세요." + "message": "개발 도구에서 상태 조사하기" }, "home.devtool_body": { - "message": "Riverpod을 사용하면 Flutter의 devtool 내부에서 상자 밖에서 상태를 확인할 수 있습니다. {br}\n 게다가, 진행 상태를 감시할 수 있습니다." + "message": "Riverpod을 사용하면 Flutter의 개발 도구에서 상태를 바로 확인할 수 있습니다. {br}또한, 완전한 상태 인스펙터(state-inspector)가 개발 중입니다." }, - "homepage.compile_safe_title": { - "message": "안전한 컴파일" + "homepage.declarative_title": { + "message": "선언적 프로그래밍" }, - "homepage.compile_safe_body": { - "message": "더 이상 {ProviderNotFound} 예외가 발생하지 않고, 로딩 상태를 처리하는 것을 걱정하지 않아도 됩니다. Riverpod를 사용하면 코드가 컴파일되어 작동합니다." + "homepage.declarative_body": { + "message": "Stateless 위젯과 유사한 방식으로 비즈니스 로직을 작성하세요.{br}필요할 때 네트워크 요청이 자동으로 재계산되도록 하고 로직을 쉽게 reusable/composable/maintainable 하게 만드세요." }, - "homepage.unlimited_provider_title": { - "message": "제한없는 Provider" + "homepage.common_ui_patterns_title": { + "message": "일반적인 UI 패턴을 쉽게 구현" }, - "homepage.unlimited_provider_body": { - "message": "Riverpod는 Provider에서 영감을 얻었지만 동일한 유형의 여러 Provider를 지원하는 것과 같은 주요 문제 중 일부를 해결합니다. 비동기 Provider를 기다리고 있습니다. 어디에서나 Provider를 추가하세요." + "homepage.common_ui_patterns_body": { + "message": "Riverpod을 사용하면,\"당겨서 새로고침\"/ \"타이핑하는대로 검색\"/등 일반적이지만 복잡한 UI 패턴을 몇 줄의 코드만으로 처리할 수 있습니다." }, - "homepage.no_flutter_dependency_title": { - "message": "Flutter에 의존하지 않습니다." + "homepage.tooling_ready_title": { + "message": "준비된 도구" }, - "homepage.no_flutter_dependency_body": { - "message": "Flutter에 의존하지 않고 Provider를 생성/공유/테스트합니다. 여기에는 {BuildContext} 없이 Provider를 수신할 수 있는 기능이 포함됩니다." + "homepage.tooling_ready_body": { + "message": "Riverpod은 일반적인 실수를 컴파일 에러로 처리하여 컴파일러를 향상시킵니다. 또한 사용자 정의 린트 규칙과 리팩터링 옵션도 제공합니다. 문서 생성을 위한 명령줄도 있습니다." + }, + "homepage.features_title": { + "message": "기능" }, "home.tagline": { - "message": "리엑티브 캐싱, 데이터바인딩 프레임워크" + "message": "리액티브 캐싱 및 데이터 바인딩 프레임워크" }, "home.get_started": { "message": "시작하기" }, "home.create_provider": { - "message": "Provider 생성하기" + "message": "네트워크 요청 만들기" }, "home.consume_provider": { - "message": "Provider 사용하기" + "message": "UI에서 네트워크 요청 수신" + }, + "theme.docs.versions.unreleasedVersionLabel": { + "message": "{siteTitle} {versionLabel}버전에 대한 미공개 문서입니다.", + "description": "The label used to tell the user that he's browsing an unreleased doc version" + }, + "theme.docs.versions.unmaintainedVersionLabel": { + "message": "이 문서는 더 이상 활발하게 유지 관리되지 않는 {siteTitle} {versionLabel}에 대한 문서입니다.", + "description": "The label used to tell the user that he's browsing an unmaintained doc version" + }, + "theme.docs.versions.latestVersionSuggestionLabel": { + "message": "최신 문서는 {latestVersionLink} ({versionLabel})를 참고하세요.", + "description": "The label used to tell the user to check the latest version" + }, + "theme.docs.versions.latestVersionLinkLabel": { + "message": "최신 버전", + "description": "The label used for the latest version suggestion link label" + }, + "custom.outdatedTranslations": { + "message": "이 페이지의 콘텐츠가 오래되었을 수 있습니다. 대신 {englishLink}를 확인해 보세요.", + "description": "The label used inside the outdated translation banner" + }, + "custom.outdatedTranslationLink": { + "message": "english version", + "description": "The link that redirects to the equivalent English doc" + }, + "Code generation": { + "message": "코드생성(code generation)" + }, + "About code generation": { + "message": "코드생성(code generation)에 대한 정보" + }, + "About hooks": { + "message": "훅(Hook)에 대한 정보" }, "theme.ErrorPageContent.title": { - "message": "이 문서가 깨졌습니다.", + "message": "이 페이지가 충돌했습니다.", "description": "The title of the fallback page when the page crashed" }, - "theme.ErrorPageContent.tryAgain": { - "message": "다시 시도해 보세요", - "description": "The label of the button to try again when the page crashed" - }, "theme.NotFound.title": { - "message": "페이지를 찾을 수 없습니다.", + "message": "페이지를 찾을 수 없음", "description": "The title of the 404 page" }, "theme.NotFound.p1": { - "message": "원하는 페이지를 찾을 수 없습니다.", + "message": "원하는 항목을 찾을 수 없습니다.", "description": "The first paragraph of the 404 page" }, "theme.NotFound.p2": { - "message": "사이트 관리자에게 링크가 깨진 것을 알려주세요.", + "message": "원래 URL로 연결한 사이트의 소유자에게 연락하여 링크가 끊어졌다는 사실을 알려주세요.", "description": "The 2nd paragraph of the 404 page" }, - "theme.AnnouncementBar.closeButtonAriaLabel": { - "message": "닫기", - "description": "The ARIA label for close button of announcement bar" + "theme.admonition.note": { + "message": "노트", + "description": "The default label used for the Note admonition (:::note)" + }, + "theme.admonition.tip": { + "message": "팁", + "description": "The default label used for the Tip admonition (:::tip)" + }, + "theme.admonition.danger": { + "message": "위험", + "description": "The default label used for the Danger admonition (:::danger)" + }, + "theme.admonition.info": { + "message": "정보", + "description": "The default label used for the Info admonition (:::info)" + }, + "theme.admonition.caution": { + "message": "주의", + "description": "The default label used for the Caution admonition (:::caution)" }, "theme.BackToTopButton.buttonAriaLabel": { - "message": "맨 위로 스크롤하기", + "message": "맨 위로 스크롤", "description": "The ARIA label for the back to top button" }, "theme.blog.archive.title": { - "message": "게시물 목록", + "message": "아카이브", "description": "The page & hero title of the blog archive page" }, "theme.blog.archive.description": { - "message": "게시물 목록", + "message": "아카이브", "description": "The page & hero description of the blog archive page" }, "theme.blog.paginator.navAriaLabel": { - "message": "블로그 게시물 목록 탐색", + "message": "블로그 목록 페이지 탐색", "description": "The ARIA label for the blog pagination" }, "theme.blog.paginator.newerEntries": { - "message": "이전 페이지", + "message": "신규 항목", "description": "The label used to navigate to the newer blog posts page (previous page)" }, "theme.blog.paginator.olderEntries": { - "message": "다음 페이지", + "message": "이전 항목", "description": "The label used to navigate to the older blog posts page (next page)" }, - "theme.blog.post.readingTime.plurals": { - "message": "약 {readingTime}분", - "description": "Pluralized label for \"{readingTime} min read\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)" - }, - "theme.blog.post.readMore": { - "message": "자세히 보기", - "description": "The label used in blog post item excerpts to link to full blog posts" - }, "theme.blog.post.paginator.navAriaLabel": { - "message": "블로그 게시물 탐색", + "message": "블로그 포스트 페이지 탐색", "description": "The ARIA label for the blog posts pagination" }, "theme.blog.post.paginator.newerPost": { - "message": "이전 게시물", + "message": "신규 포스트", "description": "The blog post button label to navigate to the newer/previous post" }, "theme.blog.post.paginator.olderPost": { - "message": "다음 게시물", + "message": "이전 포스트", "description": "The blog post button label to navigate to the older/next post" }, - "theme.blog.sidebar.navAriaLabel": { - "message": "최근 블로그 문서 둘러보기", - "description": "The ARIA label for recent posts in the blog sidebar" - }, "theme.blog.post.plurals": { - "message": "{count}개 게시물", + "message": "포스트 1개|{count}개 포스트", "description": "Pluralized label for \"{count} posts\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)" }, "theme.blog.tagTitle": { - "message": "\"{tagName}\" 태그로 연결된 {nPosts}개의 게시물이 있습니다.", + "message": "\"{tagName}\"으로 태그된 {nPosts}", "description": "The title of the page for a blog tag" }, "theme.tags.tagsPageLink": { "message": "모든 태그 보기", "description": "The label of the link targeting the tag list page" }, - "theme.CodeBlock.copyButtonAriaLabel": { - "message": "클립보드에 코드 복사", - "description": "The ARIA label for copy code blocks button" + "theme.colorToggle.ariaLabel": { + "message": "어두운 모드와 밝은 모드 간 전환 (현재 {mode})", + "description": "The ARIA label for the navbar color mode toggle" }, - "theme.CodeBlock.copied": { - "message": "복사했습니다", - "description": "The copied button label on code blocks" + "theme.colorToggle.ariaLabel.mode.dark": { + "message": "다크 모드", + "description": "The name for the dark color mode" }, - "theme.CodeBlock.copy": { - "message": "복사", - "description": "The copy button label on code blocks" + "theme.colorToggle.ariaLabel.mode.light": { + "message": "라이트 모드", + "description": "The name for the light color mode" }, - "theme.docs.sidebar.expandButtonTitle": { - "message": "사이드바 열기", - "description": "The ARIA label and title attribute for expand button of doc sidebar" + "theme.docs.breadcrumbs.navAriaLabel": { + "message": "경로", + "description": "The ARIA label for the breadcrumbs" }, - "theme.docs.sidebar.expandButtonAriaLabel": { - "message": "사이드바 열기", - "description": "The ARIA label and title attribute for expand button of doc sidebar" + "theme.docs.DocCard.categoryDescription": { + "message": "{count}개 아이템", + "description": "The default description for a category card in the generated index about how many items this category includes" + }, + "theme.docs.tagDocListPageTitle.nDocsTagged": { + "message": "태그가 지정된 문서 1개|태그가 지정된 문서 {count}개", + "description": "Pluralized label for \"{count} docs tagged\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)" + }, + "theme.docs.tagDocListPageTitle": { + "message": "{nDocsTagged} with \"{tagName}\"", + "description": "The title of the page for a docs tag" }, "theme.docs.paginator.navAriaLabel": { - "message": "문서 탐색", + "message": "문서 페이지", "description": "The ARIA label for the docs pagination" }, "theme.docs.paginator.previous": { @@ -170,96 +232,142 @@ "message": "다음", "description": "The label used to navigate to the next doc" }, - "theme.docs.sidebar.collapseButtonTitle": { - "message": "사이드바 숨기기", - "description": "The title attribute for collapse button of doc sidebar" - }, - "theme.docs.sidebar.collapseButtonAriaLabel": { - "message": "사이드바 숨기기", - "description": "The title attribute for collapse button of doc sidebar" - }, - "theme.DocSidebarItem.toggleCollapsedCategoryAriaLabel": { - "message": "접을 수 있는 사이드바 분류 '{label}' 접기(펼치기)", - "description": "The ARIA label to toggle the collapsible sidebar category" - }, - "theme.docs.tagDocListPageTitle.nDocsTagged": { - "message": "{count} 개 문서가", - "description": "Pluralized label for \"{count} docs tagged\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)" - }, - "theme.docs.tagDocListPageTitle": { - "message": "{nDocsTagged} \"{tagName}\" 태그에 분류되었습니다", - "description": "The title of the page for a docs tag" - }, - "theme.docs.versions.unreleasedVersionLabel": { - "message": "{siteTitle} {versionLabel} 문서는 아직 정식 공개되지 않았습니다.", - "description": "The label used to tell the user that he's browsing an unreleased doc version" - }, - "theme.docs.versions.unmaintainedVersionLabel": { - "message": "{siteTitle} {versionLabel} 문서는 더 이상 업데이트되지 않습니다.", - "description": "The label used to tell the user that he's browsing an unmaintained doc version" - }, - "theme.docs.versions.latestVersionSuggestionLabel": { - "message": "최신 문서는 {latestVersionLink} ({versionLabel})을 확인하세요.", - "description": "The label used to tell the user to check the latest version" - }, - "theme.docs.versions.latestVersionLinkLabel": { - "message": "최신 버전", - "description": "The label used for the latest version suggestion link label" + "theme.docs.versionBadge.label": { + "message": "버전: {versionLabel}" }, "theme.common.editThisPage": { - "message": "페이지 편집", + "message": "이 페이지 수정하기", "description": "The link label to edit the current page" }, "theme.common.headingLinkTitle": { - "message": "제목으로 바로 가기", + "message": "{heading}으로 직접 링크", "description": "Title for link to heading" }, "theme.lastUpdated.atDate": { - "message": "갱신일: {date}", + "message": " on {date}", "description": "The words used to describe on which date a page has been last updated" }, "theme.lastUpdated.byUser": { - "message": "갱신자: {user}", + "message": " by {user}", "description": "The words used to describe by who the page has been last updated" }, "theme.lastUpdated.lastUpdatedAtBy": { - "message": "{atDate} {byUser} 마지막으로 업데이트했습니다.", + "message": "최근 업데이트{atDate}{byUser}", "description": "The sentence used to display when a page has been last updated, and by who" }, - "theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel": { - "message": "← 메인 메뉴로 돌아가기", - "description": "The label of the back button to return to main menu, inside the mobile navbar sidebar secondary menu (notably used to display the docs sidebar)" - }, "theme.navbar.mobileVersionsDropdown.label": { "message": "버전", "description": "The label for the navbar versions dropdown on mobile view" }, - "theme.common.skipToMainContent": { - "message": "본문으로 건너뛰기", - "description": "The skip to content label used for accessibility, allowing to rapidly navigate to main content with keyboard tab/enter navigation" - }, "theme.tags.tagsListLabel": { "message": "태그:", "description": "The label alongside a tag list" }, + "theme.AnnouncementBar.closeButtonAriaLabel": { + "message": "닫기", + "description": "The ARIA label for close button of announcement bar" + }, + "theme.blog.sidebar.navAriaLabel": { + "message": "블로그 최근 게시물 탐색", + "description": "The ARIA label for recent posts in the blog sidebar" + }, + "theme.CodeBlock.copied": { + "message": "복사됨", + "description": "The copied button label on code blocks" + }, + "theme.CodeBlock.copyButtonAriaLabel": { + "message": "코드를 클립보드로 복사", + "description": "The ARIA label for copy code blocks button" + }, + "theme.CodeBlock.copy": { + "message": "복사", + "description": "The copy button label on code blocks" + }, + "theme.CodeBlock.wordWrapToggle": { + "message": "단어 줄바꿈 토글", + "description": "The title attribute for toggle word wrapping button of code block lines" + }, + "theme.DocSidebarItem.toggleCollapsedCategoryAriaLabel": { + "message": "접을 수 있는 사이드바 카테고리 '{label}' 토글", + "description": "The ARIA label to toggle the collapsible sidebar category" + }, + "theme.NavBar.navAriaLabel": { + "message": "Main", + "description": "The ARIA label for the main navigation" + }, + "theme.navbar.mobileLanguageDropdown.label": { + "message": "Languages", + "description": "The label for the mobile language switcher dropdown" + }, "theme.TOCCollapsible.toggleButtonLabel": { "message": "이 페이지에서", "description": "The label used by the button on the collapsible TOC component" }, + "theme.blog.post.readMore": { + "message": "자세히 보기", + "description": "The label used in blog post item excerpts to link to full blog posts" + }, + "theme.blog.post.readMoreLabel": { + "message": "{title}에 대해 자세히 알아보기", + "description": "The ARIA label for the link to full blog posts from excerpts" + }, + "theme.blog.post.readingTime.plurals": { + "message": "1분 읽기|{readingTime}분 읽기", + "description": "Pluralized label for \"{readingTime} min read\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)" + }, + "theme.docs.breadcrumbs.home": { + "message": "홈페이지", + "description": "The ARIA label for the home page in the breadcrumbs" + }, + "theme.docs.sidebar.collapseButtonTitle": { + "message": "사이드바 접기", + "description": "The title attribute for collapse button of doc sidebar" + }, + "theme.docs.sidebar.collapseButtonAriaLabel": { + "message": "사이드바 접기", + "description": "The title attribute for collapse button of doc sidebar" + }, + "theme.docs.sidebar.navAriaLabel": { + "message": "문서 사이드바", + "description": "The ARIA label for the sidebar navigation" + }, + "theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel": { + "message": "← 메인 메뉴로 돌아가기", + "description": "The label of the back button to return to main menu, inside the mobile navbar sidebar secondary menu (notably used to display the docs sidebar)" + }, + "theme.docs.sidebar.closeSidebarButtonAriaLabel": { + "message": "네비게이션바 닫기", + "description": "The ARIA label for close button of mobile sidebar" + }, + "theme.docs.sidebar.toggleSidebarButtonAriaLabel": { + "message": "네비게이션바 토글", + "description": "The ARIA label for hamburger menu button of mobile navigation" + }, + "theme.docs.sidebar.expandButtonTitle": { + "message": "사이드바 확장", + "description": "The ARIA label and title attribute for expand button of doc sidebar" + }, + "theme.docs.sidebar.expandButtonAriaLabel": { + "message": "사이드바 확장", + "description": "The ARIA label and title attribute for expand button of doc sidebar" + }, + "theme.SearchBar.seeAll": { + "message": "{count}개 결과 모두 보기" + }, "theme.SearchPage.documentsFound.plurals": { - "message": "{count}개 문서를 찾았습니다.", + "message": "문서 1개 발견|{count}개 문서 발견", "description": "Pluralized label for \"{count} documents found\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)" }, "theme.SearchPage.existingResultsTitle": { - "message": "\"{query}\"개 검색 결과가 있습니다.", + "message": "\"{query}\"에 대한 검색 결과", "description": "The search page title for non-empty query" }, "theme.SearchPage.emptyResultsTitle": { - "message": "문서를 검색합니다.", + "message": "문서 검색", "description": "The search page title for empty query" }, "theme.SearchPage.inputPlaceholder": { - "message": "여기에 검색할 키워드를 입력하세요.", + "message": "여기에 검색어를 입력하세요", "description": "The placeholder for search page input" }, "theme.SearchPage.inputLabel": { @@ -271,17 +379,117 @@ "description": "The ARIA label for Algolia mention" }, "theme.SearchPage.noResultsText": { - "message": "검색 결과가 없습니다.", + "message": "검색 결과 없음", "description": "The paragraph for empty search result" }, "theme.SearchPage.fetchingNewResults": { - "message": "새로운 검색 결과를 얻을 수 있습니다...", + "message": "새로운 결과 가져오기...", "description": "The paragraph for fetching new search results" }, "theme.SearchBar.label": { "message": "검색", "description": "The ARIA label and placeholder for search button" }, + "theme.SearchModal.searchBox.resetButtonTitle": { + "message": "검색어 지우기", + "description": "The label and ARIA label for search box reset button" + }, + "theme.SearchModal.searchBox.cancelButtonText": { + "message": "취소", + "description": "The label and ARIA label for search box cancel button" + }, + "theme.SearchModal.startScreen.recentSearchesTitle": { + "message": "최근 검색", + "description": "The title for recent searches" + }, + "theme.SearchModal.startScreen.noRecentSearchesText": { + "message": "최근 검색어 없음", + "description": "The text when no recent searches" + }, + "theme.SearchModal.startScreen.saveRecentSearchButtonTitle": { + "message": "이 검색 저장", + "description": "The label for save recent search button" + }, + "theme.SearchModal.startScreen.removeRecentSearchButtonTitle": { + "message": "검색 기록에서 이 검색어 삭제", + "description": "The label for remove recent search button" + }, + "theme.SearchModal.startScreen.favoriteSearchesTitle": { + "message": "즐겨찾기", + "description": "The title for favorite searches" + }, + "theme.SearchModal.startScreen.removeFavoriteSearchButtonTitle": { + "message": "즐겨찾기에서 이 검색어 제거하기", + "description": "The label for remove favorite search button" + }, + "theme.SearchModal.errorScreen.titleText": { + "message": "결과를 가져올 수 없습니다.", + "description": "The title for error screen of search modal" + }, + "theme.SearchModal.errorScreen.helpText": { + "message": "네트워크 연결을 확인해 보세요.", + "description": "The help text for error screen of search modal" + }, + "theme.SearchModal.footer.selectText": { + "message": "선택", + "description": "The explanatory text of the action for the enter key" + }, + "theme.SearchModal.footer.selectKeyAriaLabel": { + "message": "Enter key", + "description": "The ARIA label for the Enter key button that makes the selection" + }, + "theme.SearchModal.footer.navigateText": { + "message": "탐색", + "description": "The explanatory text of the action for the Arrow up and Arrow down key" + }, + "theme.SearchModal.footer.navigateUpKeyAriaLabel": { + "message": "위 화살표", + "description": "The ARIA label for the Arrow up key button that makes the navigation" + }, + "theme.SearchModal.footer.navigateDownKeyAriaLabel": { + "message": "아래 화살표", + "description": "The ARIA label for the Arrow down key button that makes the navigation" + }, + "theme.SearchModal.footer.closeText": { + "message": "닫기", + "description": "The explanatory text of the action for Escape key" + }, + "theme.SearchModal.footer.closeKeyAriaLabel": { + "message": "나가기 키", + "description": "The ARIA label for the Escape key button that close the modal" + }, + "theme.SearchModal.footer.searchByText": { + "message": "Search by", + "description": "The text explain that the search is making by Algolia" + }, + "theme.SearchModal.noResultsScreen.noResultsText": { + "message": "결과가 없습니다", + "description": "The text explains that there are no results for the following search" + }, + "theme.SearchModal.noResultsScreen.suggestedQueryText": { + "message": "검색해보세요", + "description": "The text for the suggested query when no results are found for the following search" + }, + "theme.SearchModal.noResultsScreen.reportMissingResultsText": { + "message": "이 검색어가 결과를 반환해야 한다고 생각하시나요?", + "description": "The text for the question where the user thinks there are missing results" + }, + "theme.SearchModal.noResultsScreen.reportMissingResultsLinkText": { + "message": "저희에게 알려주세요.", + "description": "The text for the link to report missing results" + }, + "theme.SearchModal.placeholder": { + "message": "문서 검색", + "description": "The placeholder of the input of the DocSearch pop-up modal" + }, + "theme.ErrorPageContent.tryAgain": { + "message": "다시 시도", + "description": "The label of the button to try again rendering when the React error boundary captures an error" + }, + "theme.common.skipToMainContent": { + "message": "주요 콘텐츠로 건너뛰기", + "description": "The skip to content label used for accessibility, allowing to rapidly navigate to main content with keyboard tab/enter navigation" + }, "theme.tags.tagsPageTitle": { "message": "태그", "description": "The title of the tag list page" diff --git a/website/i18n/ko/docusaurus-plugin-content-blog/options.json b/website/i18n/ko/docusaurus-plugin-content-blog/options.json index bdb16439e..9239ff706 100644 --- a/website/i18n/ko/docusaurus-plugin-content-blog/options.json +++ b/website/i18n/ko/docusaurus-plugin-content-blog/options.json @@ -11,4 +11,4 @@ "message": "Recent posts", "description": "The label for the left sidebar" } -} \ No newline at end of file +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current.json b/website/i18n/ko/docusaurus-plugin-content-docs/current.json index 6e09ba821..ca339c131 100644 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current.json +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current.json @@ -3,33 +3,57 @@ "message": "다음", "description": "The label for version current" }, - "sidebar.Sidebar.category.Guides": { - "message": "가이드", - "description": "The label for category Guides in sidebar Sidebar" + "sidebar.Sidebar.category.Introduction": { + "message": "소개(Introduction)", + "description": "The label for category Introduction in sidebar Sidebar" + }, + "sidebar.Sidebar.category.Riverpod for Provider Users": { + "message": "Provider 사용자를 위한 Riverpod", + "description": "The label for category Riverpod for Provider Users in sidebar Sidebar" + }, + "sidebar.Sidebar.category.Essentials": { + "message": "필수사항(Essentials)", + "description": "The label for category Essentials in sidebar Sidebar" + }, + "sidebar.Sidebar.category.Case studies": { + "message": "사례 연구(Case studies)", + "description": "The label for category Case studies in sidebar Sidebar" + }, + "sidebar.Sidebar.category.Advanced topics": { + "message": "고급 주제(Advanced topics)", + "description": "The label for category Advanced topics in sidebar Sidebar" }, "sidebar.Sidebar.category.Concepts": { - "message": "컨셉", + "message": "기본 개념(Concepts)", "description": "The label for category Concepts in sidebar Sidebar" }, - "sidebar.Sidebar.category.Modifiers": { - "message": "수식어", - "description": "The label for category Modifiers in sidebar Sidebar" - }, - "sidebar.Sidebar.category.Migration": { - "message": "마이그레이션", - "description": "The label for category Migration in sidebar Sidebar" + "sidebar.Sidebar.category.Migration guides": { + "message": "마이그레이션 가이드", + "description": "The label for category Migration guides in sidebar Sidebar" }, "sidebar.Sidebar.category.Official examples": { - "message": "사용예제(공식)", + "message": "공식 사용예제", "description": "The label for category Official examples in sidebar Sidebar" }, "sidebar.Sidebar.category.Third party examples": { - "message": "사용예제(서드파티)", + "message": "서드파티 사용예제", "description": "The label for category Third party examples in sidebar Sidebar" }, - "sidebar.Sidebar.category.Api references": { - "message": "Api 레퍼런스", - "description": "The label for category Api references in sidebar Sidebar" + "sidebar.Sidebar.category.Concepts 🚧": { + "message": "기본 개념(Concepts) 🚧", + "description": "The label for category Concepts 🚧 in sidebar Sidebar" + }, + "sidebar.Sidebar.category.Modifiers": { + "message": "수식어(Modifiers)", + "description": "The label for category Modifiers in sidebar Sidebar" + }, + "sidebar.Sidebar.category.All Providers 🚧": { + "message": "전체 Providers 🚧", + "description": "The label for category All Providers 🚧 in sidebar Sidebar" + }, + "sidebar.Sidebar.category.Guides 🚧": { + "message": "가이드 🚧", + "description": "The label for category Guides 🚧 in sidebar Sidebar" }, "sidebar.Sidebar.link.Counter": { "message": "카운터 앱", @@ -39,12 +63,16 @@ "message": "Todo 앱", "description": "The label for link Todo list in sidebar Sidebar, linking to https://github.com/rrousselGit/riverpod/tree/master/examples/todos" }, + "sidebar.Sidebar.link.Pub.dev client": { + "message": "Pub.dev 클라이언트", + "description": "The label for link Pub.dev client in sidebar Sidebar, linking to https://github.com/rrousselGit/riverpod/tree/master/examples/pub" + }, "sidebar.Sidebar.link.Marvel API": { "message": "Marvel API", "description": "The label for link Marvel API in sidebar Sidebar, linking to https://github.com/rrousselGit/riverpod/tree/master/examples/marvel" }, "sidebar.Sidebar.link.Android Launcher": { - "message": "Android Launcher", + "message": "안드로이드 Launcher", "description": "The label for link Android Launcher in sidebar Sidebar, linking to https://github.com/lohanidamodar/fl_live_launcher" }, "sidebar.Sidebar.link.Worldtime Clock": { @@ -55,32 +83,28 @@ "message": "사전 앱", "description": "The label for link Dictionary App in sidebar Sidebar, linking to https://github.com/lohanidamodar/fl_dictio" }, - "sidebar.Sidebar.link.Firebase Starter": { - "message": "Firebase 스타터", - "description": "The label for link Firebase Starter in sidebar Sidebar, linking to https://github.com/lohanidamodar/flutter_firebase_starter/tree/feature/riverpod" - }, "sidebar.Sidebar.link.Time Tracking App (with Firebase)": { "message": "시간관리 앱 (with Firebase)", "description": "The label for link Time Tracking App (with Firebase) in sidebar Sidebar, linking to https://github.com/bizz84/starter_architecture_flutter_firebase" }, "sidebar.Sidebar.link.Firebase Phone Authentication with Riverpod": { - "message": "전화번호 인증 (with Firebase)", + "message": "Riverpod을 사용한 Firebase 전화번호 인증", "description": "The label for link Firebase Phone Authentication with Riverpod in sidebar Sidebar, linking to https://github.com/julienlebren/flutter_firebase_phone_auth_riverpod" }, "sidebar.Sidebar.link.ListView paging with search": { - "message": "검색기능을 포함한 리스트 페이징", + "message": "검색지원 리스트 페이징", "description": "The label for link ListView paging with search in sidebar Sidebar, linking to https://github.com/tbm98/flutter_loadmore_search" }, - "sidebar.Sidebar.link.Resocoder's Weather Bloc to Weather Riverpod": { - "message": "날씨 앱 (Resocoder's)", - "description": "The label for link Resocoder's Weather Bloc to Weather Riverpod in sidebar Sidebar, linking to https://github.com/campanagerald/flutter-bloc-library-v1-tutorial" + "sidebar.Sidebar.link.Resocoder's Weather Bloc to Weather Riverpod V2": { + "message": "Resocoder의 날씨 Bloc to 날씨 Riverpod V2", + "description": "The label for link Resocoder's Weather Bloc to Weather Riverpod V2 in sidebar Sidebar, linking to https://github.com/coyksdev/flutter-bloc-library-v1-tutorial" }, "sidebar.Sidebar.link.Blood Pressure Tracker App": { - "message": "혈압관리 앱", + "message": "혈압 트래커 앱", "description": "The label for link Blood Pressure Tracker App in sidebar Sidebar, linking to https://github.com/UrosTodosijevic/blood_pressure_tracker" }, "sidebar.Sidebar.link.Firebase Authentication with Riverpod Following Flutter DDD Architecture Pattern": { - "message": "Riverpod와 DDD 아키텍처 패던을 적용한 Firebase 인증(Authentication)", + "message": "Flutter DDD 아키텍처 패턴을 따르는 Riverpod을 사용한 Firebase 인증", "description": "The label for link Firebase Authentication with Riverpod Following Flutter DDD Architecture Pattern in sidebar Sidebar, linking to https://github.com/pythonhubpy/firebase_authentication_flutter_DDD" }, "sidebar.Sidebar.link.Todo App with Backup and Restore feature": { @@ -88,35 +112,39 @@ "description": "The label for link Todo App with Backup and Restore feature in sidebar Sidebar, linking to https://github.com/TheAlphaApp/flutter_riverpod_todo_app" }, "sidebar.Sidebar.link.Integrating Hive database with Riverpod (simple example)": { - "message": "Hive 데이터베이스", + "message": "Hive 데이터베이스와 Riverpod 통합하기(간단한 예제)", "description": "The label for link Integrating Hive database with Riverpod (simple example) in sidebar Sidebar, linking to https://github.com/GitGud31/theme_riverpod_hive" }, "sidebar.Sidebar.link.Browser App with Riverpod": { - "message": "브라우저 앱", - "description": "The label for link Browser App with Riverpod, linking to https://github.com/MarioCroSite/simple_browser_app" + "message": "Riverpod을 사용한 브라우저 앱", + "description": "The label for link Browser App with Riverpod in sidebar Sidebar, linking to https://github.com/MarioCroSite/simple_browser_app" }, "sidebar.Sidebar.link.GoRouter with Riverpod": { - "message": "GoRouter", - "description": "The label for link GoRouter with Riverpod, linking to https://github.com/lucavenir/go_router_riverpod" + "message": "Riverpod을 사용한 Go라우터", + "description": "The label for link GoRouter with Riverpod in sidebar Sidebar, linking to https://github.com/lucavenir/go_router_riverpod" }, "sidebar.Sidebar.link.Piano Chords Test": { "message": "피아노 화음 테스트", - "description": "The label for link Piano Chords Test, linking to https://github.com/akvus/piano_fun" + "description": "The label for link Piano Chords Test in sidebar Sidebar, linking to https://github.com/akvus/piano_fun" + }, + "sidebar.Sidebar.link.Movies API App with Caching & Pagination": { + "message": "캐싱 및 페이징이 있는 동영상 API 앱", + "description": "The label for link Movies API App with Caching & Pagination in sidebar Sidebar, linking to https://github.com/Roaa94/movies_app" }, - "sidebar.Sidebar.category.All Providers": { - "message": "프로바이더", - "description": "The label for category All Providers in sidebar Sidebar" + "sidebar.Sidebar.link.AWS Amplify Storage Gallery App with Riverpod & Freezed": { + "message": "Riverpod과 Freezed를 사용한 AWS Amplify 저장소 갤러리 앱", + "description": "The label for link AWS Amplify Storage Gallery App with Riverpod & Freezed in sidebar Sidebar, linking to https://github.com/offlineprogrammer/amplify_storage_app" }, - "sidebar.Sidebar.link.riverpod": { - "message": "riverpod", - "description": "The label for link riverpod in sidebar Sidebar, linking to https://pub.dev/documentation/riverpod/latest/riverpod/riverpod-library.html" + "sidebar.Sidebar.link.Clean Architecture demonstration with Riverpod": { + "message": "Riverpod을 사용한 클린 아키텍처 데모", + "description": "The label for link Clean Architecture demonstration with Riverpod in sidebar Sidebar, linking to https://github.com/Uuttssaavv/flutter-clean-architecture-riverpod" }, - "sidebar.Sidebar.link.flutter_riverpod": { - "message": "flutter_riverpod", - "description": "The label for link flutter_riverpod in sidebar Sidebar, linking to https://pub.dev/documentation//flutter_riverpod/latest/flutter_riverpod/flutter_riverpod-library.html" + "sidebar.Sidebar.link.Delivery App with Google Maps and Live Tracking": { + "message": "Google 지도 및 실시간 추적 기능을 갖춘 배달 앱", + "description": "The label for link Delivery App with Google Maps and Live Tracking in sidebar Sidebar, linking to https://github.com/AhmedLSayed9/deliverzler" }, - "sidebar.Sidebar.link.hooks_riverpod": { - "message": "hooks_riverpod", - "description": "The label for link hooks_riverpod in sidebar Sidebar, linking to https://pub.dev/documentation/hooks_riverpod/latest/hooks_riverpod/hooks_riverpod-library.html" + "sidebar.Sidebar.link.API reference": { + "message": "API 참조", + "description": "The label for link API reference in sidebar Sidebar, linking to https://pub.dev/documentation/hooks_riverpod/latest/hooks_riverpod/hooks_riverpod-library.html" } } diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/about_code_generation.mdx b/website/i18n/ko/docusaurus-plugin-content-docs/current/about_code_generation.mdx deleted file mode 100644 index 3875144bc..000000000 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/about_code_generation.mdx +++ /dev/null @@ -1,67 +0,0 @@ ---- -title: Code Generation 알아보기 ---- - -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; -import CodeBlock from "@theme/CodeBlock"; -import fetchUser from "!!raw-loader!./about_codegen/main.dart"; -import rawFetchUser from "!!raw-loader!./about_codegen/raw.dart"; -import { - trimSnippet, - CodeSnippet, -} from "../../../../src/components/CodeSnippet"; - -Code generation is the idea of using a tool to generate code for us. -In Dart, it comes with the downside of requiring an extra step to "compile" -an application. Although this problem may be solved in the near future, as the -Dart team is working on a potential solution to this problem. - -In the context of Riverpod, code generation is about slightly changing the syntax -for defining a "provider". For example, instead of: - -{trimSnippet(rawFetchUser)} - -Using code generation, we would write: - -{trimSnippet(fetchUser)} - -When using Riverpod, code generation is completely optional. It is entirely possible -to use Riverpod without. -At the same time, Riverpod embraces code generation and recommends using it. - -For information on how to install and use Riverpod's code generator, refer to -the [getting started](./getting_started) page. Make sure to enable code generation -in the documentation's sidebar. - -## Why use code generation with Riverpod? - -You may be wondering: "If code generation is optional in Riverpod, why use it?" - -As always with packages: To make your life easier. -This includes but is not limited to: - -- better syntax, more readable/flexible and with reduced learning curve. - - - No need to worry about `FutureProvider` vs `Provider` vs etc. Write your logic, - and Riverpod will pick the most suitable provider for you. - - Passing parameters to providers is now unrestricted. Instead of being limited to - using [family](./concepts/modifiers/family) and passing a single positional parameter, - you can now pass any form of parameter. This includes named parametes, optional ones, - and even default values. - -- **stateful hot-reload** of the code written in Riverpod. -- better debugging, through the generation of extra metadata that the debugger then picks-up. -- some Riverpod features will be available only with code generation. - -At the same time, many applications already use code generation with packages such -as [Freezed](https://pub.dev/packages/freezed) or [json_serializable](https://pub.dev/packages/json_serializable). -In that case, your project probably is already setup for code generation, and -using for Riverpod should be simple. - -[hookwidget]: https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/HookWidget-class.html -[statefulwidget]: https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html -[riverpod]: https://github.com/rrousselgit/riverpod -[hooks_riverpod]: https://pub.dev/packages/hooks_riverpod -[flutter_riverpod]: https://pub.dev/packages/flutter_riverpod -[flutter_hooks]: https://github.com/rrousselGit/flutter_hooks diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/about_hooks.mdx b/website/i18n/ko/docusaurus-plugin-content-docs/current/about_hooks.mdx deleted file mode 100644 index 229757933..000000000 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/about_hooks.mdx +++ /dev/null @@ -1,283 +0,0 @@ ---- -title: hooks 알아보기 ---- - -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; -import CodeBlock from "@theme/CodeBlock"; -import pubspec from "./getting_started/pubspec"; -import dartHelloWorld from "./getting_started/dart_hello_world"; -import helloWorld from "./getting_started/hello_world"; -import dartPubspec from "./getting_started/dart_pubspec"; -import { - trimSnippet, - AutoSnippet, - When, -} from "../../../../src/components/CodeSnippet"; - -This page explains what are hooks and how they are related to Riverpod. - -"Hooks" are utilities common from a separate package, independent from Riverpod: -[flutter_hooks]. -Although [flutter_hooks] is a completely separate package and does not have anything -to do with Riverpod (at least directly), it is common to pair Riverpod -and [flutter_hooks] together. After-all, Riverpod and [flutter_hooks] are -maintained by the same team. - -Hooks are completely optional. You do not have to use hooks, especially if you -are starting Flutter. They are powerful tools, but not very "Flutter-like". -As such, it may make sense to start first with plain Flutter/Riverpod, and come back -to hooks once you have a bit more experience. - -## What are hooks? - -Hooks are functions used inside widgets. They are designed as an alternative -to [StatefulWidget]s, to make logic more reusable and composable. - -Hooks are a concept coming from [React](https://reactjs.org/), and [flutter_hooks] -is merely a port of the React implementation to Flutter. -As such, yes, hooks may feel a bit out of place in Flutter. Ideally, -in the future we would have a solution to the problem that hooks solves, -designed specifically for Flutter. - -If Riverpod's providers are for "global" application state, hooks are for -local widget state. Hooks are typically used for dealing with stateful UI objects, -such as [TextEditingController](https://api.flutter.dev/flutter/widgets/TextEditingController-class.html), -[AnimationController](https://api.flutter.dev/flutter/animation/AnimationController-class.html). -They can also serve as a replacement to the "builder" pattern, replacing widgets -such as [FutureBuilder](https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html)/[TweenAnimatedBuilder](https://api.flutter.dev/flutter/widgets/TweenAnimationBuilder-class.html) -by an alterative that does not invole "nesting" – drastically improving readability. - -In general, hooks are helpful for: - -- forms -- animations -- reacting to user events -- ... - -As an example, we could use hooks to manually implement a fade-in animation, -where a widget starts invisible and slowly appears. - -If we were to use [StatefulWidget], the code would look like this: - -```dart -class FadeIn extends StatefulWidget { - const FadeIn({Key? key, required this.child}) : super(key: key); - - final Widget child; - - @override - State createState() => _FadeInState(); -} - -class _FadeInState extends State with SingleTickerProviderStateMixin { - late final AnimationController animationController = AnimationController( - vsync: this, - duration: const Duration(seconds: 2), - ); - - @override - void initState() { - super.initState(); - animationController.forward(); - } - - @override - void dispose() { - animationController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return AnimatedBuilder( - animation: animationController, - builder: (context, child) { - return Opacity( - opacity: animationController.value, - child: widget.child, - ); - }, - ); - } -} -``` - -Using hooks, the equivalent would be: - -```dart -class FadeIn extends HookWidget { - const FadeIn({Key? key, required this.child}) : super(key: key); - - final Widget child; - - @override - Widget build(BuildContext context) { - // Create an AnimationController. The controller will automatically be - // disposed when the widget is unmounted. - final animationController = useAnimationController( - duration: const Duration(seconds: 2), - ); - - // useEffect is the equivalent of initState + didUpdateWidget + dispose. - // The callback passed to useEffect is executed the first time the hook is - // invoked, and then whenever the list passed as second parameter changes. - // Since we pass an empty const list here, that's strictly equivalent to `initState`. - useEffect(() { - // start the animation when the widget is first rendered. - animationController.forward(); - // We could optionally return some "dispose" logic here - return null; - }, const []); - - // Tell Flutter to rebuild this widget when the animation updates. - // This is equivalent to AnimatedBuilder - useAnimation(animationController); - - return Opacity( - opacity: animationController.value, - child: child, - ); - } -} -``` - -There are a few interesting things to note in this code: - -- There is no memory leak. This code does not recreate a new `AnimationController` whenever the - widget rebuild, and the controller is correctly released when the widget is unmounted. - -- It is possible to use hooks as many time as we want within the same widget. - As such, we can create multiple `AnimationController` if we want: - - ```dart - @override - Widget build(BuildContext context) { - final animationController = useAnimationController( - duration: const Duration(seconds: 2), - ); - final anotherController = useAnimationController( - duration: const Duration(seconds: 2), - ); - - ... - } - ``` - - This creates two controllers, without any sort of negative consequence. - -- If we wanted, we could refactor this logic into a separate reusable function: - - ```dart - double useFadeIn() { - final animationController = useAnimationController( - duration: const Duration(seconds: 2), - ); - useEffect(() { - animationController.forward(); - return null; - }, const []); - useAnimation(animationController); - return animationController.value; - } - ``` - - We could then use this function inside our widgets, as long as that widget is a [HookWidget]: - - ```dart - class FadeIn extends HookWidget { - const FadeIn({Key? key, required this.child}) : super(key: key); - - final Widget child; - - @override - Widget build(BuildContext context) { - final fade = useFadeIn(); - - return Opacity(opacity: fade, child: child); - } - } - ``` - - Note how our `useFadeIn` function is completely independent from our - `FadeIn` widget. - If we wanted, we could use that `useFadeIn` function in a completely different - widget, and it would still work! - -## How to use hooks - -Hooks comes with unique constraints: - -- They can only be used within the `build` method of a widget that extends [HookWidget]: - - **Good**: - - ```dart - class Example extends HookWidget { - @override - Widget build(BuildContext context) { - final controller = useAnimationController(); - ... - } - } - ``` - - **Bad**: - - ```dart - // Not a HookWidget - class Example extends StatelessWidget { - @override - Widget build(BuildContext context) { - final controller = useAnimationController(); - ... - } - } - ``` - - **Bad**: - - ```dart - class Example extends HookWidget { - @override - Widget build(BuildContext context) { - return ElevatedButton( - onPressed: () { - // Not _actually_ inside the "build" method, but instead inside - // a user interaction lifecycle (here "on pressed"). - final controller = useAnimationController(); - }, - child: Text('click me'), - ); - } - } - ``` - -- They cannot be used conditionally or in a loop. - - **Bad**: - - ```dart - class Example extends HookWidget { - const Example({required this.condition, super.key}); - final bool condition; - @override - Widget build(BuildContext context) { - if (condition) { - // Hooks should not be used inside "if"s/"for"s, ... - final controller = useAnimationController(); - } - ... - } - } - ``` - - -For more information about hooks, see [flutter_hooks]. - -[hookwidget]: https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/HookWidget-class.html -[statefulwidget]: https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html -[riverpod]: https://github.com/rrousselgit/riverpod -[hooks_riverpod]: https://pub.dev/packages/hooks_riverpod -[flutter_riverpod]: https://pub.dev/packages/flutter_riverpod -[flutter_hooks]: https://github.com/rrousselGit/flutter_hooks diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/advanced/select.mdx b/website/i18n/ko/docusaurus-plugin-content-docs/current/advanced/select.mdx new file mode 100644 index 000000000..600fcc1ea --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/advanced/select.mdx @@ -0,0 +1,59 @@ +--- +title: 성능 최적화하기 +--- + +import { AutoSnippet } from "../../../../../src/components/CodeSnippet"; +import select from "./select/select"; + +import selectAsync from "./select/select_async"; + +지금까지 살펴본 내용을 통해 이미 모든 기능을 갖춘 애플리케이션을 구축할 수 있습니다. +하지만 성능과 관련하여 궁금한 점이 있을 수 있습니다. + +이 페이지에서는 코드를 최적화할 수 있는 몇 가지 팁과 요령을 다룰 것입니다. + +:::caution +최적화를 수행하기 전에 애플리케이션을 벤치마킹(benchmark)해야 합니다. +최적화로 인한 복잡성 증가는 소소한 이득에 비해 가치가 없을 수 있습니다. +::: + +## "select"을 사용하여 위젯/provider 다시 빌드하는 것을 필터링합니다. + +기본적으로 `ref.watch`를 사용하면 객체의 _어떠한(any)_ 프로퍼티라도 변경될때마다, consumers/providers가 다시 빌드하는 것을 보셨을 것입니다. +예를 들어, `User`를 watch하고 "name"만 사용해도 'age'가 변경되면 Consumer는 여전히 다시 빌드됩니다. + +그러나 일부 속성만 사용하는 consumer가 있는 경우, 다른 속성이 변경될 때 위젯을 다시 빌드하지 않고 싶을 수 있습니다. + +이는 provider의 `select` 기능을 사용하여 수행할 수 있습니다. +이렇게 하면 `ref.watch`는 더 이상 전체 객체를 반환하지 않고 선택된 프로퍼티만 반환합니다. +그리고 consumer/provider는 이제 선택된 프로퍼티가 변경되는 경우에만 다시 빌드됩니다. + + + +:::info +`select`는 원하는 횟수만큼 호출할 수 있습니다. +원하는 속성당 한 번씩 자유롭게 호출할 수 있습니다. +::: + +:::caution +선택된 프로퍼티는 변경되지 않을 것으로 예상됩니다. +`List`를 반환한 다음 해당 리스트를 변경해도 리빌드가 트리거되지 않습니다. +::: + +:::caution +`select`을 사용하면 무효(invididual) 읽기 작업 속도가 약간 느려지고, 코드의 복잡성이 약간 증가합니다. +이러한 "다른 속성"이 거의 변경되지 않는 경우에는 사용할 가치가 없을 수 있습니다. +::: + +### Selecting asynchronous properties + +다른 provider를 수신하는 provider를 최적화하려는 경우 다른 provider가 비동기식일 가능성이 있습니다. + +일반적으로는 `ref.watch(anotherProvider.future)`를 사용해 값을 가져옵니다. +문제는 `select`이 `AsyncValue`에 적용된다는 것인데, 이는 기다릴 수 있는(await) 상황이 아닙니다. + +이를 위해 `selectAsync`를 대신 사용할 수 있습니다. +이 함수는 비동기 코드에 고유하며, 공급자가 방출한 데이터에 대해 `select` 연산을 수행할 수 있게 해줍니다. +사용법은 `select`와 비슷하지만 대신 `Future`를 반환합니다: + + diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/advanced/select/select/codegen.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/advanced/select/select/codegen.dart new file mode 100644 index 000000000..f875f67e2 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/advanced/select/select/codegen.dart @@ -0,0 +1,30 @@ +// ignore_for_file: unused_local_variable, avoid_multiple_declarations_per_line, omit_local_variable_types, prefer_final_locals, use_key_in_widget_constructors + +import 'package:flutter/widgets.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +/* SNIPPET START */ +class User { + late String firstName, lastName; +} + +@riverpod +User example(ExampleRef ref) => User() + ..firstName = 'John' + ..lastName = 'Doe'; + +class ConsumerExample extends ConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + // 이렇게 작성하는 대신: + // String name = ref.watch(provider).firstName!; + // 이렇게 작성할수 있습니다: + String name = ref.watch(exampleProvider.select((it) => it.firstName)); + // 이렇게 하면 위젯이 "firstName"의 변경 사항만 수신합니다. + + return Text('Hello $name'); + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/advanced/select/select/codegen.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/advanced/select/select/codegen.g.dart new file mode 100644 index 000000000..018542dba --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/advanced/select/select/codegen.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$exampleHash() => r'72881c6147d44adb957180debefe7696d93107f0'; + +/// See also [example]. +@ProviderFor(example) +final exampleProvider = AutoDisposeProvider.internal( + example, + name: r'exampleProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$exampleHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ExampleRef = AutoDisposeProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/advanced/select/select/index.ts b/website/i18n/ko/docusaurus-plugin-content-docs/current/advanced/select/select/index.ts new file mode 100644 index 000000000..9d50c6614 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/advanced/select/select/index.ts @@ -0,0 +1,4 @@ +import raw from '!!raw-loader!./raw.dart'; +import codegen from '!!raw-loader!./codegen.dart'; + +export default { raw, codegen }; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/advanced/select/select/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/advanced/select/select/raw.dart new file mode 100644 index 000000000..46524af88 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/advanced/select/select/raw.dart @@ -0,0 +1,29 @@ +// ignore_for_file: unused_local_variable, avoid_multiple_declarations_per_line, omit_local_variable_types, prefer_final_locals, use_key_in_widget_constructors + +import 'package:flutter/widgets.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +/* SNIPPET START */ +class User { + late String firstName, lastName; +} + +final provider = Provider( + (ref) => User() + ..firstName = 'John' + ..lastName = 'Doe', +); + +class ConsumerExample extends ConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + // Instead of writing: + // String name = ref.watch(provider).firstName!; + // We can write: + String name = ref.watch(provider.select((it) => it.firstName)); + // This will cause the widget to only listen to changes on "firstName". + + return Text('Hello $name'); + } +} +/* SNIPPET END */ diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/advanced/select/select_async/codegen.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/advanced/select/select_async/codegen.dart new file mode 100644 index 000000000..dba161bdf --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/advanced/select/select_async/codegen.dart @@ -0,0 +1,26 @@ +// ignore_for_file: unused_local_variable, avoid_multiple_declarations_per_line, omit_local_variable_types, prefer_final_locals, use_key_in_widget_constructors, body_might_complete_normally_nullable + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +class User { + late String firstName, lastName; +} + +final userProvider = FutureProvider( + (ref) => User() + ..firstName = 'John' + ..lastName = 'Doe', +); +/* SNIPPET START */ +@riverpod +Object? example(ExampleRef ref) async { + // user를 사용할 수 있을 때까지 기다렸다가 "firstName" 속성만 수신합니다. + final firstName = await ref.watch( + userProvider.selectAsync((it) => it.firstName), + ); + + // TODO use "firstName" to fetch something else +} +/* SNIPPET END */ diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/advanced/select/select_async/codegen.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/advanced/select/select_async/codegen.g.dart new file mode 100644 index 000000000..ab6da16f7 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/advanced/select/select_async/codegen.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$exampleHash() => r'1fccbdbec0e3585bc9d3a5709ac88a8919dd78fa'; + +/// See also [example]. +@ProviderFor(example) +final exampleProvider = AutoDisposeProvider.internal( + example, + name: r'exampleProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$exampleHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ExampleRef = AutoDisposeProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/advanced/select/select_async/index.ts b/website/i18n/ko/docusaurus-plugin-content-docs/current/advanced/select/select_async/index.ts new file mode 100644 index 000000000..9d50c6614 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/advanced/select/select_async/index.ts @@ -0,0 +1,4 @@ +import raw from '!!raw-loader!./raw.dart'; +import codegen from '!!raw-loader!./codegen.dart'; + +export default { raw, codegen }; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/advanced/select/select_async/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/advanced/select/select_async/raw.dart new file mode 100644 index 000000000..6f3c7ddf9 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/advanced/select/select_async/raw.dart @@ -0,0 +1,23 @@ +// ignore_for_file: unused_local_variable, avoid_multiple_declarations_per_line, omit_local_variable_types, prefer_final_locals, use_key_in_widget_constructors + +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +class User { + late String firstName, lastName; +} + +final userProvider = FutureProvider( + (ref) => User() + ..firstName = 'John' + ..lastName = 'Doe', +); +/* SNIPPET START */ +final provider = FutureProvider((ref) async { + // Wait for a user to be available, and listen to only the "firstName" property + final firstName = await ref.watch( + userProvider.selectAsync((it) => it.firstName), + ); + + // TODO use "firstName" to fetch something else +}); +/* SNIPPET END */ diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel.mdx b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel.mdx new file mode 100644 index 000000000..27fcea046 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel.mdx @@ -0,0 +1,118 @@ +--- +title: 네트워크요청 디바운싱/취소 (Debouncing/Cancelling) +--- + +import { Link } from "../../../../../src/components/Link"; +import { AutoSnippet, When } from "../../../../../src/components/CodeSnippet"; +import homeScreen from "!raw-loader!./cancel/home_screen.dart"; +import extension from "!raw-loader!./cancel/extension.dart"; +import detailScreen from "./cancel/detail_screen"; +import detailScreenCancel from "./cancel/detail_screen_cancel"; +import detailScreenDebounce from "./cancel/detail_screen_debounce"; +import providerWithExtension from "./cancel/provider_with_extension"; + +애플리케이션이 복잡해짐에 따라 동시에 여러 개의 네트워크 요청이 발생하는 것이 일반적입니다. +예를 들어, 사용자가 검색창에 입력할 때마다 새로운 요청이 트리거될 수 있습니다. +사용자가 빠르게 입력하는 경우 애플리케이션에 동시에 많은 요청이 전송될 수 있습니다. + +또는 사용자가 요청을 트리거한 후 요청이 완료되기 전에 다른 페이지로 이동할 수도 있습니다. +이 경우 애플리케이션에 더 이상 필요하지 않은 요청이 전송 중일 수 있습니다. + +이러한 상황에서 성능을 최적화하기 위해 사용할 수 있는 몇 가지 기술이 있습니다: + +- 요청 '디바운스'. + 즉, 사용자가 일정 시간 동안 입력을 멈출 때까지 기다렸다가 요청을 전송하는 방식입니다. + 이렇게 하면 사용자가 빠르게 입력하더라도 주어진 입력에 대해 한 번의 요청만 전송할 수 있습니다. +- 요청 '취소'. + 즉, 요청이 완료되기 전에 사용자가 페이지에서 다른 곳으로 이동하는 경우 요청을 취소합니다. + 이렇게 하면 사용자가 볼 수 없는 응답을 처리하느라 시간을 낭비하지 않아도 됩니다. + +Riverpod에서는 이 두 가지 기술을 비슷한 방식으로 구현할 수 있습니다. +핵심은 `ref.onDispose`를 "자동 폐기(automatic disposal)" 또는 `ref.watch`와 함께 사용하여 원하는 동작을 달성하는 것입니다. + +이를 보여주기 위해 두 페이지로 구성된 간단한 애플리케이션을 만들어 보겠습니다: + +- 새 페이지를 여는 버튼이 있는 홈 화면 +- [Bored API](https://www.boredapi.com/)에서 임의의 액티비티를 표시하는 상세 페이지로, 액티비티를 새로 고칠 수 있는 기능이 있습니다. + 당겨서 새로고침(pull to refresh)를 구현하는 방법에 대한 자세한 내용은 를 참조하세요. + +그런 다음 다음 동작을 구현합니다: + +- 사용자가 세부 정보 페이지를 열었다가 즉시 다시 이동하면 액티비티에 대한 요청을 취소(cancel)합니다. +- 사용자가 연속으로 여러 번 액티비티을 새로 고치면 요청을 디바운스(debounce)하여 사용자가 새로 고침을 중지한 후 한 번만 요청을 보내도록 합니다. + +## 어플리케이션 + +Gif showcasing the application, opening the detail page and refreshing the activity. + +먼저, 디바운스나 취소 없이 애플리케이션을 만들어 봅시다. +여기서는 멋진 것을 사용하지 않고, 세부 정보 페이지를 여는 `Navigator.push`가 있는 평범한 `FloatingActionButton`을 사용하겠습니다. + +먼저 홈 화면을 정의하는 것부터 시작하겠습니다. +평소와 마찬가지로 애플리케이션의 루트에 `ProviderScope`를 지정하는 것을 잊지 마세요. + + + +그런 다음 세부 정보 페이지를 정의해 보겠습니다. +활동을 가져오고 당겨서 새로고침(pull to refresh)를 구현하려면 사례 연구를 참조하세요. + + + +## 요청 취소하기 + +이제 애플리케이션이 작동하므로 취소(cancellation) 로직을 구현해 보겠습니다. + +이를 위해 사용자가 페이지에서 다른 곳으로 이동할 때 `ref.onDispose`를 사용하여 요청을 취소할 것입니다. +이 기능이 작동하려면 provider의 자동 폐기(automatic disposal)가 활성화되어 있어야 합니다. + +요청을 취소하는 데 필요한 정확한 코드는 HTTP 클라이언트에 따라 다릅니다. +이 예에서는 `package:http`를 사용하지만 다른 클라이언트에도 동일한 원칙이 적용됩니다. + +여기서 중요한 점은 사용자가 다른 곳으로 이동할 때 `ref.onDispose`가 호출된다는 것입니다. +이는 provider가 더 이상 사용되지 않으므로 자동 폐기를 통해 폐기되기 때문입니다. +따라서 이 콜백을 사용하여 요청을 취소할 수 있습니다. +`package:http`를 사용하는 경우 HTTP 클라이언트를 닫으면 이 작업을 수행할 수 있습니다. + + + +## 요청 디바운싱(Debouncing) + +이제 취소를 구현했으니 이제 디바운싱을 구현해 보겠습니다. +현재로서는 사용자가 활동을 연속으로 여러 번 새로 고치면 새로 고칠 때마다 요청을 보내게 됩니다. + +기술적으로는 취소를 구현했으므로 문제가 되지 않습니다. +사용자가 활동을 연속으로 여러 번 새로 고치면 새 요청이 이루어질 때 이전 요청이 취소됩니다. + +하지만 이는 이상적이지 않습니다. 여전히 여러 요청을 전송하고 대역폭과 서버 리소스를 낭비하게 됩니다. +대신 사용자가 일정 시간 동안 활동 새로 고침을 중지할 때까지 요청을 지연시키는 방법을 사용할 수 있습니다. + +여기서 로직은 취소 로직과 매우 유사합니다. 다시 `ref.onDispose`를 사용합니다. +하지만 여기서는 HTTP 클라이언트를 닫는 대신 `onDispose`에 의존하여 요청이 시작되기 전에 중단한다는 점이 다릅니다. +그런 다음 요청을 보내기 전에 임의로 500ms를 기다립니다. +그런 다음 사용자가 500ms가 경과하기 전에 활동을 다시 새로고침하면 `onDispose`가 호출되어 요청이 중단됩니다. + +:::info +요청을 중단하려면 자발적으로 던지는(throw) 것이 일반적입니다. +provider가 폐기된(disposed) 후에는 공급자 내부에 던지는(throw) 것이 안전합니다. +예외는 당연히 Riverpod에 의해 잡히고 무시됩니다. +::: + + + +## 더 나아가기: 두 가지를 한 번에 수행하기 + +이제 요청을 디바운스하고 취소하는 방법을 알았습니다. +하지만 현재 다른 요청을 수행하려면 동일한 로직을 여러 곳에 복사하여 붙여넣어야 합니다. 이것은 이상적이지 않습니다. + +하지만 여기서 더 나아가 재사용 가능한 유틸리티를 구현하여 두 가지 작업을 한 번에 수행할 수 있습니다. + +여기서는 단일 메서드에서 취소와 디바운싱을 모두 처리하는 확장 메서드(extension method)를 `Ref`에 구현하는 것이 아이디어입니다. + + + +그런 다음 다음과 같이 공급자에서 이 확장 메서드를 사용할 수 있습니다: + + diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen/codegen.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen/codegen.dart new file mode 100644 index 000000000..e3bf193a2 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen/codegen.dart @@ -0,0 +1,61 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:http/http.dart' as http; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.freezed.dart'; +part 'codegen.g.dart'; + +/* SNIPPET START */ +@freezed +class Activity with _$Activity { + factory Activity({ + required String activity, + required String type, + required int participants, + required double price, + }) = _Activity; + + factory Activity.fromJson(Map json) => + _$ActivityFromJson(json); +} + +@riverpod +Future activity(ActivityRef ref) async { + final response = await http.get( + Uri.https('www.boredapi.com', '/api/activity'), + ); + + final json = jsonDecode(response.body) as Map; + return Activity.fromJson(Map.from(json)); +} + +class DetailPageView extends ConsumerWidget { + const DetailPageView({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final activity = ref.watch(activityProvider); + + return Scaffold( + appBar: AppBar( + title: const Text('Detail page'), + ), + body: RefreshIndicator( + onRefresh: () => ref.refresh(activityProvider.future), + child: ListView( + children: [ + switch (activity) { + AsyncValue(:final valueOrNull?) => Text(valueOrNull.activity), + AsyncValue(:final error?) => Text('Error: $error'), + _ => const Center(child: CircularProgressIndicator()), + }, + ], + ), + ), + ); + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen/codegen.freezed.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen/codegen.freezed.dart new file mode 100644 index 000000000..1020def96 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen/codegen.freezed.dart @@ -0,0 +1,209 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'codegen.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +Activity _$ActivityFromJson(Map json) { + return _Activity.fromJson(json); +} + +/// @nodoc +mixin _$Activity { + String get activity => throw _privateConstructorUsedError; + String get type => throw _privateConstructorUsedError; + int get participants => throw _privateConstructorUsedError; + double get price => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $ActivityCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ActivityCopyWith<$Res> { + factory $ActivityCopyWith(Activity value, $Res Function(Activity) then) = + _$ActivityCopyWithImpl<$Res, Activity>; + @useResult + $Res call({String activity, String type, int participants, double price}); +} + +/// @nodoc +class _$ActivityCopyWithImpl<$Res, $Val extends Activity> + implements $ActivityCopyWith<$Res> { + _$ActivityCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? activity = null, + Object? type = null, + Object? participants = null, + Object? price = null, + }) { + return _then(_value.copyWith( + activity: null == activity + ? _value.activity + : activity // ignore: cast_nullable_to_non_nullable + as String, + type: null == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as String, + participants: null == participants + ? _value.participants + : participants // ignore: cast_nullable_to_non_nullable + as int, + price: null == price + ? _value.price + : price // ignore: cast_nullable_to_non_nullable + as double, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$ActivityImplCopyWith<$Res> + implements $ActivityCopyWith<$Res> { + factory _$$ActivityImplCopyWith( + _$ActivityImpl value, $Res Function(_$ActivityImpl) then) = + __$$ActivityImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String activity, String type, int participants, double price}); +} + +/// @nodoc +class __$$ActivityImplCopyWithImpl<$Res> + extends _$ActivityCopyWithImpl<$Res, _$ActivityImpl> + implements _$$ActivityImplCopyWith<$Res> { + __$$ActivityImplCopyWithImpl( + _$ActivityImpl _value, $Res Function(_$ActivityImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? activity = null, + Object? type = null, + Object? participants = null, + Object? price = null, + }) { + return _then(_$ActivityImpl( + activity: null == activity + ? _value.activity + : activity // ignore: cast_nullable_to_non_nullable + as String, + type: null == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as String, + participants: null == participants + ? _value.participants + : participants // ignore: cast_nullable_to_non_nullable + as int, + price: null == price + ? _value.price + : price // ignore: cast_nullable_to_non_nullable + as double, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$ActivityImpl implements _Activity { + _$ActivityImpl( + {required this.activity, + required this.type, + required this.participants, + required this.price}); + + factory _$ActivityImpl.fromJson(Map json) => + _$$ActivityImplFromJson(json); + + @override + final String activity; + @override + final String type; + @override + final int participants; + @override + final double price; + + @override + String toString() { + return 'Activity(activity: $activity, type: $type, participants: $participants, price: $price)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ActivityImpl && + (identical(other.activity, activity) || + other.activity == activity) && + (identical(other.type, type) || other.type == type) && + (identical(other.participants, participants) || + other.participants == participants) && + (identical(other.price, price) || other.price == price)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => + Object.hash(runtimeType, activity, type, participants, price); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$ActivityImplCopyWith<_$ActivityImpl> get copyWith => + __$$ActivityImplCopyWithImpl<_$ActivityImpl>(this, _$identity); + + @override + Map toJson() { + return _$$ActivityImplToJson( + this, + ); + } +} + +abstract class _Activity implements Activity { + factory _Activity( + {required final String activity, + required final String type, + required final int participants, + required final double price}) = _$ActivityImpl; + + factory _Activity.fromJson(Map json) = + _$ActivityImpl.fromJson; + + @override + String get activity; + @override + String get type; + @override + int get participants; + @override + double get price; + @override + @JsonKey(ignore: true) + _$$ActivityImplCopyWith<_$ActivityImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen/codegen.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen/codegen.g.dart new file mode 100644 index 000000000..69ee1d982 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen/codegen.g.dart @@ -0,0 +1,46 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$ActivityImpl _$$ActivityImplFromJson(Map json) => + _$ActivityImpl( + activity: json['activity'] as String, + type: json['type'] as String, + participants: json['participants'] as int, + price: (json['price'] as num).toDouble(), + ); + +Map _$$ActivityImplToJson(_$ActivityImpl instance) => + { + 'activity': instance.activity, + 'type': instance.type, + 'participants': instance.participants, + 'price': instance.price, + }; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$activityHash() => r'c73d0af18bcf7072f6a5a913b0b272649fb99a81'; + +/// See also [activity]. +@ProviderFor(activity) +final activityProvider = AutoDisposeFutureProvider.internal( + activity, + name: r'activityProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$activityHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ActivityRef = AutoDisposeFutureProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen/index.ts b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen/index.ts new file mode 100644 index 000000000..9d50c6614 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen/index.ts @@ -0,0 +1,4 @@ +import raw from '!!raw-loader!./raw.dart'; +import codegen from '!!raw-loader!./codegen.dart'; + +export default { raw, codegen }; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen/raw.dart new file mode 100644 index 000000000..b3ad1faaf --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen/raw.dart @@ -0,0 +1,65 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:http/http.dart' as http; + +/* SNIPPET START */ +class Activity { + Activity({ + required this.activity, + required this.type, + required this.participants, + required this.price, + }); + + factory Activity.fromJson(Map json) { + return Activity( + activity: json['activity']! as String, + type: json['type']! as String, + participants: json['participants']! as int, + price: json['price']! as double, + ); + } + + final String activity; + final String type; + final int participants; + final double price; +} + +final activityProvider = FutureProvider.autoDispose((ref) async { + final response = await http.get( + Uri.https('www.boredapi.com', '/api/activity'), + ); + + final json = jsonDecode(response.body) as Map; + return Activity.fromJson(json); +}); + +class DetailPageView extends ConsumerWidget { + const DetailPageView({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final activity = ref.watch(activityProvider); + + return Scaffold( + appBar: AppBar( + title: const Text('Detail page'), + ), + body: RefreshIndicator( + onRefresh: () => ref.refresh(activityProvider.future), + child: ListView( + children: [ + switch (activity) { + AsyncValue(:final valueOrNull?) => Text(valueOrNull.activity), + AsyncValue(:final error?) => Text('Error: $error'), + _ => const Center(child: CircularProgressIndicator()), + }, + ], + ), + ), + ); + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_cancel/codegen.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_cancel/codegen.dart new file mode 100644 index 000000000..3d5dff27d --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_cancel/codegen.dart @@ -0,0 +1,28 @@ +import 'dart:convert'; + +import 'package:http/http.dart' as http; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../detail_screen/codegen.dart'; + +part 'codegen.g.dart'; + +/* SNIPPET START */ +@riverpod +Future activity(ActivityRef ref) async { + // package:http를 사용하여 HTTP 클라이언트를 생성합니다. + final client = http.Client(); + // dispose 시 클라이언트를 닫습니다. + // 그러면 클라이언트에 있을 수 있는 모든 보류 중인 요청이 취소됩니다. + ref.onDispose(client.close); + + // 이제 "get" 함수 대신 클라이언트를 사용하여 요청을 수행합니다. + final response = await client.get( + Uri.https('www.boredapi.com', '/api/activity'), + ); + + // 나머지 코드는 이전과 동일합니다. + final json = jsonDecode(response.body) as Map; + return Activity.fromJson(Map.from(json)); +} +/* SNIPPET END */ diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_cancel/codegen.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_cancel/codegen.g.dart new file mode 100644 index 000000000..d8ec45ada --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_cancel/codegen.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$activityHash() => r'304864a6b8051925061a2bba397574ec45b94d08'; + +/// See also [activity]. +@ProviderFor(activity) +final activityProvider = AutoDisposeFutureProvider.internal( + activity, + name: r'activityProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$activityHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ActivityRef = AutoDisposeFutureProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_cancel/index.ts b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_cancel/index.ts new file mode 100644 index 000000000..9d50c6614 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_cancel/index.ts @@ -0,0 +1,4 @@ +import raw from '!!raw-loader!./raw.dart'; +import codegen from '!!raw-loader!./codegen.dart'; + +export default { raw, codegen }; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_cancel/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_cancel/raw.dart new file mode 100644 index 000000000..2bf5a4d5e --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_cancel/raw.dart @@ -0,0 +1,25 @@ +import 'dart:convert'; + +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:http/http.dart' as http; + +import '../detail_screen/codegen.dart'; + +/* SNIPPET START */ +final activityProvider = FutureProvider.autoDispose((ref) async { + // We create an HTTP client using package:http + final client = http.Client(); + // On dispose, we close the client. + // This will cancel any pending request that the client might have. + ref.onDispose(client.close); + + // We now use the client to make the request instead of the "get" function. + final response = await client.get( + Uri.https('www.boredapi.com', '/api/activity'), + ); + + // The rest of the code is the same as before + final json = jsonDecode(response.body) as Map; + return Activity.fromJson(Map.from(json)); +}); +/* SNIPPET END */ diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_debounce/codegen.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_debounce/codegen.dart new file mode 100644 index 000000000..1dacaaeca --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_debounce/codegen.dart @@ -0,0 +1,39 @@ +import 'dart:convert'; + +import 'package:http/http.dart' as http; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../detail_screen/codegen.dart'; + +part 'codegen.g.dart'; + +/* SNIPPET START */ +@riverpod +Future activity(ActivityRef ref) async { + // provider가 현재 폐기(disposed)되었는지 여부를 캡처합니다. + var didDispose = false; + ref.onDispose(() => didDispose = true); + + // 사용자가 새로 고침(refreshing)을 중단할 때까지 기다리기 위해 + // 요청을 500밀리초 지연합니다. + await Future.delayed(const Duration(milliseconds: 500)); + + // 지연 중에 provider가 폐기(disposed)되었다면 사용자가 다시 새로고침했다는 의미입니다. + // 예외를 던져 요청을 취소합니다. + // Riverpod에 의해 잡히므로 여기서 예외를 사용하는 것이 안전합니다. + if (didDispose) { + throw Exception('Cancelled'); + } + + // 다음 코드는 이전 스니펫에서 변경되지 않았습니다. + final client = http.Client(); + ref.onDispose(client.close); + + final response = await client.get( + Uri.https('www.boredapi.com', '/api/activity'), + ); + + final json = jsonDecode(response.body) as Map; + return Activity.fromJson(Map.from(json)); +} +/* SNIPPET END */ diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_debounce/codegen.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_debounce/codegen.g.dart new file mode 100644 index 000000000..6d0d3104b --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_debounce/codegen.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$activityHash() => r'ef908e3b46693862f082769663b14d5369d6e155'; + +/// See also [activity]. +@ProviderFor(activity) +final activityProvider = AutoDisposeFutureProvider.internal( + activity, + name: r'activityProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$activityHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ActivityRef = AutoDisposeFutureProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_debounce/index.ts b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_debounce/index.ts new file mode 100644 index 000000000..9d50c6614 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_debounce/index.ts @@ -0,0 +1,4 @@ +import raw from '!!raw-loader!./raw.dart'; +import codegen from '!!raw-loader!./codegen.dart'; + +export default { raw, codegen }; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_debounce/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_debounce/raw.dart new file mode 100644 index 000000000..624842ecf --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_debounce/raw.dart @@ -0,0 +1,35 @@ +import 'dart:convert'; + +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:http/http.dart' as http; + +import '../detail_screen/codegen.dart'; + +/* SNIPPET START */ +final activityProvider = FutureProvider.autoDispose((ref) async { + // We capture whether the provider is currently disposed or not. + var didDispose = false; + ref.onDispose(() => didDispose = true); + + // We delay the request by 500ms, to wait for the user to stop refreshing. + await Future.delayed(const Duration(milliseconds: 500)); + + // If the provider was disposed during the delay, it means that the user + // refreshed again. We throw an exception to cancel the request. + // It is safe to use an exception here, as it will be caught by Riverpod. + if (didDispose) { + throw Exception('Cancelled'); + } + + // The following code is unchanged from the previous snippet + final client = http.Client(); + ref.onDispose(client.close); + + final response = await client.get( + Uri.https('www.boredapi.com', '/api/activity'), + ); + + final json = jsonDecode(response.body) as Map; + return Activity.fromJson(Map.from(json)); +}); +/* SNIPPET END */ diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/extension.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/extension.dart new file mode 100644 index 000000000..02f04dcc3 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/extension.dart @@ -0,0 +1,31 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:http/http.dart' as http; + +/* SNIPPET START */ +extension DebounceAndCancelExtension on Ref { + /// [duration] (기본값은 500ms) 동안 기다린 다음 요청에 사용할 수 있는 [http.Client]를 반환합니다. + /// + /// 해당 클라이언트는 provider가 폐기되면 자동으로 닫힙니다. + Future getDebouncedHttpClient([Duration? duration]) async { + // 먼저 디바운싱을 처리합니다. + var didDispose = false; + onDispose(() => didDispose = true); + + // 사용자가 새로 고침을 중단할 때까지 기다리기 위해 요청을 500밀리초 지연합니다. + await Future.delayed(duration ?? const Duration(milliseconds: 500)); + + // 지연 중에 provider가 폐기(disposed)되었다면 사용자가 다시 새로고침했다는 의미입니다. + // 예외를 던져 요청을 취소합니다. + // 리버포드에 의해 포착되므로 여기서 예외를 사용하는 것이 안전합니다. + if (didDispose) { + throw Exception('Cancelled'); + } + + // 이제 클라이언트를 생성하고 provider가 폐기되면 클라이언트를 닫습니다. + final client = http.Client(); + onDispose(client.close); + + // 마지막으로 클라이언트를 반환하여 provider가 요청을 할 수 있도록 합니다. + return client; + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/home_screen.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/home_screen.dart new file mode 100644 index 000000000..28f0c50cf --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/home_screen.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'detail_screen/codegen.dart'; + +/* SNIPPET START */ +void main() => runApp(const ProviderScope(child: MyApp())); + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + routes: { + '/detail-page': (_) => const DetailPageView(), + }, + home: const ActivityView(), + ); + } +} + +class ActivityView extends ConsumerWidget { + const ActivityView({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Scaffold( + appBar: AppBar(title: const Text('Home screen')), + body: const Center( + child: Text('Click the button to open the detail page'), + ), + floatingActionButton: FloatingActionButton( + onPressed: () => Navigator.of(context).pushNamed('/detail-page'), + child: const Icon(Icons.add), + ), + ); + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/provider_with_extension/codegen.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/provider_with_extension/codegen.dart new file mode 100644 index 000000000..2868bf735 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/provider_with_extension/codegen.dart @@ -0,0 +1,25 @@ +import 'dart:convert'; + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../detail_screen/codegen.dart'; +import '../extension.dart'; + +part 'codegen.g.dart'; + +/* SNIPPET START */ +@riverpod +Future activity(ActivityRef ref) async { + // 앞서 만든 확장자를 사용하여 HTTP 클라이언트를 가져옵니다. + final client = await ref.getDebouncedHttpClient(); + + // 이제 "get" 함수 대신 클라이언트를 사용하여 요청을 수행합니다. + // 사용자가 페이지를 떠나면 요청은 자연스럽게 디바운스(debounced)되고 취소(cancelled)됩니다. + final response = await client.get( + Uri.https('www.boredapi.com', '/api/activity'), + ); + + final json = jsonDecode(response.body) as Map; + return Activity.fromJson(Map.from(json)); +} +/* SNIPPET END */ diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/provider_with_extension/codegen.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/provider_with_extension/codegen.g.dart new file mode 100644 index 000000000..b3407fc98 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/provider_with_extension/codegen.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$activityHash() => r'f045dd6e89fde6bbe12a89f243290d289a3e692d'; + +/// See also [activity]. +@ProviderFor(activity) +final activityProvider = AutoDisposeFutureProvider.internal( + activity, + name: r'activityProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$activityHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ActivityRef = AutoDisposeFutureProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/provider_with_extension/index.ts b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/provider_with_extension/index.ts new file mode 100644 index 000000000..9d50c6614 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/provider_with_extension/index.ts @@ -0,0 +1,4 @@ +import raw from '!!raw-loader!./raw.dart'; +import codegen from '!!raw-loader!./codegen.dart'; + +export default { raw, codegen }; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/provider_with_extension/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/provider_with_extension/raw.dart new file mode 100644 index 000000000..36ef098f7 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/cancel/provider_with_extension/raw.dart @@ -0,0 +1,23 @@ +import 'dart:convert'; + +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../detail_screen/codegen.dart'; +import '../extension.dart'; + +/* SNIPPET START */ +final activityProvider = FutureProvider.autoDispose((ref) async { + // We obtain an HTTP client using the extension we created earlier. + final client = await ref.getDebouncedHttpClient(); + + // We now use the client to make the request instead of the "get" function. + // Our request will naturally be debounced and be cancelled if the user + // leaves the page. + final response = await client.get( + Uri.https('www.boredapi.com', '/api/activity'), + ); + + final json = jsonDecode(response.body) as Map; + return Activity.fromJson(Map.from(json)); +}); +/* SNIPPET END */ diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh.mdx b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh.mdx new file mode 100644 index 000000000..8add0ebe9 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh.mdx @@ -0,0 +1,119 @@ +--- +title: 당겨서 새로고침(Pull to refresh) +--- + +import { Link } from "../../../../../src/components/Link"; +import { AutoSnippet, When } from "../../../../../src/components/CodeSnippet"; +import activity from "./pull_to_refresh/activity"; +import fetchActivity from "./pull_to_refresh/fetch_activity"; +import displayActivity from "!!raw-loader!./pull_to_refresh/display_activity.dart"; +import displayActivity2 from "!!raw-loader!./pull_to_refresh/display_activity2.dart"; +import displayActivity3 from "!!raw-loader!./pull_to_refresh/display_activity3.dart"; +import displayActivity4 from "!!raw-loader!./pull_to_refresh/display_activity4.dart"; +import fullApp from "./pull_to_refresh/full_app"; + +Riverpod은 선언적 성격 덕분에 당겨서 새로고침(pull-to-refresh)를 기본적으로 지원합니다. + +일반적으로 당겨서 새로고침(pull-to-refresh)는 해결해야 할 문제가 많기 때문에 복잡할 수 있습니다: + +- 페이지에 처음 들어갔을 때 스피너(spinner)를 표시하고 싶습니다. + 하지만 새로 고침 중에는 대신 갱신표시기(refresh indicator)를 표시하고 싶습니다. + 갱신표시기(refresh indicator)_와_ 스피너(spinner)를 모두 표시해서는 안 됩니다. +- 새로 고침이 보류 중인 동안 이전 데이터/오류를 표시하고 싶습니다. +- 새로 고침이 진행되는 동안 갱신표시기(refresh indicator)를 표시해야 합니다. + +Riverpod를 사용하여 이 문제를 해결하는 방법을 살펴봅시다. +이를 위해 사용자에게 임의의 액티비티를 추천하는 간단한 예제를 만들어 보겠습니다. +그리고 당겨서 새로고침(pull-to-refresh)를 수행하면 새로운 제안이 트리거됩니다: + +A gif of the previously described application working + +## 기본(bare-bones) 애플리케이션 만들기 + +당겨서 새로고침(pull-to-refresh) 기능을 구현하기 전에 먼저 새로고침할 무언가가 필요합니다. +[Bored API](https://www.boredapi.com/)를 사용하여 사용자에게 임의의 액티비티를 제안하는 간단한 애플리케이션을 만들 수 있습니다. + +먼저 `Activity` 클래스를 정의하겠습니다: + + + +이 클래스는 제안된 액티비티를 타입에 안전한(type-safe) 방식으로 표현하고 JSON 인코딩/디코딩을 처리합니다. +Freezed/json_serializable을 반드시 사용해야 하는 것은 아니지만 권장합니다. + +이제 단일 액티비티를 가져오기 위해 HTTP GET 요청을 하는 provider를 정의하겠습니다: + + + +이제 이 provider를 사용하여 임의의 액티비티를 표시할 수 있습니다. +지금은 로딩/오류 상태를 처리하지 않고 사용 가능한 경우 액티비티만 표시합니다: + + + +## `RefreshIndicator` 추가 + +이제 간단한 애플리케이션을 만들었으니 여기에 `RefreshIndicator`를 추가하면 됩니다. +이 위젯은 사용자가 화면을 아래로 내릴 때 새로고침 표시기(refresh indicator)를 표시하는 공식 머티리얼(Material) 위젯입니다. + +`RefreshIndicator`를 사용하려면 스크롤 가능한 표면(surface)이 필요합니다. +하지만 지금까지는 하나도 없습니다. +`ListView`/`GridView`/`SingleChildScrollView`/등을 사용하면 이 문제를 해결할 수 있습니다: + + + +이제 사용자가 화면을 아래로 내릴 수 있습니다. 하지만 데이터는 아직 새로 고쳐지지 않았습니다. + +## 새로 고침 로직 추가 + +사용자가 화면을 아래로 내리면 `RefreshIndicator`가 `onRefresh` 콜백을 호출합니다. +이 콜백을 사용해 데이터를 새로 고칠 수 있습니다. +여기서 `ref.refresh`를 사용하여 선택한 공급자를 새로 고칠 수 있습니다. + +**참고**: `onRefresh`는 `Future`를 반환할 것으로 예상됩니다. +그리고 새로 고침이 완료될 때 그 future가 완료되는 것이 중요합니다. + +이러한 future를 얻으려면 공급자의 '.future' 속성을 읽으면 됩니다. +그러면 공급자가 해결(resolved)될 때 완료되는 future가 반환됩니다. + +따라서 `RefreshIndicator`를 다음과 같이 업데이트할 수 있습니다: + + + +## 초기 로드 및 오류 처리 중에만 스피너를 표시합니다. + +현재 저희 UI는 오류/로딩 상태를 처리하지 않습니다. +대신 로딩/새로 고침이 완료되면 데이터가 마술처럼 나타납니다. + +이러한 상태를 우아하게 처리하여 이를 변경해 보겠습니다. 두 가지 경우가 있습니다: + +- 초기 로드 중에는 전체 화면 스피너를 표시하고 싶습니다. +- 새로 고침 중에는 새로 고침 표시기와 이전 데이터/오류를 표시하고 싶습니다. + +다행히도 Riverpod에서 비동기 프로바이더를 수신할 때, Riverpod는 필요한 모든 것을 제공하는 `AsyncValue`를 제공합니다. + +이 `AsyncValue`는 다음과 같이 Dart 3.0의 패턴 일치(pattern matching)와 사용할 수 있습니다: + + + +:::caution +여기서는 현재와 같이 `valueOrNull`을 사용하여 에러/로딩 상태인 경우 `value`를 사용합니다. + +Riverpod 3.0에서는 `value`가 `valueOrNull`처럼 동작하도록 변경될 예정입니다. +하지만 지금은 `valueOrNull`을 고수하겠습니다. +::: + +:::tip +패턴 매칭에서 `:final valueOrNull?` 구문이 사용된 것을 주목하세요. +이 구문은 `activityProvider`가 널이 아닌 `Activity`를 반환할 때만 사용할 수 있습니다. + +데이터가 `null`일 수 있는 경우, 대신 `AsyncValue(hasData: true, :final valueOrNull)`를 사용할 수 있습니다. +이렇게 하면 몇 개의 문자가 추가되는 대신 데이터가 `null`인 경우를 올바르게 처리할 수 있습니다. +::: + +## 마무리: 전체 애플리케이션 + +지금까지 다룬 모든 내용을 정리한 소스는 다음과 같습니다: + + diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/activity/codegen.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/activity/codegen.dart new file mode 100644 index 000000000..ac3e1b17d --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/activity/codegen.dart @@ -0,0 +1,19 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'codegen.g.dart'; +part 'codegen.freezed.dart'; + +/* SNIPPET START */ +@freezed +class Activity with _$Activity { + factory Activity({ + required String activity, + required String type, + required int participants, + required double price, + }) = _Activity; + + factory Activity.fromJson(Map json) => + _$ActivityFromJson(json); +} +/* SNIPPET END */ diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/activity/codegen.freezed.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/activity/codegen.freezed.dart new file mode 100644 index 000000000..1020def96 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/activity/codegen.freezed.dart @@ -0,0 +1,209 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'codegen.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +Activity _$ActivityFromJson(Map json) { + return _Activity.fromJson(json); +} + +/// @nodoc +mixin _$Activity { + String get activity => throw _privateConstructorUsedError; + String get type => throw _privateConstructorUsedError; + int get participants => throw _privateConstructorUsedError; + double get price => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $ActivityCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ActivityCopyWith<$Res> { + factory $ActivityCopyWith(Activity value, $Res Function(Activity) then) = + _$ActivityCopyWithImpl<$Res, Activity>; + @useResult + $Res call({String activity, String type, int participants, double price}); +} + +/// @nodoc +class _$ActivityCopyWithImpl<$Res, $Val extends Activity> + implements $ActivityCopyWith<$Res> { + _$ActivityCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? activity = null, + Object? type = null, + Object? participants = null, + Object? price = null, + }) { + return _then(_value.copyWith( + activity: null == activity + ? _value.activity + : activity // ignore: cast_nullable_to_non_nullable + as String, + type: null == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as String, + participants: null == participants + ? _value.participants + : participants // ignore: cast_nullable_to_non_nullable + as int, + price: null == price + ? _value.price + : price // ignore: cast_nullable_to_non_nullable + as double, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$ActivityImplCopyWith<$Res> + implements $ActivityCopyWith<$Res> { + factory _$$ActivityImplCopyWith( + _$ActivityImpl value, $Res Function(_$ActivityImpl) then) = + __$$ActivityImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String activity, String type, int participants, double price}); +} + +/// @nodoc +class __$$ActivityImplCopyWithImpl<$Res> + extends _$ActivityCopyWithImpl<$Res, _$ActivityImpl> + implements _$$ActivityImplCopyWith<$Res> { + __$$ActivityImplCopyWithImpl( + _$ActivityImpl _value, $Res Function(_$ActivityImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? activity = null, + Object? type = null, + Object? participants = null, + Object? price = null, + }) { + return _then(_$ActivityImpl( + activity: null == activity + ? _value.activity + : activity // ignore: cast_nullable_to_non_nullable + as String, + type: null == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as String, + participants: null == participants + ? _value.participants + : participants // ignore: cast_nullable_to_non_nullable + as int, + price: null == price + ? _value.price + : price // ignore: cast_nullable_to_non_nullable + as double, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$ActivityImpl implements _Activity { + _$ActivityImpl( + {required this.activity, + required this.type, + required this.participants, + required this.price}); + + factory _$ActivityImpl.fromJson(Map json) => + _$$ActivityImplFromJson(json); + + @override + final String activity; + @override + final String type; + @override + final int participants; + @override + final double price; + + @override + String toString() { + return 'Activity(activity: $activity, type: $type, participants: $participants, price: $price)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ActivityImpl && + (identical(other.activity, activity) || + other.activity == activity) && + (identical(other.type, type) || other.type == type) && + (identical(other.participants, participants) || + other.participants == participants) && + (identical(other.price, price) || other.price == price)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => + Object.hash(runtimeType, activity, type, participants, price); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$ActivityImplCopyWith<_$ActivityImpl> get copyWith => + __$$ActivityImplCopyWithImpl<_$ActivityImpl>(this, _$identity); + + @override + Map toJson() { + return _$$ActivityImplToJson( + this, + ); + } +} + +abstract class _Activity implements Activity { + factory _Activity( + {required final String activity, + required final String type, + required final int participants, + required final double price}) = _$ActivityImpl; + + factory _Activity.fromJson(Map json) = + _$ActivityImpl.fromJson; + + @override + String get activity; + @override + String get type; + @override + int get participants; + @override + double get price; + @override + @JsonKey(ignore: true) + _$$ActivityImplCopyWith<_$ActivityImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/activity/codegen.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/activity/codegen.g.dart new file mode 100644 index 000000000..79ae3cd00 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/activity/codegen.g.dart @@ -0,0 +1,25 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$ActivityImpl _$$ActivityImplFromJson(Map json) => + _$ActivityImpl( + activity: json['activity'] as String, + type: json['type'] as String, + participants: json['participants'] as int, + price: (json['price'] as num).toDouble(), + ); + +Map _$$ActivityImplToJson(_$ActivityImpl instance) => + { + 'activity': instance.activity, + 'type': instance.type, + 'participants': instance.participants, + 'price': instance.price, + }; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/activity/index.ts b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/activity/index.ts new file mode 100644 index 000000000..9d50c6614 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/activity/index.ts @@ -0,0 +1,4 @@ +import raw from '!!raw-loader!./raw.dart'; +import codegen from '!!raw-loader!./codegen.dart'; + +export default { raw, codegen }; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/activity/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/activity/raw.dart new file mode 100644 index 000000000..42365b7bb --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/activity/raw.dart @@ -0,0 +1,24 @@ +/* SNIPPET START */ +class Activity { + Activity({ + required this.activity, + required this.type, + required this.participants, + required this.price, + }); + + factory Activity.fromJson(Map json) { + return Activity( + activity: json['activity']! as String, + type: json['type']! as String, + participants: json['participants']! as int, + price: json['price']! as double, + ); + } + + final String activity; + final String type; + final int participants; + final double price; +} +/* SNIPPET END */ diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/display_activity.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/display_activity.dart new file mode 100644 index 000000000..8313781ca --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/display_activity.dart @@ -0,0 +1,22 @@ +// ignore_for_file: use_key_in_widget_constructors + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'fetch_activity/codegen.dart'; + +/* SNIPPET START */ +class ActivityView extends ConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + final activity = ref.watch(activityProvider); + + return Scaffold( + appBar: AppBar(title: const Text('Pull to refresh')), + body: Center( + // 액티비티가 있으면 표시하고, 그렇지 않으면 대기합니다. + child: Text(activity.valueOrNull?.activity ?? ''), + ), + ); + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/display_activity2.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/display_activity2.dart new file mode 100644 index 000000000..7394ea664 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/display_activity2.dart @@ -0,0 +1,28 @@ +// ignore_for_file: avoid_print, use_key_in_widget_constructors + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'fetch_activity/codegen.dart'; + +/* SNIPPET START */ +class ActivityView extends ConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + final activity = ref.watch(activityProvider); + + return Scaffold( + appBar: AppBar(title: const Text('Pull to refresh')), + /* highlight-start */ + body: RefreshIndicator( + onRefresh: () async => print('refresh'), + child: ListView( + children: [ + /* highlight-end */ + Text(activity.valueOrNull?.activity ?? ''), + ], + ), + ), + ); + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/display_activity3.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/display_activity3.dart new file mode 100644 index 000000000..17e661a0c --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/display_activity3.dart @@ -0,0 +1,31 @@ +// ignore_for_file: avoid_print, use_key_in_widget_constructors + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'fetch_activity/codegen.dart'; + +/* SNIPPET START */ +class ActivityView extends ConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + final activity = ref.watch(activityProvider); + + return Scaffold( + appBar: AppBar(title: const Text('Pull to refresh')), + body: RefreshIndicator( + // "activityProvider.future"를 새로 고침으로, 해당 결과를 반환하면, + // 새 액티비티를 가져올 때까지 + // 새로 고침 표시기(refresh indicator)가 계속 표시됩니다. + /* highlight-start */ + onRefresh: () => ref.refresh(activityProvider.future), + /* highlight-end */ + child: ListView( + children: [ + Text(activity.valueOrNull?.activity ?? ''), + ], + ), + ), + ); + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/display_activity4.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/display_activity4.dart new file mode 100644 index 000000000..54564c4c0 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/display_activity4.dart @@ -0,0 +1,35 @@ +// ignore_for_file: avoid_print, use_key_in_widget_constructors + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'activity/codegen.dart'; +import 'fetch_activity/codegen.dart'; + +/* SNIPPET START */ +class ActivityView extends ConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + final activity = ref.watch(activityProvider); + + return Scaffold( + appBar: AppBar(title: const Text('Pull to refresh')), + body: RefreshIndicator( + onRefresh: () => ref.refresh(activityProvider.future), + child: ListView( + children: [ + switch (activity) { + // 일부 데이터를 사용할 수 있는 경우 해당 데이터를 표시합니다. + // 새로 고침 중에도 데이터를 계속 사용할 수 있습니다. + AsyncValue(:final valueOrNull?) => Text(valueOrNull.activity), + // 오류를 사용할 수 있으므로 렌더링합니다. + AsyncValue(:final error?) => Text('Error: $error'), + // 데이터/오류가 없으므로 로딩 상태입니다. + _ => const CircularProgressIndicator(), + }, + ], + ), + ), + ); + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/fetch_activity/codegen.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/fetch_activity/codegen.dart new file mode 100644 index 000000000..0cec67cba --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/fetch_activity/codegen.dart @@ -0,0 +1,20 @@ +import 'dart:convert'; + +import 'package:http/http.dart' as http; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../activity/codegen.dart'; + +part 'codegen.g.dart'; + +/* SNIPPET START */ +@riverpod +Future activity(ActivityRef ref) async { + final response = await http.get( + Uri.https('www.boredapi.com', '/api/activity'), + ); + + final json = jsonDecode(response.body) as Map; + return Activity.fromJson(Map.from(json)); +} +/* SNIPPET END */ diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/fetch_activity/codegen.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/fetch_activity/codegen.g.dart new file mode 100644 index 000000000..16aa62d6b --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/fetch_activity/codegen.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$activityHash() => r'c73d0af18bcf7072f6a5a913b0b272649fb99a81'; + +/// See also [activity]. +@ProviderFor(activity) +final activityProvider = AutoDisposeFutureProvider.internal( + activity, + name: r'activityProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$activityHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ActivityRef = AutoDisposeFutureProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/fetch_activity/index.ts b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/fetch_activity/index.ts new file mode 100644 index 000000000..9d50c6614 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/fetch_activity/index.ts @@ -0,0 +1,4 @@ +import raw from '!!raw-loader!./raw.dart'; +import codegen from '!!raw-loader!./codegen.dart'; + +export default { raw, codegen }; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/fetch_activity/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/fetch_activity/raw.dart new file mode 100644 index 000000000..1cec3b128 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/fetch_activity/raw.dart @@ -0,0 +1,17 @@ +import 'dart:convert'; + +import 'package:http/http.dart' as http; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../activity/raw.dart'; + +/* SNIPPET START */ +final activityProvider = FutureProvider.autoDispose((ref) async { + final response = await http.get( + Uri.https('www.boredapi.com', '/api/activity'), + ); + + final json = jsonDecode(response.body) as Map; + return Activity.fromJson(json); +}); +/* SNIPPET END */ diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/full_app/codegen.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/full_app/codegen.dart new file mode 100644 index 000000000..b7d53c744 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/full_app/codegen.dart @@ -0,0 +1,69 @@ +// ignore_for_file: use_key_in_widget_constructors, unreachable_from_main + +/* SNIPPET START */ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:http/http.dart' as http; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; +part 'codegen.freezed.dart'; + +void main() => runApp(ProviderScope(child: MyApp())); + +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MaterialApp(home: ActivityView()); + } +} + +class ActivityView extends ConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + final activity = ref.watch(activityProvider); + + return Scaffold( + appBar: AppBar(title: const Text('Pull to refresh')), + body: RefreshIndicator( + onRefresh: () => ref.refresh(activityProvider.future), + child: ListView( + children: [ + switch (activity) { + AsyncValue(:final valueOrNull?) => + Text(valueOrNull.activity), + AsyncValue(:final error?) => Text('Error: $error'), + _ => const CircularProgressIndicator(), + }, + ], + ), + ), + ); + } +} + +@riverpod +Future activity(ActivityRef ref) async { + final response = await http.get( + Uri.https('www.boredapi.com', '/api/activity'), + ); + + final json = jsonDecode(response.body) as Map; + return Activity.fromJson(Map.from(json)); +} + +@freezed +class Activity with _$Activity { + factory Activity({ + required String activity, + required String type, + required int participants, + required double price, + }) = _Activity; + + factory Activity.fromJson(Map json) => + _$ActivityFromJson(json); +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/full_app/codegen.freezed.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/full_app/codegen.freezed.dart new file mode 100644 index 000000000..1020def96 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/full_app/codegen.freezed.dart @@ -0,0 +1,209 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'codegen.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +Activity _$ActivityFromJson(Map json) { + return _Activity.fromJson(json); +} + +/// @nodoc +mixin _$Activity { + String get activity => throw _privateConstructorUsedError; + String get type => throw _privateConstructorUsedError; + int get participants => throw _privateConstructorUsedError; + double get price => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $ActivityCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ActivityCopyWith<$Res> { + factory $ActivityCopyWith(Activity value, $Res Function(Activity) then) = + _$ActivityCopyWithImpl<$Res, Activity>; + @useResult + $Res call({String activity, String type, int participants, double price}); +} + +/// @nodoc +class _$ActivityCopyWithImpl<$Res, $Val extends Activity> + implements $ActivityCopyWith<$Res> { + _$ActivityCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? activity = null, + Object? type = null, + Object? participants = null, + Object? price = null, + }) { + return _then(_value.copyWith( + activity: null == activity + ? _value.activity + : activity // ignore: cast_nullable_to_non_nullable + as String, + type: null == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as String, + participants: null == participants + ? _value.participants + : participants // ignore: cast_nullable_to_non_nullable + as int, + price: null == price + ? _value.price + : price // ignore: cast_nullable_to_non_nullable + as double, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$ActivityImplCopyWith<$Res> + implements $ActivityCopyWith<$Res> { + factory _$$ActivityImplCopyWith( + _$ActivityImpl value, $Res Function(_$ActivityImpl) then) = + __$$ActivityImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String activity, String type, int participants, double price}); +} + +/// @nodoc +class __$$ActivityImplCopyWithImpl<$Res> + extends _$ActivityCopyWithImpl<$Res, _$ActivityImpl> + implements _$$ActivityImplCopyWith<$Res> { + __$$ActivityImplCopyWithImpl( + _$ActivityImpl _value, $Res Function(_$ActivityImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? activity = null, + Object? type = null, + Object? participants = null, + Object? price = null, + }) { + return _then(_$ActivityImpl( + activity: null == activity + ? _value.activity + : activity // ignore: cast_nullable_to_non_nullable + as String, + type: null == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as String, + participants: null == participants + ? _value.participants + : participants // ignore: cast_nullable_to_non_nullable + as int, + price: null == price + ? _value.price + : price // ignore: cast_nullable_to_non_nullable + as double, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$ActivityImpl implements _Activity { + _$ActivityImpl( + {required this.activity, + required this.type, + required this.participants, + required this.price}); + + factory _$ActivityImpl.fromJson(Map json) => + _$$ActivityImplFromJson(json); + + @override + final String activity; + @override + final String type; + @override + final int participants; + @override + final double price; + + @override + String toString() { + return 'Activity(activity: $activity, type: $type, participants: $participants, price: $price)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ActivityImpl && + (identical(other.activity, activity) || + other.activity == activity) && + (identical(other.type, type) || other.type == type) && + (identical(other.participants, participants) || + other.participants == participants) && + (identical(other.price, price) || other.price == price)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => + Object.hash(runtimeType, activity, type, participants, price); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$ActivityImplCopyWith<_$ActivityImpl> get copyWith => + __$$ActivityImplCopyWithImpl<_$ActivityImpl>(this, _$identity); + + @override + Map toJson() { + return _$$ActivityImplToJson( + this, + ); + } +} + +abstract class _Activity implements Activity { + factory _Activity( + {required final String activity, + required final String type, + required final int participants, + required final double price}) = _$ActivityImpl; + + factory _Activity.fromJson(Map json) = + _$ActivityImpl.fromJson; + + @override + String get activity; + @override + String get type; + @override + int get participants; + @override + double get price; + @override + @JsonKey(ignore: true) + _$$ActivityImplCopyWith<_$ActivityImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/full_app/codegen.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/full_app/codegen.g.dart new file mode 100644 index 000000000..69ee1d982 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/full_app/codegen.g.dart @@ -0,0 +1,46 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$ActivityImpl _$$ActivityImplFromJson(Map json) => + _$ActivityImpl( + activity: json['activity'] as String, + type: json['type'] as String, + participants: json['participants'] as int, + price: (json['price'] as num).toDouble(), + ); + +Map _$$ActivityImplToJson(_$ActivityImpl instance) => + { + 'activity': instance.activity, + 'type': instance.type, + 'participants': instance.participants, + 'price': instance.price, + }; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$activityHash() => r'c73d0af18bcf7072f6a5a913b0b272649fb99a81'; + +/// See also [activity]. +@ProviderFor(activity) +final activityProvider = AutoDisposeFutureProvider.internal( + activity, + name: r'activityProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$activityHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ActivityRef = AutoDisposeFutureProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/full_app/index.ts b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/full_app/index.ts new file mode 100644 index 000000000..9d50c6614 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/full_app/index.ts @@ -0,0 +1,4 @@ +import raw from '!!raw-loader!./raw.dart'; +import codegen from '!!raw-loader!./codegen.dart'; + +export default { raw, codegen }; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/full_app/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/full_app/raw.dart new file mode 100644 index 000000000..c4186fb31 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/full_app/raw.dart @@ -0,0 +1,73 @@ +// ignore_for_file: use_key_in_widget_constructors, unreachable_from_main + +/* SNIPPET START */ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:http/http.dart' as http; + +void main() => runApp(ProviderScope(child: MyApp())); + +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MaterialApp(home: ActivityView()); + } +} + +class ActivityView extends ConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + final activity = ref.watch(activityProvider); + + return Scaffold( + appBar: AppBar(title: const Text('Pull to refresh')), + body: RefreshIndicator( + onRefresh: () => ref.refresh(activityProvider.future), + child: ListView( + children: [ + switch (activity) { + AsyncValue(:final valueOrNull?) => + Text(valueOrNull.activity), + AsyncValue(:final error?) => Text('Error: $error'), + _ => const CircularProgressIndicator(), + }, + ], + ), + ), + ); + } +} + +final activityProvider = FutureProvider.autoDispose((ref) async { + final response = await http.get( + Uri.https('www.boredapi.com', '/api/activity'), + ); + + final json = jsonDecode(response.body) as Map; + return Activity.fromJson(json); +}); + +class Activity { + Activity({ + required this.activity, + required this.type, + required this.participants, + required this.price, + }); + + factory Activity.fromJson(Map json) { + return Activity( + activity: json['activity']! as String, + type: json['type']! as String, + participants: json['participants']! as int, + price: json['price']! as double, + ); + } + + final String activity; + final String type; + final int participants; + final double price; +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_code_generation.mdx b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_code_generation.mdx new file mode 100644 index 000000000..6d3046125 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_code_generation.mdx @@ -0,0 +1,355 @@ +--- +title: 코드 생성(Code generation)에 대한 정보 +version: 1 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; +import CodeBlock from "@theme/CodeBlock"; +import fetchUser from "!!raw-loader!./about_codegen/main.dart"; +import rawFetchUser from "!!raw-loader!./about_codegen/raw.dart"; +import { Link } from "../../../../../src/components/Link"; +import { trimSnippet, CodeSnippet } from "../../../../../src/components/CodeSnippet"; +import syncFn from "!!raw-loader!./about_codegen/provider_type/sync_fn.dart"; +import syncClass from "!!raw-loader!./about_codegen/provider_type/sync_class.dart"; +import asyncFnFuture from "!!raw-loader!./about_codegen/provider_type/async_fn_future.dart"; +import asyncClassFuture from "!!raw-loader!./about_codegen/provider_type/async_class_future.dart"; +import asyncFnStream from "!!raw-loader!./about_codegen/provider_type/async_fn_stream.dart"; +import asyncClassStream from "!!raw-loader!./about_codegen/provider_type/async_class_stream.dart"; +import familyFn from "!!raw-loader!./about_codegen/provider_type/family_fn.dart"; +import familyClass from "!!raw-loader!./about_codegen/provider_type/family_class.dart"; +import provider from "!!raw-loader!./about_codegen/provider_type/non_code_gen/provider.dart"; +import notifierProvider from "!!raw-loader!./about_codegen/provider_type/non_code_gen/notifier_provider.dart"; +import futureProvider from "!!raw-loader!./about_codegen/provider_type/non_code_gen/future_provider.dart"; +import asyncNotifierProvider from "!!raw-loader!./about_codegen/provider_type/non_code_gen/async_notifier_provider.dart"; +import streamProvider from "!!raw-loader!./about_codegen/provider_type/non_code_gen/stream_provider.dart"; +import streamNotifierProvider from "!!raw-loader!./about_codegen/provider_type/non_code_gen/stream_notifier_provider.dart"; +import autoDisposeCodeGen from "!!raw-loader!./about_codegen/provider_type/auto_dispose.dart"; +import autoDisposeNonCodeGen from "!!raw-loader!./about_codegen/provider_type/non_code_gen/auto_dispose.dart"; +import familyCodeGen from "!!raw-loader!./about_codegen/provider_type/family.dart"; +import familyNonCodeGen from "!!raw-loader!./about_codegen/provider_type/non_code_gen/family.dart"; +const TRANSPARENT_STYLE = { backgroundColor: "transparent" }; +const RED_STYLE = { color: "indianred", fontWeight: "700" }; +const BLUE_STYLE = { color: "rgb(103, 134, 196)", fontWeight: "700" }; +const FONT_16_STYLE = { + fontSize: "16px", + fontWeight: "700", +}; +const BLUE_20_STYLE = { + color: "rgb(103, 134, 196)", + fontSize: "20px", + fontWeight: "700", +}; +const PROVIDER_STYLE = { + textAlign: "center", + fontWeight: "600", + maxWidth: "210px", +}; +const BEFORE_STYLE = { + minWidth: "120px", + textAlign: "center", + fontWeight: "600", + color: "crimson", +}; +const AFTER_STYLE = { + minWidth: "120px", + textAlign: "center", + fontWeight: "600", + color: "rgb(40,180,40)", +}; + +코드 생성은 도구를 사용하여 코드를 생성하는 것을 말합니다. +Dart에서는 애플리케이션을 'Compile'하는 데 추가 단계가 필요하다는 단점이 있습니다. +이 문제는 Dart 팀이 이 문제를 해결하려는 잠재적인 해결방안을 연구하고 있기 때문에 곧 해결될 수도 있습니다. + +Riverpod 환경(Context)에서의 코드 생성은 "provider"를 선언하는 문법을 약간 변경하는 것을 의미합니다: + +{trimSnippet(rawFetchUser)} + +코드 생성을 사용하면 다음과 같이 작성할 수 있습니다: + +{trimSnippet(fetchUser)} + +Riverpod 사용 시 코드 생성은 완전히 선택 사항입니다. +코드 생성을 하지 않고도 Riverpod을 사용할 수 있습니다. 동시에 Riverpod은 코드 생성을 수용하며 사용을 권장합니다. + +Riverpod의 코드 생성을 사용하려면, 페이지를 참조하십시오. +문서의 사이드바에서 코드 생성(Code Generation)을 활성화해야 합니다. + +## 코드 생성을 사용해야 하나요? + +코드 생성은 Riverpod에서 선택 사항입니다. +이를 염두에 두고, 코드 생성을 사용해야 할지 말지 궁금할 수 있습니다. + +답변은 **대부분의 경우 예** 입니다. +코드 생성을 사용하는 것이 Riverpod을 사용하는 권장 방법입니다. +이는 보다 미래 지향적인 접근방식(future-proof approach)이며, Riverpod의 잠재력을 최대한 활용할 수 있게 해줍니다. +동시에 많은 애플리케이션에서 이미 [Freezed](https://pub.dev/packages/freezed)나 [json_serializable](https://pub.dev/packages/json_serializable)와 같은 패키지를 사용하여 코드 생성을 사용하고 있습니다. +이 경우, 프로젝트가 이미 코드 생성을 위해 설정되어 있으며, Riverpod을 사용하는 것은 간단합니다. + +현재 코드 생성은 `build_runner`를 많은 사람이 싫어하기 때문에 선택 사항입니다. +하지만, [Static Metaprogramming](https://github.com/dart-lang/language/issues/1482)이 Dart에서 사용 가능해지면, +`build_runner`는 더 이상 문제가 되지 않을 것입니다. +그 때부터는 Riverpod에 코드 생성 문법만 유일한 문법이 될 것입니다. + +`build_runner`를 사용하는 것이 거부감이 든다면, 코드 생성을 사용하지 않는 것을 고려할 수 있습니다. +하지만, 이 경우에는 일부 기능을 사용할 수 없으며, 미래에 코드 생성으로 마이그레이션해야 합니다. +이 때 Riverpod은 마이그레이션을 가능한 한 원활하게 진행할 수 있도록 마이그레이션 도구를 제공할 것입니다. + +## 코드 생성을 사용하면 어떤 이점이 있나요? + +아마 궁금하실 겁니다: "Riverpod에서 코드 생성은 선택 사항이면, 왜 사용해야 하지?" + +패키지는 언제나 그렇듯: 사용자의 삶을 더 쉽게 만들기 위한 것입니다. 이는 다음 내용이 포함되지많 여기에만 국한되지는 않습니다: + +- 더 나은 문법, 더 읽기 쉽고 유연하며, 학습 곡선이 낮습니다. + - provider의 타입을 직접 지정할 필요가 없습니다. 로직을 작성하고, Riverpod이 가장 적합한 provider를 선택합니다. + - 문법이 "더러운 전역 변수(dirty global variable)"를 정의하는 것처럼 보이지 않습니다. 대신, 사용자 정의 함수/클래스를 정의합니다. + - provider에 매개변수를 전달하는 것이 더 이상 제한되지 않습니다. + 를 사용하여 단일 위치 매개변수(single positional parameter)를 전달하는 것이 아니라, + 모든 매개변수를 전달할 수 있습니다. 이는 명명된 매개변수(named parameters), 선택적(optional) 매개변수, 기본값(default values)도 포함합니다. +- Riverpod에서 작성한 코드는 **상태를 가지는 핫 리로드(stateful hot-reload)**를 지원합니다. +- 더 나은 디버깅을 위해 추가 메타데이터를 생성하고 디버거가 수집합니다. +- 일부 Riverpod 기능은 코드 생성을 통해서만 사용할 수 있습니다. + +## 문법 + +### provider 정의하기: + +코드 생성을 사용하여 provider를 정의할 때 아래 사항을 알아 두면 도움이 됩니다: + +- provider는 어노테이션 function 또는 어노테이션 class로 정의할 수 있습니다. + 둘은 거의 동일하지만 class 기반 provider는 외부 객체가 provider의 상태를 수정할 수 있는 공용 메서드(Public Method)를 포함할 수 있다는 장점이 있습니다(부수적인 효과). + 함수형 provider는 빌드 메서드만으로 클래스 기반 provider를 작성하기 위한 달콤한 문법(Sugar syntax)이며, 따라서 UI에서 수정할 수 없습니다. +- 모든 Dart async primitives(Future, FutureOr, Stream)가 지원됩니다. +- 함수가 async로 표시되면, provider는 오류(errors)/로딩(loading) 상태를 자동으로 처리하고 AsyncValue를 노출합니다. + + + + + + + + + + + + + + + + + + + + + + + +
+ Functional +
+ (공개(Public) 메소드로 +
+ 부가작업(Side-Effects)을 처리할 수 없습니다) +
+ Class-Based +
+ (공개(Public) 메소드로 +
+ 부가작업(Side-Effects)을 처리할 수 있습니다) +
+ + Sync + + + {trimSnippet(syncFn)} + + {trimSnippet(syncClass)} +
+ + Async - Future + + + {trimSnippet(asyncFnFuture)} + + {trimSnippet(asyncClassFuture)} +
+ + Async - Stream + + + {trimSnippet(asyncFnStream)} + + {trimSnippet(asyncClassStream)} +
+ +### 자동폐기(autoDispose) 활성화/비활성화: + +코드 생성을 사용할때, provider는 기본적으로 autoDispose가 활성화됩니다. +코드 생성을 사용할 때 공급자는 기본적으로 자동 폐기됩니다. +즉, 연결(ref.watch/ref.listen)된 리스너가 없을 때 자동으로 폐기됩니다. +이 기본 설정은 Riverpod의 철학에 더 잘 부합합니다. +코드 생성하지 않는 버전을 사용하면, `package:provider`에서 마이그레이션하는 사용자를 위해 자동폐기(autoDispose) 기능이 기본적으로 꺼져 있었습니다. + +자동폐기(autoDispose)를 비활성화하려면, `autoDispose: false`를 어노테이션에 전달하면 됩니다. + +{trimSnippet(autoDisposeCodeGen)} + +### provider에 매개변수 전달하기 (family): + +코드 생성을 사용할 때, provider에 매개변수를 전달하기위해 더 이상 `family` 수정자(modifier)를 사용할 필요가 없습니다. +대신 provider의 메인 함수(main function)는 명명된 매개변수(named parameters), 선택적 매개변수(optional parameters), 기본값(default values)을 포함하여 모든 매개변수를 받을 수 있습니다. +그러나 이 매개변수들은 여전히 일관된 ==를 가져야 한다는 점에 유의하세요. +즉, 값이 캐시되거나 매개변수가 ==를 재정의해야 한다는 의미입니다. + + + + + + + + + + + + + + +
+ Functional + + Class-Based +
+ {trimSnippet(familyFn)} + + {trimSnippet(familyClass)} +
+ +## 코드 생성을 사용하지 않는 방식(non-code-generation variant)에서 마이그레이션하기: + +코드 생성 방식을 사용하지 않는 경우에는 provider의 타입을 직접 결정해야 합니다. +다음은 코드 생성 방식으로 전환하기 위한 해당 옵션입니다: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Provider +
Before + {trimSnippet(provider)} +
After + {trimSnippet(syncFn)} +
+ NotifierProvider +
Before + {trimSnippet(notifierProvider)} +
After + {trimSnippet(syncClass)} +
+ FutureProvider +
Before + {trimSnippet(futureProvider)} +
After + {trimSnippet(asyncFnFuture)} +
+ StreamProvider +
Before + {trimSnippet(streamProvider)} +
After + {trimSnippet(asyncFnStream)} +
+ AsyncNotifierProvider +
Before + + {trimSnippet(asyncNotifierProvider)} + +
After + {trimSnippet(asyncClassFuture)} +
+ StreamNotifierProvider +
Before + + {trimSnippet(streamNotifierProvider)} + +
After + {trimSnippet(asyncClassStream)} +
+ +[hookwidget]: https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/HookWidget-class.html +[statefulwidget]: https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html +[riverpod]: https://github.com/rrousselgit/riverpod +[hooks_riverpod]: https://pub.dev/packages/hooks_riverpod +[flutter_riverpod]: https://pub.dev/packages/flutter_riverpod +[flutter_hooks]: https://github.com/rrousselGit/flutter_hooks +[build]: https://pub.dev/documentation/riverpod/latest/riverpod/Notifier/build.html diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/about_codegen/main.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/main.dart similarity index 100% rename from website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/about_codegen/main.dart rename to website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/main.dart diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/about_codegen/main.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/main.g.dart similarity index 100% rename from website/i18n/ko/docusaurus-plugin-content-docs/current/about_codegen/main.g.dart rename to website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/main.g.dart diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_class_future.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_class_future.dart new file mode 100644 index 000000000..1988ec140 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_class_future.dart @@ -0,0 +1,14 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'async_class_future.g.dart'; + +/* SNIPPET START */ +@riverpod +class Example extends _$Example { + @override + Future build() async { + return Future.value('foo'); + } + + // 상태(State) 변경(Mutation)을 위한 메서드 추가 +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_class_future.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_class_future.g.dart new file mode 100644 index 000000000..749d6e6dc --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_class_future.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'async_class_future.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$exampleHash() => r'8a906741b8ea4b9b0d3f0b924779704b3e1773a1'; + +/// See also [Example]. +@ProviderFor(Example) +final exampleProvider = + AutoDisposeAsyncNotifierProvider.internal( + Example.new, + name: r'exampleProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$exampleHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$Example = AutoDisposeAsyncNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_class_stream.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_class_stream.dart new file mode 100644 index 000000000..fdde7f0ef --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_class_stream.dart @@ -0,0 +1,14 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'async_class_stream.g.dart'; + +/* SNIPPET START */ +@riverpod +class Example extends _$Example { + @override + Stream build() async* { + yield 'foo'; + } + + // 상태(State) 변경(Mutation)을 위한 메서드 추가 +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_class_stream.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_class_stream.g.dart new file mode 100644 index 000000000..77984b80a --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_class_stream.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'async_class_stream.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$exampleHash() => r'4bca936132b77a9a804549f086f33571724b4804'; + +/// See also [Example]. +@ProviderFor(Example) +final exampleProvider = + AutoDisposeStreamNotifierProvider.internal( + Example.new, + name: r'exampleProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$exampleHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$Example = AutoDisposeStreamNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_fn_future.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_fn_future.dart new file mode 100644 index 000000000..95fdd909c --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_fn_future.dart @@ -0,0 +1,9 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'async_fn_future.g.dart'; + +/* SNIPPET START */ +@riverpod +Future example(ExampleRef ref) async { + return Future.value('foo'); +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_fn_future.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_fn_future.g.dart new file mode 100644 index 000000000..3274e8a9b --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_fn_future.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'async_fn_future.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$exampleHash() => r'e620af6b870a76eea4228989433de0666957d813'; + +/// See also [example]. +@ProviderFor(example) +final exampleProvider = AutoDisposeFutureProvider.internal( + example, + name: r'exampleProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$exampleHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ExampleRef = AutoDisposeFutureProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_fn_stream.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_fn_stream.dart new file mode 100644 index 000000000..74da790ad --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_fn_stream.dart @@ -0,0 +1,9 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'async_fn_stream.g.dart'; + +/* SNIPPET START */ +@riverpod +Stream example(ExampleRef ref) async* { + yield 'foo'; +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_fn_stream.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_fn_stream.g.dart new file mode 100644 index 000000000..42904d939 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_fn_stream.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'async_fn_stream.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$exampleHash() => r'8a2b19776fb9bbb1631f898bd6446b57b102dd9d'; + +/// See also [example]. +@ProviderFor(example) +final exampleProvider = AutoDisposeStreamProvider.internal( + example, + name: r'exampleProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$exampleHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ExampleRef = AutoDisposeStreamProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/auto_dispose.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/auto_dispose.dart new file mode 100644 index 000000000..587576695 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/auto_dispose.dart @@ -0,0 +1,12 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'auto_dispose.g.dart'; + +/* SNIPPET START */ +// 자동폐기(AutoDispose) provider (keepAlive는 기본적으로 false) +@riverpod +String example1(Example1Ref ref) => 'foo'; + +// 비자동폐기(Non autoDispose) provider +@Riverpod(keepAlive: true) +String example2(Example2Ref ref) => 'foo'; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/auto_dispose.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/auto_dispose.g.dart new file mode 100644 index 000000000..e6e00432c --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/auto_dispose.g.dart @@ -0,0 +1,40 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'auto_dispose.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$example1Hash() => r'8a5f0865f758792cc8e4f2ca67db334196df6e88'; + +/// See also [example1]. +@ProviderFor(example1) +final example1Provider = AutoDisposeProvider.internal( + example1, + name: r'example1Provider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$example1Hash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef Example1Ref = AutoDisposeProviderRef; +String _$example2Hash() => r'bc25731d759be185125d12d995d0b89b07d1e271'; + +/// See also [example2]. +@ProviderFor(example2) +final example2Provider = Provider.internal( + example2, + name: r'example2Provider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$example2Hash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef Example2Ref = ProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/family.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/family.dart new file mode 100644 index 000000000..e1ee685fb --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/family.dart @@ -0,0 +1,7 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'family.g.dart'; + +/* SNIPPET START */ +@riverpod +String example(ExampleRef ref, int param) => 'Hello $param'; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/family.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/family.g.dart new file mode 100644 index 000000000..a037ab96c --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/family.g.dart @@ -0,0 +1,159 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'family.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$exampleHash() => r'c4f5a651a55bcf34b0c92d98d77436844cbdc097'; + +/// Copied from Dart SDK +class _SystemHash { + _SystemHash._(); + + static int combine(int hash, int value) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + value); + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); + return hash ^ (hash >> 6); + } + + static int finish(int hash) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + // ignore: parameter_assignments + hash = hash ^ (hash >> 11); + return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); + } +} + +/// See also [example]. +@ProviderFor(example) +const exampleProvider = ExampleFamily(); + +/// See also [example]. +class ExampleFamily extends Family { + /// See also [example]. + const ExampleFamily(); + + /// See also [example]. + ExampleProvider call( + int param, + ) { + return ExampleProvider( + param, + ); + } + + @override + ExampleProvider getProviderOverride( + covariant ExampleProvider provider, + ) { + return call( + provider.param, + ); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'exampleProvider'; +} + +/// See also [example]. +class ExampleProvider extends AutoDisposeProvider { + /// See also [example]. + ExampleProvider( + int param, + ) : this._internal( + (ref) => example( + ref as ExampleRef, + param, + ), + from: exampleProvider, + name: r'exampleProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$exampleHash, + dependencies: ExampleFamily._dependencies, + allTransitiveDependencies: ExampleFamily._allTransitiveDependencies, + param: param, + ); + + ExampleProvider._internal( + super._createNotifier, { + required super.name, + required super.dependencies, + required super.allTransitiveDependencies, + required super.debugGetCreateSourceHash, + required super.from, + required this.param, + }) : super.internal(); + + final int param; + + @override + Override overrideWith( + String Function(ExampleRef provider) create, + ) { + return ProviderOverride( + origin: this, + override: ExampleProvider._internal( + (ref) => create(ref as ExampleRef), + from: from, + name: null, + dependencies: null, + allTransitiveDependencies: null, + debugGetCreateSourceHash: null, + param: param, + ), + ); + } + + @override + AutoDisposeProviderElement createElement() { + return _ExampleProviderElement(this); + } + + @override + bool operator ==(Object other) { + return other is ExampleProvider && other.param == param; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, param.hashCode); + + return _SystemHash.finish(hash); + } +} + +mixin ExampleRef on AutoDisposeProviderRef { + /// The parameter `param` of this provider. + int get param; +} + +class _ExampleProviderElement extends AutoDisposeProviderElement + with ExampleRef { + _ExampleProviderElement(super.provider); + + @override + int get param => (origin as ExampleProvider).param; +} +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/family_class.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/family_class.dart new file mode 100644 index 000000000..f071d44f6 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/family_class.dart @@ -0,0 +1,17 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'family_class.g.dart'; + +/* SNIPPET START */ +@riverpod +class Example extends _$Example { + @override + String build( + int param1, { + String param2 = 'foo', + }) { + return 'Hello $param1 & param2'; + } + + // 상태(State) 변경(Mutation)을 위한 메서드 추가 +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/family_class.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/family_class.g.dart new file mode 100644 index 000000000..2e9a830bc --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/family_class.g.dart @@ -0,0 +1,195 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'family_class.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$exampleHash() => r'c81e9d94e763b25403ab6b7fa03f092003570142'; + +/// Copied from Dart SDK +class _SystemHash { + _SystemHash._(); + + static int combine(int hash, int value) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + value); + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); + return hash ^ (hash >> 6); + } + + static int finish(int hash) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + // ignore: parameter_assignments + hash = hash ^ (hash >> 11); + return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); + } +} + +abstract class _$Example extends BuildlessAutoDisposeNotifier { + late final int param1; + late final String param2; + + String build( + int param1, { + String param2 = 'foo', + }); +} + +/// See also [Example]. +@ProviderFor(Example) +const exampleProvider = ExampleFamily(); + +/// See also [Example]. +class ExampleFamily extends Family { + /// See also [Example]. + const ExampleFamily(); + + /// See also [Example]. + ExampleProvider call( + int param1, { + String param2 = 'foo', + }) { + return ExampleProvider( + param1, + param2: param2, + ); + } + + @override + ExampleProvider getProviderOverride( + covariant ExampleProvider provider, + ) { + return call( + provider.param1, + param2: provider.param2, + ); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'exampleProvider'; +} + +/// See also [Example]. +class ExampleProvider extends AutoDisposeNotifierProviderImpl { + /// See also [Example]. + ExampleProvider( + int param1, { + String param2 = 'foo', + }) : this._internal( + () => Example() + ..param1 = param1 + ..param2 = param2, + from: exampleProvider, + name: r'exampleProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$exampleHash, + dependencies: ExampleFamily._dependencies, + allTransitiveDependencies: ExampleFamily._allTransitiveDependencies, + param1: param1, + param2: param2, + ); + + ExampleProvider._internal( + super._createNotifier, { + required super.name, + required super.dependencies, + required super.allTransitiveDependencies, + required super.debugGetCreateSourceHash, + required super.from, + required this.param1, + required this.param2, + }) : super.internal(); + + final int param1; + final String param2; + + @override + String runNotifierBuild( + covariant Example notifier, + ) { + return notifier.build( + param1, + param2: param2, + ); + } + + @override + Override overrideWith(Example Function() create) { + return ProviderOverride( + origin: this, + override: ExampleProvider._internal( + () => create() + ..param1 = param1 + ..param2 = param2, + from: from, + name: null, + dependencies: null, + allTransitiveDependencies: null, + debugGetCreateSourceHash: null, + param1: param1, + param2: param2, + ), + ); + } + + @override + AutoDisposeNotifierProviderElement createElement() { + return _ExampleProviderElement(this); + } + + @override + bool operator ==(Object other) { + return other is ExampleProvider && + other.param1 == param1 && + other.param2 == param2; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, param1.hashCode); + hash = _SystemHash.combine(hash, param2.hashCode); + + return _SystemHash.finish(hash); + } +} + +mixin ExampleRef on AutoDisposeNotifierProviderRef { + /// The parameter `param1` of this provider. + int get param1; + + /// The parameter `param2` of this provider. + String get param2; +} + +class _ExampleProviderElement + extends AutoDisposeNotifierProviderElement + with ExampleRef { + _ExampleProviderElement(super.provider); + + @override + int get param1 => (origin as ExampleProvider).param1; + @override + String get param2 => (origin as ExampleProvider).param2; +} +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/family_fn.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/family_fn.dart new file mode 100644 index 000000000..863df6f40 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/family_fn.dart @@ -0,0 +1,13 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'family_fn.g.dart'; + +/* SNIPPET START */ +@riverpod +String example( + ExampleRef ref, + int param1, { + String param2 = 'foo', +}) { + return 'Hello $param1 & param2'; +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/family_fn.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/family_fn.g.dart new file mode 100644 index 000000000..aa8282264 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/family_fn.g.dart @@ -0,0 +1,176 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'family_fn.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$exampleHash() => r'99b3ed3d53932bd1354259200ebf08493af9ada2'; + +/// Copied from Dart SDK +class _SystemHash { + _SystemHash._(); + + static int combine(int hash, int value) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + value); + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); + return hash ^ (hash >> 6); + } + + static int finish(int hash) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + // ignore: parameter_assignments + hash = hash ^ (hash >> 11); + return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); + } +} + +/// See also [example]. +@ProviderFor(example) +const exampleProvider = ExampleFamily(); + +/// See also [example]. +class ExampleFamily extends Family { + /// See also [example]. + const ExampleFamily(); + + /// See also [example]. + ExampleProvider call( + int param1, { + String param2 = 'foo', + }) { + return ExampleProvider( + param1, + param2: param2, + ); + } + + @override + ExampleProvider getProviderOverride( + covariant ExampleProvider provider, + ) { + return call( + provider.param1, + param2: provider.param2, + ); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'exampleProvider'; +} + +/// See also [example]. +class ExampleProvider extends AutoDisposeProvider { + /// See also [example]. + ExampleProvider( + int param1, { + String param2 = 'foo', + }) : this._internal( + (ref) => example( + ref as ExampleRef, + param1, + param2: param2, + ), + from: exampleProvider, + name: r'exampleProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$exampleHash, + dependencies: ExampleFamily._dependencies, + allTransitiveDependencies: ExampleFamily._allTransitiveDependencies, + param1: param1, + param2: param2, + ); + + ExampleProvider._internal( + super._createNotifier, { + required super.name, + required super.dependencies, + required super.allTransitiveDependencies, + required super.debugGetCreateSourceHash, + required super.from, + required this.param1, + required this.param2, + }) : super.internal(); + + final int param1; + final String param2; + + @override + Override overrideWith( + String Function(ExampleRef provider) create, + ) { + return ProviderOverride( + origin: this, + override: ExampleProvider._internal( + (ref) => create(ref as ExampleRef), + from: from, + name: null, + dependencies: null, + allTransitiveDependencies: null, + debugGetCreateSourceHash: null, + param1: param1, + param2: param2, + ), + ); + } + + @override + AutoDisposeProviderElement createElement() { + return _ExampleProviderElement(this); + } + + @override + bool operator ==(Object other) { + return other is ExampleProvider && + other.param1 == param1 && + other.param2 == param2; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, param1.hashCode); + hash = _SystemHash.combine(hash, param2.hashCode); + + return _SystemHash.finish(hash); + } +} + +mixin ExampleRef on AutoDisposeProviderRef { + /// The parameter `param1` of this provider. + int get param1; + + /// The parameter `param2` of this provider. + String get param2; +} + +class _ExampleProviderElement extends AutoDisposeProviderElement + with ExampleRef { + _ExampleProviderElement(super.provider); + + @override + int get param1 => (origin as ExampleProvider).param1; + @override + String get param2 => (origin as ExampleProvider).param2; +} +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/async_notifier_provider.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/async_notifier_provider.dart new file mode 100644 index 000000000..05afdc3ef --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/async_notifier_provider.dart @@ -0,0 +1,15 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +/* SNIPPET START */ +final exampleProvider = AsyncNotifierProvider.autoDispose( + ExampleNotifier.new, +); + +class ExampleNotifier extends AutoDisposeAsyncNotifier { + @override + Future build() async { + return Future.value('foo'); + } + + // 상태(State) 변경(Mutation)을 위한 메서드 추가 +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/auto_dispose.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/auto_dispose.dart new file mode 100644 index 000000000..6bfb8babd --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/auto_dispose.dart @@ -0,0 +1,12 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +/* SNIPPET START */ +// 자동폐기(autodispose) 프로바이더 +final example1Provider = Provider.autoDispose((ref) { + return 'foo'; +}); + +// 비동기 자동폐기(autodispose) 프로바이더 +final example2Provider = Provider((ref) { + return 'foo'; +}); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/family.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/family.dart new file mode 100644 index 000000000..a9e59d48d --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/family.dart @@ -0,0 +1,6 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +/* SNIPPET START */ +final exampleProvider = Provider.family((ref, param) { + return 'Hello $param'; +}); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/future_provider.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/future_provider.dart new file mode 100644 index 000000000..6306a76fb --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/future_provider.dart @@ -0,0 +1,7 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +/* SNIPPET START */ +final exampleProvider = + FutureProvider.autoDispose((ref) async { + return Future.value('foo'); +}); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/notifier_provider.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/notifier_provider.dart new file mode 100644 index 000000000..852beb863 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/notifier_provider.dart @@ -0,0 +1,15 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +/* SNIPPET START */ +final exampleProvider = NotifierProvider.autoDispose( + ExampleNotifier.new, +); + +class ExampleNotifier extends AutoDisposeNotifier { + @override + String build() { + return 'foo'; + } + + // 상태(State) 변경(Mutation)을 위한 메서드 추가 +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/provider.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/provider.dart new file mode 100644 index 000000000..1f541352a --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/provider.dart @@ -0,0 +1,8 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +/* SNIPPET START */ +final exampleProvider = Provider.autoDispose( + (ref) { + return 'foo'; + }, +); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/stream_notifier_provider.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/stream_notifier_provider.dart new file mode 100644 index 000000000..2ba322eb1 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/stream_notifier_provider.dart @@ -0,0 +1,15 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +/* SNIPPET START */ +final exampleProvider = StreamNotifierProvider.autoDispose(() { + return ExampleNotifier(); +}); + +class ExampleNotifier extends AutoDisposeStreamNotifier { + @override + Stream build() async* { + yield 'foo'; + } + + // 상태(State) 변경(Mutation)을 위한 메서드 추가 +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/stream_provider.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/stream_provider.dart new file mode 100644 index 000000000..60d227f8f --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/stream_provider.dart @@ -0,0 +1,6 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +/* SNIPPET START */ +final exampleProvider = StreamProvider.autoDispose((ref) async* { + yield 'foo'; +}); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/sync_class.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/sync_class.dart new file mode 100644 index 000000000..dbe0773f9 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/sync_class.dart @@ -0,0 +1,14 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'sync_class.g.dart'; + +/* SNIPPET START */ +@riverpod +class Example extends _$Example { + @override + String build() { + return 'foo'; + } + + // 상태(State) 변경(Mutation)을 위한 메서드 추가 +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/sync_class.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/sync_class.g.dart new file mode 100644 index 000000000..e03372d41 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/sync_class.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'sync_class.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$exampleHash() => r'c237193ab6d57674973aaa02eb73db6f6822eb26'; + +/// See also [Example]. +@ProviderFor(Example) +final exampleProvider = AutoDisposeNotifierProvider.internal( + Example.new, + name: r'exampleProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$exampleHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$Example = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/sync_fn.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/sync_fn.dart new file mode 100644 index 000000000..0d4922734 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/sync_fn.dart @@ -0,0 +1,9 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'sync_fn.g.dart'; + +/* SNIPPET START */ +@riverpod +String example(ExampleRef ref) { + return 'foo'; +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/sync_fn.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/sync_fn.g.dart new file mode 100644 index 000000000..dbc326dcd --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/sync_fn.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'sync_fn.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$exampleHash() => r'dd4e9043c704a42a3fc025e7fef9515f659fc78a'; + +/// See also [example]. +@ProviderFor(example) +final exampleProvider = AutoDisposeProvider.internal( + example, + name: r'exampleProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$exampleHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ExampleRef = AutoDisposeProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/about_codegen/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/raw.dart similarity index 93% rename from website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/about_codegen/raw.dart rename to website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/raw.dart index 6e2543d77..4630c27ff 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/about_codegen/raw.dart +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_codegen/raw.dart @@ -13,7 +13,7 @@ class Http { final http = Http(); /* SNIPPET START */ -final fetchUserProvider = FutureProvider.autoDispose.family((ref, userId) async { +final fetchUserProvider = FutureProvider.autoDispose.family((ref, userId) async { final json = await http.get('api/user/$userId'); return User.fromJson(json); }); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_hooks.mdx b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_hooks.mdx new file mode 100644 index 000000000..5dcf7354d --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_hooks.mdx @@ -0,0 +1,310 @@ +--- +title: 훅(hooks)에 대한 정보 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; +import hookAndConsumer from "!!raw-loader!./about_hooks/hook_and_consumer.dart"; +import hookConsumer from "!!raw-loader!./about_hooks/hook_consumer.dart"; +import hookConsumerWidget from "!!raw-loader!./about_hooks/hook_consumer_widget.dart"; +import { CodeSnippet } from "../../../../../src/components/CodeSnippet"; +import { Link } from "../../../../../src/components/Link"; + +이 페이지는 훅(hooks)이 무엇이고 Riverpod과 어떻게 관련되어 있는지 설명합니다. + +"훅(hooks)"은 Riverpod과는 독립적으로 별도의 패키지에서 제공되는 공통 유틸리티입니다: [flutter_hooks]. +[flutter_hooks]는 완전히 별개의 패키지로 Riverpod과 (적어도 직접적으로는) 관련이 없지만, Riverpod과 [flutter_hooks]를 함께 사용하는 것이 일반적입니다. + +## 훅(hooks)을 사용해야 할까요? + +훅(hooks)은 강력한 도구이지만 모든 사람에게 적합한 것은 아닙니다. +Riverpod에 처음 사용하는 분이라면, 훅(hooks) 사용을 피하는 것이 좋습니다. + +훅(hooks)은 유용하지만 Riverpod에 필수적인 것은 아닙니다. +Riverpod 때문에 훅(hooks)을 사용해서는 안됩니다. 오히려 훅(hooks)을 사용하고 싶어서 훅(hooks)을 사용해야 합니다. + +훅(hooks)사용은 장단점(tradeoff)가 있습니다. +훅(hooks)은 강력(robust)하고 재사용(reusable) 가능한 코드를 생성하는데 유용하지만, 배워야하는 새로운 개념이기 때문에 처음에는 혼란스러울 수 있습니다. +훅(hooks)은 Flutter의 핵심 개념이 아닙니다. 따라서 Flutter/Dart에서는 어색하게 느껴질 수 있습니다. + +## 훅(hooks)이란 무엇인가? + +훅(hooks)은 위젯 내부에서 사용되는 함수입니다. +[StatefulWidget]의 대안으로 설계되었으며, 로직을 더 재사용(reusable) 가능하고 조합(composable) 가능하게 만듭니다. + +훅(hooks)은 [React](https://reactjs.org/)에서 온 개념이며, [flutter_hooks]는 단지 React 구현을 Flutter로 포팅한 것입니다. +따라서 훅(hooks)은 Flutter에서 약간 어색할 수 있습니다. +이상적으로 향후에 Flutter를 위해 특별히 설계된 훅(hooks)이 해결하는 문제에 대한 솔루션이 있을 것입니다. + +Riverpod의 provider가 "전역(Global)" 애플리케이션 상태(State)를 위한 것이라면, 훅(Hooks)은 로컬 위젯 상태를 위한 것입니다. +훅(Hooks)dms 일반적으로 상태 저장형 UI 객체를 처리하는 데 사용됩니다, +[TextEditingController](https://api.flutter.dev/flutter/widgets/TextEditingController-class.html), +[AnimationController](https://api.flutter.dev/flutter/animation/AnimationController-class.html). +또한 "빌더(Builder)" 패턴을 대체하는 역할도 수행할 수 있는데, +[FutureBuilder](https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html)/[TweenAnimatedBuilder](https://api.flutter.dev/flutter/widgets/TweenAnimationBuilder-class.html)와 같은 위젯을 "중첩(nesting)"을 포함하지 않는 대안 유발하지 않는 방식으로 변경으로 가독성을 크게 향상시킬 수 있습니다. + +일반적으로, 훅(hooks)은 다음과 같은 경우에 유용합니다: + +- 폼(forms) +- 애니메이션(animations) +- 사용자 이벤트에 반응하기(reacting to user events) +- ... + +예를 들어, 훅(Hooks)를 위젯이 보이지 않다가 서서히 나타나는 페이드인(Fade-in) 애니메이션을 직접 구현하는데 사용할 수 있습니다. + +만약 [StatefulWidget]을 사용한다면, 코드는 다음과 같을 것입니다: + +```dart +class FadeIn extends StatefulWidget { + const FadeIn({Key? key, required this.child}) : super(key: key); + + final Widget child; + + @override + State createState() => _FadeInState(); +} + +class _FadeInState extends State with SingleTickerProviderStateMixin { + late final AnimationController animationController = AnimationController( + vsync: this, + duration: const Duration(seconds: 2), + ); + + @override + void initState() { + super.initState(); + animationController.forward(); + } + + @override + void dispose() { + animationController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: animationController, + builder: (context, child) { + return Opacity( + opacity: animationController.value, + child: widget.child, + ); + }, + ); + } +} +``` + +훅(hooks)을 사용하면, 동일한 코드는 다음과 같을 것입니다: + +```dart +class FadeIn extends HookWidget { + const FadeIn({Key? key, required this.child}) : super(key: key); + + final Widget child; + + @override + Widget build(BuildContext context) { + // AnimationController 생성. + // 위젯인 마운트해제(unmounted)되면 컨트롤러는 자동으로 폐기(disposed)됩니다. + final animationController = useAnimationController( + duration: const Duration(seconds: 2), + ); + + // useEffect는 initState + didUpdateWidget + dispose와 동일합니다 + // useEffect에 잔달된 콜백(callback)은 훅(hook)이 처음 호출될 때 실행되고, + // 두 번째 매개변수로 전달된 목록이 변경될 때마다 실행됩니다. + // 여기서는 빈 const list를 전달하므로, 이는 엄밀히 말해 `initState`와 동일합니다. + useEffect(() { + // 위젯이 처음 렌더링될 때 애니메이션이 시작 + animationController.forward(); + // 여기서 선택적으로 "Dispose"로직을 반환할 수 있습니다. + return null; + }, const []); + + // 애니메이션이 업데이트될 때 이 위젯을 다시 빌드하도록 Flutter에 알립니다. + // 이는 AnimatedBuilder와 동일합니다 + useAnimation(animationController); + + return Opacity( + opacity: animationController.value, + child: child, + ); + } +} +``` + +이 코드에서 주목해야 할 몇 가지 흥미로운 사항이 있습니다: + +- 메모리 누수가 없습니다. 이 코드는 위젯이 리빌드될 때마다 새로운 `AnimationController`를 다시 생성하지 않으며, + 위젯이 마운트해제(unmounted)될 때 컨트롤러가 올바르게 해제(released)됩니다. + +- 동일한 위젯 내에서 원하는 만큼 많이 훅(Hooks)을 사용할 수 있습니다. + 따라서 원한다면 여러 `AnimationController`를 생성할 수 있습니다: + + ```dart + @override + Widget build(BuildContext context) { + final animationController = useAnimationController( + duration: const Duration(seconds: 2), + ); + final anotherController = useAnimationController( + duration: const Duration(seconds: 2), + ); + + ... + } + ``` + + 이렇게 하면 어떤 부정적인 결과도 없이 두 개의 컨트롤러가 생성됩니다. + +- 원한다면 이 로직을 재사용 가능한 별도의 함수로 리팩터링할 수 있습니다: + + ```dart + double useFadeIn() { + final animationController = useAnimationController( + duration: const Duration(seconds: 2), + ); + useEffect(() { + animationController.forward(); + return null; + }, const []); + useAnimation(animationController); + return animationController.value; + } + ``` + + 그런 다음 위젯이 [HookWidget]이라면 위젯 내에서 이 함수를 사용할 수 있습니다: + + ```dart + class FadeIn extends HookWidget { + const FadeIn({Key? key, required this.child}) : super(key: key); + + final Widget child; + + @override + Widget build(BuildContext context) { + final fade = useFadeIn(); + + return Opacity(opacity: fade, child: child); + } + } + ``` + + `useFadeIn` 함수가 `FadeIn` 위젯과 완전히 독립적이라는 점에 주목하세요. + 원한다면 완전히 다른 위젯에서 `useFadeIn` 함수를 사용할 수 있고, 여전히 동작할 것입니다! + +## 훅(Hooks) 규칙 + +훅(Hooks)은 고유한 제약 조건이 있습니다: + +- [HookWidget]을 확장(extends)하는 위젯의 `build` 메서드 내에서만 사용할 수 있습니다: + + **Good**: + + ```dart + class Example extends HookWidget { + @override + Widget build(BuildContext context) { + final controller = useAnimationController(); + ... + } + } + ``` + + **Bad**: + + ```dart + // Not a HookWidget + class Example extends StatelessWidget { + @override + Widget build(BuildContext context) { + final controller = useAnimationController(); + ... + } + } + ``` + + **Bad**: + + ```dart + class Example extends HookWidget { + @override + Widget build(BuildContext context) { + return ElevatedButton( + onPressed: () { + // _실제로_ 'build' 메서드 내부가 아니라 사용자 상호작용 라이프사이클 내부 (여기서는 'onPressed'). + final controller = useAnimationController(); + }, + child: Text('click me'), + ); + } + } + ``` + +- 조건부 또는 루프 안에서 사용할 수 없습니다. + + **Bad**: + + ```dart + class Example extends HookWidget { + const Example({required this.condition, super.key}); + final bool condition; + @override + Widget build(BuildContext context) { + if (condition) { + // 훅(Hooks)은 "if"/"for", ... 안에서 사용할 수 없습니다. + final controller = useAnimationController(); + } + ... + } + } + ``` + +훅(Hook)에 대한 자세한 내용은 [flutter_hooks]를 참조하세요. + +## 훅(Hooks)과 Riverpod + +### 설치 +훅(Hook)은 Riverpod과는 별개이므로 별도로 훅(Hook)를 설치해야 합니다. +훅(Hook)를 사용하려면 [hooks_riverpod]를 설치하는 것만으로는 충분하지 않습니다. +여전히 종속성에 [flutter_hooks]를 추가해야 합니다. +자세한 내용은 를 참조하세요. + +### 사용법 + +경우에 따라서는 훅(Hook)과 Riverpod을 모두 사용하는 위젯을 작성하고 싶을 수도 있습니다. +하지만 이미 알고 계실 수도 있겠지만, 훅(Hook)과 Riverpod 모두 +고유한 사용자 정의 위젯 기본유형을 제공합니다.: [HookWidget] 및 [ConsumerWidget] +하지만 클래스는 한 번에 하나의 수퍼클래스만 확장(extend)할 수 있습니다. + +이 문제를 해결하기 위해 [hooks_riverpod] 패키지를 사용할 수 있습니다. +이 패키지는 [HookWidget]과 [ConsumerWidget]을 단일 유형으로 결합하는 [HookConsumerWidget] 클래스를 제공합니다. +따라서 [HookWidget] 대신 [HookConsumerWidget]을 서브클래싱할 수 있습니다: + + + +또는, 두 패키지 모두에서 제공하는 "Builder"를 사용할 수도 있습니다. +예를 들어, `StatelessWidget`을 계속 사용하고 `HookBuilder`와 `Consumer`를 모두 사용할 수 있습니다. + + + +:::note +이 접근 방식은 [hooks_riverpod]를 사용하지 않고도 작동합니다. [flutter_riverpod] 만 만 필요합니다. +::: + +이 접근 방식이 마음에 들면, 두 빌더를 하나로 결합한 [HookConsumer]를 제공하여 간소화할 수 있습니다: + + + +[hookwidget]: https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/HookWidget-class.html +[hookconsumer]: https://pub.dev/documentation/hooks_riverpod/latest/hooks_riverpod/HookConsumer-class.html +[hookconsumerwidget]: https://pub.dev/documentation/hooks_riverpod/latest/hooks_riverpod/HookConsumerWidget-class.html +[consumerwidget]: https://pub.dev/documentation/flutter_riverpod/latest/flutter_riverpod/ConsumerWidget-class.html +[statefulwidget]: https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html +[riverpod]: https://github.com/rrousselgit/riverpod +[hooks_riverpod]: https://pub.dev/packages/hooks_riverpod +[flutter_riverpod]: https://pub.dev/packages/flutter_riverpod +[flutter_hooks]: https://github.com/rrousselGit/flutter_hooks diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_hooks/hook_and_consumer.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_hooks/hook_and_consumer.dart new file mode 100644 index 000000000..b8d4be89e --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_hooks/hook_and_consumer.dart @@ -0,0 +1,28 @@ +// ignore_for_file: use_key_in_widget_constructors, unused_local_variable + +import 'package:flutter/widgets.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +import '../providers/creating_a_provider/codegen.dart'; + +class MyValue {} + +/* SNIPPET START */ + +class Example extends StatelessWidget { + @override + Widget build(BuildContext context) { + // 두 패키지 모두에서 제공하는 빌더를 사용할 수 있습니다. + return Consumer( + builder: (context, ref, child) { + return HookBuilder(builder: (context) { + final counter = useState(0); + final value = ref.watch(myProvider); + + return Text('Hello $counter $value'); + }); + }, + ); + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_hooks/hook_consumer.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_hooks/hook_consumer.dart new file mode 100644 index 000000000..5d23c0d0a --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_hooks/hook_consumer.dart @@ -0,0 +1,26 @@ +// ignore_for_file: use_key_in_widget_constructors, unused_local_variable + +import 'package:flutter/widgets.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +import '../providers/creating_a_provider/codegen.dart'; + +class MyValue {} + +/* SNIPPET START */ + +class Example extends StatelessWidget { + @override + Widget build(BuildContext context) { + // Consumer와 HookBuilder 모두 사용하는 것과 동일합니다. + return HookConsumer( + builder: (context, ref, child) { + final counter = useState(0); + final value = ref.watch(myProvider); + + return Text('Hello $counter $value'); + }, + ); + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_hooks/hook_consumer_widget.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_hooks/hook_consumer_widget.dart new file mode 100644 index 000000000..9d857d887 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/about_hooks/hook_consumer_widget.dart @@ -0,0 +1,23 @@ +// ignore_for_file: use_key_in_widget_constructors, unused_local_variable + +import 'package:flutter/widgets.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +import '../providers/creating_a_provider/codegen.dart'; + +class MyValue {} + +/* SNIPPET START */ + +// HookWidget 대신 HookConsumerWidget을 확장합니다. +class Example extends HookConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + // 여기서는 hooks과 providers를 모두 사용할 수 있습니다. + final counter = useState(0); + final value = ref.watch(myProvider); + + return Text('Hello $counter $value'); + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/async_initialization.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/async_initialization.dart new file mode 100644 index 000000000..18817b776 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/async_initialization.dart @@ -0,0 +1,58 @@ +// ignore_for_file: omit_local_variable_types, prefer_final_locals + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class LoadingScreen extends StatelessWidget { + const LoadingScreen({super.key}); + + @override + Widget build(BuildContext context) { + return const CircularProgressIndicator(); + } +} + +/* SNIPPET START */ +// We'd like to obtain an instance of shared preferences synchronously in a provider +final countProvider = StateProvider((ref) { + final preferences = ref.watch(sharedPreferencesProvider); + final currentValue = preferences.getInt('count') ?? 0; + ref.listenSelf((prev, curr) { + preferences.setInt('count', curr); + }); + return currentValue; +}); + +// We don't have an actual instance of SharedPreferences, and we can't get one except asynchronously +final sharedPreferencesProvider = + Provider((ref) => throw UnimplementedError()); + +Future main() async { + // Show a loading indicator before running the full app (optional) + // The platform's loading screen will be used while awaiting if you omit this. + runApp(const LoadingScreen()); + + // Get the instance of shared preferences + final prefs = await SharedPreferences.getInstance(); + return runApp( + ProviderScope( + overrides: [ + // Override the unimplemented provider with the value gotten from the plugin + sharedPreferencesProvider.overrideWithValue(prefs), + ], + child: const MyApp(), + ), + ); +} + +class MyApp extends ConsumerWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + // Use the provider without dealing with async issues + final count = ref.watch(countProvider); + return Text('$count'); + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/characters_provider/codegen.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/characters_provider/codegen.dart new file mode 100644 index 000000000..b544b3778 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/characters_provider/codegen.dart @@ -0,0 +1,29 @@ +import 'package:dio/dio.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import 'models.dart'; + +part 'codegen.g.dart'; + +final dio = Dio(); + +/* SNIPPET START */ + +// The current search filter +final searchProvider = StateProvider((ref) => ''); + +@riverpod +Stream configs(ConfigsRef ref) { + return Stream.value(Configuration()); +} + +@riverpod +Future> characters(CharactersRef ref) async { + final search = ref.watch(searchProvider); + final configs = await ref.watch(configsProvider.future); + final response = await dio.get>>( + '${configs.host}/characters?search=$search'); + + return response.data!.map(Character.fromJson).toList(); +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/characters_provider/codegen.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/characters_provider/codegen.g.dart new file mode 100644 index 000000000..acc45c280 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/characters_provider/codegen.g.dart @@ -0,0 +1,40 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$configsHash() => r'166cbe95e6b49ed7bc78c96041fb14abddbf6911'; + +/// See also [configs]. +@ProviderFor(configs) +final configsProvider = AutoDisposeStreamProvider.internal( + configs, + name: r'configsProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$configsHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ConfigsRef = AutoDisposeStreamProviderRef; +String _$charactersHash() => r'b1e8e15bbeab60d92fe959d9e1dd4ceba6a31446'; + +/// See also [characters]. +@ProviderFor(characters) +final charactersProvider = AutoDisposeFutureProvider>.internal( + characters, + name: r'charactersProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$charactersHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef CharactersRef = AutoDisposeFutureProviderRef>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/characters_provider/index.tsx b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/characters_provider/index.tsx new file mode 100644 index 000000000..339b89a56 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/characters_provider/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./codegen.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/characters_provider/models.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/characters_provider/models.dart new file mode 100644 index 000000000..e21e1d7c6 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/characters_provider/models.dart @@ -0,0 +1,17 @@ + +class Character { + Character(); + + // ignore: avoid_unused_constructor_parameters + factory Character.fromJson(Map json) { + return Character(); + } +} + +class Configuration { + Configuration({ + this.host = '', + }); + + final String host; +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/characters_provider/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/characters_provider/raw.dart new file mode 100644 index 000000000..948148bd0 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/characters_provider/raw.dart @@ -0,0 +1,25 @@ +import 'package:dio/dio.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'models.dart'; + +final dio = Dio(); + +/* SNIPPET START */ + +// The current search filter +final searchProvider = StateProvider((ref) => ''); + +/// Configurations which can change over time +final configsProvider = StreamProvider( + (ref) => Stream.value(Configuration()), +); + +final charactersProvider = FutureProvider>((ref) async { + final search = ref.watch(searchProvider); + final configs = await ref.watch(configsProvider.future); + final response = await dio.get>>( + '${configs.host}/characters?search=$search'); + + return response.data!.map(Character.fromJson).toList(); +}); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/city_provider/codegen.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/city_provider/codegen.dart new file mode 100644 index 000000000..8b8f234b9 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/city_provider/codegen.dart @@ -0,0 +1,7 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +/* SNIPPET START */ +@riverpod +String city(CityRef ref) => 'London'; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/city_provider/codegen.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/city_provider/codegen.g.dart new file mode 100644 index 000000000..674b539be --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/city_provider/codegen.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$cityHash() => r'2ccdee096b5d5c1cafa736b3e52b788431b9af38'; + +/// See also [city]. +@ProviderFor(city) +final cityProvider = AutoDisposeProvider.internal( + city, + name: r'cityProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$cityHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef CityRef = AutoDisposeProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/city_provider/index.tsx b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/city_provider/index.tsx new file mode 100644 index 000000000..339b89a56 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/city_provider/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./codegen.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/city_provider/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/city_provider/raw.dart new file mode 100644 index 000000000..d8093431c --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/city_provider/raw.dart @@ -0,0 +1,4 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +/* SNIPPET START */ +final cityProvider = Provider((ref) => 'London'); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/filtered_todo_list_provider/codegen.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/filtered_todo_list_provider/codegen.dart new file mode 100644 index 000000000..0d2e49b28 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/filtered_todo_list_provider/codegen.dart @@ -0,0 +1,31 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../todo_list_provider/raw.dart'; + +part 'codegen.g.dart'; + +enum Filter { + none, + completed, + uncompleted, +} + +final filterProvider = StateProvider((ref) => Filter.none); + +/* SNIPPET START */ + +@riverpod +List filteredTodoList(FilteredTodoListRef ref) { + final filter = ref.watch(filterProvider); + final todos = ref.watch(todoListProvider); + + switch (filter) { + case Filter.none: + return todos; + case Filter.completed: + return todos.where((todo) => todo.completed).toList(); + case Filter.uncompleted: + return todos.where((todo) => !todo.completed).toList(); + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/filtered_todo_list_provider/codegen.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/filtered_todo_list_provider/codegen.g.dart new file mode 100644 index 000000000..f8563b534 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/filtered_todo_list_provider/codegen.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$filteredTodoListHash() => r'1c35eb0fce8fc7c7cda86413b02f606f8c8ae2b4'; + +/// See also [filteredTodoList]. +@ProviderFor(filteredTodoList) +final filteredTodoListProvider = AutoDisposeProvider>.internal( + filteredTodoList, + name: r'filteredTodoListProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$filteredTodoListHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef FilteredTodoListRef = AutoDisposeProviderRef>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/filtered_todo_list_provider/index.tsx b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/filtered_todo_list_provider/index.tsx new file mode 100644 index 000000000..339b89a56 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/filtered_todo_list_provider/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./codegen.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/filtered_todo_list_provider/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/filtered_todo_list_provider/raw.dart new file mode 100644 index 000000000..85885e800 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/filtered_todo_list_provider/raw.dart @@ -0,0 +1,26 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../todo_list_provider/raw.dart'; + +enum Filter { + none, + completed, + uncompleted, +} + +final filterProvider = StateProvider((ref) => Filter.none); + +/* SNIPPET START */ + +final filteredTodoListProvider = Provider>((ref) { + final filter = ref.watch(filterProvider); + final todos = ref.watch(todoListProvider); + + switch (filter) { + case Filter.none: + return todos; + case Filter.completed: + return todos.where((todo) => todo.completed).toList(); + case Filter.uncompleted: + return todos.where((todo) => !todo.completed).toList(); + } +}); \ No newline at end of file diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/read_in_provider/codegen.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/read_in_provider/codegen.dart new file mode 100644 index 000000000..b1cb3dbe3 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/read_in_provider/codegen.dart @@ -0,0 +1,17 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +@riverpod +MyValue another(AnotherRef ref) => MyValue(); + +class MyValue {} + +/* SNIPPET START */ + +@riverpod +MyValue my(MyRef ref) { + // Bad practice to call `read` here + final value = ref.read(anotherProvider); + return value; +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/read_in_provider/codegen.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/read_in_provider/codegen.g.dart new file mode 100644 index 000000000..acabbbbfe --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/read_in_provider/codegen.g.dart @@ -0,0 +1,40 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$anotherHash() => r'bb412edc55657c14eace37792cd18e5254604a36'; + +/// See also [another]. +@ProviderFor(another) +final anotherProvider = AutoDisposeProvider.internal( + another, + name: r'anotherProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$anotherHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef AnotherRef = AutoDisposeProviderRef; +String _$myHash() => r'2712c772be4dbaabd4c99fd803f927a7e9938b21'; + +/// See also [my]. +@ProviderFor(my) +final myProvider = AutoDisposeProvider.internal( + my, + name: r'myProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$myHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef MyRef = AutoDisposeProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/read_in_provider/index.tsx b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/read_in_provider/index.tsx new file mode 100644 index 000000000..339b89a56 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/read_in_provider/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./codegen.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/read_in_provider/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/read_in_provider/raw.dart new file mode 100644 index 000000000..76bae4653 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/read_in_provider/raw.dart @@ -0,0 +1,13 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +final anotherProvider = Provider((ref) => MyValue()); + +class MyValue {} + +/* SNIPPET START */ + +final myProvider = Provider((ref) { + // Bad practice to call `read` here + final value = ref.read(anotherProvider); + return value; +}); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/select_async_provider/codegen.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/select_async_provider/codegen.dart new file mode 100644 index 000000000..e44e202bc --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/select_async_provider/codegen.dart @@ -0,0 +1,25 @@ + +import 'package:dio/dio.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import 'models.dart'; + +part 'codegen.g.dart'; + +final dio = Dio(); + +/* SNIPPET START */ + +@riverpod +Stream config(ConfigRef ref) => Stream.value(Configuration()); + +@riverpod +Future> products(ProductsRef ref) async { + // Listens only to the host. If something else in the configurations + // changes, this will not pointlessly re-evaluate our provider. + final host = await ref.watch(configProvider.selectAsync((config) => config.host)); + + final result = await dio.get>>('$host/products'); + + return result.data!.map(Product.fromJson).toList(); +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/select_async_provider/codegen.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/select_async_provider/codegen.g.dart new file mode 100644 index 000000000..ba5c2aec1 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/select_async_provider/codegen.g.dart @@ -0,0 +1,40 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$configHash() => r'3021d1a8aac384e99d5d22714ffe6e868954888b'; + +/// See also [config]. +@ProviderFor(config) +final configProvider = AutoDisposeStreamProvider.internal( + config, + name: r'configProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$configHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ConfigRef = AutoDisposeStreamProviderRef; +String _$productsHash() => r'd1f4523880408cf8ee0e68969c40cf87d5c78557'; + +/// See also [products]. +@ProviderFor(products) +final productsProvider = AutoDisposeFutureProvider>.internal( + products, + name: r'productsProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$productsHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ProductsRef = AutoDisposeFutureProviderRef>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/select_async_provider/index.tsx b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/select_async_provider/index.tsx new file mode 100644 index 000000000..339b89a56 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/select_async_provider/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./codegen.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/select_async_provider/models.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/select_async_provider/models.dart new file mode 100644 index 000000000..3a711b4b6 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/select_async_provider/models.dart @@ -0,0 +1,21 @@ +// ignore_for_file: sort_constructors_first + +class Product { + const Product({this.title = ''}); + + final String title; + + factory Product.fromJson(Map map) { + return Product( + title: map['title'] as String, + ); + } +} + +class Configuration { + Configuration({ + this.host = '', + }); + + final String host; +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/select_async_provider/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/select_async_provider/raw.dart new file mode 100644 index 000000000..ae35300b0 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/select_async_provider/raw.dart @@ -0,0 +1,20 @@ +import 'package:dio/dio.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'models.dart'; + +final dio = Dio(); + +/* SNIPPET START */ + +final configProvider = + StreamProvider((ref) => Stream.value(Configuration())); + +final productsProvider = FutureProvider>((ref) async { + // Listens only to the host. If something else in the configurations + // changes, this will not pointlessly re-evaluate our provider. + final host = await ref.watch(configProvider.selectAsync((config) => config.host)); + final result = await dio.get>>('$host/products'); + + return result.data!.map(Product.fromJson).toList(); +}); \ No newline at end of file diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/todo_list_provider/codegen.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/todo_list_provider/codegen.dart new file mode 100644 index 000000000..a1942dbd4 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/todo_list_provider/codegen.dart @@ -0,0 +1,24 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.freezed.dart'; +part 'codegen.g.dart'; + +@freezed +class Todo with _$Todo { + factory Todo({ + required String id, + required String description, + required bool completed, + }) = _Todo; +} + +/* SNIPPET START */ + +@riverpod +class TodoList extends _$TodoList { + @override + List build() { + return []; + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/todo_list_provider/codegen.freezed.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/todo_list_provider/codegen.freezed.dart new file mode 100644 index 000000000..0b73d3548 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/todo_list_provider/codegen.freezed.dart @@ -0,0 +1,166 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'codegen.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +/// @nodoc +mixin _$Todo { + String get id => throw _privateConstructorUsedError; + String get description => throw _privateConstructorUsedError; + bool get completed => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $TodoCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $TodoCopyWith<$Res> { + factory $TodoCopyWith(Todo value, $Res Function(Todo) then) = + _$TodoCopyWithImpl<$Res, Todo>; + @useResult + $Res call({String id, String description, bool completed}); +} + +/// @nodoc +class _$TodoCopyWithImpl<$Res, $Val extends Todo> + implements $TodoCopyWith<$Res> { + _$TodoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? description = null, + Object? completed = null, + }) { + return _then(_value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + completed: null == completed + ? _value.completed + : completed // ignore: cast_nullable_to_non_nullable + as bool, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$TodoImplCopyWith<$Res> implements $TodoCopyWith<$Res> { + factory _$$TodoImplCopyWith( + _$TodoImpl value, $Res Function(_$TodoImpl) then) = + __$$TodoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String id, String description, bool completed}); +} + +/// @nodoc +class __$$TodoImplCopyWithImpl<$Res> + extends _$TodoCopyWithImpl<$Res, _$TodoImpl> + implements _$$TodoImplCopyWith<$Res> { + __$$TodoImplCopyWithImpl(_$TodoImpl _value, $Res Function(_$TodoImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? description = null, + Object? completed = null, + }) { + return _then(_$TodoImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + completed: null == completed + ? _value.completed + : completed // ignore: cast_nullable_to_non_nullable + as bool, + )); + } +} + +/// @nodoc + +class _$TodoImpl implements _Todo { + _$TodoImpl( + {required this.id, required this.description, required this.completed}); + + @override + final String id; + @override + final String description; + @override + final bool completed; + + @override + String toString() { + return 'Todo(id: $id, description: $description, completed: $completed)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$TodoImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.description, description) || + other.description == description) && + (identical(other.completed, completed) || + other.completed == completed)); + } + + @override + int get hashCode => Object.hash(runtimeType, id, description, completed); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$TodoImplCopyWith<_$TodoImpl> get copyWith => + __$$TodoImplCopyWithImpl<_$TodoImpl>(this, _$identity); +} + +abstract class _Todo implements Todo { + factory _Todo( + {required final String id, + required final String description, + required final bool completed}) = _$TodoImpl; + + @override + String get id; + @override + String get description; + @override + bool get completed; + @override + @JsonKey(ignore: true) + _$$TodoImplCopyWith<_$TodoImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/todo_list_provider/codegen.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/todo_list_provider/codegen.g.dart new file mode 100644 index 000000000..5cb2dae07 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/todo_list_provider/codegen.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$todoListHash() => r'6c965beb867ffeee119133f0ae2e6ebeb68e6f7e'; + +/// See also [TodoList]. +@ProviderFor(TodoList) +final todoListProvider = + AutoDisposeNotifierProvider>.internal( + TodoList.new, + name: r'todoListProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$todoListHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$TodoList = AutoDisposeNotifier>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/todo_list_provider/index.tsx b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/todo_list_provider/index.tsx new file mode 100644 index 000000000..339b89a56 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/todo_list_provider/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./codegen.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/todo_list_provider/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/todo_list_provider/raw.dart new file mode 100644 index 000000000..a341b7f5b --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/todo_list_provider/raw.dart @@ -0,0 +1,36 @@ +// ignore_for_file: public_member_api_docs, sort_constructors_first +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +class Todo { + const Todo({ + required this.id, + required this.description, + required this.completed, + }); + + // All properties should be `final` on our class. + final String id; + final String description; + final bool completed; + + // Since Todo is immutable, we implement a method that allows cloning the + // Todo with slightly different content. + Todo copyWith({String? id, String? description, bool? completed}) { + return Todo( + id: id ?? this.id, + description: description ?? this.description, + completed: completed ?? this.completed, + ); + } +} + +/* SNIPPET START */ + +class TodoList extends Notifier> { + @override + List build() { + return []; + } +} + +final todoListProvider = NotifierProvider>(TodoList.new); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/weather_provider/codegen.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/weather_provider/codegen.dart new file mode 100644 index 000000000..8f7af2644 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/weather_provider/codegen.dart @@ -0,0 +1,20 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +@riverpod +String city(CityRef ref) => 'London'; + +class Weather {} + +Future fetchWeather({required String city}) async => Weather(); +/* SNIPPET START */ +@riverpod +Future weather(WeatherRef ref) { + // We use `ref.watch` to listen to another provider, and we pass it the provider + // that we want to consume. Here: cityProvider + final city = ref.watch(cityProvider); + + // We can then use the result to do something based on the value of `cityProvider`. + return fetchWeather(city: city); +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/weather_provider/codegen.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/weather_provider/codegen.g.dart new file mode 100644 index 000000000..5398e9c62 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/weather_provider/codegen.g.dart @@ -0,0 +1,40 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$cityHash() => r'2ccdee096b5d5c1cafa736b3e52b788431b9af38'; + +/// See also [city]. +@ProviderFor(city) +final cityProvider = AutoDisposeProvider.internal( + city, + name: r'cityProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$cityHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef CityRef = AutoDisposeProviderRef; +String _$weatherHash() => r'9a79d0269032630918eef9d3f562ff35b5860061'; + +/// See also [weather]. +@ProviderFor(weather) +final weatherProvider = AutoDisposeFutureProvider.internal( + weather, + name: r'weatherProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$weatherHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef WeatherRef = AutoDisposeFutureProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/weather_provider/index.tsx b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/weather_provider/index.tsx new file mode 100644 index 000000000..339b89a56 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/weather_provider/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./codegen.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/weather_provider/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/weather_provider/raw.dart new file mode 100644 index 000000000..d25ec4ffc --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/weather_provider/raw.dart @@ -0,0 +1,18 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +final cityProvider = Provider((ref) => 'London'); + +class Weather {} + +Future fetchWeather({required String city}) async => Weather(); + +/* SNIPPET START */ + +final weatherProvider = FutureProvider((ref) async { + // We use `ref.watch` to listen to another provider, and we pass it the provider + // that we want to consume. Here: cityProvider + final city = ref.watch(cityProvider); + + // We can then use the result to do something based on the value of `cityProvider`. + return fetchWeather(city: city); +}); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/whole_object_provider/codegen.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/whole_object_provider/codegen.dart new file mode 100644 index 000000000..1a90008ed --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/whole_object_provider/codegen.dart @@ -0,0 +1,24 @@ +import 'package:dio/dio.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import 'models.dart'; + +part 'codegen.g.dart'; + +final dio = Dio(); + +/* SNIPPET START */ + +@riverpod +Stream config(ConfigRef ref) => Stream.value(Configuration()); + +@riverpod +Future> products(ProductsRef ref) async { + // Will cause productsProvider to re-fetch the products if anything in the + // configurations changes + final configs = await ref.watch(configProvider.future); + + final result = + await dio.get>>('${configs.host}/products'); + return result.data!.map(Product.fromJson).toList(); +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/whole_object_provider/codegen.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/whole_object_provider/codegen.g.dart new file mode 100644 index 000000000..895d80ff7 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/whole_object_provider/codegen.g.dart @@ -0,0 +1,40 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$configHash() => r'3021d1a8aac384e99d5d22714ffe6e868954888b'; + +/// See also [config]. +@ProviderFor(config) +final configProvider = AutoDisposeStreamProvider.internal( + config, + name: r'configProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$configHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ConfigRef = AutoDisposeStreamProviderRef; +String _$productsHash() => r'637254615fa398af0d36e212f09e5d3d8ff866aa'; + +/// See also [products]. +@ProviderFor(products) +final productsProvider = AutoDisposeFutureProvider>.internal( + products, + name: r'productsProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$productsHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ProductsRef = AutoDisposeFutureProviderRef>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/whole_object_provider/index.tsx b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/whole_object_provider/index.tsx new file mode 100644 index 000000000..339b89a56 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/whole_object_provider/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./codegen.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/whole_object_provider/models.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/whole_object_provider/models.dart new file mode 100644 index 000000000..8009c2fd8 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/whole_object_provider/models.dart @@ -0,0 +1,16 @@ +// ignore_for_file: avoid_unused_constructor_parameters + +class Product { + Product(); + factory Product.fromJson(Map json) { + return Product(); + } +} + +class Configuration { + Configuration({ + this.host = '', + }); + + final String host; +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/whole_object_provider/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/whole_object_provider/raw.dart new file mode 100644 index 000000000..be2be903c --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/whole_object_provider/raw.dart @@ -0,0 +1,20 @@ +import 'package:dio/dio.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'models.dart'; + +final dio = Dio(); + +/* SNIPPET START */ + +final configProvider = + StreamProvider((ref) => Stream.value(Configuration())); + +final productsProvider = FutureProvider>((ref) async { + // Will cause productsProvider to re-fetch the products if anything in the + // configurations changes + final configs = await ref.watch(configProvider.future); + + final result = await dio.get>>('${configs.host}/products'); + return result.data!.map(Product.fromJson).toList(); +}); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_providers.mdx b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_providers.mdx index d71f1d0d9..5f5d33c9e 100644 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_providers.mdx +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/combining_providers.mdx @@ -1,65 +1,73 @@ --- -title: 프로바이더 결합하기 +title: Combining Provider States --- -들어가기 앞서 먼저 [프로바이더란](/docs/concepts/providers)문서를 먼저 읽어주세요. -이 가이드는 복수의 프로바이더들(providers) 결합하는 방법에 대해서 알아볼 예정입니다. +import charactersProvider from "./combining_provider_states/characters_provider"; +import cityProvider from "./combining_provider_states/city_provider"; +import filteredTodoListProvider from "./combining_provider_states/filtered_todo_list_provider"; +import readInProvider from "./combining_provider_states/read_in_provider"; +import selectAsyncProvider from "./combining_provider_states/select_async_provider"; +import todoListProvider from "./combining_provider_states/todo_list_provider"; +import weatherProvider from "./combining_provider_states/weather_provider"; +import wholeObjectProvider from "./combining_provider_states/whole_object_provider"; +import { Link } from "../../../../../src/components/Link"; +import { + trimSnippet, + AutoSnippet, + When, +} from "../../../../../src/components/CodeSnippet"; + +:::caution +The content of this page may be outdated. +It will be updated in the future, but for now you may want to refer to the content +in the top of the sidebar instead (introduction/essentials/case-studies/...) +::: -## 프로바이더 결합하기 +Make sure to read first. +In this guide, we will learn about combining provider states. -이전에 간단한 프로바이더를 생성하는 방법을 알아보았습니다. 그런데 현실에서는 -수많은 상황 중에서 하나의 프로바이더가 다른 프로바이더의 상태를 읽어 사용하는 경우가 많이 있습니다. +## Combining provider states -여러 프로바이더를 결합하기 위해서 [ref] 객체를 사용하여 콜백을 전달하거나 [watch] 메소드를 사용할 수 있습니다. +We've previously seen how to create a simple provider. But the reality is, +in many situations a provider will want to read the state of another provider. -예를 들어, 아래와 같은 프로바이더가 있다고 고려해 보겠습니다. +To do that, we can use the [ref] object passed to the callback of our provider, +and use its [watch] method. -```dart -final cityProvider = Provider((ref) => 'London'); -``` +As an example, consider the following provider: -이제 `cityProvider`를 사용하고 싶은 다른 프로바이더를 만들어 보려고 합니다. + -```dart -final weatherProvider = FutureProvider((ref) async { - // `ref.watch`를 사용해 다른 프로바이더를 읽어올 수 있습니다. - // 그리고 프로바이더를 넘겨줄 수 있습니다. (여기서는 cityProvider가 되겠습니다.) - final city = ref.watch(cityProvider); +We can now create another provider that will consume our `cityProvider`: - // `cityProvider` 값을 기반으로한 무언가의 결과를 반환할 수 있습니다. - return fetchWeather(city: city); -}); -``` + -이상입니다. 여기서 우리는 다른 프로바이더에 의존하는 한개의 프로바이더를 만드는 방법을 알아보았습니다. +That's it. We've created a provider that depends on another provider. ## FAQ -### 구독중인 값이 시간이 지남에 따라 변경되면 어떻게 되나요? +### What if the value being listened to changes over time? -관찰하거나 구독하고 있는 프로바이더에 의존하여 값이 매번 갱신이 될때 마다 새로운 상태값을 -얻게 됩니다. 예를 들어, [StateNotifierProvider]를 구독하고 있거나 [ProviderContainer.refresh]/[ref.refresh] -에 의해 강제적으로 갱신이 되었다면 물론 구독중인 상태값은 갱신되어 전달될 것입니다. +Depending on the provider that you are listening to, the value obtained may +change over time. +For example, you may be listening to a [NotifierProvider], or the provider +being listened to may have been forced to refresh through the use of +[ProviderContainer.refresh]/[ref.refresh]. -[watch]를 사용할 때, Riverpod은 상태값의 변화를 검출하고 _자동적_ 으로 프로바이더를 필요할 때 재 실행합니다. +When using [watch], Riverpod is able to detect that the value being listened to changed +and will _automatically_ re-execute the provider's creation callback when needed. -이것은 상태들을 계산하기 유용할 수 있습니다. -예를 들어 [StateNotifierProvider]가 할일 목록(a todo-list)의 값을 가진다고 생각해 봅시다. +This can be useful for computed states. +For example, consider a that exposes a todo-list: -```dart -class TodoList extends StateNotifier> { - TodoList(): super(const []); -} - -final todoListProvider = StateNotifierProvider((ref) => TodoList()); -``` + +A common use-case would be to have the UI filter the list of todos to show +only the completed/uncompleted todos. -일반적인 사용방법으로 완료된/완료되지 않은 할일만 표시하도록 UI가 할일 목록을 필터링하도록 하는 것입니다. +An easy way to implement such a scenario would be to: -이러한 시나리오를 구현하는 쉬운 방법은 다음과 같습니다. - -- [StateProvider]를 생성합니다. `filterProvider`는 현재 어떤 필터를 선택하고 있는지에 대한 상태 값을 가집니다. +- create a [StateProvider], which exposes the currently selected filter method: ```dart enum Filter { @@ -71,88 +79,58 @@ final todoListProvider = StateNotifierProvider((ref) => TodoList()); final filterProvider = StateProvider((ref) => Filter.none); ``` -- 필터 메소드(`filterProvider`)와 할일 목록(`todoListProvider`)을 결합하여 필터링된 할일 목록 값을 제공하는 별도의 프로바이더(`filteredTodoListProvider`)를 만듭니다. +- make a separate provider which combines the filter method and the todo-list + to expose the filtered todo-list: - ```dart - final filteredTodoListProvider = Provider>((ref) { - final filter = ref.watch(filterProvider); - final todos = ref.watch(todoListProvider); - - switch (filter) { - case Filter.none: - return todos; - case Filter.completed: - return todos.where((todo) => todo.completed).toList(); - case Filter.uncompleted: - return todos.where((todo) => !todo.completed).toList(); - } - }); - ``` + -그리고, UI에서 `filteredTodoListProvider`를 구독하여 필터링된 할일 목록을 받아 올 수 있습니다. -필터와 할일 목록이 각각 갱신(변경)될때 마다 자동적으로 UI 업데이트가 이루어 집니다. +Then, our UI can listen to `filteredTodoListProvider` to listen to the filtered todo-list. +Using such an approach, the UI will automatically update when either the filter +or the todo-list changes. -이러한 방법으로 접근한 애플리케이션 샘플은 [Todo List -example](https://github.com/rrousselGit/riverpod/tree/master/examples/todos)에서 확인해 볼 수 있습니다. +To see this approach in action, you can look at the source code of the [Todo List +example](https://github.com/rrousselGit/riverpod/tree/master/examples/todos). :::info -이 행위는 [Provider]에 국한되지 않습니다. 그리고 모든 프로바이더에 적용하여 사용 가능합니다. - -예를 들어, 실시간적으로 값이 변하는 것을 지원하는 검색 기능을 구현하기 위해 [FutureProvider]와 함께 [watch]를 결합하여 사용할 수 있습니다. +This behavior is not specific to [Provider], and works with all providers. For example, you could combine [watch] with [FutureProvider] to implement a search feature that supports live-configuration changes: -```dart -// 현재 검색 필터 값 입니다. ('') -final searchProvider = StateProvider((ref) => ''); - -/// 매 시간마다 변경될 수 있는 구성(Configurations) -final configsProvider = StreamProvider(...); - -final charactersProvider = FutureProvider>((ref) async { - final search = ref.watch(searchProvider); - final configs = await ref.watch(configsProvider.future); - final response = await dio.get('${configs.host}/characters?search=$search'); - - return response.data.map((json) => Character.fromJson(json)).toList(); -}); -``` - -이 코드는 `characters` 목록을 서비스로 부터 가져옵니다. 그리고 구성(configurations)이 변하거나 검색 쿼리가 변경되면 -자동적으로 다시 `characters` 목록을 가져옵니다. + +This code will fetch a list of characters from the service, and automatically +re-fetch the list whenever the configurations change or when the search query changes. ::: -### 구독없이 프로바이더를 읽을 수 있나요? +### Can I read a provider without listening to it? -때때로 우린 값이 변경되어도 다시 생성되는 작업 없이 프로바이더의 컨텐츠를 읽기 원합니다. Sometimes, we want to read the content of a provider, but without re-creating the value exposed when the value obtained changes. -예를 들어 인증을 위한 사용자 토큰을 다른 프로바이더로 부터 읽어오는 `Repository` 프로바이더가 있다고 생각해 봅시다. -여기서 우리는 [watch]를 사용하고 사용자 토큰이 변경될 때마다 새로운 `Repository`를 생성할 수 있지만 그렇게 하는 것은 거의 소용이 없습니다. - -이 경우에는, [watch]와 유사한 기능을 가지는 [read]를 사용할 수 있습니다. -그러나, 상태 값이 변경될 때 프로바이더가 노출하는 값을 다시 생성하지 않습니다. +An example would be a `Repository`, which reads from another provider the user token +for authentication. +We could use [watch] and create a new `Repository` whenever the user token changes, +but there is little to no use in doing that. -이 경우 일반적으로 생성된 객체에 'ref.read'를 전달합니다. -생성된 객체는 원할 때마다 프로바이더를 읽을 수 있습니다. +In this situation, we can use [read], which is similar to [watch], but will not +cause the provider to recreate the value it exposes when the value obtained changes. +In that case, a common practice is to pass the provider's `Ref` to the object created. +The object created will then be able to read providers whenever it wants. ```dart final userTokenProvider = StateProvider((ref) => null); -final repositoryProvider = Provider((ref) => Repository(ref.read)); +final repositoryProvider = Provider(Repository.new); class Repository { - Repository(this.read); + Repository(this.ref); - /// `ref.read` 함수입니다. - final Reader read; + final Ref ref; Future fetchCatalog() async { - String token = read(userTokenProvider); + String token = ref.read(userTokenProvider); final response = await dio.get('/path', queryParameters: { 'token': token, @@ -163,50 +141,24 @@ class Repository { } ``` -:::note +:::danger **DON'T** call [read] inside the body of a provider -`ref.read`대신에 `ref` 를 객체에 전달해서 사용하도록 합니다. - -```dart -final repositoryProvider = Provider((ref) => Repository(ref)); - -class Repository { - Repository(this.ref); - - final Ref ref; -} -``` - -그러나 `ref.read`를 전달하면 코드가 약간 덜 장황해지고 객체가 `ref.watch`를 사용하지 않을 것입니다. -다시 말해, `ref`대신 `ref.read`를 전달하면 `ref.watch`는 사용할 수 없다는 말과 동일합니다. - -::: - -:::위험 **DON'T** : [read]를 프로바이더 내부에서 호출하지 마세요. - -```dart -final myProvider = Provider((ref) { - // 여기서 'read'를 호출하는 것은 좋지 않습니다. - final value = ref.read(anotherProvider); -}); -``` - -만약 객체의 원치 않는 재빌드을 방지하기 위해 [read]를 사용한 경우, -[프로바이더 갱신이 너무 자주일어나는데 어떻게 개선하면 좋을까요?](#프로바이더-갱신이-너무-자주일어나는데-어떻게-개선하면-좋을까요) -항목을 참고해 주세요. + +If you used [read] as an attempt to avoid unwanted rebuilds of your object, +refer to [My provider updates too often, what can I do?](#my-provider-updates-too-often-what-can-i-do) ::: -### 생성자의 매개변수로 [read]를 전달 받는 객체는 어떻게 테스트 하면 좋은가요? +### How to test an object that receives [ref] as a parameter of its constructor? -만약 [구독없이 프로바이더를 읽을 수 있나요?](#구독없이-프로바이더를-읽을-수-있나요)에서 사용한 패턴을 사용한다면, -어떻게 객체를 테스트할지 의문이 들 수 있습니다. +If you are using the pattern described in [Can I read a provider without listening to it?](#can-i-read-a-provider-without-listening-to-it), +you may be wondering how to write tests for your object. -이 시나리오에서는 raw 객체 대신에 프로바이더를 직접 테스트 하는 것이 좋습니다. -[ProviderContainer] 클래스를 사용하여 테스트를 진행할 수 있습니다. +In this scenario, consider testing the provider directly instead of the raw object. +You can do so by using the [ProviderContainer] class: ```dart -final repositoryProvider = Provider((ref) => Repository(ref.read)); +final repositoryProvider = Provider((ref) => Repository(ref)); test('fetches catalog', () async { final container = ProviderContainer(); @@ -221,57 +173,38 @@ test('fetches catalog', () async { }); ``` -### 프로바이더 갱신이 너무 자주일어나는데 어떻게 개선하면 좋을까요? - - -객체가 너무 자주 다시 생성되는 경우 프로바이더가 갱신에 무관계한 요소를 수신하고 있는 것입니다. - -에들 들어, `Configuration` 객체 뿐만 아니라 `host` 속성을 구독하고 있다고 있다고 가정해 봅시다. -`host`의 속성만 변경되었지만 본래 필요없는 값까지 모두 재평가(계산)하여 전반적인 프로바이더의 갱신을 가져올 것 입니다. - -해결 방법으로는 분리된 프로바이더를 만드는 것입니다. 즉, 프로바이더를 속성별로 분리하는 것입니다. -다시 말해, `Configuration`(즉 `host`)에서 필요한 것만 노출하는 별도의 프로바이더를 만드는 것입니다. - - -**AVOID** 객체 전반적인 관찰: +### My provider updates too often, what can I do? -```dart -final configsProvider = StreamProvider(...); +If your object is re-created too often your provider is likely listening +to objects that it doesn't care about. -final productsProvider = FutureProvider>((ref) async { - // 어떤 구성이 변경되었다고 한다면 productsProvider는 products를 다시 가져옵니다. - final configs = await ref.watch(configsProvider.future); +For example, you may be listening to a `Configuration` object, but only use the `host` +property. +By listening to the entire `Configuration` object, if a property other than `host` +changes, this still causes your provider to be re-evaluated – which may be +undesired. - return dio.get('${configs.host}/products'); -}); -``` +The solution to this problem is to create a separate provider that exposes _only_ +what you need in `Configuration` (so `host`): -**PREFER** 실제 사용하는 속성만 관찰: +**AVOID** listening to the entire object: -```dart -final configsProvider = StreamProvider(...); + -/// 현재 host값만 노출하는 프로바이더 -final _hostProvider = FutureProvider((ref) async { - final config = await ref.watch(configsProvider.future); - return config.host; -}); +**PREFER** using select when you only need a single property of an object: -final productsProvider = FutureProvider>((ref) async { - /// 호스트(the host)만 구독. - /// 만약 구성들의 변화가 발생한다면, 무의미하게 재평가하지 않습니다. - final host = await ref.watch(_hostProvider.future); + - return dio.get('$host/products'); -}); -``` +This will only rebuild the `productsProvider` when the `host` changes. [provider]: ../providers/provider [stateprovider]: ../providers/state_provider [futureprovider]: ../providers/future_provider [statenotifierprovider]: ../providers/state_notifier_provider +[notifierProvider]: ../providers/notifier_provider [ref]: https://pub.dev/documentation/riverpod/latest/riverpod/Ref-class.html [watch]: https://pub.dev/documentation/riverpod/latest/riverpod/Ref/watch.html [read]: https://pub.dev/documentation/riverpod/latest/riverpod/Ref/read.html [providercontainer.refresh]: https://pub.dev/documentation/riverpod/latest/riverpod/ProviderContainer/refresh.html [ref.refresh]: https://pub.dev/documentation/flutter_riverpod/latest/flutter_riverpod/WidgetRef/refresh.html +[providercontainer]: https://pub.dev/documentation/riverpod/latest/riverpod/ProviderContainer-class.html diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/dialog_scope.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/dialog_scope.dart new file mode 100644 index 000000000..4ee7792f9 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/dialog_scope.dart @@ -0,0 +1,58 @@ +// ignore_for_file: omit_local_variable_types, prefer_final_locals + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +/* SNIPPET START */ + +// Have a counter that is being incremented by the FloatingActionButton +final counterProvider = StateProvider((ref) => 0); + +class Home extends ConsumerWidget { + const Home({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + // We want to show a dialog with the count on a button press + return Scaffold( + body: Column( + children: [ + ElevatedButton( + onPressed: () { + showDialog( + context: context, + builder: (c) { + // We wrap the dialog with a ProviderScope widget, providing the + // parent container to ensure the dialog can access the same providers + // that are accessible by the Home widget. + return ProviderScope( + parent: ProviderScope.containerOf(context), + child: const AlertDialog( + content: CounterDisplay(), + ), + ); + }, + ); + }, + child: const Text('Show Dialog'), + ), + ], + ), + floatingActionButton: FloatingActionButton( + child: const Icon(Icons.add), + onPressed: () { + ref.read(counterProvider.notifier).state++; + }, + )); + } +} + +class CounterDisplay extends ConsumerWidget { + const CounterDisplay({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final count = ref.watch(counterProvider); + return Text('$count'); + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/lifecycle_on_dispose/codegen.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/lifecycle_on_dispose/codegen.dart new file mode 100644 index 000000000..0977a8087 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/lifecycle_on_dispose/codegen.dart @@ -0,0 +1,21 @@ +// ignore_for_file: unnecessary_lambdas + +import 'dart:async'; + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +/* SNIPPET START */ +@riverpod +Stream example(ExampleRef ref) { + final streamController = StreamController(); + + ref.onDispose(() { + // Closes the StreamController when the state of this provider is destroyed. + streamController.close(); + }); + + return streamController.stream; +} + diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/lifecycle_on_dispose/codegen.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/lifecycle_on_dispose/codegen.g.dart new file mode 100644 index 000000000..24f936699 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/lifecycle_on_dispose/codegen.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$exampleHash() => r'e2c4eb8a7cf06c7a0e5d07ee2bd51db254033fa6'; + +/// See also [example]. +@ProviderFor(example) +final exampleProvider = AutoDisposeStreamProvider.internal( + example, + name: r'exampleProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$exampleHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ExampleRef = AutoDisposeStreamProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/lifecycle_on_dispose/index.tsx b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/lifecycle_on_dispose/index.tsx new file mode 100644 index 000000000..339b89a56 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/lifecycle_on_dispose/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./codegen.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/lifecycle_on_dispose/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/lifecycle_on_dispose/raw.dart new file mode 100644 index 000000000..a9b5741d8 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/lifecycle_on_dispose/raw.dart @@ -0,0 +1,17 @@ +// ignore_for_file: unnecessary_lambdas + +import 'dart:async'; + +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +/* SNIPPET START */ +final example = StreamProvider.autoDispose((ref) { + final streamController = StreamController(); + + ref.onDispose(() { + // Closes the StreamController when the state of this provider is destroyed. + streamController.close(); + }); + + return streamController.stream; +}); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/modifiers/auto_dispose.mdx b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/modifiers/auto_dispose.mdx index 0eb1a7f8e..eaece8112 100644 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/modifiers/auto_dispose.mdx +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/modifiers/auto_dispose.mdx @@ -2,24 +2,27 @@ title: .autoDispose --- -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; - -통상적으로 프로바이더를 사용할때, 프로바이더를 더이상 사용하지 않는다면 -시스템 메모리상에서 해제(destroy)하고 싶은 경우가 있습니다. +:::caution +The content of this page may be outdated. +It will be updated in the future, but for now you may want to refer to the content +in the top of the sidebar instead (introduction/essentials/case-studies/...) +::: -destroy하고 싶은 경우는 다양한 이유가 있지만 아래의 경우를 생각해볼 수 있습니다. +A common use case is to destroy the state of a provider +when it is no-longer used. -- 파이어베이스(Firebase)를 사용할떄 불필요한 코스트 발생을 피하기 위해 연결을 끊는 경우 -- 사용자가 화면상에서 떠나고 재진입했을때 상태를 초기화 해줘야 하는 경우 +There are multiple reasons for doing so, such as: -위의 경우등을 대응할 수 있도록 프로바이더는 빌트인으로 `.autoDispose` 수식어를 지원합니다. +- When using Firebase, to close the connection and avoid unnecessary cost. +- To reset the state when the user leaves a screen and re-enters it. -## 사용방법 +Providers come with built-in support for this use case, through the `.autoDispose` +modifier. -Riverpod에 더이상 사용하지 않는 프로바이더의 상태를 소멸(destroy)하기위해 -`.autoDispose` 수식어를 프로바이더에 붙이기만 하면 됩니다. +## Usage +To tell Riverpod to destroy the state of a provider when it is no longer used, +simply append `.autoDispose` to your provider: ```dart final userProvider = StreamProvider.autoDispose((ref) { @@ -27,14 +30,14 @@ final userProvider = StreamProvider.autoDispose((ref) { }); ``` -이것으로 `userProvider`가 더 이상 사용되지 않을때 자동으로 해제 작업을 처리합니다. +That's it. Now, the state of `userProvider` will automatically be destroyed +when it is no longer used. -제네릭 파라미터가 `autoDispose`전에 붙는게 아닌 `autoDispose`뒤에 붙는 것으로 -알 수 있듯이 `autoDispose`는 a named constructor가 아닙니다. +Note how the generic parameters are passed after `autoDispose` instead of before – +`autoDispose` is not a named constructor. :::note -`.autoDispose`와 다른 수식어를 결합할 수 있습니다. - +You can combine `.autoDispose` with other modifiers if you need to: ```dart final userProvider = StreamProvider.autoDispose.family((ref, id) { @@ -46,12 +49,13 @@ final userProvider = StreamProvider.autoDispose.family((ref, id) { ### ref.keepAlive -프로바이더에 `autoDispose` 수식어를 붙이면 `ref`객체에 `keepAlive` 메소드를 사용할 수 있습니다. +Marking a provider with `autoDispose` also adds an extra method on `ref`: `keepAlive`. -`keepAlive` 메소드를 실행하는 것으로, 프로바이더가 참조되지 않게 되었을 때에도 -상태를 유지하도록 Riverpod 에 전할 수가 있습니다. +The `keepAlive` function is used to tell Riverpod that the state of the provider +should be preserved even if no longer listened to. -좀 더 이해를 돕기위해서 HTTP 요청이 완료되면 플레그를 `true`로 설정하는 예시 코드입니다. +A use-case would be to set this flag to `true` after an HTTP request has +completed: ```dart final myProvider = FutureProvider.autoDispose((ref) async { @@ -61,67 +65,70 @@ final myProvider = FutureProvider.autoDispose((ref) async { }); ``` -위의 방식을 사용하면, 만약에 요청이 실패하고 사용자가 화면상에서 떠나고 다시 들어온 경우 -요청을 재 실행할 수 있도록 합니다. 그러나, 만약 요청이 성공적으로 처리된다면, 상태가 유지될 것입니다. -그리고 화면에 재 진입해도 새로운 요청을 위한 트리거링이 발생하지 않습니다. +This way, if the request fails and the user leaves the screen then re-enters +it, then the request will be performed again. +But if the request completed successfully, the state will be preserved +and re-entering the screen will not trigger a new request. +:::info +In version 1.0.x, the equivalent of `keepAlive` is the property called `maintainState`. +::: -## 사용예: 더 이상 프로바이더를 사용하지 않을 때, HTTP 요청 취소하기 +## Example: Canceling HTTP requests when no longer used -`autoDispose` 수식어는 [FutureProvider]와 `ref.onDispose`에 결합 할 수 있습니다. -프로바이더를 더 이상 사용하지 않을 때 쉽게 HTTP 요청을 취소할 수 있습니다. +The `autoDispose` modifier could be combined with [FutureProvider] and `ref.onDispose` +to easily cancel HTTP requests when they are no longer needed. -이번 예시의 목표는 다음과 같습니다. +The goal is: -- 사용자가 화면에 들어왔을 때 HTTP 요청을 시작합니다. -- 만약 요청이 완료되기 전에 사용자가 화면을 떠났다면, HTTP을 취소합니다. -- 만약 요청이 성공했다면, 화면을 떠나거나 재 진입했을때 새로운 요청을 시작하지 않습니다. +- Start an HTTP request when the user enters a screen +- if the user leaves the screen before the request completed, cancel the HTTP request +- if the request succeeded, leaving and re-entering the screen does not start a new request -코드로 구현해 본다면 아래와 같습니다. +In code, this would be: ```dart final myProvider = FutureProvider.autoDispose((ref) async { - // http 요청을 취소하기 위한 package:dio 객체 + // An object from package:dio that allows cancelling http requests final cancelToken = CancelToken(); - // 프로바이더가 해제(destroyed)될 때, http 요청을 취소합니다. + // When the provider is destroyed, cancel the http request ref.onDispose(() => cancelToken.cancel()); - // 데이터를 가져오고 취소하기 위한 `cancelToken`을 파라미터로 념겨줍니다. + // Fetch our data and pass our `cancelToken` for cancellation to work final response = await dio.get('path', cancelToken: cancelToken); - // 만약 요청이 성곡적으로 완료되었다면, 현재 상태를 유지합니다. + // If the request completed successfully, keep the state ref.keepAlive(); return response; }); ``` -## 'AutoDisposeProvider'인수 타입은 'AlwaysAliveProviderBase' 매개변수에 할당할 수 없습니다. +## The argument type 'AutoDisposeProvider' can't be assigned to the parameter type 'AlwaysAliveProviderBase' -`.autoDispose`를 사용할 때, 아래와 같은 에러가 발생하면서 컴파일이 되지 않는 경우를 -만나볼 수 있습니다. +When using `.autoDispose`, you may find yourself in a situation where your +application does not compile with an error similar to: > The argument type 'AutoDisposeProvider' can't be assigned to the parameter > type 'AlwaysAliveProviderBase' -걱정 하지 마세요! 이 에러는 자발적으로 발생하는 오류입니다. -버그가 발생할 가능성이 높기 때문에 발생하는 오류입니다. - -`.autoDispose`의 수식어를 붙이지 않은 프로바이더에서 `.autoDispose` 수식어를 사용한 -프로바이더를 사용할 경우 발생할 수 있습니다. +Don't worry! This error is voluntary. It happens because you most likely +have a bug: +You tried to listen to a provider marked with `.autoDispose` in a provider that +is **not** marked with `.autoDispose`, such as: ```dart final firstProvider = Provider.autoDispose((ref) => 0); final secondProvider = Provider((ref) { - // 'AutoDisposeProvider'인자 값을 - // 'AlwaysAliveProviderBase' 파라미터 타입으로 할당할 수 없습니다. + // The argument type 'AutoDisposeProvider' can't be assigned to the + // parameter type 'AlwaysAliveProviderBase' ref.watch(firstProvider); }); ``` -위의 코드의 경우 `firstProvider`가 절대 disposed 처리 않음으로 사용상 바람직하지 않습니다. +This is undesired, as it would cause `firstProvider` to never be disposed. -이를 수정하기 위해 `secondProvider`에 `.autoDispose`를 추가할 필요가 있습니다. +To fix this, consider marking `secondProvider` with `.autoDispose` too: ```dart final firstProvider = Provider.autoDispose((ref) => 0); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/modifiers/family.mdx b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/modifiers/family.mdx index 483296ae4..1b6346231 100644 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/modifiers/family.mdx +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/modifiers/family.mdx @@ -4,47 +4,49 @@ title: .family import Tabs from "@theme/Tabs"; import TabItem from "@theme/TabItem"; +import { Link } from "../../../../../../src/components/Link"; -본 문서를 읽기 전에 [프로바이더란](/docs/concepts/providers)와 [프로바이더 읽기](/docs/concepts/reading)문서를 -먼저 읽고 오면 좋습니다. - -여기서는 `.family` 수식어에 대해 상세하게 알아보도록 하겠습니다. +:::caution +The content of this page may be outdated. +It will be updated in the future, but for now you may want to refer to the content +in the top of the sidebar instead (introduction/essentials/case-studies/...) +::: -`.family` 수식어(modifier)의 하나의 목적을 가집니다. -외부 값을 이용하여 프로바이더를 생성할때 사용합니다. +Before reading this, consider reading about and . +In this part, we will talk in detail about the `.family` provider modifier. -통상적인 `family` 사용예는 다음과 같습니다. +The `.family` modifier has one purpose: Getting a unique provider based on external parameters. Some common use-cases for `family` would be: -- [FutureProvider]를 `.family`와 결합하여 ID에서 `Message`를 가져오는 경우. -- 번역을 다루기 위해 현재 `Locale` 값을 프로바이더로 전달하는 경우. +- Combining [FutureProvider] with `.family` to fetch a `Message` from its ID +- Passing the current `Locale` to a provider, so that we can handle translations -## 사용방법 +## Usage -프로바이더에 `.family` 수식어를 결합하면 파라미터가 추가됩니다. -이 파라미터를 프로바이더가 어떠한 특정 상태를 생성하기 위해 사용됩니다. +The way families works is by adding an extra parameter to the provider. +This parameter can then be freely used in our provider to create some state. -예를 들어, `family` 수식어를 [FutureProvider]에 결합하여 `Message`의 ID를 통해 -`Message`를 가져오는 경우를 확인해 보겠습니다. (위의 1번 예시) +For example, we can combine `family` with [FutureProvider] to fetch +a `Message` from its ID: ```dart final messagesFamily = FutureProvider.family((ref, id) async { return dio.get('http://my_api.dev/messages/$id'); }); ``` -그리고 `messagesFamily` 프로바이더를 사용할때 사용 구문(the syntax)에는 다소 차이가 있습니다. -일반적인 구문은 컴파일 상에서 에러를 발생시킵니다. + +When using our `messagesFamily` provider, the syntax is slightly different. +The usual syntax will not work anymore: ```dart Widget build(BuildContext context, WidgetRef ref) { - // Error – messagesFamily is not a provider - // 에러 - messagesFamily는 프로바이더가 아닙니다. + // Error – messagesFamily is not a provider final response = ref.watch(messagesFamily); } ``` -아래와 같이, `messagesFamily`에 파라미터를 넘겨줄 필요가 있습니다. +Instead, we need to pass a parameter to `messagesFamily`: ```dart Widget build(BuildContext context, WidgetRef ref) { @@ -53,10 +55,9 @@ Widget build(BuildContext context, WidgetRef ref) { ``` :::info - - `family` 를 사용하는 프로바이더에 다른 파라미터를 동시에 전달하는 것도 가능합니다. - 예를 들어, `titleFamily` 프로바이더에 프랑스어(French)와 영어(English)의 값을 얻기 위해 - 동시에 각기 다른 Locale 변수를 파라미터로 전달하고 있는 것을 확인할 수 있습니다. +It is possible to use a family with different parameters simultaneously. +For example, we could use a `titleFamily` to read both the French and English +translations at the same time: ```dart @override @@ -70,24 +71,22 @@ Widget build(BuildContext context, WidgetRef ref) { ::: -## 파라미터 제한하기 +## Parameter restrictions -`family`를 정상적으로 올바르게 작동시키기 위해서는 전달하는 파라미터에 대한 -`hashCode` and `==`의 등가성이 정의되어야할 필요가 있습니다. +For families to work correctly, it is critical for the parameter passed to +a provider to have a consistent `hashCode` and `==`. -이상적으로, 매개변수는 primitive자료형(bool/int/double/String), 정수(providers) 또는 -`==` 와 `hashCode`를 오버라이드 할 수 았는 불변(immutable) 객체 이어야 합니다. +Ideally, the parameter should either be a primitive (bool/int/double/String), +a constant (providers), or an immutable object that overrides `==` and `hashCode`. -- primitive 자료형: 컴퓨터 프로그램을 만드는 데 사용되는 기초적인 언어 구성. +### _PREFER_ using `autoDispose` when the parameter is not constant: +You may want to use families to pass the input of a search field to your provider. +But that value can change often and never be reused. +This could cause memory leaks as, by default, a provider is never destroyed even +if no longer used. -### _PREFER_ 파라미터가 정수가 아닐때는 `autoDispose`를 사용하세요. - -`family`를 사용해서 검색창에 입력된 값을 프로바이더의 매개변수로 전달하고 싶을 때가 있습니다. -그러나, 값은 종종 변경될 수 있고 결코 그 값은 재사용되지 않습니다. -이것은 심지어 더 이상 사용되지 않을때, 기본적으로 프로바이더는 절대 해제되지 않기 떄문에 메모리 누수를 야기할 수 있습니다. - -`.family` 와 `.autoDispose`를 함께 사용함으로써 메모리 누수 문제를 개선할 수 있습니다. +Using both `.family` and `.autoDispose` fixes that memory leak: ```dart final characters = FutureProvider.autoDispose.family, String>((ref, filter) async { @@ -95,19 +94,20 @@ final characters = FutureProvider.autoDispose.family, String>((r }); ``` -## 복수의 파라미터 전달하기 +## Passing multiple parameters to a family -`family` 수식어는 프로바이더로 복수의 파라미터를 전달하는것이 불가능합니다. +Families have no built-in support for passing multiple values to a provider. -반면에, 앞에서 설명한 제한 사항을 충족하는 한 모든 객체를 전달할 수 있습니다. +On the other hand, that value could be _anything_ (as long as it matches with +the restrictions mentioned previously). -아래의 패키지들을 이용하면 가능합니다. +This includes: - A tuple from [tuple](http://pub.dev/packages/tuple) - Objects generated with [Freezed] or [built_value](https://pub.dev/packages/built_value) - Objects using [equatable](https://pub.dev/packages/equatable) -여기 [Freezed] 와 [equatable]를 사용한 예시가 있습니다. +Here's an example of using [Freezed] or [equatable] for multiple parameters: ((ref, myParameter) { print(myParameter.userId); print(myParameter.locale); - // userId/locale를 사용하여 무언가를 처리 + // Do something with userId/locale }); @override Widget build(BuildContext context, WidgetRef ref) { - int userId; // userId를 어디선가 읽어 옴. + int userId; // Read the user ID from somewhere final locale = Localizations.localeOf(context); final something = ref.watch( @@ -168,12 +168,12 @@ class MyParameter extends Equatable { final exampleProvider = Provider.family((ref, myParameter) { print(myParameter.userId); print(myParameter.locale); - // userId/locale를 사용하여 무언가를 처리 + // Do something with userId/locale }); @override Widget build(BuildContext context, WidgetRef ref) { - int userId; // userId를 어디선가 읽어 옴. + int userId; // Read the user ID from somewhere final locale = Localizations.localeOf(context); final something = ref.watch( @@ -183,6 +183,7 @@ Widget build(BuildContext context, WidgetRef ref) { ... } ``` + diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/provider_lifecycles.mdx b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/provider_lifecycles.mdx new file mode 100644 index 000000000..971ae56ef --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/provider_lifecycles.mdx @@ -0,0 +1,108 @@ +--- +title: Provider Lifecycles +--- + +import CodeBlock from "@theme/CodeBlock"; +import onDispose from "./lifecycle_on_dispose"; +import { trimSnippet, When, AutoSnippet } from "../../../../../src/components/CodeSnippet"; + +:::caution +The content of this page may be outdated. +It will be updated in the future, but for now you may want to refer to the content +in the top of the sidebar instead (introduction/essentials/case-studies/...) +::: + +## When does my Provider get created and disposed? + +The states that all different types of providers can go through are the same: + +- Uninitialized +- Alive +- Paused +- Disposed + +### Disposed / Uninitialized + +An **Uninitialized** or **Disposed** provider does not take up any memory since its state is not initialized. +Essentially it is just a definition of how to create the provider's state when you need it. +It will stay that way until an **Alive** provider or a [WidgetRef] from the UI reads, watches, or listens to it. + +### Creating -> Alive + +When an **Uninitialized** provider is read, listened to or watched it's state will be created. + +During creation your provider's build function will be run. +Any providers that you read or watch using the `ref` exposed by the callback will be created as needed and their state will be retrieved. + +If there are any circular dependencies during this creation process Riverpod will throw an error. +The best way to fix this error is to redesign your dependencies to have a uni-directional dataflow. + +The provider's state is stored in a [ProviderContainer]. In a Flutter app this container is in a [ProviderScope] widget. + +As such, even though the definition of how to create the state (the provider) is global, the state is actually local, +and can be different in different portions of your UI using nested [ProviderScope] widgets and overrides. + +This is very similar to how flutter widgets work. You only pay for the definition once, but can reuse the state in different parts of the tree as needed. + +### Alive + +When your provider is **Alive**, changes to its state will cause dependent providers and/or the dependent UI to rebuild. + +From the other perspective, as a reactive framework, you can watch other providers to have the provider recreate itself whenever one of it's dependencies changes. + +If you need to have some long-lived state that depends on other state you can use Ref's [listen] method to subscribe for changes on another provider without causing a rebuild of the provider. + +If you need to use the state from another provider in a side-effect, you can use Ref's [read] method to obtain the current state from another provider. + +Typically when constructing a [StateNotifier] or [ChangeNotifier] class you should pass in the `ref` to allow the Notifier to obtain the current value of dependencies as needed. By using the new [Notifier] and [AsyncNotifier] classes from Riverpod 2.0, the ref is already available as an instance member of the class. + +### Alive -> Paused +When an **Alive** provider is no longer listened to by other providers or the UI, it goes into a **Paused** state. +This means that it no longer will react to changes on providers it is listening to. +This is an optimization, as if you are not listening to the provider, there is no need to keep it alive. +Every provider not being used will be returned to a **Paused** state, reducing the computational burden of your app. + +If you need to keep a provider alive for side-effects, make sure to listen to it in an appropriate place in the UI where it should be kept alive. + +If you need to perform some action when a provider is paused use the ref's [onCancel] method to register callbacks. + +If you need to perform some action when a provider resumes to an Alive state from a paused state, use the ref's [onResume] method to register callbacks. + +If you want the state to be disposed, so that in addition to taking no computational resources, it also disposes of the memory of the state, use the `.autoDispose` modifier on your provider definition. +This will cause it to transition to a **Disposed** state instead of **Paused** when it is no longer being used. + +### Alive -> Disposing + +There are a few reasons for a provider to be disposed. + +- When defined using the `.autoDispose` modifier and no longer being watched by the UI or another provider +- When the provider is being manually refreshed or invalidated +- When the provider is being recreated due to one of it's watched dependencies changing + +Refreshing causes the provider to immediately go through the creation process again, whereas invalidating causes the next read / watch of the provider to cause the provider to be rebuilt. + +## Performing actions before the state destruction +If you need to perform some action when a provider is disposed, use the ref's [onDispose] method to register callbacks. + +The following example uses onDispose to close a StreamController: + + + +:::note +Depending on the provider used, it may already take care of the clean-up process. +For example, [StateNotifierProvider] will call the `dispose` method of the returned [StateNotifier]. +::: + +[onDispose]: https://pub.dev/documentation/riverpod/latest/riverpod/Ref/onDispose.html +[listen]: https://pub.dev/documentation/riverpod/latest/riverpod/Ref/listen.html +[onCancel]: https://pub.dev/documentation/riverpod/latest/riverpod/Ref/onCancel.html +[onResume]: https://pub.dev/documentation/riverpod/latest/riverpod/Ref/onResume.html +[ProviderContainer]: https://pub.dev/documentation/riverpod/latest/riverpod/ProviderContainer-class.html +[Notifier]: https://pub.dev/documentation/riverpod/latest/riverpod/Notifier-class.html +[AsyncNotifier]: https://pub.dev/documentation/riverpod/latest/riverpod/AsyncNotifier-class.html +[ProviderScope]: https://pub.dev/documentation/flutter_riverpod/latest/flutter_riverpod/ProviderScope-class.html +[WidgetRef]: https://pub.dev/documentation/flutter_riverpod/latest/flutter_riverpod/WidgetRef-class.html +[StateNotifier]: https://pub.dev/documentation/state_notifier/latest/state_notifier/StateNotifier-class.html +[StateNotifierProvider]: https://pub.dev/documentation/riverpod/latest/riverpod/StateNotifierProvider-class.html +[StreamController]: https://api.dart.dev/stable/2.15.1/dart-async/StreamController-class.html +[ChangeNotifier]: https://api.flutter.dev/flutter/foundation/ChangeNotifier-class.html diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/provider_observer.mdx b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/provider_observer.mdx index 2fbae4b78..a611d07d6 100644 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/provider_observer.mdx +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/provider_observer.mdx @@ -3,27 +3,32 @@ title: ProviderObserver --- import CodeBlock from "@theme/CodeBlock"; -import logger from "!!raw-loader!/i18n/ko/docusaurus-plugin-content-docs/current/concepts/provider_observer_logger.dart"; +import logger from "!!raw-loader!/docs/concepts/provider_observer_logger.dart"; import { trimSnippet } from "../../../../../src/components/CodeSnippet"; -[ProviderObserver]는 ProviderContainer의 변화를 관찰합니다. +:::caution +The content of this page may be outdated. +It will be updated in the future, but for now you may want to refer to the content +in the top of the sidebar instead (introduction/essentials/case-studies/...) +::: -[ProviderObserver]를 사용하기 위해서, 사용하고자 하는 클래스에 `ProviderObserver`를 상속하고 -메소드를 오버라이드(override)하여 사용할 수 있습니다. +[ProviderObserver] listens to the changes of a ProviderContainer. -[ProviderObserver]는 3개의 메소드를 가집니다. +To use it, extend the class ProviderObserver and override the method you want to use. -- `didAddProvider`: 프로바이더(provider)가 초기화 될때 마다 호출됩니다. 노출되는 값은 `값(value)`입니다. -- `didDisposeProvider`: 프로바이더(provider)가 `disposed`될 때마다 호출됩니다. -- `didUpdateProvider`: 프로바이더(provider)값이 변경(when they emit a notification)될 때 마다 호출됩니다. +[ProviderObserver] has three methods : -### 사용법: +- `didAddProvider` is called every time a provider was initialized, and the value exposed is `value`. +- `didDisposeProvider` is called every time a provider was disposed. +- `didUpdateProvider` is called every time by providers when they emit a notification. -[ProviderObserver]는 `didUpdateProvider`를 오버라이딩 하여 프로바이더들(providers)의 변화를 로깅할 수 있습니다. +### Usage : + +A simple use case for [ProviderObserver] is to log the changes in providers by overriding the `didUpdateProvider` method. {trimSnippet(logger)} -이제 프로바이더 상태 값이 갱신될때 마다 로거에 기록으로 출력될 것입니다. +Now, every time the value of our provider is updated, the logger will log it: ``` I/flutter (16783): { @@ -32,4 +37,13 @@ I/flutter (16783): "newValue": "1" I/flutter (16783): } ``` +:::note: +For states that are mutable such as [StateController] (the state of [StateProvider.state]) and +[ChangeNotifier] the previousValue and newValue will be the same +::: +since they reference the same `StateController` / `ChangeNotifier`. + [providerobserver]: https://pub.dev/documentation/riverpod/latest/riverpod/ProviderObserver-class.html +[statecontroller]: https://pub.dev/documentation/riverpod/latest/riverpod/StateController-class.html +[stateprovider.state]: https://pub.dev/documentation/riverpod/latest/riverpod/StateProvider/state.html +[changenotifier]: https://api.flutter.dev/flutter/foundation/ChangeNotifier-class.html diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/provider_observer_logger.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/provider_observer_logger.dart index 1574317f9..40f8120e6 100644 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/provider_observer_logger.dart +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/provider_observer_logger.dart @@ -5,7 +5,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; /* SNIPPET START */ -// riverpod과 Logger를 사용한 카운터 앱 예제 +// A Counter example implemented with riverpod with Logger class Logger extends ProviderObserver { @override @@ -25,8 +25,8 @@ class Logger extends ProviderObserver { void main() { runApp( - // ProviderScope를 추가하여 프로젝트 전반적으로 Riverpod을 사용가능하도록 합니다. - // observers 리스트(목록)에 위에서 정의한 Logger 클래스를 추가합니다. + // Adding ProviderScope enables Riverpod for the entire project + // Adding our Logger to the list of observers ProviderScope(observers: [Logger()], child: const MyApp()), ); } diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/providers.mdx b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/providers.mdx index 066e4eed1..2bdf5467d 100644 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/providers.mdx +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/providers.mdx @@ -1,95 +1,99 @@ --- -title: 프로바이더란? +title: Providers --- -[Riverpod] 설치를 완료했다면 이제 "providers"(프로바이더)에 대해 알아보도록 하겠습니다. +import creatingProvider from "./providers/creating_a_provider"; +import declaringManyProviders from "./providers/declaring_many_providers"; +import { + AutoSnippet, +} from "../../../../../src/components/CodeSnippet"; +import { Link } from "../../../../../src/components/Link"; -프로바이더는 [Riverpod] 애플리케이션의 가장 중요한 파트 입니다. +:::caution +The content of this page may be outdated. +It will be updated in the future, but for now you may want to refer to the content +in the top of the sidebar instead (introduction/essentials/case-studies/...) +::: -프로바이더는 하나의 상태조각의 압축(encapsulates)된 객체이자 -상태의 변화를 감시하는 역할을 가지고 있습니다. +Now that we have installed [Riverpod], let's talk about "providers". -## 왜 프로바이더를 사용하나요? +Providers are the most important part of a [Riverpod] application. +A provider is an object that encapsulates a piece of state and allows listening +to that state. -상태를 프로바이더로 감싸게 되면 아래의 서술된 내용들이 가능합니다. +## Why use providers? -- 코드상 다양한 위치에서 상태를 쉽게 접근할 수 있습니다. - 프로바이더는 Singletons, Service Locators, Dependency Injection 또는 InheritedWidgets - 과 같은 디자인 패턴들을 완벽하게 대체할 수 있습니다. +Wrapping a piece of state in a provider: -- 다른 프로바이더 상태와 간편하게 결합하여 사용할 수 있습니다. - 혹시 여러 객체들을 하나로 병합하는데 어려움을 격은적이 있나요? - 프로바이더를 사용하면 프로바이더의 간단한 문법을 사용하여 구현할 수 있습니다. +- Allows easily accessing that state in multiple locations. + Providers are a complete replacement for patterns like Singletons, + Service Locators, Dependency Injection or InheritedWidgets. -- 퍼포먼스 최적화가 가능합니다. - 위젯을 다시 빌드하는것을 필터링 하거나 비용이 높은 상태 계산을 캐싱하거나 - 프로바이더는 상태 변경의 영향을 받는 항목만 다시 계산합니다. +- Simplifies combining this state with others. + Ever struggled to merge multiple objects into one? This scenario is built + directly inside providers. -- 애플리케이션의 태스트 용이성이 높아집니다. - 프로바이더와 함께라면, 복잡한 `setUp`/`tearDown` 단계가 불필요 합니다. - 게다가 어떠한 프로바이더이든지 테스트 중의 프로바이더 행위를 오버라이드 할 수 있습니다. - 매우 특정한 행위(동작)을 테스트 하기 쉽습니다. +- Enables performance optimizations. + Whether for filtering widget rebuilds or for caching expensive state computations; + providers ensure that only what is impacted by a state change is recomputed. -- 고급 기능들과 함께 손 쉬운 통합이 가능합니다. 예를들어 로깅(logging) 또는 pull-to-refresh가 있습니다. +- Increases the testability of your application. + With providers, you do not need complex `setUp`/`tearDown` steps. Furthermore, + any provider can be overridden to behave differently during a test, which + allows easily testing a very specific behavior. -## 프로바이더 생성하기 +- Allows easy integration with advanced features, such as logging or + pull-to-refresh. -다양한 프로바이더들이 있지만, 기본적으로 모두 동일한 방식으로 동작합니다. +## Creating a provider -가장 보편적으로 사용하는 방법으로 전역 변수(global constants)로 선언하여 사용하는 방법이 있습니다. +Providers come in many variants, but they all work the same way. +The most common usage is to declare them as global constants like so: -```dart -final myProvider = Provider((ref) { - return MyValue(); -}); -``` + :::note -프로바이더를 글로벌하게 전역변수로 선언하여 사용하는것을 두려워하지 마세요. -프로바이더는 완전 immutable 특성을 가집니다. 프로바이더를 선언하는 것은 함수를 선언하는것과 -다르지 않습니다. 그리고 프로바이더들은 테스트할 수 있고 유지보수 할 수 있습니다. +Do not be frightened by the global aspect of providers. +Providers are fully immutable. Declaring a provider is no different from declaring +a function, and providers are testable and maintainable. ::: -위 코드 정보는 3개의 컴포넌트를 구성하고 있습니다. +This snippet consists of three components: -- `final myProvider`, 변수 선언입니다. - 이 변수는 항상 불변(immutable) 특성을 가질 것 입니다. - 그리고 추후 프로바이더의 상태를 읽기위해 사용하게 됩니다. +- `final myProvider`, the declaration of a variable. + This variable is what we will use in the future to read the state of our provider. + Providers should always be `final`. -- `Provider`, 우리가 코드에서 사용하기 위해 결정한 프로바이더 입니다. - [Provider]는 모든 프로바이더 종류들 중 가장 기본이 되는 친구입니다. 이 객체는 위에서 설명한 것 처럼 - 변하지 않는 불변의 특성을 가지게 됩니다. 우리는 여기서 어떻게 상태(값)과 상호 작용하는지에 따라서 - [Provider]를 다른 종류의 프로바이더로 바꿔 사용할 수 있습니다. - 예를 들어 [StreamProvider] 또는 [StateNotifierProvider] 가 있습니다. +- `Provider`, the provider that we decided to use. + [Provider] is the most basic of all providers. It exposes an object that never + changes. + We could replace [Provider] with other providers like [StreamProvider] or + [NotifierProvider], to change how the value is interacted with. -- 공유 상태를 생성하는 함수입니다. - 이 함수는 `ref` 객체를 파라미터로 받습니다. 이 객체는 다른 프로바이더들을 읽기 위해서 사용하거나 - 프로바이더의 상태가 소멸될 때 일부 작업을 수행할 수 있도록 합니다. +- A function that creates the shared state. + That function will always receive an object called `ref` as a parameter. This object + allows us to read other providers, perform some operations when the state + of our provider will be destroyed, and much more. -프로바이더 내부에서 생성되는 객체의 형태는 사용하는 프로바이더의 종류에 따라 다르게 생성됩니다. -예를 들어 [Provider]의 함수는 어떤 객체든 생성가능합니다. 반면에 [StreamProvider]는 [Stream] 객체를 -생성할 필요가 있습니다. 여기서 생성이라고 함은 [StreamProvider]에서 [Stream]을 반환해야합니다. +The type of the object returned by the function passed to a provider depends on +the provider used. +For example, the function of a [Provider] can create any object. +On the other hand, [StreamProvider]'s callback will be expected to return a [Stream]. :::info -어떠한 제한 없이 원하는데로 수 많은 종류의 프로바이더들을 선언할 수 있습니다. -`package:provider`를 사용할때와 반대로, [Riverpod]에서는 같은 "type"의 상태를 노출하는 -2개의 프로바이더를 가지게 됩니다. +You can declare as many providers as you want without limitations. +As opposed to when using `package:provider`, [Riverpod] allows creating multiple +providers exposing a state of the same "type": + -```dart -final cityProvider = Provider((ref) => 'London'); -final countryProvider = Provider((ref) => 'England'); -``` - -사실 위의 예에서는 2개의 프로바이더가 `String` 값을 생성하는 것은 -어떠한 문제의 원인이 되지 않습니다. -(역자: '2개의 프로바이더가 `String`값을 생성하는데 문제될 것이 없다'라는 의미로 -받아들이면 좋겠습니다.) +The fact that both providers create a `String` does not cause any problem. ::: :::caution -프로바이더가 동작하기 위해서 [ProviderScope]를 Flutter 앱의 가장 최상위(root) 부모 위젯으로 감싸줘야 합니다. +For providers to work, you must add [ProviderScope] at the root of your +Flutter applications: ```dart void main() { @@ -99,52 +103,52 @@ void main() { ::: -## 상태가 소멸되기 전에 액션 취하기 +## Different Types of Providers -일부 케이스에서, 프로바이더의 상태는 소멸(파괴)되거나 재 생성될 수 있습니다. -통상적인 경우 이러한 상황들은 프로바이더 소멸 상태 전에 초기화를 진행하는 경우 입니다. -예를 들어 `StreamController`를 닫아주는 경우가 있습니다. +There are multiple types of providers for multiple different use cases. -이것은 프로바이더 내부에서 사용하는 `ref` 객체의 [onDispose] 메소드를 사용할 수 있습니다. +With all of these providers available, it is sometimes difficult to understand when to use one provider type over another. +Use the table below to choose a provider that fits what you want to provide to the widget tree. -다음 사용예시에서 [onDispose]를 사용하여 `StreamController`를 닫는 과정을 알아봅시다. +| Provider Type | Provider Create Function | Example Use Case | +| ------------------------ | ------------------------------------- | ----------------------------------------------------------------------------------------------------- | +| [Provider] | Returns any type | A service class / computed property (filtered list) | +| [StateProvider] | Returns any type | A filter condition / simple state object | +| [FutureProvider] | Returns a Future of any type | A result from an API call | +| [StreamProvider] | Returns a Stream of any type | A stream of results from an API | +| [NotifierProvider] | Returns a subclass of (Async)Notifier | A complex state object that is immutable except through an interface | +| [StateNotifierProvider] | Returns a subclass of StateNotifier | A complex state object that is immutable except through an interface. Prefer using a notifierProvider | +| [ChangeNotifierProvider] | Returns a subclass of ChangeNotifier | A complex state object that requires mutability | +:::caution +While all providers have their purpose, [ChangeNotifierProvider]s are not recommended for scalable applications. See . It exists in the +`flutter_riverpod` package to provide an easy migration path from +`package:provider`, and allows for some flutter specific use-cases such as +integration with some Navigator 2 packages. ::: -```dart -final example = StreamProvider.autoDispose((ref) { - final streamController = StreamController(); - - ref.onDispose(() { - // 프로바이더의 상태가 소멸되기 전 StreamController를 닫습니다(close). - streamController.close(); - }); +## Provider Modifiers - return streamController.stream; -}); -``` +All Providers have a built-in way to add extra functionalities to your different providers. -:::note -사용하는 프로바이더에 따라서, 이러한 리소스 해제 처리 작업을 내부에서 자동으로 진행하는 경우도 있습니다. -예를 들어 [StateNotifierProvider]는 `StateNotifier`의 `dispose`메소드를 호출합니다. -::: - -## 프로바이더 수식자(Modifiers) - -모든 프로바이더들은 다른 프로바이더와 추가적인 기능을 추가하기 위한 방법이 기본적으로 내장되어 있습니다. -`ref` object에 새로운 특징을 추가하거나 프로바이더가 어떻게 상태를 소모(사용)하는지를 변경할 수 있습니다. -Modifiers는 모든 프로바이더에서 사용할 수 있습니다. 이는 named constructor(생성자) 문법과 유사합니다. +They may add new features to the `ref` object or change slightly how the provider +is consumed. +Modifiers can be used on all providers, with a syntax similar to named constructor: ```dart final myAutoDisposeProvider = StateProvider.autoDispose((ref) => 0); final myFamilyProvider = Provider.family((ref, id) => '$id'); ``` -지금은 2개의 사용가능한 수식어가 있습니다. -- [`.autoDispose`](/docs/concepts/modifiers/auto_dispose) 는 더 이상 상태를 구독하지 않을때 자동으로 프로바이터를 소멸되도록 합니다. -- [`.family`](/docs/concepts/modifiers/family) 외부 파라미터로부터 프로바이더를 생성할 때 사용합니다. +At the moment, there are two modifiers available: + +- , which will make the + provider automatically destroy its state when it is no longer being listened + to. +- , which allows creating a + provider from external parameters. :::note -프로바이더는 복수의 수식어(modifiers)를 한번에 사용할 수 있습니다. +A provider can use multiple modifiers at once: ```dart final userProvider = FutureProvider.autoDispose.family((ref, userId) async { @@ -154,11 +158,10 @@ final userProvider = FutureProvider.autoDispose.family((ref, userId) ::: -프로바이더에 대한 설명은 여기까지 입니다! - -[프로바이더 읽기](/docs/concepts/reading) 문서에서 계속됩니다. -또는 [프로바이더 결합하기](/docs/concepts/combining_providers)문서도 확인할 수 있습니다. +That's it for this guide! +You can continue with . +Alternatively, you can see . [get_it]: http://pub.dev/packages/get_it [inheritedwidget]: https://api.flutter.dev/flutter/widgets/InheritedWidget-class.html @@ -168,7 +171,11 @@ final userProvider = FutureProvider.autoDispose.family((ref, userId) [hooks_riverpod]: https://pub.dev/packages/hooks_riverpod [flutter_riverpod]: https://pub.dev/packages/flutter_riverpod [flutter_hooks]: https://github.com/rrousselGit/flutter_hooks -[provider]: ../providers/provider -[streamprovider]: ../providers/stream_provider -[statenotifierprovider]: ../providers/state_notifier_provider +[provider]: /docs/providers/provider +[streamprovider]: /docs/providers/stream_provider +[futureprovider]: /docs/providers/future_provider +[stateprovider]: /docs/providers/state_provider +[statenotifierprovider]: /docs/providers/state_notifier_provider +[notifierProvider]: /docs/providers/notifier_provider +[changenotifierprovider]: https://pub.dev/documentation/flutter_riverpod/latest/flutter_riverpod/ChangeNotifierProvider-class.html [providerscope]: https://pub.dev/documentation/flutter_riverpod/latest/flutter_riverpod/ProviderScope-class.html diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/providers/creating_a_provider/codegen.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/providers/creating_a_provider/codegen.dart new file mode 100644 index 000000000..8dc6c5e12 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/providers/creating_a_provider/codegen.dart @@ -0,0 +1,12 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +class MyValue {} + +/* SNIPPET START */ + +@riverpod +MyValue my(MyRef ref) { + return MyValue(); +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/providers/creating_a_provider/codegen.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/providers/creating_a_provider/codegen.g.dart new file mode 100644 index 000000000..601ed73b6 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/providers/creating_a_provider/codegen.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$myHash() => r'0810ee24cae78c131d00773ac20d254c83eefab7'; + +/// See also [my]. +@ProviderFor(my) +final myProvider = AutoDisposeProvider.internal( + my, + name: r'myProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$myHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef MyRef = AutoDisposeProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/providers/creating_a_provider/index.tsx b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/providers/creating_a_provider/index.tsx new file mode 100644 index 000000000..339b89a56 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/providers/creating_a_provider/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./codegen.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/providers/creating_a_provider/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/providers/creating_a_provider/raw.dart new file mode 100644 index 000000000..d63faf94b --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/providers/creating_a_provider/raw.dart @@ -0,0 +1,9 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +class MyValue {} + +/* SNIPPET START */ + +final myProvider = Provider((ref) { + return MyValue(); +}); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/providers/declaring_many_providers/codegen.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/providers/declaring_many_providers/codegen.dart new file mode 100644 index 000000000..a9472e02a --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/providers/declaring_many_providers/codegen.dart @@ -0,0 +1,11 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +/* SNIPPET START */ + +@riverpod +String city(CityRef ref) => 'London'; +@riverpod +String country(CountryRef ref) => 'England'; + diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/providers/declaring_many_providers/codegen.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/providers/declaring_many_providers/codegen.g.dart new file mode 100644 index 000000000..b3a642c74 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/providers/declaring_many_providers/codegen.g.dart @@ -0,0 +1,40 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$cityHash() => r'2ccdee096b5d5c1cafa736b3e52b788431b9af38'; + +/// See also [city]. +@ProviderFor(city) +final cityProvider = AutoDisposeProvider.internal( + city, + name: r'cityProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$cityHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef CityRef = AutoDisposeProviderRef; +String _$countryHash() => r'd1513349c3bc0c99763cb4fb29eb012f2351bc4c'; + +/// See also [country]. +@ProviderFor(country) +final countryProvider = AutoDisposeProvider.internal( + country, + name: r'countryProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$countryHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef CountryRef = AutoDisposeProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/providers/declaring_many_providers/index.tsx b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/providers/declaring_many_providers/index.tsx new file mode 100644 index 000000000..339b89a56 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/providers/declaring_many_providers/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./codegen.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/providers/declaring_many_providers/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/providers/declaring_many_providers/raw.dart new file mode 100644 index 000000000..60a3d4786 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/providers/declaring_many_providers/raw.dart @@ -0,0 +1,6 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +/* SNIPPET START */ + +final cityProvider = Provider((ref) => 'London'); +final countryProvider = Provider((ref) => 'England'); \ No newline at end of file diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading.mdx b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading.mdx index ce68a31a4..78345456a 100644 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading.mdx +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading.mdx @@ -1,245 +1,267 @@ --- -title: 프로바이더 읽기 +title: Reading a Provider --- -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; import CodeBlock from "@theme/CodeBlock"; -import counter from "!!raw-loader!/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading_counter.dart"; -import consumerWidget from "!!raw-loader!/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading_consumer_widget.dart"; -import consumerStatefulWidget from "!!raw-loader!/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading_consumer_stateful_widget.dart"; -import consumerHookWidget from "!!raw-loader!/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading_consumer_hook_widget.dart"; -import consumerHook from "!!raw-loader!/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading_consumer_hook.dart"; -import watch from "!!raw-loader!/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading_watch.dart"; -import watchBuild from "!!raw-loader!/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading_watch_build.dart"; -import listen from "!!raw-loader!/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading_listen.dart"; -import listenBuild from "!!raw-loader!/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading_listen_build.dart"; -import read from "!!raw-loader!/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading_read.dart"; -import readBuild from "!!raw-loader!/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading_read_build.dart"; -import readNotifierBuild from "!!raw-loader!/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading_read_notifier_build.dart"; -import watchNotifierBuild from "!!raw-loader!/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading_watch_notifier_build.dart"; -import { trimSnippet } from "../../../../../src/components/CodeSnippet"; +import counter from "./reading/counter"; +import consumerWidget from "./reading/consumer_widget"; +import consumerStatefulWidget from "./reading/consumer_stateful_widget"; +import consumerHook from "!!raw-loader!/docs/concepts/reading/consumer_hook.dart"; +import watch from "./reading/watch"; +import watchBuild from "./reading/watch_build"; +import listen from "./reading/listen"; +import listenBuild from "./reading/listen_build"; +import read from "./reading/read"; +import readBuild from "./reading/read_build"; +import readNotifierBuild from "./reading/read_notifier_build"; +import watchNotifierBuild from "./reading/watch_notifier_build"; +import provider from "./reading/provider"; +import { + trimSnippet, + AutoSnippet, + When, +} from "../../../../../src/components/CodeSnippet"; +import { Link } from "../../../../../src/components/Link"; -본 가이드를 읽기전에 [프로바이더란](/docs/concepts/providers)에 대해서 먼저 알아보는 것을 추천합니다. +:::caution +The content of this page may be outdated. +It will be updated in the future, but for now you may want to refer to the content +in the top of the sidebar instead (introduction/essentials/case-studies/...) +::: -이 가이드 문서는 프로바이더를 어떻게 사용/소비(consume)하는지에 대해 알아봅니다. +Before reading this guide, make sure to first. -## "ref" 객체 얻기 +In this guide, we will see how to consume a provider. -프로바이더를 읽기전 맨 처음 "ref" 라는 객체를 얻을 필요가 있습니다. +## Obtaining a "ref" object -이 객체('ref')는 프로바이더간 상호작용을 도와주고 위젯이나 다른 프로바이더에서 얻을 수 있습니다. +First and foremost, before reading a provider, we need to obtain a "ref" object. -### 프로바이더로 부터 "ref" 객체 얻기 +This object is what allows us to interact with providers, be it from a widget +or another provider. -모든 프로바이더들은 "ref"객체를 파라미터로서 받게 됩니다. +### Obtaining a "ref" from a provider -```dart -final provider = Provider((ref) { - // 다른 프로바이더 객체를 얻기위해 ref를 사용합니다. - // 여기서 repositoryProvider 프로바이더를 Provider 에서 읽는 것을 확인합니다. - final repository = ref.watch(repositoryProvider); +All providers receive a "ref" as a parameter: - return SomeValue(repository); -}) -``` + + +This parameter is safe to pass to the value exposed by the provider. + + + +A common use-case is to pass the provider's "ref" to a `StateNotifier` -이 매개변수는 프로바이더가 노출한 값으로 전달하는 것이 안전합니다. + -예를 들어, 통상적으로 프로바이더의 "ref"를 [StateNotifier]로 전달합니다. + -{trimSnippet(counter)} +Doing so allows our `Counter` class to read providers. -[StateNotifier]를 상속 받은 `Counter` 클래스는 `ref` 객체를 통해 -다른 프로바이더를 사용하거나 읽을 수 있게 되었습니다. +### Obtaining a "ref" from a widget -### 위젯에서 "ref" 객체 얻기 +Widgets naturally do not have a ref parameter. But [Riverpod] offers multiple +solutions to obtain one from widgets. -위젯들(Widgets)은 "ref" 파라미터를 가지고 있지 않습니다. -그러나, [Riverpod]에서는 위젯에서 "ref" 객체를 얻기위한 다양한 솔루션을 제공합니다. + -#### StatelessWidget 대신 ConsumerWidget으로 상속받기 +#### Extending ConsumerWidget instead of StatelessWidget -가장 일반적인 해결방법은 [StatelessWidget]를 [ConsumerWidget]의로 바꿔서 위젯을 생성하는 것입니다. -(여기서 말하고자하는 해결방법이란 위에서 언급한 위젯에서 "ref" 객체를 얻는 방법입니다.) +The most common way to obtain a ref in the widget tree is +to replace [StatelessWidget] with [ConsumerWidget]. -[ConsumerWidget]은 [StatelessWidget]와 기본적으로 동일합니다. -차이점은 build 메소드에서 추가적으로 "ref" 객체를 받는 것입니다. +[ConsumerWidget] is identical in use to [StatelessWidget], with the only +difference being that it has an extra parameter on its build method: the "ref" object. -전형적인 [ConsumerWidget] 사용방법은 아래 예시 코드와 같습니다. +A typical [ConsumerWidget] looks like: -{trimSnippet(consumerWidget)} + -#### StatefulWidget+State 대신에 ConsumerStatefulWidget+ConsumerState 상속받기 +#### Extending ConsumerStatefulWidget+ConsumerState instead of StatefulWidget+State -[ConsumerWidget]과 유사하게, [ConsumerStatefulWidget]과 [ConsumerState]는 [StatefulWidget]과 [State]와 동일합니다. -차이점은 "ref" 객체를 상태로 가진다는 점 입니다. +Similar to [ConsumerWidget], [ConsumerStatefulWidget] and [ConsumerState] are the equivalent of a +[StatefulWidget] with its [State], with the difference that the state has a "ref" object. -이번에는 "ref"객체는 build 메소드의 파라미터로서 전달되지 않습니다. -"ref" 객체는 [ConsumerState]객체의 속성이 됩니다. +This time, the "ref" isn't passed as parameter of the build method, but is +a property of the [ConsumerState] object: -아래의 예시 코드를 확인해 봅시다. + -{trimSnippet(consumerStatefulWidget)} + -#### HookWidget 대신 HookConsumerWidget 상속받기 + -이 방법은 [flutter_hooks]를 사용하는 사용자에게 한정됩니다. -[flutter_hooks]은 [HookWidget]을 상속받는게 필요하기 때문에 hooks을 사용하기위해 위젯에서 -[ConsumerWidget]을 상속하는것은 불가능합니다. +#### Extending HookConsumerWidget instead of HookWidget -하나의 해결 방안으로 [hooks_riverpod] 패키지를 사용하면 됩니다. -이 패키지는 [HookWidget]를 [HookConsumerWidget]으로 대체할 수 있는 해결방안을 제공합니다. +This option is for [flutter_hooks] users. Since [flutter_hooks] requires +extending [HookWidget] to work, widgets that use hooks are unable to extend +[ConsumerWidget]. -[HookConsumerWidget]은 [ConsumerWidget]과 [HookWidget]의 기능을 포함하고 있습니다. -즉, 프로바이더를 구독하는것과 hooks을 사용하는 것이 모두 가능합니다. +The package [hooks_riverpod] exposes a new widget called [HookConsumerWidget]. +[HookConsumerWidget] acts as both a [ConsumerWidget] and a [HookWidget]. This +allows a widget to both listen to providers and use hooks. -예제를 확인해 봅시다. +An example would be: -{trimSnippet(consumerHookWidget)} + -#### Consumer 와 HookConsumer 위젯 +#### Extending StatefulHookConsumerWidget instead of HookWidget -위젯(widgets)안에서 "ref" 객체를 얻기 위한 마지막 방안으로 -[Consumer]/[HookConsumer] 위젯을 사용하는 것이 있습니다. +This option is for [flutter_hooks] users, who need to use [StatefulWidget] lifecycle methods in addition to hooks. -이 클래스들은([Consumer]/[HookConsumer]) "ref"를 얻기위해 사용하는 위젯들입니다. -[ConsumerWidget]/[HookConsumerWidget]로서 동일한 속성을 가집니다. +An example would be: -이 위젯들은 "ref"를 얻기위해 별도의 class를 정의할 필요가 없습니다. -아래의 예시 코드를 통해 확인해 봅시다. + + +#### Consumer and HookConsumer widgets + +A final way to obtain a "ref" inside widgets is to rely on [Consumer]/ +[HookConsumer]. + +These classes are widgets that can be used to obtain a "ref" in a builder callback, with the same +properties as [ConsumerWidget]/[HookConsumerWidget]. + +As such, these widgets are a way to obtain a "ref" without having to define a class. +An example would be: {trimSnippet(consumerHook)} -## `ref`를 사용해서 프로바이더와 상호작용하기 + -이제 "ref" 겍체와 사용방법에 대해 알아봅시다. -"ref"는 3개지 주요한 용도가 있습니다. +## Using ref to interact with providers -- `ref.watch` = 프로바이더의 값을 취득하고 변화를 구독합니다. 값의 변경이 발생하면, - 위젯(widget)을 다시 빌드하거나 값을 구독(subscribed)하고 있는 위치에 상태 값을 전달 및 제공합니다. +Now that we have a "ref", we can start using it. -- `ref.listen` = 프로바이더의 상태 값을 구독하거나 상태값이 변했을때 어떠한 행위를 취해야할 경우 사용합니다. +There are three primary usages for "ref": -- `ref.read` = 프로바이더의 상태값을 취득합니다. 이벤트 콜백함수에 사용하기 유용한데 예를들어 버튼의 `onPressed` - 콜백 함수에서 프로바이더의 필요한 상태값을 얻기위해서 사용할 수 있습니다. +- obtaining the value of a provider and listening to changes, such that when + this value changes, this will rebuild the widget or provider that subscribed + to the value. + This is done using `ref.watch` +- adding a listener on a provider, to execute an action such as navigating to a new + page or showing a modal whenever that provider changes. + This is done using `ref.listen`. +- obtaining the value of a provider while ignoring changes. + This is useful when we need the value of a provider in an event + such as "on click". + This is done using `ref.read`. :::note -기능을 구현할때는 가급적 `ref.read` 또는 `ref.listen` 보다 `ref.watch` 사용을 권장합니다. - -`ref.watch`을 사용하게되면 reactive(리엑티브)와 declarative(선언형)에 가까워 지고 -애플리케이션을 더 유지보수 하기 편리하게 만들어 줍니다. +Whenever possible, prefer using `ref.watch` over `ref.read` or `ref.listen` to +implement a feature. +By relying on `ref.watch`, your application becomes both reactive +and declarative, which makes it more maintainable. ::: -### `ref.watch`를 사용해서 프로바이더 관찰하기 - -위젯의 `build` 메소드 내부 또는 프로바이더 내부에서 `ref.watch`를 사용함으로서 -프로바이더의 상태 값을 구독(listen)할 수 있습니다. +### Using ref.watch to observe a provider -예를 들어, 프로바이더에서 `ref.watch`를 사용하여 복수의 프로바이더와 결합해 새로운 값을 -생성 할 수도 있습니다. +`ref.watch` is used inside the `build` method of a widget or +inside the body of a provider to have the widget/provider listen to a provider: -할일 목록(a todo-list)에서 필터링하는 방법의 한가지 예시 입니다. -여기서 2개의 프로바이더를 사용했습니다. +For example, a provider could use `ref.watch` to combine multiple providers +into a new value. An example would be filtering a todo-list. - We could have two providers: -- `filterTypeProvider`: 현재 설정한 필터타입(none, show only completed tasks, ...)에대한 상태를 나타내는 프로바이더입니다. - -- `todosProvider`: 할일(tasks)에 대한 목록 전체 값을 가지는 프로바이더 입니다. +- `filterTypeProvider`, a provider that exposes the current type of filter + (none, show only completed tasks, ...) +- `todosProvider`, a provider that exposes the entire list of tasks -그리고 `ref.watch`를 사용하여 두개의 프로바이더를 결합해 필터링된 작업 목록 생성하는 세 번째 프로바이더를 만들 수 있습니다. +And by using `ref.watch`, we could make a third provider that combines both providers to +create a filtered list of tasks: -{trimSnippet(watch)} + -위의 코드에서 확인할 수 있듯이 `filteredTodoListProvider` 프로바이더는 필터링된 할일 목록을 가집니다. +With this code, `filteredTodoListProvider` now exposes the filtered list of tasks. -필터링된 목록은 할일 목록이 변경되거나 필터 상태값이 변경되면 자동적으로 갱신될 것 입니다. -그러나, 필터 상태가 동일하거나 할일 목록이 동일하다면 다시 계산되거나 갱신되지 않습니다. +The filtered list will also automatically update if either the filter or the list of tasks +changed. At the same time, the filtered list will not be recomputed if +neither the filter nor the list of tasks changed. -이와 유사하게 위젯에 `ref.watch`를 사용하는 경우 프로바이더상에 의존하는 사용자 인터페이스를 -표시할 수 있습니다. +Similarly, a widget can use `ref.watch` to show +the content from a provider and update the user interface whenever that content changes: -{trimSnippet(watchBuild)} + -이 예시코드는 카운터 상태값을 저장하고 있는 프로바이더를 구독하고 있는 위젯을 보여줍니다. -그리고 만약 카운터 값이 변경되면 위젯을 다시 빌드되고 UI에 새로 갠신된 값을 표기할 것 입니다. +This snippet shows a widget that listens to a provider which stores a `count`. +And if that `count` changes, the widget will rebuild and the UI will update +to show the new value. :::caution -`watch`메소드는 비동기처리(asynchronously)에 호출하지 마세요. -예를 들어 [ElevatedButton]의 `onPressed` 콜백 함수 안이나 `initState` 그리고 다른 [State]의 생명주기 안에서는 -`watch`메소드를 호출하면 안됩니다. +The `watch` method should not be called asynchronously, +like inside an `onPressed` of an [ElevatedButton]. Nor should it be used +inside `initState` and other [State] life-cycles. -이때는 `ref.watch`대신 `ref.read`메소드를 사용하는 것을 권장합니다. +In those cases, consider using `ref.read` instead. ::: -### `ref.listen`을 사용하여 프로바이더 변화에 대응하기 +### Using ref.listen to react to a provider change -`ref.watch`와 유사하게 프로바이더를 관찰하기 위해 `ref.listen`을 사용할 수 있습니다. -`ref.watch`와 `ref.listen`의 주요한 차이점은 `ref.watch`는 프로바이더 상태값이 변경이되면 widget/provider을 다시 빌드하지만 -`ref.listen`은 함수를 호출한다는 점입니다. 함수는 커스텀된 함수이며 사용자에 따라 다르게 정의해서 사용할 수 있습니다. +Similarly to `ref.watch`, it is possible to use `ref.listen` to observe a provider. -예를 들어 에러가 발생할때 스낵바(snackbar)를 표시하거나 어떠한 반응의 변화에 대응해야 할때 -유용하게 사용할 수 있습니다. +The main difference between them is that, rather than rebuilding the widget/provider +if the listened to provider changes, using `ref.listen` will instead call a custom function. -`ref.listen` 메소드는 2개의 위치인자(positional arguments)가 필요합니다. -첫번째는 프로바이더이고 두번쨰는 콜백함수 입니다. 콜백함수는 상태변화에 대응하여 수행할 함수 입니다. -콜백함수로 2개의 값이 전달됩니다. 하나는 이전 상태 값이고 나머지 하는 갱신된 상태 값입니다. +That can be useful for performing actions when a certain change happens, such +as showing a snackbar when an error happens. -`ref.listen` 메소드는 프로바이더 내부에서도 사용할 수 있습니다. -아래의 코드를 확인해 볼 수 있습니다. +The `ref.listen` method needs 2 positional arguments, the first one is the Provider and the second one is the callback function that we want to execute when the state changes. +The callback function when called will be passed 2 values, the value of the previous State and the value of the new State. -{trimSnippet(listen)} +The `ref.listen` method can be used inside the body of a provider: -또는 `build` 메소드 안에서 사용할 수 있습니다. + -{trimSnippet(listenBuild)} +or inside the `build` method of a widget: + + :::caution -[ElevatedButton]의 `onPressed` 콜백함수 내부, `initState` 내부 그리고 다른 상태주기의 [State]상에서와 같이 -`listen`메소드 또한 비동기(asynchronously)로 호출 가능한 곳에서는 사용을 피해야합니다. +The `listen` method should not be called asynchronously, +like inside an `onPressed` of an [ElevatedButton]. Nor should it be used +inside `initState` and other [State] life-cycles. ::: -### `ref.read`을 사용하여 프로바이더의 상태를 한번 취득하기 +### Using ref.read to obtain the state of a provider -`ref.read` 메소드는 어떠한 부가적인 효과 없이 프로바이더의 상태를 얻기위한 방법을 제공합니다. +The `ref.read` method is a way to obtain the state of a provider without listening to it. -`ref.read`는 일반적으로 사용자 상호작용으로 발생가능한 트리거 함수내부에서 주로 사용합니다. -예를들어 버튼을 눌렀을떄 카운터의 값이 증가시키고 싶을때 `ref.read`메소드를 사용할 수 있습니다. +It is commonly used inside functions triggered by user interactions. +For example, we can use `ref.read` to increment a counter when a user clicks a button: -{trimSnippet(read)} + :::note -가능한 `ref.read`사용을 피해주세요. +Using `ref.read` should be avoided as much as possible because it is not reactive. -`ref.read`메소드는 `watch` 또는 `listen`이 어려운곳에서 사용할 수 있는 대응책으로 사용하되, -가능한 `watch`/`listen`를 사용해주세요. 여기서 더 추천하는 메소드는 `watch` 메소드입니다. +It exists for cases where using `watch` or `listen` would cause issues. +If you can, it is almost always better to use `watch`/`listen`, especially `watch`. ::: -#### [**DONT'T**] `ref.read`를 `build` 메소드 안에서 **사용하지 마세요.** +#### **DON'T** use `ref.read` inside the build method -위젯의 성능 최적화를 위해 아래의 예시처럼 `ref.read`를 사용하고 있을 수 있습니다. +You might be tempted to use `ref.read` to optimize the performance of a widget +by doing: -{trimSnippet(readBuild)} + -그러나, 위의 예시는 나쁜 예시를 나타내고 있습니다. 그리고 이렇게 사용할 경우 -다루기 어려운 버그들을 유발수 있습니다. +But this is a very bad practice and can cause bugs that are difficult to track. -`ref.read`를 사용하기위해 이 방법은 일반적으로 다음과 같이 생각해서 발생할 수 있습니다. -"프로바이더의 상태값이 변경되지 않으니까 'ref.read'를 사용하는것이 안전하겠다." -그러나, 오늘은 비록 상태 값이 갱신되지 않더라도 내일 상태 값이 같을 것이라고 보장할 수 없기 때문입니다. +Using `ref.read` this way is commonly associated with the thought "The value +exposed by a provider never changes so using 'ref.read' is safe". The problem +with this assumption is that, while today that provider may indeed never update +its value, there is no guarantee that tomorrow will be the same. -소프트웨어는 수 없는 변화가 일어나는 경향이 있습니다. -그리고 미래에는 현재의 절대 바뀌지 않을 값이 변경될 가능성이 있습니다. -그러나, 만약 값의 변화가 시작될때, `ref.read`를 사용하게 되면 모든 `ref.read`메소드를 -전반적으로 `ref.watch`로 변경해야합니다. (이 작업에서 수많은 에러가 발생할 수 있고 특정 케이스에 -대한 변경 처리를 까먹을 수 도 있습니다.) +Software tends to change a lot, and it is likely that in the future, a value +that previously never changed will need to change. +If you use `ref.read`, when that value needs to change, you have +to go through your entire codebase to change `ref.read` into `ref.watch` – +which is error prone and you are likely to forget some cases. -반면 만약 `ref.watch` 를 사용하게되면 어떠한 문제도 걱정도 할 필요없을 것입니다. +If you use `ref.watch` to begin with, you will have fewer problems when refactoring. -**_그래도 난 위젯이 다시 빌드되는걸 줄이고 싶고 처음부터 `ref.read`를 사용하고 싶어!_** +**_But I wanted to use `ref.read` to reduce the number of times my widget rebuilds_** While the goal is commendable, it is important to note that you can achieve the exact same effect (reducing the number of builds) using `ref.watch` instead. @@ -247,53 +269,73 @@ exact same effect (reducing the number of builds) using `ref.watch` instead. Providers offer various ways to obtain a value while reducing the number of rebuilds, which you could use instead. -예를 들어 +For example instead of -{trimSnippet(readNotifierBuild)} + -위의 코드 대신에 아래의 예시코드처럼 사용하는게 좋습니다. +we could do: -{trimSnippet(watchNotifierBuild)} + -두 코드의 효과는 동일합니다. 카운트 숫자 값이 변경되어도 버튼 위젯은 다시 빌드되지 않을 것입니다. +Both snippets achieve the same effect: our button will not rebuild when the +counter increments. -반면, 후자의 접근 방법은 카운터 값이 초기화되어도 대응이 가능합니다. -예를 들어, 애플리케이션의 다른 파트에서 아래의 메소드가 호출되면 +On the other hand, the second approach supports cases where the counter is reset. +For example, another part of the application could call: ```dart ref.refresh(counterProvider); ``` -`ref.refresh`가 호출됨으로 `StateController` 객체를 재생성할 것입니다. + + +which would recreate the `StateController` object. + +If we used `ref.read` here, our button would still use the previous +`StateController` instance, which was disposed and should no-longer be used. +Whereas using `ref.watch` correctly rebuilds the button to use the new +`StateController`. + + + + + +which would recreate the `Counter` object. -만약, 여기서 `ref.read`를 사용한다면 버튼은 여전이 이전 상태의 `StateController`인스턴스를 사용할 것입니다(disposed되었거나 더이상 사용하지 않는). -`ref.watch`를 사용하면 새로운 `StateController`가 가능한 상태로 버튼이 재빌드 됩니다. +If we used `ref.read` here, our button would still use the previous `Counter` +instance, which was disposed and should no-longer be used. Whereas using +`ref.watch` correctly rebuilds the button to use the new `Counter`. -## 무엇을 읽을지 결정하기 + -사용하는 프로바이더에 따라서, 취득 가능한 값의 종류가 다양해 질 수 있습니다. -예를 들어 [StreamProvider]를 사용한다고 생각해 봅시다. +## Deciding what to read + +Depending on the provider you want to listen to, you may have multiple possible +values that you can listen to. + +As an example, consider the following [StreamProvider]: ```dart final userProvider = StreamProvider(...); ``` -`userProvider`를 읽으려고 할때 아래와 같이 사용할 수 있습니다. -- `userProvider`자체를 구독하는 것으로 동기된(synchronously) 현재 상태 값을 읽을 수 있습니다. +When reading this `userProvider`, you can: + +- synchronously read the current state by listening to `userProvider` itself: ```dart Widget build(BuildContext context, WidgetRef ref) { AsyncValue user = ref.watch(userProvider); - return user.when( - loading: () => const CircularProgressIndicator(), - error: (error, stack) => const Text('Oops'), - data: (user) => Text(user.name), - ); + return switch (user) { + AsyncData(:final value) => Text(value.name), + AsyncError(:final error) => const Text('Oops $error'), + _ => const CircularProgressIndicator(), + }; } ``` -- `userProvider.stream`을 사용하여 연결된 [Stream]을 얻을 수 있습니다. +- obtain the associated [Stream], by listening to `userProvider.stream`: ```dart Widget build(BuildContext context, WidgetRef ref) { @@ -301,7 +343,7 @@ final userProvider = StreamProvider(...); } ``` -- `userProvider.future`를 사용해 가장 최근 상태값을 가진 [Future]를 얻을 수 있습니다. +- obtain a [Future] that resolves with the latest value emitted, by listening to `userProvider.future`: ```dart Widget build(BuildContext context, WidgetRef ref) { @@ -309,21 +351,21 @@ final userProvider = StreamProvider(...); } ``` -다른 프로바이더들은 또 다른 대안 값들(alternative values)을 제공할 수 있습니다. -더 자세한 정보를 원한다면 [API reference](https://pub.dev/documentation/riverpod/latest/riverpod/riverpod-library.html) -에 설명되어 있는 각각의 프로바이더에대한 상세한 정보를 참고하세요. +Other providers may offer different alternative values. +For more information, refer to the documentation of each provider by +consulting the [API reference](https://pub.dev/documentation/riverpod/latest/riverpod/riverpod-library.html). +## Using "select" to filter rebuilds -## "select"를 사용하여 재빌드 필터링하기 +One final feature to mention related to reading providers is the ability to +reduce the number of times a widget/provider rebuilds from `ref.watch`, or how often `ref.listen` +executes a function. -마지막 특징으로 a widget/provider의 재빌드를 수를 감소하거나 -제한하는 방법을 안내합니다. (얼마나 자주 `ref.listen` 실행하는지를 포함하여) +This is important to keep in mind as, by default, listening to a provider +listens to the entire object state. But sometimes, a widget/provider may only +care about changes to some properties instead of the whole object. - -프로바이더를 감시하는 것은, 그 프로바이더가 공개하는 객체의 모든 속성을 감시하는 것입니다. -그러나 특정 경우에서 구독 범위를 좁히고 특정 속성만 모니터링 대상으로 만들 수 있습니다. - -예를 들어, 프로바이더는 `User` 상태값을 가진다고 가정해 봅시다. +For example, a provider may expose a `User`: ```dart abstract class User { @@ -332,7 +374,7 @@ abstract class User { } ``` -그런데 위젯에서는 단순히 `User`의 `name`값만 사용하고 있습니다. +But a widget may only use the user name: ```dart Widget build(BuildContext context, WidgetRef ref) { @@ -341,13 +383,13 @@ Widget build(BuildContext context, WidgetRef ref) { } ``` -만약 `ref.watch`를 사용하면 `User`의 `age` 속성이 변경되면 위젯이 재빌드 될것입니다. - -여기서 해결방법은 `select`를 사용하는 것입니다. -`select`는 [Riverpod]에서 `User`의 특정 속성만 구독/관찰하고 싶을때 사용합니다. +If we naively used `ref.watch`, this would rebuild the widget when the user's +`age` changes. +The solution is to use `select` to explicitly tell Riverpod that we only +want to listen to the name property of the `User`. -코드를 다음과 같이 개선해 볼 수 있습니다. +The updated code would be: ```dart Widget build(BuildContext context, WidgetRef ref) { @@ -356,15 +398,17 @@ Widget build(BuildContext context, WidgetRef ref) { } ``` -`select`를 통해 관찰하고 싶은 상태값을 선택하여 -선택한 속성 값의 변화가 발생했을때 사용할 수 있습니다. +By using `select`, we are able to specify a function +that returns the property that we care about. -그리고 `User` 값이 변하면, [Riverpod]은 이 함수를 호출하여 이전 값과 새로운 값을 비교합니다. -만약 상태값이 다르다면(예를 들어 `name`이 변경되었다면), [Riverpod]은 위젯을 다시 빌드하는 작업을 처리할 겁니다. -그러나 만약 값이 같다면 (예를 들어 `age`만 변경되었다면), [Riverpod]은 위젯을 재빌드 하지 않을 겁니다. +Whenever the `User` changes, Riverpod will call this function and +compare the previous and new result. If they are different (such as when the name +changed), Riverpod will rebuild the widget. +However, if they are equal (such as when the age changed), Riverpod will not +rebuild the widget. :::info -`select`는 `ref.listen`과 함께 사용할 수 있습니다. +It is also possible to use `select` with `ref.listen`: ```dart ref.listen( @@ -374,13 +418,13 @@ ref.listen( } ); ``` -`name`이 변경되었을 때 호출되어 사용할 수 있는 구조를 가지게 됩니다. +Doing so will call the listener only when the name changes. ::: :::tip -`select`를 사용하는 경우 반환하는 값이 반드시 객체일 필요는 없습니다. -`==`연자자의 오버라이드(overrides)로 객체가 동일하다고 정의된다면 반환 값으로 무엇이 오든 상관없습니다. +You don't have to return a property of the object. Any value that +overrides == will work. For example you could do: ```dart final label = ref.watch(userProvider.select((user) => 'Mr ${user.name}')); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading_consumer_hook.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/consumer_hook.dart similarity index 71% rename from website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading_consumer_hook.dart rename to website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/consumer_hook.dart index 2d38c4cfc..5f296f2fb 100644 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading_consumer_hook.dart +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/consumer_hook.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'reading_counter.dart'; +import 'counter/raw.dart'; class HomeView extends HookConsumerWidget { const HomeView({super.key}); @@ -15,10 +15,10 @@ class HomeView extends HookConsumerWidget { Scaffold( body: HookConsumer( builder: (context, ref, child) { - // HookConsumerWidget과 같이, builder안에서 hooks을 사용할 수 있습니다. + // Like HookConsumerWidget, we can use hooks inside the builder final state = useState(0); - // 프로바이더를 사용/구독(listen)하기 위해서 ref 매개변수도 사용할 수 있습니다. + // We can also use the ref parameter to listen to providers. final counter = ref.watch(counterProvider); return Text('$counter'); }, diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/consumer_stateful_widget/hooks.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/consumer_stateful_widget/hooks.dart new file mode 100644 index 000000000..7b9118cbc --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/consumer_stateful_widget/hooks.dart @@ -0,0 +1,34 @@ +// ignore_for_file: unused_local_variable + +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import '../counter/raw.dart'; + +/* SNIPPET START */ + +class HomeView extends StatefulHookConsumerWidget { + const HomeView({super.key}); + + @override + HomeViewState createState() => HomeViewState(); +} + +class HomeViewState extends ConsumerState { + @override + void initState() { + super.initState(); + // "ref" can be used in all life-cycles of a StatefulWidget. + ref.read(counterProvider); + } + + @override + Widget build(BuildContext context) { + // Like HookConsumerWidget, we can use hooks inside the builder + final state = useState(0); + + // We can also use "ref" to listen to a provider inside the build method + final counter = ref.watch(counterProvider); + return Text('$counter'); + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/consumer_stateful_widget/index.tsx b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/consumer_stateful_widget/index.tsx new file mode 100644 index 000000000..9c0ae43de --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/consumer_stateful_widget/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import hooks from "!!raw-loader!./hooks.dart"; + +export default { + raw, + hooks: hooks, + codegen: raw, + hooksCodegen: hooks, +}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading_consumer_stateful_widget.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/consumer_stateful_widget/raw.dart similarity index 69% rename from website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading_consumer_stateful_widget.dart rename to website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/consumer_stateful_widget/raw.dart index c545e9b17..98d7f3403 100644 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading_consumer_stateful_widget.dart +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/consumer_stateful_widget/raw.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'reading_counter.dart'; +import '../counter/raw.dart'; /* SNIPPET START */ @@ -15,13 +15,13 @@ class HomeViewState extends ConsumerState { @override void initState() { super.initState(); - // "ref"는 StatefulWidget의 모든 생명주기 상에서 사용할 수 있습니다. + // "ref" can be used in all life-cycles of a StatefulWidget. ref.read(counterProvider); } @override Widget build(BuildContext context) { - // "ref"는 build 메소드 안에서 프로바이더를 구독(listen)하기위해 사용할 수 있습니다. + // We can also use "ref" to listen to a provider inside the build method final counter = ref.watch(counterProvider); return Text('$counter'); } diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading_consumer_hook_widget.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/consumer_widget/hooks.dart similarity index 66% rename from website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading_consumer_hook_widget.dart rename to website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/consumer_widget/hooks.dart index 27db84a16..1c5547428 100644 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading_consumer_hook_widget.dart +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/consumer_widget/hooks.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'reading_counter.dart'; +import '../counter/raw.dart'; /* SNIPPET START */ @@ -12,10 +12,10 @@ class HomeView extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - // HookConsumerWidget은 build 메소드 안에서 hooks을 사용할 수 있도록 도와줍니다. + // HookConsumerWidget allows using hooks inside the build method final state = useState(0); - // 프로바이더를 사용/구독하기 위해서 ref 매개변수도 사용할 수 있습니다. + // We can also use the ref parameter to listen to providers. final counter = ref.watch(counterProvider); return Text('$counter'); } diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/consumer_widget/index.tsx b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/consumer_widget/index.tsx new file mode 100644 index 000000000..9c0ae43de --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/consumer_widget/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import hooks from "!!raw-loader!./hooks.dart"; + +export default { + raw, + hooks: hooks, + codegen: raw, + hooksCodegen: hooks, +}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading_consumer_widget.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/consumer_widget/raw.dart similarity index 78% rename from website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading_consumer_widget.dart rename to website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/consumer_widget/raw.dart index ffa37a065..d2df7c5a7 100644 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading_consumer_widget.dart +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/consumer_widget/raw.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'reading_counter.dart'; +import '../counter/raw.dart'; /* SNIPPET START */ @@ -9,7 +9,7 @@ class HomeView extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - // ref를 사용해 프로바이더 구독(listen)하기 + // use ref to listen to a provider final counter = ref.watch(counterProvider); return Text('$counter'); } diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/counter/codegen.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/counter/codegen.dart new file mode 100644 index 000000000..ca1edf42e --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/counter/codegen.dart @@ -0,0 +1,23 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +final repositoryProvider = Provider((ref) => Repository()); + +class Repository { + Future post(String url) async {} +} + +/* SNIPPET START */ + +@riverpod +class Counter extends _$Counter { + @override + int build() => 0; + + void increment() { + // Counter can use the "ref" to read other providers + final repository = ref.read(repositoryProvider); + repository.post('...'); + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/counter/codegen.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/counter/codegen.g.dart new file mode 100644 index 000000000..addf9363a --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/counter/codegen.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$counterHash() => r'6bd7806869af024b3288645da03c077af9478083'; + +/// See also [Counter]. +@ProviderFor(Counter) +final counterProvider = AutoDisposeNotifierProvider.internal( + Counter.new, + name: r'counterProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$counterHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$Counter = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/counter/index.tsx b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/counter/index.tsx new file mode 100644 index 000000000..339b89a56 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/counter/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./codegen.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading_counter.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/counter/raw.dart similarity index 82% rename from website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading_counter.dart rename to website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/counter/raw.dart index d593c044e..b97c93b00 100644 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading_counter.dart +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/counter/raw.dart @@ -18,7 +18,7 @@ class Counter extends StateNotifier { final Ref ref; void increment() { - // Counter 클래스는 다른 프로바이더를 읽기 위해 "ref"를 사용할 수 있습니다. + // Counter can use the "ref" to read other providers final repository = ref.read(repositoryProvider); repository.post('...'); } diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/listen/codegen.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/listen/codegen.dart new file mode 100644 index 000000000..154e25e27 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/listen/codegen.dart @@ -0,0 +1,16 @@ +// ignore_for_file: omit_local_variable_types, avoid_types_on_closure_parameters, avoid_print + +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import '../counter/raw.dart'; + +part 'codegen.g.dart'; + +/* SNIPPET START */ + +@riverpod +void another(AnotherRef ref) { + ref.listen(counterProvider, (int? previousCount, int newCount) { + print('The counter changed $newCount'); + }); + // ... +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/listen/codegen.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/listen/codegen.g.dart new file mode 100644 index 000000000..072dce6c2 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/listen/codegen.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$anotherHash() => r'2208f9221f3d898305609874d4f43c28bdfff2b4'; + +/// See also [another]. +@ProviderFor(another) +final anotherProvider = AutoDisposeProvider.internal( + another, + name: r'anotherProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$anotherHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef AnotherRef = AutoDisposeProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/listen/index.tsx b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/listen/index.tsx new file mode 100644 index 000000000..339b89a56 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/listen/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./codegen.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/concepts/reading_listen.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/listen/raw.dart similarity index 75% rename from website/i18n/it/docusaurus-plugin-content-docs/current/concepts/reading_listen.dart rename to website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/listen/raw.dart index 4ed90e7af..ab229db6e 100644 --- a/website/i18n/it/docusaurus-plugin-content-docs/current/concepts/reading_listen.dart +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/listen/raw.dart @@ -1,15 +1,13 @@ // ignore_for_file: omit_local_variable_types, avoid_types_on_closure_parameters, avoid_print import 'package:riverpod/riverpod.dart'; -import 'reading_counter.dart'; +import '../counter/raw.dart'; /* SNIPPET START */ -final counterProvider = StateNotifierProvider(Counter.new); - final anotherProvider = Provider((ref) { ref.listen(counterProvider, (int? previousCount, int newCount) { print('The counter changed $newCount'); }); // ... -}); \ No newline at end of file +}); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/listen_build/codegen.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/listen_build/codegen.dart new file mode 100644 index 000000000..e22f4b169 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/listen_build/codegen.dart @@ -0,0 +1,28 @@ +// ignore_for_file: omit_local_variable_types, avoid_types_on_closure_parameters, avoid_print + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +/* SNIPPET START */ + +@riverpod +class Counter extends _$Counter { + @override + int build() => 0; +} + +class HomeView extends ConsumerWidget { + const HomeView({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + ref.listen(counterProvider, (int? previousCount, int newCount) { + print('The counter changed $newCount'); + }); + + return Container(); + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/listen_build/codegen.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/listen_build/codegen.g.dart new file mode 100644 index 000000000..395a07f40 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/listen_build/codegen.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$counterHash() => r'4320f811608c7a6e7342b83e3031965a34f7cb8e'; + +/// See also [Counter]. +@ProviderFor(Counter) +final counterProvider = AutoDisposeNotifierProvider.internal( + Counter.new, + name: r'counterProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$counterHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$Counter = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/listen_build/codegen_hooks.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/listen_build/codegen_hooks.dart new file mode 100644 index 000000000..5b1147d28 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/listen_build/codegen_hooks.dart @@ -0,0 +1,34 @@ +// ignore_for_file: omit_local_variable_types, avoid_types_on_closure_parameters, avoid_print + +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen_hooks.g.dart'; + +/* SNIPPET START */ + +@riverpod +class Counter extends _$Counter { + @override + int build() => 0; +} + +class HomeView extends HookConsumerWidget { + const HomeView({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + ref.listen(counterProvider, (int? previousCount, int newCount) { + print('The counter changed $newCount'); + }); + + final greeting = useState('Hello'); + + return Container( + alignment: Alignment.center, + child: Text(greeting.value), + ); + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/listen_build/codegen_hooks.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/listen_build/codegen_hooks.g.dart new file mode 100644 index 000000000..6f4db9464 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/listen_build/codegen_hooks.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen_hooks.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$counterHash() => r'4320f811608c7a6e7342b83e3031965a34f7cb8e'; + +/// See also [Counter]. +@ProviderFor(Counter) +final counterProvider = AutoDisposeNotifierProvider.internal( + Counter.new, + name: r'counterProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$counterHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$Counter = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/listen_build/index.tsx b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/listen_build/index.tsx new file mode 100644 index 000000000..a856c4980 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/listen_build/index.tsx @@ -0,0 +1,11 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./codegen.dart"; +import raw_hooks from "!!raw-loader!./raw_hooks.dart"; +import codegen_hooks from "!!raw-loader!./codegen_hooks.dart"; + +export default { + raw, + hooks: raw_hooks, + codegen, + hooksCodegen: codegen_hooks, +}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading_listen_build.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/listen_build/raw.dart similarity index 82% rename from website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading_listen_build.dart rename to website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/listen_build/raw.dart index 066565b67..e59ddaf57 100644 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading_listen_build.dart +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/listen_build/raw.dart @@ -2,11 +2,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'reading_counter.dart'; +import '../counter/raw.dart'; /* SNIPPET START */ -final counterProvider = StateNotifierProvider(Counter.new); +final counterProvider = + StateNotifierProvider(Counter.new); class HomeView extends ConsumerWidget { const HomeView({super.key}); @@ -16,7 +17,7 @@ class HomeView extends ConsumerWidget { ref.listen(counterProvider, (int? previousCount, int newCount) { print('The counter changed $newCount'); }); - + return Container(); } -} \ No newline at end of file +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/listen_build/raw_hooks.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/listen_build/raw_hooks.dart new file mode 100644 index 000000000..9f86fc19f --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/listen_build/raw_hooks.dart @@ -0,0 +1,29 @@ +// ignore_for_file: omit_local_variable_types, avoid_types_on_closure_parameters, avoid_print + +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import '../counter/raw.dart'; + +/* SNIPPET START */ + +final counterProvider = + StateNotifierProvider(Counter.new); + +class HomeView extends HookConsumerWidget { + const HomeView({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + ref.listen(counterProvider, (int? previousCount, int newCount) { + print('The counter changed $newCount'); + }); + + final greeting = useState('Hello'); + + return Container( + alignment: Alignment.center, + child: Text(greeting.value), + ); + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/provider/codegen.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/provider/codegen.dart new file mode 100644 index 000000000..201c92f91 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/provider/codegen.dart @@ -0,0 +1,20 @@ +// ignore_for_file: avoid_positional_boolean_parameters +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +class Repository { + String get() => ''; +} + +@riverpod +Repository repository(RepositoryRef ref) => Repository(); + +/* SNIPPET START */ + +@riverpod +String value(ValueRef ref) { + // use ref to obtain other providers + final repository = ref.watch(repositoryProvider); + return repository.get(); +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/provider/codegen.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/provider/codegen.g.dart new file mode 100644 index 000000000..31073640e --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/provider/codegen.g.dart @@ -0,0 +1,40 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$repositoryHash() => r'c6dc3b5b727028966b5b850b27ffc7294b485273'; + +/// See also [repository]. +@ProviderFor(repository) +final repositoryProvider = AutoDisposeProvider.internal( + repository, + name: r'repositoryProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$repositoryHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef RepositoryRef = AutoDisposeProviderRef; +String _$valueHash() => r'8c26f7aaa911af815cff9e513a18e4d8dcc6d1df'; + +/// See also [value]. +@ProviderFor(value) +final valueProvider = AutoDisposeProvider.internal( + value, + name: r'valueProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$valueHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ValueRef = AutoDisposeProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/provider/index.tsx b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/provider/index.tsx new file mode 100644 index 000000000..339b89a56 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/provider/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./codegen.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/provider/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/provider/raw.dart new file mode 100644 index 000000000..28fdfed97 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/provider/raw.dart @@ -0,0 +1,19 @@ +// ignore_for_file: avoid_positional_boolean_parameters + +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +class Repository { + void get() {} +} + +final repositoryProvider = Provider((ref) { + return Repository(); +}); + +/* SNIPPET START */ + +final valueProvider = Provider((ref) { + // use ref to obtain other providers + final repository = ref.watch(repositoryProvider); + return repository.get(); +}); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/read/codegen.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/read/codegen.dart new file mode 100644 index 000000000..0742ed5e5 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/read/codegen.dart @@ -0,0 +1,32 @@ +// ignore_for_file: omit_local_variable_types, avoid_types_on_closure_parameters, avoid_print + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +/* SNIPPET START */ + +@riverpod +class Counter extends _$Counter { + @override + int build() => 0; + void increment() => state = state + 1; +} + +class HomeView extends ConsumerWidget { + const HomeView({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Scaffold( + floatingActionButton: FloatingActionButton( + onPressed: () { + // Call `increment()` on the `Counter` class + ref.read(counterProvider.notifier).increment(); + }, + ), + ); + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/read/codegen.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/read/codegen.g.dart new file mode 100644 index 000000000..c3cfff0b6 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/read/codegen.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$counterHash() => r'600c6beb47b3732049b6955a9e49d7eef30c741a'; + +/// See also [Counter]. +@ProviderFor(Counter) +final counterProvider = AutoDisposeNotifierProvider.internal( + Counter.new, + name: r'counterProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$counterHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$Counter = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/read/codegen_hooks.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/read/codegen_hooks.dart new file mode 100644 index 000000000..341087cde --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/read/codegen_hooks.dart @@ -0,0 +1,36 @@ +// ignore_for_file: omit_local_variable_types, avoid_types_on_closure_parameters, avoid_print + +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen_hooks.g.dart'; + +/* SNIPPET START */ + +@riverpod +class Counter extends _$Counter { + @override + int build() => 0; + void increment() => state = state + 1; +} + +class HomeView extends HookConsumerWidget { + const HomeView({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final greeting = useState('Hello'); + + return Scaffold( + body: Center(child: Text(greeting.value)), + floatingActionButton: FloatingActionButton( + onPressed: () { + // Call `increment()` on the `Counter` class + ref.read(counterProvider.notifier).increment(); + }, + ), + ); + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/read/codegen_hooks.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/read/codegen_hooks.g.dart new file mode 100644 index 000000000..f5cbe5535 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/read/codegen_hooks.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen_hooks.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$counterHash() => r'600c6beb47b3732049b6955a9e49d7eef30c741a'; + +/// See also [Counter]. +@ProviderFor(Counter) +final counterProvider = AutoDisposeNotifierProvider.internal( + Counter.new, + name: r'counterProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$counterHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$Counter = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/read/index.tsx b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/read/index.tsx new file mode 100644 index 000000000..d0caf89aa --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/read/index.tsx @@ -0,0 +1,11 @@ +import raw from "!!raw-loader!./raw.dart"; +import raw_hooks from "!!raw-loader!./raw_hooks.dart"; +import codegen from "!!raw-loader!./codegen.dart"; +import codegen_hooks from "!!raw-loader!./codegen_hooks.dart"; + +export default { + raw, + hooks: raw_hooks, + codegen, + hooksCodegen: codegen_hooks, +}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading_read.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/read/raw.dart similarity index 72% rename from website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading_read.dart rename to website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/read/raw.dart index b3573a256..7d8685155 100644 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading_read.dart +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/read/raw.dart @@ -3,12 +3,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'reading_counter.dart'; +import '../counter/raw.dart'; /* SNIPPET START */ -final counterProvider = - StateNotifierProvider(Counter.new); +final counterProvider = StateNotifierProvider(Counter.new); class HomeView extends ConsumerWidget { const HomeView({super.key}); @@ -18,7 +17,7 @@ class HomeView extends ConsumerWidget { return Scaffold( floatingActionButton: FloatingActionButton( onPressed: () { - // `Counter`클래스의 increment() 메소드를 호출합니다. + // Call `increment()` on the `Counter` class ref.read(counterProvider.notifier).increment(); }, ), diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/read/raw_hooks.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/read/raw_hooks.dart new file mode 100644 index 000000000..acc6ba980 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/read/raw_hooks.dart @@ -0,0 +1,31 @@ +// ignore_for_file: omit_local_variable_types + +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +import '../counter/raw.dart'; + +/* SNIPPET START */ + +final counterProvider = + StateNotifierProvider(Counter.new); + +class HomeView extends HookConsumerWidget { + const HomeView({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final greeting = useState('Hello'); + + return Scaffold( + body: Center(child: Text(greeting.value)), + floatingActionButton: FloatingActionButton( + onPressed: () { + // Call `increment()` on the `Counter` class + ref.read(counterProvider.notifier).increment(); + }, + ), + ); + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/read_build/codegen.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/read_build/codegen.dart new file mode 100644 index 000000000..9a4347159 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/read_build/codegen.dart @@ -0,0 +1,25 @@ +// ignore_for_file: omit_local_variable_types, avoid_types_on_closure_parameters, avoid_print + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +/* SNIPPET START */ + +@riverpod +class Counter extends _$Counter { + @override + int build() => 0; + void increment() => state = state + 1; +} + +Widget build(BuildContext context, WidgetRef ref) { + // use "read" to ignore updates on a provider + final counter = ref.read(counterProvider.notifier); + return ElevatedButton( + onPressed: counter.increment, + child: const Text('button'), + ); +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/read_build/codegen.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/read_build/codegen.g.dart new file mode 100644 index 000000000..c3cfff0b6 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/read_build/codegen.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$counterHash() => r'600c6beb47b3732049b6955a9e49d7eef30c741a'; + +/// See also [Counter]. +@ProviderFor(Counter) +final counterProvider = AutoDisposeNotifierProvider.internal( + Counter.new, + name: r'counterProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$counterHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$Counter = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/read_build/index.tsx b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/read_build/index.tsx new file mode 100644 index 000000000..339b89a56 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/read_build/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./codegen.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading_read_build.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/read_build/raw.dart similarity index 84% rename from website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading_read_build.dart rename to website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/read_build/raw.dart index 8f2ba0bd6..266e00224 100644 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading_read_build.dart +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/read_build/raw.dart @@ -8,7 +8,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; final counterProvider = StateProvider((ref) => 0); Widget build(BuildContext context, WidgetRef ref) { - // 프로바이더 상태 값 갱신을 무시하기위해서 "read" 사용 + // use "read" to ignore updates on a provider final counter = ref.read(counterProvider.notifier); return ElevatedButton( onPressed: () => counter.state++, diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/read_notifier_build/codegen.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/read_notifier_build/codegen.dart new file mode 100644 index 000000000..6bf061abf --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/read_notifier_build/codegen.dart @@ -0,0 +1,24 @@ +// ignore_for_file: omit_local_variable_types, prefer_final_locals + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +/* SNIPPET START */ + +@riverpod +class Counter extends _$Counter { + @override + int build() => 0; + void increment() => state = state + 1; +} + +Widget build(BuildContext context, WidgetRef ref) { + Counter counter = ref.read(counterProvider.notifier); + return ElevatedButton( + onPressed: () => counter.increment(), + child: const Text('button'), + ); +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/read_notifier_build/codegen.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/read_notifier_build/codegen.g.dart new file mode 100644 index 000000000..c3cfff0b6 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/read_notifier_build/codegen.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$counterHash() => r'600c6beb47b3732049b6955a9e49d7eef30c741a'; + +/// See also [Counter]. +@ProviderFor(Counter) +final counterProvider = AutoDisposeNotifierProvider.internal( + Counter.new, + name: r'counterProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$counterHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$Counter = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/read_notifier_build/index.tsx b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/read_notifier_build/index.tsx new file mode 100644 index 000000000..339b89a56 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/read_notifier_build/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./codegen.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/concepts/reading_read_notifier_build.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/read_notifier_build/raw.dart similarity index 99% rename from website/i18n/it/docusaurus-plugin-content-docs/current/concepts/reading_read_notifier_build.dart rename to website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/read_notifier_build/raw.dart index 34104aead..0ec5b5958 100644 --- a/website/i18n/it/docusaurus-plugin-content-docs/current/concepts/reading_read_notifier_build.dart +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/read_notifier_build/raw.dart @@ -13,4 +13,4 @@ Widget build(BuildContext context, WidgetRef ref) { onPressed: () => counter.state++, child: const Text('button'), ); -} \ No newline at end of file +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/watch/codegen.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/watch/codegen.dart new file mode 100644 index 000000000..31de6da74 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/watch/codegen.dart @@ -0,0 +1,45 @@ +// ignore_for_file: omit_local_variable_types, avoid_types_on_closure_parameters, avoid_print + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +enum FilterType { + none, + completed, +} + +abstract class Todo { + bool get isCompleted; +} + +/* SNIPPET START */ + +@riverpod +FilterType filterType(FilterTypeRef ref) { + return FilterType.none; +} + +@riverpod +class Todos extends _$Todos { + @override + List build() { + return []; + } +} + +@riverpod +List filteredTodoList(FilteredTodoListRef ref) { + // obtains both the filter and the list of todos + final FilterType filter = ref.watch(filterTypeProvider); + final List todos = ref.watch(todosProvider); + + switch (filter) { + case FilterType.completed: + // return the completed list of todos + return todos.where((todo) => todo.isCompleted).toList(); + case FilterType.none: + // returns the unfiltered list of todos + return todos; + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/watch/codegen.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/watch/codegen.g.dart new file mode 100644 index 000000000..971b32d3a --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/watch/codegen.g.dart @@ -0,0 +1,55 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$filterTypeHash() => r'42b68b163daecff7a0b9b069b16025a89910b4fb'; + +/// See also [filterType]. +@ProviderFor(filterType) +final filterTypeProvider = AutoDisposeProvider.internal( + filterType, + name: r'filterTypeProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$filterTypeHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef FilterTypeRef = AutoDisposeProviderRef; +String _$filteredTodoListHash() => r'34f1e929a9e7850946ea8634d9f3e8f38ae5687d'; + +/// See also [filteredTodoList]. +@ProviderFor(filteredTodoList) +final filteredTodoListProvider = AutoDisposeProvider>.internal( + filteredTodoList, + name: r'filteredTodoListProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$filteredTodoListHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef FilteredTodoListRef = AutoDisposeProviderRef>; +String _$todosHash() => r'b66ac2b1e5cf7ac7957d25864cfdffad1af233a6'; + +/// See also [Todos]. +@ProviderFor(Todos) +final todosProvider = AutoDisposeNotifierProvider>.internal( + Todos.new, + name: r'todosProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$todosHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$Todos = AutoDisposeNotifier>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/watch/index.tsx b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/watch/index.tsx new file mode 100644 index 000000000..339b89a56 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/watch/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./codegen.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading_watch.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/watch/raw.dart similarity index 80% rename from website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading_watch.dart rename to website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/watch/raw.dart index 9fef0a435..625c0fe9a 100644 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading_watch.dart +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/watch/raw.dart @@ -22,16 +22,16 @@ final todosProvider = StateNotifierProvider>((ref) => TodoList()); final filteredTodoListProvider = Provider((ref) { - // 할일(todos)목록과 필터(filter)상태 값을 취득합니다. + // obtains both the filter and the list of todos final FilterType filter = ref.watch(filterTypeProvider); final List todos = ref.watch(todosProvider); switch (filter) { case FilterType.completed: - // 완료된(completed) 할일 목록을 반환합니다. + // return the completed list of todos return todos.where((todo) => todo.isCompleted).toList(); case FilterType.none: - // 필터링되지 않은 목록을 반환합니다. + // returns the unfiltered list of todos return todos; } }); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/watch_build/codegen.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/watch_build/codegen.dart new file mode 100644 index 000000000..80b63d216 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/watch_build/codegen.dart @@ -0,0 +1,39 @@ +// ignore_for_file: omit_local_variable_types, avoid_types_on_closure_parameters, avoid_print + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +enum FilterType { + none, + completed, +} + +abstract class Todo { + bool get isCompleted; +} + +@riverpod +class TodoList extends _$TodoList { + @override + List build() => []; +} + +/* SNIPPET START */ + +@riverpod +int counter(CounterRef ref) => 0; + +class HomeView extends ConsumerWidget { + const HomeView({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + // use ref to listen to a provider + final counter = ref.watch(counterProvider); + + return Text('$counter'); + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/watch_build/codegen.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/watch_build/codegen.g.dart new file mode 100644 index 000000000..f8e5f6463 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/watch_build/codegen.g.dart @@ -0,0 +1,41 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$counterHash() => r'9b0db44ecc47057e79891e5ecd92d34b08637679'; + +/// See also [counter]. +@ProviderFor(counter) +final counterProvider = AutoDisposeProvider.internal( + counter, + name: r'counterProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$counterHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef CounterRef = AutoDisposeProviderRef; +String _$todoListHash() => r'77f007cd4f5105330a4c2ab8555ea0d1716945c1'; + +/// See also [TodoList]. +@ProviderFor(TodoList) +final todoListProvider = + AutoDisposeNotifierProvider>.internal( + TodoList.new, + name: r'todoListProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$todoListHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$TodoList = AutoDisposeNotifier>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/watch_build/codegen_hooks.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/watch_build/codegen_hooks.dart new file mode 100644 index 000000000..c82698304 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/watch_build/codegen_hooks.dart @@ -0,0 +1,43 @@ +// ignore_for_file: omit_local_variable_types, avoid_types_on_closure_parameters, avoid_print + +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen_hooks.g.dart'; + +enum FilterType { + none, + completed, +} + +abstract class Todo { + bool get isCompleted; +} + +@riverpod +class TodoList extends _$TodoList { + @override + List build() => []; +} + +/* SNIPPET START */ + +@riverpod +int counter(CounterRef ref) => 0; + +class HomeView extends HookConsumerWidget { + const HomeView({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + // You can use hooks inside a HookConsumerWidget + final greeting = useState('Hello'); + + // use ref to listen to a provider + final counter = ref.watch(counterProvider); + + return Text('${greeting.value} $counter'); + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/watch_build/codegen_hooks.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/watch_build/codegen_hooks.g.dart new file mode 100644 index 000000000..8a2f64454 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/watch_build/codegen_hooks.g.dart @@ -0,0 +1,41 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen_hooks.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$counterHash() => r'9b0db44ecc47057e79891e5ecd92d34b08637679'; + +/// See also [counter]. +@ProviderFor(counter) +final counterProvider = AutoDisposeProvider.internal( + counter, + name: r'counterProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$counterHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef CounterRef = AutoDisposeProviderRef; +String _$todoListHash() => r'77f007cd4f5105330a4c2ab8555ea0d1716945c1'; + +/// See also [TodoList]. +@ProviderFor(TodoList) +final todoListProvider = + AutoDisposeNotifierProvider>.internal( + TodoList.new, + name: r'todoListProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$todoListHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$TodoList = AutoDisposeNotifier>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/watch_build/index.tsx b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/watch_build/index.tsx new file mode 100644 index 000000000..a856c4980 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/watch_build/index.tsx @@ -0,0 +1,11 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./codegen.dart"; +import raw_hooks from "!!raw-loader!./raw_hooks.dart"; +import codegen_hooks from "!!raw-loader!./codegen_hooks.dart"; + +export default { + raw, + hooks: raw_hooks, + codegen, + hooksCodegen: codegen_hooks, +}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading_watch_build.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/watch_build/raw.dart similarity index 91% rename from website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading_watch_build.dart rename to website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/watch_build/raw.dart index 6ace027ca..8d5d430eb 100644 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading_watch_build.dart +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/watch_build/raw.dart @@ -25,7 +25,7 @@ class HomeView extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - // ref를 사용하여 프로바이더 구독하기. + // use ref to listen to a provider final counter = ref.watch(counterProvider); return Text('$counter'); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/watch_build/raw_hooks.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/watch_build/raw_hooks.dart new file mode 100644 index 000000000..5f297850a --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/watch_build/raw_hooks.dart @@ -0,0 +1,37 @@ +// ignore_for_file: omit_local_variable_types + +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +enum FilterType { + none, + completed, +} + +abstract class Todo { + bool get isCompleted; +} + +class TodoList extends StateNotifier> { + TodoList() : super([]); +} + +/* SNIPPET START */ + +final counterProvider = StateProvider((ref) => 0); + +class HomeView extends HookConsumerWidget { + const HomeView({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + // You can use hooks inside a HookConsumerWidget + final greeting = useState('Hello'); + + // use ref to listen to a provider + final counter = ref.watch(counterProvider); + + return Text('${greeting.value} $counter'); + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/watch_notifier_build/codegen.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/watch_notifier_build/codegen.dart new file mode 100644 index 000000000..6b76d0219 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/watch_notifier_build/codegen.dart @@ -0,0 +1,24 @@ +// ignore_for_file: omit_local_variable_types, prefer_final_locals + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +/* SNIPPET START */ + +@riverpod +class Counter extends _$Counter { + @override + int build() => 0; + void increment() => state = state + 1; +} + +Widget build(BuildContext context, WidgetRef ref) { + Counter counter = ref.watch(counterProvider.notifier); + return ElevatedButton( + onPressed: () => counter.increment(), + child: const Text('button'), + ); +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/watch_notifier_build/codegen.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/watch_notifier_build/codegen.g.dart new file mode 100644 index 000000000..c3cfff0b6 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/watch_notifier_build/codegen.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$counterHash() => r'600c6beb47b3732049b6955a9e49d7eef30c741a'; + +/// See also [Counter]. +@ProviderFor(Counter) +final counterProvider = AutoDisposeNotifierProvider.internal( + Counter.new, + name: r'counterProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$counterHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$Counter = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/watch_notifier_build/index.tsx b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/watch_notifier_build/index.tsx new file mode 100644 index 000000000..339b89a56 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/watch_notifier_build/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./codegen.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading_watch_notifier_build.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/watch_notifier_build/raw.dart similarity index 100% rename from website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading_watch_notifier_build.dart rename to website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/reading/watch_notifier_build/raw.dart diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/scopes.mdx b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/scopes.mdx new file mode 100644 index 000000000..f53d8315d --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/scopes.mdx @@ -0,0 +1,180 @@ +--- +title: Scopes +--- + +import CodeBlock from "@theme/CodeBlock"; +import asyncInitialization from "!!raw-loader!/docs/concepts/async_initialization.dart"; +import dialogScope from "!!raw-loader!/docs/concepts/dialog_scope.dart"; +import themeScope from "!!raw-loader!/docs/concepts/theme_scope.dart"; +import subtreeScope from "!!raw-loader!/docs/concepts/subtree_scope.dart"; +import { trimSnippet } from "../../../../../src/components/CodeSnippet"; +import { Link } from "../../../../../src/components/Link"; + +:::caution +The content of this page may be outdated. +It will be updated in the future, but for now you may want to refer to the content +in the top of the sidebar instead (introduction/essentials/case-studies/...) +::: + +Scoping in Riverpod is a very powerful feature, but like all powerful features, it should be used wisely and intentionally. + +A few of the things that scoping enables are: + +- Override the state of providers for a specific subtree (similar to how theming and `InheritedWidgets` work in flutter) [(see example)](#subtree-scope) +- Creating synchronous providers for normally async APIs [(see example)](#initialization-of-synchronous-provider-for-async-apis) +- Allowing `Dialog`s and `Overlay`s to inherit the state of providers from the widget subtree that cause them to be shown [(see example)](#showing-dialogs) +- Optimizing rebuilds of widgets by removing parameters from Widget constructors allowing you to make them `const` + +If you are wanting to use scope for the first point, chances are you can use families instead. +Families have the advantages of allowing you to access each of those instances of the state from anywhere in the widget tree rather than just the state scoped to the specific subtree that you are in. + +Using scope to create multiple instances of a provider's state is similar to how `package:provider` works. + +However, using scope to accomplish that task, is more restrictive, as you cannot decide to access other instances from that scope. + +As such, before scoping every provider you use, consider carefully why you want to scope the provider. + +## ProviderScope and ProviderContainer + +A scope is introduced by a [ProviderContainer]. This container holds the current state of all of your providers. +It manages the lookup and subscriptions between providers. + +In Flutter you should use the [ProviderScope] widget, which contains a [ProviderContainer] +internally, and provides a way to access that container to the rest of the widget tree. + +```dart +final valueProvider = StateProvider((ref) => 0); + +// DO this +void main() { + runApp(ProviderScope(child: MyApp())); +} + +//DON'T do this: +final myProviderContainer = ProviderContainer(); +void main(){ + runApp(MyApp()); +} +``` + +:::warning +Do not use multiple [ProviderContainer]s, without an understanding of how they work. +Each will have it's own separate thread of states, which will not be able to access each other. +Tests are an example of when you might want to use separate [ProviderContainer]s +in order to make each test's state independent of the others. +::: + +Only create a [ProviderContainer] without a [ProviderScope] for testing and dart-only usage. + +## How Riverpod Finds a Provider + +When a widget or provider requests the value of a provider, Riverpod looks up the state of that provider in the nearest +[ProviderScope] widget. If neither the provider nor one of it's explicitly listed dependencies is overridden in that scope Riverpod continues it's lookup up the widget tree. +If the provider has not been overridden in any Widget subtrees the lookup defaults to the [ProviderContainer] in the root [ProviderScope]. + +Once this process locates the scope in which the provider should reside it determines if the provider has been created yet. +If so, it will return the state of the provider. +However, if the provider has been invalidated or is not currently initialized it will create the state using the provider's build method. + +## Initialization of Synchronous Provider for Async APIs + +Often you might have some async initialization of a dependency such as `SharedPreferences` or `FirebaseApp`. +Many other providers might rely on this, and dealing with the error / loading states in each of those providers is redundant. + +You might be able to guarantee that those providers will not have errors and will load quickly when the app is started. + +So how do you makes these sorts of provider states available synchronously? + +Here is an example that shows how scoping allows you override a dummy provider when your asynchronous API is ready. + +{trimSnippet(asyncInitialization)} + +## Showing Dialogs + +When you show a `Dialog` or `OverlayEntry`, flutter creates a new `Route` or adds to an `Overlay` that has a different build scope, +so that it can escape the layout of it's parent, and can be shown above other `Routes`. +This presents a problem for `InheritedWidget`s in general, and since [ProviderScope] is an `InheritedWidget`, it is also affected. + +To solve this problem, Riverpod allows you to create a `ProviderScope` that can access the state of all providers in a `parent` scope. + +The following example shows how to use this, to allow a `Dialog` to access the state of a counter from the context that caused the `Dialog` to be shown. + +{trimSnippet(dialogScope)} + +## Subtree Scoping + +Scoping allows you to override the state of a provider for a specific subtree of your widget tree. +In this way it can provide a similar mechanism to `InheritedWidget` from flutter, or the providers from `package:provider`. + +For example, in flutter you can override the `Theme` for a particular subtree of your widget tree, by wrapping it in a `Theme` widget. + +{trimSnippet(themeScope)} + +Under the hood, `Theme` is an `InheritedWidget` and when widgets look up the `Theme` they get the `Theme` from the nearest `Theme` widget above it in the widget tree. + +Riverpod works differently, since all of the state of your application is typically stored in a root [ProviderScope] widget. +Don't worry, this doesn't cause your whole application to rebuild when the state changes, it just allows you to access the state from anywhere in your widget tree. + +What if you want different providers depending on which page you are in? + +The first thing that you should consider is whether the provided behavior will differ in any way. + +If so -> just create a new provider with a different name and use it in that page + +If not -> Consider using a . + +Often you start by thinking that you only need a provider on a particular page, but end up wanting to use it in another page later on. +Families protect you against this eventuality, and are a major difference in how you should adjust your thinking if you are coming from `package:provider`. + +If families really do not fit your use case, the following example shows you how to override a provider for a particular subtree. + +{trimSnippet(subtreeScope)} + +## When to choose Scoped Providers or Families + +While scopes are important to understand, it is easy to get carried away when using scopes. + +If you want a different instance of a provider's state depending on where it is in the widget tree you have a few alternatives available to you: `Scoping`, `Families`, or a combination. +The appropriate choice depends on your use case. + +Families: + +- Pro: You can show multiple of the states no matter which subtree you are in +- Pro: This makes it a more flexible and scalable solution for many use cases + +Scoping: + +- Con: You end up with more nesting of [ProviderScope] widgets in your widget tree +- Con: You can only access the one override in your section of the widget tree +- Con: You end up having to explicitly list the dependencies of most of your providers +- Pro: You can reduce the number of parameters in your widget constructors +- Pro: You get a slight performance advantage, and can potentially make some of your widget constructors `const` + +Using a combination of the two approaches, you can get the pros of both approaches, but you still have to deal with the cons of scoping. + +:::warning +Remember that scopes introduce a new instance of the state of every provider that is overridden or has listed a dependency on a provider that was overridden. +If you override with the same parameter in a different subtree of the app, it will **not** be the same instance of the provider's state. +Families are more flexible in general, and with the upcoming code generation feature it is easy to use multiple parameters for a family. +Often a good combination is to use both families and scoping. Use a family to provide general access to a piece of state anywhere in your app, and then use scoping to +provide a specific instance of the family's state depending on where you are in the widget tree. +::: + +### Less common usages of Scopes + +Sometimes you may want to override a whole set of providers in a specific subtree of your app. +By listing a common provider in the dependencies list of each of those providers, you can easily create new states for all of them at once, by overriding the common one. + +Note that if you try to use families for this, you will end up with many families that all have the same parameter, and you could end up passing that parameter all over the widget tree. +In this case it is also acceptable to use scopes. + +:::warning +Once you start using scope, make sure to always list your dependencies and keep them up to date, to prevent runtime exceptions. +To help with this we have created [riverpod_lint] which will warn you if there is a missing dependency. +Additionally with [riverpod_generator] the code generator automatically generates the dependency list. +::: + +[ProviderContainer]: https://pub.dev/documentation/riverpod/latest/riverpod/ProviderContainer-class.html +[ProviderScope]: https://pub.dev/documentation/flutter_riverpod/latest/flutter_riverpod/ProviderScope-class.html +[riverpod_lint]: https://github.com/rrousselGit/riverpod/tree/master/packages/riverpod_lint +[riverpod_generator]: https://github.com/rrousselGit/riverpod/tree/master/packages/riverpod_generator diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/subtree_scope.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/subtree_scope.dart new file mode 100644 index 000000000..9aa1903e5 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/subtree_scope.dart @@ -0,0 +1,78 @@ +// ignore_for_file: omit_local_variable_types, prefer_final_locals + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +/* SNIPPET START */ + +/// A counter that is being incremented by each [CounterDisplay]'s ElevatedButton +final counterProvider = StateProvider( + (ref) => 0, +); + +final adjustedCountProvider = Provider( + (ref) => ref.watch(counterProvider) * 2, + // Note that if a provider depends on a provider that is overridden for a subtree, + // you must explicitly list that provider in your dependencies list. + dependencies: [counterProvider], +); + +class Home extends ConsumerWidget { + const Home({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Scaffold( + body: Column( + children: [ + ProviderScope( + /// Just specify which provider you want to have a copy of in the subtree + /// + /// Note that dependant providers such as [adjustedCountProvider] will + /// also be copied for this subtree. If that is not the behavior you want, + /// consider using families instead + overrides: [counterProvider], + child: const CounterDisplay(), + ), + ProviderScope( + // You can change the provider's behavior in a particular subtree + overrides: [counterProvider.overrideWith((ref) => 1)], + child: const CounterDisplay(), + ), + ProviderScope( + overrides: [ + counterProvider, + // You can also change dependent provider's behaviors + adjustedCountProvider.overrideWith( + (ref) => ref.watch(counterProvider) * 3, + ), + ], + child: const CounterDisplay(), + ), + // This particular display will use the provider state from the root ProviderScope + const CounterDisplay(), + ], + )); + } +} + +class CounterDisplay extends ConsumerWidget { + const CounterDisplay({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final count = ref.watch(counterProvider); + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text('$count'), + ElevatedButton( + onPressed: () { + ref.read(counterProvider.notifier).state++; + }, + child: const Text('Increment Count'), + ), + ], + ); + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/theme_scope.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/theme_scope.dart new file mode 100644 index 000000000..d17f15f02 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/theme_scope.dart @@ -0,0 +1,68 @@ +// ignore_for_file: omit_local_variable_types, prefer_final_locals + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +/* SNIPPET START */ + +void main() { + runApp( + ProviderScope( + child: MaterialApp( + theme: ThemeData(primaryColor: Colors.blue), + home: const Home(), + ), + ), + ); +} + +// Have a counter that is being incremented +final counterProvider = StateProvider( + (ref) => 0, +); + +class Home extends ConsumerWidget { + const Home({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Scaffold( + body: Column( + children: [ + // This counter will have a primary color of green + Theme( + data: Theme.of(context).copyWith(primaryColor: Colors.green), + child: const CounterDisplay(), + ), + // This counter will have a primary color of blue + const CounterDisplay(), + ElevatedButton( + onPressed: () { + ref.read(counterProvider.notifier).state++; + }, + child: const Text('Increment Count'), + ), + ], + )); + } +} + +class CounterDisplay extends ConsumerWidget { + const CounterDisplay({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final count = ref.watch(counterProvider); + final theme = Theme.of(context); + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + '$count', + style: theme.textTheme.displayMedium + ?.copyWith(color: theme.primaryColor), + ), + ], + ); + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/why_immutability.mdx b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/why_immutability.mdx index bb4ecd124..4e23da9fc 100644 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/why_immutability.mdx +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/why_immutability.mdx @@ -1,124 +1,94 @@ --- -title: 불변성(Immutability)의 중요성 +title: Why Immutability --- import Tabs from "@theme/Tabs"; import TabItem from "@theme/TabItem"; +import whyImmutability from "./why_immutability" +import { + trimSnippet, + AutoSnippet, + When, +} from "../../../../../src/components/CodeSnippet"; + +:::caution +The content of this page may be outdated. +It will be updated in the future, but for now you may want to refer to the content +in the top of the sidebar instead (introduction/essentials/case-studies/...) +::: -## 불변성(Immutability)이란? +## What is Immutability? -불변성은 객체의 모든 필드가 final 또는 late final인 경우를 의미합니다. -이러한 필드들은 생성 시에만 설정되며, 이후에는 변경할 수 없습니다. +Immutability is when all fields of an `Object` are final or late final. +They are set exactly once upon construction. -불변성은 여러 가지 이유에서 유용합니다. +Immutability is desireable for many different reasons -- reference equality(레퍼런스 동등성)이 아닌 value equality(값의 동등성)을 가져옵니다. -- 코드의 지역적인 이해가 용이해집니다. - - 다른 곳에 있는 코드가 레퍼런스에 접근하여 객체를 변경하는 것을 방지할 수 있습니다 -- 비동기 및 병렬 작업에 대해 이해하기 쉬워집니다. - - 작업 사이에 다른 코드가 객체를 변경할 수 없습니다. -- API의 안전성이 보장됩니다. - - 메서드에 전달한 객체는 호출자 또는 피호출자에 의해 변경될 수 없습니다. +- Value equality rather than reference equality +- Local reasoning about a piece of code + - A far distant piece of code can't obtain a reference and change the object from underneath you +- Easier to reason about for asynchronous and parallel tasks + - Other code can't mutate your object in between operations +- Safety of APIs + - What you pass into a method cannot be changed by the callee / caller -copyWith 메서드는 기존 객체에서 몇 가지 속성만 변경된 새로운 객체를 생성할 때, 코드의 복잡도를 줄이는 데 도움이 될 수 있습니다. +A copyWith method helps with reducing verbosity when creating a new object with just a few things changed. -복사 작업은 예상보다 더 효율적입니다. 왜냐하면 Dart는 변경되지 않은 하위 객체에 대한 참조를 재사용할 수 있기 때문입니다. +Copying is more efficient than you might think, since dart can reuse any references to sub-objects that have not changed. :::warning -객체가 깊은 불변성(deeply immutable)을 가지고 있는지 확인하십시오. 그렇지 않으면 당신은 깊은 복사 메커니즘을 구현해야 할 수 있습니다. +Make sure your objects are deeply immutable, otherwise you'll have to implement some sort of deep copy mechanism. ::: -## 최적의 방법 +## Best Practices -불변 상태를 만들기 위해 다양한 패키지를 이용할 수 있습니다. +You can use any package you want to create immutable state. -immutable한 객체의 경우 +For immutable objects: - [package:freezed](https://pub.dev/packages/freezed) - [package:built_value](https://pub.dev/packages/built_value) -immutable한 컬렉션의 경우 (Map, Set, List) +For immutable collections (Map, Set, List): - [package:fast_immutable_collections](https://pub.dev/packages/fast_immutable_collections) - [package:built_collection](https://pub.dev/packages/built_collection) - [package:kt_dart](https://pub.dev/packages/kt_dart) - [package:dartz](https://pub.dev/packages/dartz) -[freezed] 사용을 높이 권장합니다. -이 패키지에는 불변의 객체를 만드는 것 이외에도 몇 가지 멋진 추가 기능들이 있습니다. +It is highly recommended to use [freezed], +since it has several nice additions beyond just making immutable objects including: -- copyWith 메서드 -- 깊은 복사 (중첩된 freezed 객체의 copyWith) -- Union 타입 -- Union 매핑 함수 +- A generated copyWith method +- Deep copy (copyWith on nested freezed objects) +- Union types +- Union mapping functions -불변 상태를 다루기 위해 code generation을 사용해야 할 필요는 없지만, 이를 사용하면 작업이 더 쉬워집니다. +You do not need to use code generation to work with immutable state, but it makes it much easier. :::warning -빌트인 컬렉션을 사용하려면, 업데이트할 때 컬렉션을 복사하는 규칙을 강제해야 합니다. -컬렉션을 복사하지 않으면 riverpod는 객체에 대한 레퍼런스가 변경되었는지를 기반으로 새 상태를 전달할지 결정합니다. -객체를 변경하는 메서드를 단순히 호출한다면, 레퍼런스는 이전과 동일하기 때문에 객체가 변경되지 않을 수 있습니다. +If you want to use the built-in collections, make sure to enforce a discipline of making copies of collections when updating them. +The issue with not copying a collection is that riverpod determines whether to emit a new state based on whether the reference to the object has changed. +If you just call a method that mutates an object, the reference is the same. ::: -### 불변 상태 사용하기 - -불변 상태는 [StateNotifier]와 [StateNotifierProvider]를 결합하여 사용하는 것이 가장 적합합니다. -[StateNotifier]를 사용하면 상태를 '변경'할 수 있는 인터페이스를 노출할 수 있습니다. -[StateNotifier]를 확장하는 클래스 밖에서는 상태를 변경할 수 없으므로, 역할 분리를 강제하고 비즈니스 로직을 UI 바깥에 유지할 수 있습니다. - -다음은 앱 테마를 변경하는 불변 상태의 설정 클래스 예제입니다. - -```dart -final themeProvider = StateNotifierProvider((ref) => ThemeNotifier()); - -class ThemeNotifier extends StateNotifier { - ThemeNotifier(): super( - ThemeSettings( - mode: ThemeMode.system, - primaryColor: Colors.blue, - )); - - void toggle() { - state = state.copyWith(mode: state.mode.toggle); - } - void setDarkTheme() { - state = state.copyWith(mode: ThemeMode.dark); - } - void setLightTheme() { - state = state.copyWith(mode: ThemeMode.light); - } - void setSystemTheme() { - state = state.copyWith(mode: ThemeMode.system); - } - void setPrimaryColor(Color color) { - state = state.copyWith(primaryColor: color); - } - -} - -@freezed -class ThemeSettings with _$ThemeSettings { - const factory ThemeSettings({ThemeMode mode, Color primaryColor}) = _ThemeSettings; -} - -extension ToggleTheme on ThemeMode { - ThemeMode get toggle { - switch (this){ - case ThemeMode.dark: - return ThemeMode.light; - case ThemeMode.light: - return ThemeMode.dark; - case ThemeMode.system: - return ThemeMode.system; - } - } -} -``` -이 코드를 적용하려면, 'freezed_annotation'을 import하고 part 지시문을 추가한 후, [build_runner]를 실행하여 freezed 클래스를 생성해야 하는 것을 기억하세요! +### Using immutable state + +Immutable state is best fit for using a [Notifier] in combination with [NotifierProvider] . +A [Notifier] allows you to expose an interface through which you can 'mutate' the state. +You cannot mutate the state from outside the class you define that extends [Notifier]. +This enforces a separation of concerns and keeps business logic outside of your UI. + +Here is an example of a simple immutable settings class for changing an app theme. + + + +To use this code, remember to import `freezed_annotation`, add the part directive and run [build_runner] to generate the freezed classes! [changenotifier]: https://api.flutter.dev/flutter/foundation/ChangeNotifier-class.html [statenotifier]: https://pub.dev/documentation/riverpod/latest/riverpod/StateNotifier-class.html [statenotifierprovider]: https://pub.dev/documentation/riverpod/latest/riverpod/StateNotifierProvider-class.html +[notifier]: https://pub.dev/documentation/riverpod/latest/riverpod/Notifier-class.html +[notifierprovider]: https://pub.dev/documentation/riverpod/latest/riverpod/NotifierProvider.html [asyncvalue]: https://pub.dev/documentation/riverpod/latest/riverpod/AsyncValue-class.html [freezed]: https://pub.dev/packages/freezed [build_runner]: https://pub.dev/packages/build_runner - diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/why_immutability/codegen.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/why_immutability/codegen.dart new file mode 100644 index 000000000..fd286a36c --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/why_immutability/codegen.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.freezed.dart'; +part 'codegen.g.dart'; + +/* SNIPPET START */ + +@riverpod +class ThemeNotifier extends _$ThemeNotifier { + @override + ThemeSettings build() => const ThemeSettings( + mode: ThemeMode.light, + primaryColor: Colors.blue, + ); + + void toggle() { + state = state.copyWith(mode: state.mode.toggle); + } + + void setDarkTheme() { + state = state.copyWith(mode: ThemeMode.dark); + } + + void setLightTheme() { + state = state.copyWith(mode: ThemeMode.light); + } + + void setSystemTheme() { + state = state.copyWith(mode: ThemeMode.system); + } + + void setPrimaryColor(Color color) { + state = state.copyWith(primaryColor: color); + } +} + +@freezed +class ThemeSettings with _$ThemeSettings { + const factory ThemeSettings({ + required ThemeMode mode, + required Color primaryColor, + }) = _ThemeSettings; +} + +extension ToggleTheme on ThemeMode { + ThemeMode get toggle { + switch (this) { + case ThemeMode.dark: + return ThemeMode.light; + case ThemeMode.light: + return ThemeMode.dark; + case ThemeMode.system: + return ThemeMode.system; + } + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/why_immutability/codegen.freezed.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/why_immutability/codegen.freezed.dart new file mode 100644 index 000000000..6e8c8082b --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/why_immutability/codegen.freezed.dart @@ -0,0 +1,151 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'codegen.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +/// @nodoc +mixin _$ThemeSettings { + ThemeMode get mode => throw _privateConstructorUsedError; + Color get primaryColor => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $ThemeSettingsCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ThemeSettingsCopyWith<$Res> { + factory $ThemeSettingsCopyWith( + ThemeSettings value, $Res Function(ThemeSettings) then) = + _$ThemeSettingsCopyWithImpl<$Res, ThemeSettings>; + @useResult + $Res call({ThemeMode mode, Color primaryColor}); +} + +/// @nodoc +class _$ThemeSettingsCopyWithImpl<$Res, $Val extends ThemeSettings> + implements $ThemeSettingsCopyWith<$Res> { + _$ThemeSettingsCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? mode = null, + Object? primaryColor = null, + }) { + return _then(_value.copyWith( + mode: null == mode + ? _value.mode + : mode // ignore: cast_nullable_to_non_nullable + as ThemeMode, + primaryColor: null == primaryColor + ? _value.primaryColor + : primaryColor // ignore: cast_nullable_to_non_nullable + as Color, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$ThemeSettingsImplCopyWith<$Res> + implements $ThemeSettingsCopyWith<$Res> { + factory _$$ThemeSettingsImplCopyWith( + _$ThemeSettingsImpl value, $Res Function(_$ThemeSettingsImpl) then) = + __$$ThemeSettingsImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ThemeMode mode, Color primaryColor}); +} + +/// @nodoc +class __$$ThemeSettingsImplCopyWithImpl<$Res> + extends _$ThemeSettingsCopyWithImpl<$Res, _$ThemeSettingsImpl> + implements _$$ThemeSettingsImplCopyWith<$Res> { + __$$ThemeSettingsImplCopyWithImpl( + _$ThemeSettingsImpl _value, $Res Function(_$ThemeSettingsImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? mode = null, + Object? primaryColor = null, + }) { + return _then(_$ThemeSettingsImpl( + mode: null == mode + ? _value.mode + : mode // ignore: cast_nullable_to_non_nullable + as ThemeMode, + primaryColor: null == primaryColor + ? _value.primaryColor + : primaryColor // ignore: cast_nullable_to_non_nullable + as Color, + )); + } +} + +/// @nodoc + +class _$ThemeSettingsImpl implements _ThemeSettings { + const _$ThemeSettingsImpl({required this.mode, required this.primaryColor}); + + @override + final ThemeMode mode; + @override + final Color primaryColor; + + @override + String toString() { + return 'ThemeSettings(mode: $mode, primaryColor: $primaryColor)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ThemeSettingsImpl && + (identical(other.mode, mode) || other.mode == mode) && + (identical(other.primaryColor, primaryColor) || + other.primaryColor == primaryColor)); + } + + @override + int get hashCode => Object.hash(runtimeType, mode, primaryColor); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$ThemeSettingsImplCopyWith<_$ThemeSettingsImpl> get copyWith => + __$$ThemeSettingsImplCopyWithImpl<_$ThemeSettingsImpl>(this, _$identity); +} + +abstract class _ThemeSettings implements ThemeSettings { + const factory _ThemeSettings( + {required final ThemeMode mode, + required final Color primaryColor}) = _$ThemeSettingsImpl; + + @override + ThemeMode get mode; + @override + Color get primaryColor; + @override + @JsonKey(ignore: true) + _$$ThemeSettingsImplCopyWith<_$ThemeSettingsImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/why_immutability/codegen.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/why_immutability/codegen.g.dart new file mode 100644 index 000000000..e423516f0 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/why_immutability/codegen.g.dart @@ -0,0 +1,28 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$themeNotifierHash() => r'e119d56d9bf8b8d7c19624997f99d116098b45e9'; + +/// See also [ThemeNotifier]. +@ProviderFor(ThemeNotifier) +final themeNotifierProvider = + AutoDisposeNotifierProvider.internal( + ThemeNotifier.new, + name: r'themeNotifierProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$themeNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$ThemeNotifier = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/why_immutability/index.tsx b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/why_immutability/index.tsx new file mode 100644 index 000000000..339b89a56 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/why_immutability/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./codegen.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/why_immutability/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/why_immutability/raw.dart new file mode 100644 index 000000000..1e34c694c --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/concepts/why_immutability/raw.dart @@ -0,0 +1,67 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +/* SNIPPET START */ + +final themeProvider = + NotifierProvider(ThemeNotifier.new); + +class ThemeNotifier extends Notifier { + @override + ThemeSettings build() { + return ThemeSettings(mode: ThemeMode.system, primaryColor: Colors.blue); + } + + void toggle() { + state = state.copyWith(mode: state.mode.toggle); + } + + void setDarkTheme() { + state = state.copyWith(mode: ThemeMode.dark); + } + + void setLightTheme() { + state = state.copyWith(mode: ThemeMode.light); + } + + void setSystemTheme() { + state = state.copyWith(mode: ThemeMode.system); + } + + void setPrimaryColor(Color color) { + state = state.copyWith(primaryColor: color); + } +} + +class ThemeSettings { + ThemeSettings({ + required this.mode, + required this.primaryColor, + }); + + final ThemeMode mode; + final Color primaryColor; + + ThemeSettings copyWith({ + ThemeMode? mode, + Color? primaryColor, + }) { + return ThemeSettings( + mode: mode ?? this.mode, + primaryColor: primaryColor ?? this.primaryColor, + ); + } +} + +extension ToggleTheme on ThemeMode { + ThemeMode get toggle { + switch (this) { + case ThemeMode.dark: + return ThemeMode.light; + case ThemeMode.light: + return ThemeMode.dark; + case ThemeMode.system: + return ThemeMode.system; + } + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/cookbooks/refresh.mdx b/website/i18n/ko/docusaurus-plugin-content-docs/current/cookbooks/refresh.mdx deleted file mode 100644 index edf6ab805..000000000 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/cookbooks/refresh.mdx +++ /dev/null @@ -1,10 +0,0 @@ ---- -title: Pull-to-refresh / Retry-on-error ---- - -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; - -In this guide, we will see how Providers can be used to easily implement -a pull-to-refresh or retry-on-error feature. - diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/cookbooks/search_as_we_type.mdx b/website/i18n/ko/docusaurus-plugin-content-docs/current/cookbooks/search_as_we_type.mdx index b89da0ca2..bbb1a477f 100644 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/cookbooks/search_as_we_type.mdx +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/cookbooks/search_as_we_type.mdx @@ -2,11 +2,17 @@ title: Search as we type --- +:::caution +The content of this page may be outdated. +It will be updated in the future, but for now you may want to refer to the content +in the top of the sidebar instead (introduction/essentials/case-studies/...) +::: + A real world example could be to use `FutureProvider` to implement a searchbar. ## Usage example: "Search as we type" searchbar -Implementing a "seach as we type" can seem daunting at first when using +Implementing a "search as we type" can seem daunting at first when using conventional means. There are lots of moving parts, such as: @@ -26,10 +32,10 @@ is to split it into multiple providers: the other providers/[family]. The first step would be to store the user input somewhere. For this example, -we will use `StateProvider` (as the seach state is only a single `String`): +we will use `StateProvider` (as the search state is only a single `String`): ```dart -final seachInputProvider = StateProvider((ref) => ''); +final searchInputProvider = StateProvider((ref) => ''); ``` We can then connect this provider to a [TextField] by doing: @@ -113,14 +119,11 @@ class MyHomePage extends ConsumerWidget { ref.read(searchFieldProvider.notifier).state = value, ), Expanded( - child: questions.when( - loading: () => const Center(child: CircularProgressIndicator()), - error: (error, stack) => Center(child: Text('Error $error')), - data: (questions) { - return ListView.builder( - itemCount: questions.length, + child: switch (questions) { + AsyncData(:final value) => ListView.builder( + itemCount: value.length, itemBuilder: (context, index) { - final question = questions[index]; + final question = value[index]; return ListTile( title: Text( @@ -128,9 +131,10 @@ class MyHomePage extends ConsumerWidget { ), ); }, - ); - }, - ), + );, + AsyncError(:final error) => Center(child: Text('Error $error')), + _ => const Center(child: CircularProgressIndicator()), + }, ), ], ), diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/cookbooks/testing.mdx b/website/i18n/ko/docusaurus-plugin-content-docs/current/cookbooks/testing.mdx index 2ca4f4dba..3e91042c9 100644 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/cookbooks/testing.mdx +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/cookbooks/testing.mdx @@ -1,46 +1,57 @@ --- -title: 테스트 +title: Testing --- import Tabs from "@theme/Tabs"; import TabItem from "@theme/TabItem"; import CodeBlock from "@theme/CodeBlock"; -import testingOriginalTestFlutter from "!!raw-loader!/i18n/ko/docusaurus-plugin-content-docs/current/cookbooks/testing_original_test_flutter.dart"; -import testingOriginalTestDart from "!!raw-loader!/i18n/ko/docusaurus-plugin-content-docs/current/cookbooks/testing_original_test_dart.dart"; -import repositorySnippet from "!!raw-loader!/i18n/ko/docusaurus-plugin-content-docs/current/cookbooks/testing_repository.dart"; -import testFlutter from "!!raw-loader!/i18n/ko/docusaurus-plugin-content-docs/current/cookbooks/testing_flutter.dart"; -import testDart from "!!raw-loader!/i18n/ko/docusaurus-plugin-content-docs/current/cookbooks/testing_dart.dart"; -import testFull from "!!raw-loader!/i18n/ko/docusaurus-plugin-content-docs/current/cookbooks/testing_full.dart"; -import testOverrideInfo from "!!raw-loader!/i18n/ko/docusaurus-plugin-content-docs/current/cookbooks/testing_override_info.dart"; +import testingOriginalTestFlutter from "!!raw-loader!/docs/cookbooks/testing_original_test_flutter.dart"; +import testingOriginalTestDart from "!!raw-loader!/docs/cookbooks/testing_original_test_dart.dart"; +import repositorySnippet from "!!raw-loader!/docs/cookbooks/testing_repository.dart"; +import testFlutter from "!!raw-loader!/docs/cookbooks/testing_flutter.dart"; +import testDart from "!!raw-loader!/docs/cookbooks/testing_dart.dart"; +import testFull from "!!raw-loader!/docs/cookbooks/testing_full.dart"; +import testOverrideInfo from "!!raw-loader!/docs/cookbooks/testing_override_info.dart"; import { trimSnippet } from "../../../../../src/components/CodeSnippet"; -중, 대규모 애플리케이션에서 테스트는 중요한 작업입니다. +:::caution +The content of this page may be outdated. +It will be updated in the future, but for now you may want to refer to the content +in the top of the sidebar instead (introduction/essentials/case-studies/...) +::: + +For any medium to large-scale applications, it is critical to test the application. -우리의 앱을 성공적으로 테스트하기 위해서 아래의 몇가지 포인트를 생각해 보아야합니다. +To successfully test our application, we will want the following things: -- `test`/`testWidgets`간 상태를 공유하지 않습니다. - 이것은 글로벌 상태를 가지지 않은 앱 또는 모든 글로벌 상태들이 테스트 후에 초기화됨을 의미합니다. +- No state should be preserved between `test`/`testWidgets`. + That means no global state in the application, or all global states should reset after each test. -- 프로바이더에 특정한 상태를 가질 수 있도록 할 수 있는데, mocking 또는 조작을 통해 원하는 상태로 만들어 줄 수 있습니다. +- Being able to force our providers to have a specific state, either through + mocking or by manipulating them until we reach the desired state. -[Riverpod]의 기능을 어떻게 활용할 수 있는지 하나씩 확인해봅시다. +Let's see one by one how [Riverpod] helps you with these. -## `test`와`testWidgets`간 상태를 공유(보존)하지 않음. +## No state should be preserved between `test`/`testWidgets`. -당신은 프로바이더가 전역(글로벌)변수로서 선언되어 사용되기 때문에 이 부분을 걱정할 수 도 있습니다. -어쨋든, 전역 상태를 만들어 테스팅하는것은 `setUp`/`tearDown`이 많이 요구되기 때문에 매우 까다롭습니다. +Since providers are usually declared as global variables, you might worry about +that one. +After all, global state makes testing very difficult, because it can require +lengthy `setUp`/`tearDown`. -그러나 실제로는 프로바이더가 전역(상태변수)로서 선언되는 동안 프로바이더의 상태는 전역이**아닙니다**. +But the reality is: While providers are declared as globals, the state of a provider +is **not** global. -대신에, 상태들은 [ProviderContainer]의 객체 이름을 가지고 저장됩니다. -Dart만 사용하는 경우(dart-only)의 샘플 코드에서 [ProviderContainer]객체를 보셨을 수 있습니다. -만약 모르고 있었다면 [ProviderContainer] 객체는 암묵적으로 우리의 프로젝트에서 [Riverpod]을 사용하기위해 -사용하는 위젯 [ProviderScope]에 의해 생성됩니다. +Instead, it is stored in an object named [ProviderContainer], which you may have +seen if you looked at the dart-only examples. +If you haven't, know that this [ProviderContainer] object is implicitly created +by [ProviderScope], the widget that enables [Riverpod] on our project. -구체적으로, 상태가 글로벌(전역)이 아니라는 것은 프로바이더를 사용하는 2개의 `testWidgets` 간 상태는 공유되지 않습니다. -엄밀히 말해서 `setUp`/`tearDown`을 모두 필요로 하지 않습니다. +Concretely what this means is, two `testWidgets` using providers do not share +any state. +As such, there is no need for any `setUp`/`tearDown` at all. -길게 설정하는것보다 예제로 확인해보도록 합시다. +But an example is better than lengthy explanations: -보았듯이 `counterProvider`가 전역상태번수로 선언되어 있음에도 테스트 간 상태는 공유되지 않습니다. -그래서 우리는 각각의 테스트가 독립된 환경에서 실행되지 때문에 실행순서에 따라 테스트 결과가 달라지는 것을 걱정하지 않아도 됩니다. - -## 테스트 하는동안 프로바이더의 동작을 오버라이딩 하는 경우. +As you can see, while `counterProvider` was declared as a global, no state was +shared between tests. +As such, we do not have to worry about our tests potentially behaving differently +if executed in a different order, since they are running in complete isolation. -통상적인 현실세계의 애플리케이션은 아래와 같은 객체를 가지고 있다고 생각합니다. +## Overriding the behavior of a provider during tests. A common real-world application may have the following objects: -- 타입세이프(a type-safe)와 HTTP 요청 수행 기능을 제공하는 `Repository` 클래스를 가지고 있습니다. +- It will have a `Repository` class, which provides a type-safe and simple API + to perform HTTP requests. -- 앱 상태를 관리하고 `Repository`를 사용해 다양한 조건을 기반으로 한 HTTP 요청을 수행하는 객체를 가지고 있습니다. 이것은 아마도 `ChangeNotifier`, `Bloc` 또는 프로바이더 일것입니다. +- An object that manages the application state, and may use `Repository` to perform + HTTP requests based on different factors. + This may be a `ChangeNotifier`, `Bloc`, or even a provider. -[Riverpod]을 사용하면 아래와 같이 표현 할 수 있습니다. +Using [Riverpod], this may be represented as follows: {trimSnippet(repositorySnippet)} -이 상황에서는 a unit/widget test를 만들때, `Repository` 인스턴스를 실제 HTTP요청 대신에 사전 정의된 응답을 반환하는 fake 구현 레포지토리로 대체합니다. -`todoListProvider` 또는 `Repository`의 구현된 mock과 동등한 객체를 사용합니다. +In this situation, when making a unit/widget test, we will typically want to +replace our `Repository` instance with a fake implementation that returns +a pre-defined response instead of making a real HTTP request. -이를 달성하기위해서, `repositoryProvider`의 행위를 오버라이드 하기위해 [ProviderScope]/[ProviderContainer]의 `overrides` 파라미터를 사용할 수 있습니다. +We will then want our `todoListProvider` or equivalent to use the mocked implementation +of `Repository`. + +To achieve this, we can use the `overrides` parameter of [ProviderScope]/[ProviderContainer] +to override the behavior of `repositoryProvider`: -하이라이트된 코드를 확인해보면, [ProviderScope]/[ProviderContainer]는 다른 동작을 하는 프로바이더의 구현체로 교체할 수 있는것을 허용합니다. - +As you can see by the highlighted code, [ProviderScope]/[ProviderContainer] +allows replacing the implementation of a provider with a different behavior. :::info -프로바이더에 따라 프로바이더가 가지는 동작을 오버라이드하기 위해 간단한 방법을 노출합니다. -예를 들어 [FutureProvider]는 `AsyncValue`와 함께 프로바이더를 오버라이딩 할 수 있습니다. +Some providers expose simplified ways to override their behavior. +For example, [FutureProvider] allows overriding the provider with an `AsyncValue`: {trimSnippet(testOverrideInfo)} +**Note**: As part of the 2.0.0 release, `overrideWithValue` methods are temporarily +removed. They will be added back in later versions. + ::: :::info -프로바이더를 오버라이딩하기 위해 사용하는 `family` 수식자와 구문이 조금 차이가 있습니다. +The syntax for overriding a provider with the `family` modifier is slightly different. -만약 아래와 같이 프로바이더를 사용한다면 +If you used a provider like this: ```dart final response = ref.watch(myProvider('12345')); ``` -아래와 같이 프로바이더를 오버라이드할 수 있습니다. +You could override the provider as: ```dart myProvider('12345').overrideWithValue(...)); @@ -130,9 +152,9 @@ myProvider('12345').overrideWithValue(...)); ::: -## 모든 위젯 테스트 예제 Full widget test example +## Full widget test example -마지막으로 위의 내용을 정리한 모든 테스트 코드를 확인해봅시다. +Wrapping up, here is the entire full code for our Flutter test. {trimSnippet(testFull)} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/cookbooks/testing_dart.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/cookbooks/testing_dart.dart index ec0c951cf..2d25bb8e8 100644 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/cookbooks/testing_dart.dart +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/cookbooks/testing_dart.dart @@ -16,36 +16,36 @@ final todoListProvider = FutureProvider>((ref) => []); void main() { /* SNIPPET START */ - test('override repositoryProvider', () async { - final container = ProviderContainer( - overrides: [ - // repositoryProvider의 행위를 오버라이드하여 - // Repository 대신 FakeRepository를 반환합니다. - /* highlight-start */ - repositoryProvider.overrideWithValue(FakeRepository()) - /* highlight-end */ - // 오버라이드된 repositoryProvider를 자동적으로 사용하기 때문에 - // `todoListProvider`를 override하지 않아도 됩니다. - ], - ); - - // 처음 상태가 로딩중임을 확인합니다. - expect( - container.read(todoListProvider), - const AsyncValue>.loading(), - ); - - /// 요청이 종료될때까지 대기합니다. - await container.read(todoListProvider.future); - - // 취득된 데이터를 노출시킵니다. - expect(container.read(todoListProvider).value, [ - isA() - .having((s) => s.id, 'id', '42') - .having((s) => s.label, 'label', 'Hello world') - .having((s) => s.completed, 'completed', false), - ]); - }); +test('override repositoryProvider', () async { + final container = ProviderContainer( + overrides: [ + // Override the behavior of repositoryProvider to return + // FakeRepository instead of Repository. + /* highlight-start */ + repositoryProvider.overrideWithValue(FakeRepository()) + /* highlight-end */ + // We do not have to override `todoListProvider`, it will automatically + // use the overridden repositoryProvider + ], + ); + + // The first read if the loading state + expect( + container.read(todoListProvider), + const AsyncValue>.loading(), + ); + + /// Wait for the request to finish + await container.read(todoListProvider.future); + + // Exposes the data fetched + expect(container.read(todoListProvider).value, [ + isA() + .having((s) => s.id, 'id', '42') + .having((s) => s.label, 'label', 'Hello world') + .having((s) => s.completed, 'completed', false), + ]); +}); /* SNIPPET END */ } diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/cookbooks/testing_flutter.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/cookbooks/testing_flutter.dart index eb475ff23..6685a1bdb 100644 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/cookbooks/testing_flutter.dart +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/cookbooks/testing_flutter.dart @@ -19,22 +19,22 @@ class FakeRepository {} void main() { /* SNIPPET START */ - testWidgets('override repositoryProvider', (tester) async { - await tester.pumpWidget( - ProviderScope( - overrides: [ - // repositoryProvider의 행위를 오버라이드하여 - // Repository 대신 FakeRepository를 반환합니다. - /* highlight-start */ - repositoryProvider.overrideWithValue(FakeRepository()) - /* highlight-end */ - // 오버라이드된 repositoryProvider를 자동적으로 사용하기 때문에 - // `todoListProvider`를 override하지 않아도 됩니다. - ], - child: MyApp(), - ), - ); - }); +testWidgets('override repositoryProvider', (tester) async { + await tester.pumpWidget( + ProviderScope( + overrides: [ + // Override the behavior of repositoryProvider to return + // FakeRepository instead of Repository. + /* highlight-start */ + repositoryProvider.overrideWithValue(FakeRepository()) + /* highlight-end */ + // We do not have to override `todoListProvider`, it will automatically + // use the overridden repositoryProvider + ], + child: MyApp(), + ), + ); +}); /* SNIPPET END */ } diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/cookbooks/testing_full.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/cookbooks/testing_full.dart index 8b290d56d..0f79b4ee8 100644 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/cookbooks/testing_full.dart +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/cookbooks/testing_full.dart @@ -1,3 +1,4 @@ + import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -18,20 +19,20 @@ class Todo { final bool completed; } -// Repository를 사용하는 프로바이더 인스턴스 +// We expose our instance of Repository in a provider final repositoryProvider = Provider((ref) => Repository()); -/// 할일(Todo) 목록 -/// [Repository]를 사용하여 서버로부터 값을 취득하는 FutureProvider 인스턴스 +/// The list of todos. Here, we are simply fetching them from the server using +/// [Repository] and doing nothing else. final todoListProvider = FutureProvider((ref) async { - // Repository 인스턴스를 취득합니다. + // Obtains the Repository instance final repository = ref.read(repositoryProvider); - // Todo 목록을 가져오고 이를 UI에 노출시킵니다. + // Fetch the todos and expose them to the UI. return repository.fetchTodos(); }); -/// 레포지토리의 Mock 구현: 사전 정의된 할일 목록을 반환하는 역할 +/// A mocked implementation of Repository that returns a pre-defined list of todos class FakeRepository implements Repository { @override Future> fetchTodos() async { @@ -54,14 +55,16 @@ void main() { testWidgets('override repositoryProvider', (tester) async { await tester.pumpWidget( ProviderScope( - overrides: [repositoryProvider.overrideWithValue(FakeRepository())], - // todoListProvider로부터 상태 값을 읽어 todo 목록을 표시하는 앱 - // MyApp 위젯으로도 가능. + overrides: [ + repositoryProvider.overrideWithValue(FakeRepository()) + ], + // Our application, which will read from todoListProvider to display the todo-list. + // You may extract this into a MyApp widget child: MaterialApp( home: Scaffold( body: Consumer(builder: (context, ref, _) { final todos = ref.watch(todoListProvider); - // 할일 목록이 로딩 중이거나 에러가 발생했을 때의 대응 + // The list of todos is loading or in error if (todos.asData == null) { return const CircularProgressIndicator(); } @@ -76,17 +79,16 @@ void main() { ), ); - // 처음 프레임 상태가 로딩중임을 확인. + // The first frame is a loading state. expect(find.byType(CircularProgressIndicator), findsOneWidget); - // 재 렌더링을 수행 - // (TodoListProvider가 할일 목록을 가져오기를 끝냈을 것이라 예상) + // Re-render. TodoListProvider should have finished fetching the todos by now await tester.pump(); - // CircularProgressIndicator을 찾아 loading 상태인지 확인 . + // No longer loading expect(find.byType(CircularProgressIndicator), findsNothing); - // FakeRepository가 반환한 값이 1개의 TodoItem으로 렌더링되었는지 확인. + // Rendered one TodoItem with the data returned by FakeRepository expect(tester.widgetList(find.byType(TodoItem)), [ isA() .having((s) => s.todo.id, 'todo.id', '42') @@ -94,4 +96,4 @@ void main() { .having((s) => s.todo.completed, 'todo.completed', false), ]); }); -} +} \ No newline at end of file diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/cookbooks/testing_original_test_dart.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/cookbooks/testing_original_test_dart.dart index 215bb7951..6995de9b8 100644 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/cookbooks/testing_original_test_dart.dart +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/cookbooks/testing_original_test_dart.dart @@ -4,48 +4,48 @@ import 'package:riverpod/riverpod.dart'; /* SNIPPET START */ -// Dart만을 사용하여 구현된 카운터 앱을 테스트해봅시다. (플러터에 의존하지 않은 앱) +// A Counter implemented and tested with Dart only (no dependency on Flutter) -// provider를 전역변수로 선언하합니다. 그리고 만약 테스트간 상태가 `0`으로 초기화 되는것을 -// 확인하기 위한 2개의 테스트를 실행해볼겁니다. +// We declared a provider globally, and we will use it in two tests, to see +// if the state correctly resets to `0` between tests. final counterProvider = StateProvider((ref) => 0); -// mockito를 사용하여 프로바이더가 notify할 때의 값을 추적하기 위한 listeners 객체를 생성합니다. +// Using mockito to keep track of when a provider notify its listeners class Listener extends Mock { void call(int? previous, int value); } void main() { test('defaults to 0 and notify listeners when value changes', () { - // 프로바이더를 사용하기위한 객체 - // 테스트간 공유되지 않습니다. + // An object that will allow us to read providers + // Do not share this between tests. final container = ProviderContainer(); addTearDown(container.dispose); final listener = Listener(); - // 프로바이더를 관찰하고 값 변화를 검출합니다. + // Observe a provider and spy the changes. container.listen( counterProvider, listener.call, fireImmediately: true, ); - // 리스너는 0을 기본값으로 호출됩니다. + // the listener is called immediately with 0, the default value verify(listener(null, 0)).called(1); verifyNoMoreInteractions(listener); - // 여기서 상태 값을 증가시켜 봅니다. + // We increment the value container.read(counterProvider.notifier).state++; - // 리스터는 다시 호출되고 상태 값은 1을 가집니다. + // The listener was called again, but with 1 this time verify(listener(0, 1)).called(1); verifyNoMoreInteractions(listener); }); test('the counter state is not shared between tests', () { - // 프로바이더를 사용하기 위해 다른 ProviderContainer를 사용합니다. - // 이걸로 테스트간 상태를 재사용하지 않는것을 확인할 수 있습니다. + // We use a different ProviderContainer to read our provider. + // This ensure that no state is reused between tests final container = ProviderContainer(); addTearDown(container.dispose); final listener = Listener(); @@ -56,7 +56,7 @@ void main() { fireImmediately: true, ); - // 새로운 테스트는 기본 값을 0으로 가진 상태를 출력하는 것을 확인할 수 있습니다. + // The new test correctly uses the default value: 0 verify(listener(null, 0)).called(1); verifyNoMoreInteractions(listener); }); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/cookbooks/testing_original_test_flutter.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/cookbooks/testing_original_test_flutter.dart index 60b545a5d..f76abe181 100644 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/cookbooks/testing_original_test_flutter.dart +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/cookbooks/testing_original_test_flutter.dart @@ -6,13 +6,14 @@ import 'package:flutter_test/flutter_test.dart'; /* SNIPPET START */ -// 플러터를 사용해 구현된 카운터 앱 테스트 하기 +// A Counter implemented and tested using Flutter + +// We declared a provider globally, and we will use it in two tests, to see +// if the state correctly resets to `0` between tests. -// provider를 전역변수로 선언하합니다. 그리고 만약 테스트간 상태가 `0`으로 초기화 되는것을 -// 확인하기 위한 2개의 테스트를 실행해볼겁니다. final counterProvider = StateProvider((ref) => 0); -// 현재의 상태값과 버튼을 누르면 상태가 값이 증가하는 기능을 가지는 화면을 생성합니다. +// Renders the current state and a button that allows incrementing the state class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { @@ -32,16 +33,15 @@ void main() { testWidgets('update the UI when incrementing the state', (tester) async { await tester.pumpWidget(ProviderScope(child: MyApp())); - // 프로바이더에서 선언한 그대로 기본값은 `0`입니다. + // The default value is `0`, as declared in our provider expect(find.text('0'), findsOneWidget); expect(find.text('1'), findsNothing); - // ElevatedButton를 찾아 탭을 수행하여 상태를 증가시킵니다. - // 그리고 다시 랜더링을 실행합니다. + // Increment the state and re-render await tester.tap(find.byType(ElevatedButton)); await tester.pump(); - // 상태가 적절하게 증가되어 있는지 확인합니다. + // The state have properly incremented expect(find.text('1'), findsOneWidget); expect(find.text('0'), findsNothing); }); @@ -49,8 +49,7 @@ void main() { testWidgets('the counter state is not shared between tests', (tester) async { await tester.pumpWidget(ProviderScope(child: MyApp())); - // 상태가 공유되어있지 않기떄문에 다시 `0`값을 가집니다. - // tearDown/setUp 작업이 필요없습니다. + // The state is `0` once again, with no tearDown/setUp needed expect(find.text('0'), findsOneWidget); expect(find.text('1'), findsNothing); }); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/cookbooks/testing_override_info.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/cookbooks/testing_override_info.dart index 8ad1eca93..d71ae3c69 100644 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/cookbooks/testing_override_info.dart +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/cookbooks/testing_override_info.dart @@ -34,7 +34,7 @@ final foo = /* SKIP END */ ProviderScope( overrides: [ - /// 고정된 값을 반환하기 위해 FutureProvider를 오버라이딩 합니다. + /// Allows overriding a FutureProvider to return a fixed value todoListProvider.overrideWithValue( AsyncValue.data([Todo(id: '42', label: 'Hello', completed: true)]), ), diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/cookbooks/testing_repository.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/cookbooks/testing_repository.dart index 820147057..c499279fc 100644 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/cookbooks/testing_repository.dart +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/cookbooks/testing_repository.dart @@ -8,15 +8,15 @@ class Repository { Future> fetchTodos() async => []; } -// 프로바이더안에 레포지토리의 인스턴스를 노출합니다. +// We expose our instance of Repository in a provider final repositoryProvider = Provider((ref) => Repository()); -/// Todo의 목록 -/// 여기 [Repository]를 사용하여 서버로부터 값을 가져오고 있습니다. +/// The list of todos. Here, we are simply fetching them from the server using +/// [Repository] and doing nothing else. final todoListProvider = FutureProvider((ref) async { - // Repository 인스턴스를 생성합니다. + // Obtains the Repository instance final repository = ref.watch(repositoryProvider); - // Todo 목록을 취득하고 UI에 노출시킵니다. + // Fetch the todos and expose them to the UI. return repository.fetchTodos(); }); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose.mdx b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose.mdx new file mode 100644 index 000000000..e3f1407da --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose.mdx @@ -0,0 +1,153 @@ +--- +title: "캐시 지우기 및 상태 폐기(disposal)에 반응하기" +--- + +import { Link } from "../../../../../src/components/Link"; +import { AutoSnippet, When } from "../../../../../src/components/CodeSnippet"; +import onDisposeExample from "./auto_dispose/on_dispose_example"; +import codegenKeepAlive from "!!raw-loader!./auto_dispose/codegen_keep_alive.dart"; +import rawAutoDispose from "!!raw-loader!./auto_dispose/raw_auto_dispose.dart"; +import invalidateExample from "!!raw-loader!./auto_dispose/invalidate_example.dart"; +import keepAlive from "./auto_dispose/keep_alive"; +import cacheForExtension from "!!raw-loader!./auto_dispose/cache_for_extension.dart"; +import cacheForUsage from "./auto_dispose/cache_for_usage"; +import invalidateFamilyExample from './auto_dispose/invalidate_family_example' + +지금까지 일부 상태(state)를 생성하거나 업데이트하는 방법을 살펴보았습니다. +하지만 언제 상태가 소멸(destruction)되는지에 대해서는 아직 이야기하지 않았습니다. + +Riverpod은 상태 폐기(disposal)와 상호작용할 수 있는 다양한 방법을 제공합니다. +여기에는 상태 폐기를 지연(delaying)시키는 것부터 소멸에 반응하는 것까지 다양합니다. + +## 상태는 언제 파괴되며, 그 것을 어떻게 변경하나요? + + + +코드 생성(code-generation)을 사용할 때 기본적으로 provider가 수신이 중지되면 상태가 파괴됩니다. +이는 리스너에 전체 프레임에 대한 활성 리스너가 없을 때 발생합니다. 이 경우 상태가 소멸됩니다. + +이 동작은 `keepAlive: true`를 사용하여 해제(opted out)할 수 있습니다. +이렇게 하면 모든 리스너가 제거될 때 상태가 소멸되는 것을 방지할 수 있습니다. + + + + + + + +코드 생성을 사용하지 않는 경우 기본적으로 provider의 수신이 중단되어도 상태는 파괴되지 않습니다. + +선택적으로 이 동작을 변경하여 자동 폐기를 사용할 수 있습니다. +그렇게 하면, Riverpod은 provider에 리스너가 있는지 여부를 추적합니다. +그런 다음 provider에 전체 프레임에 대한 리스너가 없는 경우 상태가 삭제됩니다. + +자동 폐기를 활성화하려면 provider 타입 옆에 `.autoDispose`를 사용하면 됩니다: + + + + + +:::note +자동 폐기를 활성화/비활성화해도 provider가 다시 계산될 때 상태가 소멸되는지 여부에는 영향을 미치지 않습니다. +provider가 다시 계산될 때 상태는 항상 소멸(destroyed)됩니다. +::: + +:::caution +provider가 매개변수를 받을 때 자동 폐기를 활성화하는 것이 좋습니다. +그렇지 않으면 매개변수 조합당 하나의 상태가 생성되어 메모리 누수가 발생할 수 있기 때문입니다. +::: + +## 상태 폐기(disposal)에 대한 반응 + +Riverpod에는 상태를 파기하는 몇 가지 기본 제공 방법이 있습니다: + +- provider가 더 이상 사용되지 않고 "auto dispose" 모드에 있는 경우(자세한 내용은 나중에 설명). + 이 경우 provider와 관련된 모든 상태가 소멸됩니다. +- `ref.watch`와 같이 provider가 다시 계산됩니다. + 이 경우 이전 상태가 폐기되고 새 상태가 생성됩니다. + +두 경우 모두. 이 경우 몇 가지 로직을 실행하고 싶을 수 있습니다. +이는 `ref.onDispose`로 가능합니다. 이 메서드를 사용하면 상태가 소멸될 때마다 리스너를 등록할 수 있습니다. + +예를 들어, 이 메서드를 사용해 활성화된 `StreamController`를 닫을 수 있습니다: + + + +:::caution +`ref.onDispose`의 콜백은 부작용을 유발하지 않아야 합니다. +`onDispose` 내에서 프로바이더를 수정하면 예기치 않은 동작이 발생할 수 있습니다. +::: + +:::info +다음과 같은 다른 유용한 수명 주기(life-cycles)가 있습니다: + +- provider의 마지막 리스너가 제거될 때 호출되는 `ref.onCancel`. +- `onCancel`이 호출된 후 새 리스너가 추가될 때 호출되는 `ref.onResume`. + +::: + +:::info +`ref.onDispose`는 원하는 횟수만큼 호출할 수 있습니다. +provider의 Disposable 객체당 한 번씩 호출해도 됩니다. +이렇게 하면 물건을 버리는 것(dispose of something)을 잊어버렸을 때 쉽게 알아차릴 수 있습니다. +::: + +## `ref.invalidate`를 사용하여 provider를 수동으로 강제 삭제하기 + +때로는 provider를 강제로 파괴하고 싶을 때가 있습니다. +다른 provider나 위젯에서 호출할 수 있는 `ref.invalidate`를 사용하면 이 작업을 수행할 수 있습니다. + +`ref.invalidate`를 사용하면 현재 provider 상태가 파괴됩니다. +그러면 두 가지 결과가 발생할 수 있습니다: + +- provider가 청취되고 있으면 새 상태가 생성됩니다. +- provider를 청취되고 있지 않으면 provider가 완전히 소멸됩니다. + + + +:::info +provider가 `ref.invalidateSelf`를 사용하여 스스로 무효화할 수 있습니다. +이 경우 항상 새로운 상태가 생성됩니다. +::: + +:::tip +매개변수를 수신하는 provider를 무효화하려고 할 때, +특정 매개변수 조합 하나를 무효화하거나 모든 매개변수 조합을 한 번에 무효화할 수 있습니다: + + +::: + +## `ref.keepAlive`를 사용하여 폐기(disposal)를 조정하기 + +위에서 언급했듯이 자동 폐기를 사용하도록 설정하면 provider에 전체 프레임에 대한 리스너가 없는 경우 상태가 삭제됩니다. + +하지만 이 동작을 보다 세밀하게 제어하고 싶을 수도 있습니다. +예를 들어, 성공한 네트워크 요청의 상태는 유지하되 실패한 요청은 캐시하지 않으려 할 수 있습니다. + +이는 자동 폐기를 활성화한 후 `ref.keepAlive`를 사용하면 가능합니다. +이 함수를 사용하면 상태의 자동 폐기를 중지하는 _시점_을 결정할 수 있습니다. + + + +:::note +provider가 다시 계산되면 자동 폐기가 다시 활성화됩니다. + +`ref.keepAlive`의 반환 값을 사용하여 자동 폐기로 되돌릴 수도 있습니다. +::: + +## 예: 특정 시간 동안 상태를 살아있게 유지하기 + +현재 Riverpod은 특정 시간 동안 상태를 유지하는 내장된 방법을 제공하지 않습니다. +하지만 지금까지 살펴본 도구를 사용하면 이러한 기능을 쉽게 구현하고 재사용할 수 있습니다. + +`Timer` + `ref.keepAlive`를 사용하면 특정 시간 동안 상태를 유지할 수 있습니다. +이 로직을 재사용할 수 있게 하려면 확장 메서드(extension method)로 구현하면 됩니다: + + + +그러면 이렇게 사용할 수 있습니다: + + + +이 로직은 필요에 맞게 조정할 수 있습니다. +예를 들어 `ref.onCancel`/`ref.onResume`을 사용하여 특정 시간 동안 공급자가 수신 대기하지 않은 경우에만 상태를 삭제할 수 있습니다. diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/cache_for_extension.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/cache_for_extension.dart new file mode 100644 index 000000000..6ea5d7fef --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/cache_for_extension.dart @@ -0,0 +1,17 @@ +import 'dart:async'; + +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +/* SNIPPET START */ +extension CacheForExtension on AutoDisposeRef { + /// [duration]동안 공급자를 살아있게 유지합니다. + void cacheFor(Duration duration) { + // 상태가 파괴되는 것을 즉시 방지합니다. + final link = keepAlive(); + // 기간이 경과하면 자동 폐기를 다시 활성화합니다. + final timer = Timer(duration, link.close); + + // 선택 사항: provider가 다시 계산되면(예: ref.watch 사용) 보류 중인 타이머를 취소합니다. + onDispose(timer.cancel); + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/cache_for_usage/codegen.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/cache_for_usage/codegen.dart new file mode 100644 index 000000000..db007483d --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/cache_for_usage/codegen.dart @@ -0,0 +1,17 @@ +// ignore_for_file: unused_local_variable + +import 'package:http/http.dart' as http; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../cache_for_extension.dart'; + +part 'codegen.g.dart'; + +/* SNIPPET START */ +@riverpod +Future example(ExampleRef ref) async { + /// 5분 동안 상태를 유지합니다. + ref.cacheFor(const Duration(minutes: 5)); + + return http.get(Uri.https('example.com')); +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/cache_for_usage/codegen.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/cache_for_usage/codegen.g.dart new file mode 100644 index 000000000..eb2dcfb0d --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/cache_for_usage/codegen.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$exampleHash() => r'3ff29b1cd8fa864286a2a04e39adf1c8589b4275'; + +/// See also [example]. +@ProviderFor(example) +final exampleProvider = AutoDisposeFutureProvider.internal( + example, + name: r'exampleProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$exampleHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ExampleRef = AutoDisposeFutureProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/cache_for_usage/index.ts b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/cache_for_usage/index.ts new file mode 100644 index 000000000..9d50c6614 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/cache_for_usage/index.ts @@ -0,0 +1,4 @@ +import raw from '!!raw-loader!./raw.dart'; +import codegen from '!!raw-loader!./codegen.dart'; + +export default { raw, codegen }; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/cache_for_usage/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/cache_for_usage/raw.dart new file mode 100644 index 000000000..2a2cf358a --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/cache_for_usage/raw.dart @@ -0,0 +1,15 @@ +// ignore_for_file: unused_local_variable + +import 'package:http/http.dart' as http; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../cache_for_extension.dart'; + +/* SNIPPET START */ +final provider = FutureProvider.autoDispose((ref) async { + /// Keeps the state alive for 5 minutes + ref.cacheFor(const Duration(minutes: 5)); + + return http.get(Uri.https('example.com')); +}); +/* SNIPPET END */ diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/codegen_keep_alive.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/codegen_keep_alive.dart new file mode 100644 index 000000000..d3a3536c4 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/codegen_keep_alive.dart @@ -0,0 +1,11 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen_keep_alive.g.dart'; + +/* SNIPPET START */ +// 어노테이션에 "keepAlive"를 지정하여 +// 자동 상태 소멸을 비활성화할 수 있습니다. +@Riverpod(keepAlive: true) +int example(ExampleRef ref) { + return 0; +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/codegen_keep_alive.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/codegen_keep_alive.g.dart new file mode 100644 index 000000000..9f567e514 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/codegen_keep_alive.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen_keep_alive.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$exampleHash() => r'78f9426f6cbda80564387a9db8cd02368d890a85'; + +/// See also [example]. +@ProviderFor(example) +final exampleProvider = Provider.internal( + example, + name: r'exampleProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$exampleHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ExampleRef = ProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/invalidate_example.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/invalidate_example.dart new file mode 100644 index 000000000..09390ff9e --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/invalidate_example.dart @@ -0,0 +1,23 @@ +// ignore_for_file: use_key_in_widget_constructors + +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +// 자동 상태 소멸을 활성화하려면 autoDispose를 지정할 수 있습니다. +final someProvider = Provider.autoDispose((ref) { + return 0; +}); + +/* SNIPPET START */ +class MyWidget extends ConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + return ElevatedButton( + onPressed: () { + // 클릭 시 공급자를 삭제합니다. + ref.invalidate(someProvider); + }, + child: const Text('dispose a provider'), + ); + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/invalidate_family_example/codegen.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/invalidate_family_example/codegen.dart new file mode 100644 index 000000000..9ce0d96b8 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/invalidate_family_example/codegen.dart @@ -0,0 +1,24 @@ +// ignore_for_file: unused_local_variable + +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +late WidgetRef ref; + +/* SNIPPET START */ +@riverpod +String label(LabelRef ref, String userName) { + return 'Hello $userName'; +} + +// ... + +void onTap() { + // 이 provider의 가능한 모든 매개변수 조합을 무효화합니다. + ref.invalidate(labelProvider); + // 특정 조합만 무효화 + ref.invalidate(labelProvider('John')); +} +/* SNIPPET END */ diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/invalidate_family_example/codegen.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/invalidate_family_example/codegen.g.dart new file mode 100644 index 000000000..ffe8961bd --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/invalidate_family_example/codegen.g.dart @@ -0,0 +1,159 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$labelHash() => r'20aa8ce0231205540f466f91259732bd86953c64'; + +/// Copied from Dart SDK +class _SystemHash { + _SystemHash._(); + + static int combine(int hash, int value) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + value); + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); + return hash ^ (hash >> 6); + } + + static int finish(int hash) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + // ignore: parameter_assignments + hash = hash ^ (hash >> 11); + return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); + } +} + +/// See also [label]. +@ProviderFor(label) +const labelProvider = LabelFamily(); + +/// See also [label]. +class LabelFamily extends Family { + /// See also [label]. + const LabelFamily(); + + /// See also [label]. + LabelProvider call( + String userName, + ) { + return LabelProvider( + userName, + ); + } + + @override + LabelProvider getProviderOverride( + covariant LabelProvider provider, + ) { + return call( + provider.userName, + ); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'labelProvider'; +} + +/// See also [label]. +class LabelProvider extends AutoDisposeProvider { + /// See also [label]. + LabelProvider( + String userName, + ) : this._internal( + (ref) => label( + ref as LabelRef, + userName, + ), + from: labelProvider, + name: r'labelProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$labelHash, + dependencies: LabelFamily._dependencies, + allTransitiveDependencies: LabelFamily._allTransitiveDependencies, + userName: userName, + ); + + LabelProvider._internal( + super._createNotifier, { + required super.name, + required super.dependencies, + required super.allTransitiveDependencies, + required super.debugGetCreateSourceHash, + required super.from, + required this.userName, + }) : super.internal(); + + final String userName; + + @override + Override overrideWith( + String Function(LabelRef provider) create, + ) { + return ProviderOverride( + origin: this, + override: LabelProvider._internal( + (ref) => create(ref as LabelRef), + from: from, + name: null, + dependencies: null, + allTransitiveDependencies: null, + debugGetCreateSourceHash: null, + userName: userName, + ), + ); + } + + @override + AutoDisposeProviderElement createElement() { + return _LabelProviderElement(this); + } + + @override + bool operator ==(Object other) { + return other is LabelProvider && other.userName == userName; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, userName.hashCode); + + return _SystemHash.finish(hash); + } +} + +mixin LabelRef on AutoDisposeProviderRef { + /// The parameter `userName` of this provider. + String get userName; +} + +class _LabelProviderElement extends AutoDisposeProviderElement + with LabelRef { + _LabelProviderElement(super.provider); + + @override + String get userName => (origin as LabelProvider).userName; +} +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/invalidate_family_example/index.ts b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/invalidate_family_example/index.ts new file mode 100644 index 000000000..9d50c6614 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/invalidate_family_example/index.ts @@ -0,0 +1,4 @@ +import raw from '!!raw-loader!./raw.dart'; +import codegen from '!!raw-loader!./codegen.dart'; + +export default { raw, codegen }; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/invalidate_family_example/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/invalidate_family_example/raw.dart new file mode 100644 index 000000000..1f3164bfb --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/invalidate_family_example/raw.dart @@ -0,0 +1,20 @@ +// ignore_for_file: unused_local_variable + +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +late WidgetRef ref; + +/* SNIPPET START */ +final provider = Provider.autoDispose.family((ref, name) { + return 'Hello $name'; +}); + +// ... + +void onTap() { + // Invalidate all possible parameter combinations of this provider. + ref.invalidate(provider); + // Invalidate a specific combination only + ref.invalidate(provider('John')); +} +/* SNIPPET END */ diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/keep_alive/codegen.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/keep_alive/codegen.dart new file mode 100644 index 000000000..1828451dc --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/keep_alive/codegen.dart @@ -0,0 +1,20 @@ +// ignore_for_file: unused_local_variable + +import 'package:http/http.dart' as http; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +/* SNIPPET START */ +@riverpod +Future example(ExampleRef ref) async { + final response = await http.get(Uri.parse('https://example.com')); + // 요청이 성공적으로 완료된 후에만 프로바이더를 살아있게 유지합니다. + // 요청이 실패한 경우(그리고 throw된 경우), 공급자에 청취를 중단하면 상태가 소멸됩니다. + ref.keepAlive(); + + // `link`를 사용하여 자동 폐기 동작을 복원할 수 있습니다: + // link.close(); + + return response.body; +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/keep_alive/codegen.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/keep_alive/codegen.g.dart new file mode 100644 index 000000000..dfe5d12cc --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/keep_alive/codegen.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$exampleHash() => r'4fa856c55e84da9525dcecfab1c897e61456325b'; + +/// See also [example]. +@ProviderFor(example) +final exampleProvider = AutoDisposeFutureProvider.internal( + example, + name: r'exampleProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$exampleHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ExampleRef = AutoDisposeFutureProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/keep_alive/index.ts b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/keep_alive/index.ts new file mode 100644 index 000000000..9d50c6614 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/keep_alive/index.ts @@ -0,0 +1,4 @@ +import raw from '!!raw-loader!./raw.dart'; +import codegen from '!!raw-loader!./codegen.dart'; + +export default { raw, codegen }; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/keep_alive/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/keep_alive/raw.dart new file mode 100644 index 000000000..f0fc6a3ba --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/keep_alive/raw.dart @@ -0,0 +1,19 @@ +// ignore_for_file: unused_local_variable + +import 'package:http/http.dart' as http; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +/* SNIPPET START */ +final provider = FutureProvider.autoDispose((ref) async { + final response = await http.get(Uri.parse('https://example.com')); + // We keep the provider alive only after the request has successfully completed. + // If the request failed (and threw an exception), then when the provider stops being + // listened to, the state will be destroyed. + final link = ref.keepAlive(); + + // We can use the `link` to restore the auto-dispose behavior with: + // link.close(); + + return response.body; +}); +/* SNIPPET END */ diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/on_dispose_example/codegen.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/on_dispose_example/codegen.dart new file mode 100644 index 000000000..013988467 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/on_dispose_example/codegen.dart @@ -0,0 +1,23 @@ +// ignore_for_file: unused_local_variable + +import 'dart:async'; + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +@riverpod +int other(OtherRef ref) => 0; + +/* SNIPPET START */ +@riverpod +Stream example(ExampleRef ref) { + final controller = StreamController(); + + // 상태가 소멸되면 StreamController를 닫습니다. + ref.onDispose(controller.close); + + // TO-DO: StreamController의 값들을 푸시합니다. + return controller.stream; +} +/* SNIPPET END */ diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/on_dispose_example/codegen.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/on_dispose_example/codegen.g.dart new file mode 100644 index 000000000..5ae67d1d2 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/on_dispose_example/codegen.g.dart @@ -0,0 +1,40 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$otherHash() => r'b23696171643dfbab23d167ed9b5ab0639e6a86c'; + +/// See also [other]. +@ProviderFor(other) +final otherProvider = AutoDisposeProvider.internal( + other, + name: r'otherProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$otherHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef OtherRef = AutoDisposeProviderRef; +String _$exampleHash() => r'29f92958e0d0e3f13ac033e92cd2e4072339c7db'; + +/// See also [example]. +@ProviderFor(example) +final exampleProvider = AutoDisposeStreamProvider.internal( + example, + name: r'exampleProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$exampleHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ExampleRef = AutoDisposeStreamProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/on_dispose_example/index.ts b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/on_dispose_example/index.ts new file mode 100644 index 000000000..9d50c6614 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/on_dispose_example/index.ts @@ -0,0 +1,4 @@ +import raw from '!!raw-loader!./raw.dart'; +import codegen from '!!raw-loader!./codegen.dart'; + +export default { raw, codegen }; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/on_dispose_example/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/on_dispose_example/raw.dart new file mode 100644 index 000000000..4e4940777 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/on_dispose_example/raw.dart @@ -0,0 +1,17 @@ +// ignore_for_file: unused_local_variable + +import 'dart:async'; + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +/* SNIPPET START */ +final provider = StreamProvider((ref) { + final controller = StreamController(); + + // When the state is destroyed, we close the StreamController. + ref.onDispose(controller.close); + + // TO-DO: Push some values in the StreamController + return controller.stream; +}); +/* SNIPPET END */ diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/raw_auto_dispose.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/raw_auto_dispose.dart new file mode 100644 index 000000000..e6746e2a0 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/auto_dispose/raw_auto_dispose.dart @@ -0,0 +1,7 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +/* SNIPPET START */ +// 자동 상태 소멸을 활성화하려면 autoDispose를 지정할 수 있습니다. +final provider = Provider.autoDispose((ref) { + return 0; +}); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests.mdx b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests.mdx new file mode 100644 index 000000000..ede4c45d0 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests.mdx @@ -0,0 +1,126 @@ +--- +title: 요청 결합하기 +version: 1 +--- + +import { Link } from "../../../../../src/components/Link"; +import { AutoSnippet } from "../../../../../src/components/CodeSnippet"; +import functionalRef from "./combining_requests/functional_ref"; +import notifierRef from "./combining_requests/notifier_ref"; +import watchExample from "./combining_requests/watch_example"; +import watchPlacement from "./combining_requests/watch_placement"; +import listenExample from "./combining_requests/listen_example"; +import readExample from './combining_requests/read_example' + +지금까지는 요청이 서로 독립적인 경우만 보았습니다. +하지만 일반적인 사용 사례는 다른 요청의 결과에 따라 요청을 트리거해야 하는 경우입니다. + +이를 위해 provider의 결과를 다른 provider에게 매개변수(parameter)로 전달하여 메커니즘을 _사용할 수_ 있습니다. + +하지만 이 접근 방식에는 몇 가지 단점이 있습니다: + +- 구현 세부 정보가 유출(leaks)됩니다. + 이제 UI는 다른 provider가 사용하는 모든 providers에 대해 알아야 합니다. +- 매개변수(parameter)가 변경될 때마다 완전히 새로운 상태가 만들어집니다. + 매개변수를 전달하면 매개변수가 변경될 때 이전 상태를 유지할 수 있는 방법이 없습니다. +- 요청을 결합(combining requests)하기가 더 어려워집니다. +- 따라서 툴링의 유용성이 떨어집니다. 개발 도구는 providers 간의 관계에 대해 알 수 없습니다. + +이를 개선하기 위해 Riverpod은 요청을 결합하는 다른 접근 방식을 제공합니다. + +## 기본 사항: "ref" 획득하기 + +요청을 결합하는 모든 방법에는 한 가지 공통점이 있습니다. 모두 `Ref` 객체를 기반으로 한다는 점입니다. + +`Ref` 객체는 모든 providers가 접근할 수 있는 객체입니다. +이 객체는 다양한 라이프사이클 리스너에 대한 액세스 권한을 부여할 뿐만 아니라, providers를 결합하는 다양한 메서드도 제공합니다. + +`Ref`를 얻을 수 있는 위치는 provider 타입에 따라 다릅니다. + +함수형 provider의 경우, `Ref`는 provider의 함수에 매개변수로 전달됩니다: + + + +클래스 변형에서 `Ref`는 Notifier 클래스의 속성입니다: + + + +## ref를 사용하여 provider를 읽습니다. + +## `ref.watch` 메소드 + +이제 `Ref`를 얻었으므로 이를 사용하여 요청을 결합할 수 있습니다. +이를 수행하는 주된 방법은 `ref.watch`를 사용하는 것입니다. +일반적으로 유지 관리가 더 쉽기 때문에 일반적으로 다른 옵션보다 `ref.watch`를 사용할 수 있도록 코드를 설계하는 것이 좋습니다. + + +`ref.watch` 메서드는 provider를 받아 현재 상태를 반환합니다. +그러면 리스닝된 provider가 변경될 때마다 provider가 무효화(invalidated)되고 다음 프레임 또는 다음 읽기(read) 시 다시 빌드됩니다. + +`ref.watch`를 사용하면 로직이 "reactive"이면서 "declarative"이게 됩니다. +즉, 필요할 때 로직이 자동으로 다시 계산(recompute)된다는 뜻입니다. +그리고 업데이트 메커니즘이 'on change'와 같은 부작용(side-effects)에 의존하지 않습니다. +이는 StatelessWidgets의 작동 방식과 유사합니다. + +예를 들어 사용자의 위치를 수신하는 provider를 정의할 수 있습니다. +그런 다음 이 위치를 사용하여 사용자 근처의 레스토랑 목록을 가져올 수 있습니다. + + + +:::info +수신 중인 provider가 변경되어 요청이 다시 계산되면 새 요청이 완료될 때까지 이전 상태가 유지됩니다. +동시에 요청이 보류(pending)되는 동안 "isLoading" 및 "isReloading" 플래그가 설정됩니다. + +이를 통해 UI에 이전 상태 또는 로딩 표시기를 표시하거나 둘 다 표시할 수 있습니다. +::: + +:::info +`ref.watch(locationProvider)` 대신 `ref.watch(locationProvider.future)`를 사용한 것을 주목하세요. +`locationProvider`가 비동기적이기 때문입니다. 따라서 초기 값을 사용할 수 있을 때까지 기다려야 합니다. + +이 `.future`를 생략하면 `locationProvider`의 현재 상태에 대한 스냅샷인 `AsyncValue`를 받게 됩니다. +하지만 아직 사용할 수 있는 위치가 없다면 아무 것도 할 수 없습니다. +::: + +:::caution +"명령형 문법(imperatively)"으로 실행되는 코드 내에서 `ref.watch`를 호출하는 것은 나쁜 습관으로 간주됩니다. +이는 provider의 빌드 단계에서 실행되지 않을 가능성이 있는 모든 코드를 의미합니다. +여기에는 "listener" 콜백(callbacks)이나 Notifier의 메서드가 포함됩니다: + + +::: + +## `ref.listen`/`listenSelf` 메소드 + +`ref.listen` 메서드는 `ref.watch`의 대안입니다. +이 메서드는 기존의 "listen"/"addListener" 메서드와 유사합니다. +이 메서드는 provider와 callback을 받으며, provider의 콘텐츠가 변경될 때마다 해당 callback을 호출합니다. + +`ref.listen` 대신 `ref.watch`를 사용할 수 있도록 코드를 리팩토링하는 것이 일반적으로 권장되는데, +전자는 명령형으로 인해 오류가 발생하기 쉽기 때문입니다. +하지만 `ref.listen`는 큰 리팩토링을 하지 않고도 빠른 로직을 추가하는 데 유용할 수 있습니다. + +`ref.watch` 예제를 다시 작성하여 `ref.listen`을 대신 사용할 수 있습니다. + + + +:::info +provider의 빌드 단계에서 `ref.listen`을 사용하는 것은 전적으로 안전합니다. +provider가 어떻게든 다시 계산되면 이전 리스너가 제거됩니다. + +또는 `ref.listen`의 반환 값을 사용하여 원할 때 리스너를 수동으로 제거할 수 있습니다. +::: + +## `ref.read` 메소드 + +마지막으로 사용할 수 있는 옵션은 `ref.read`입니다. +이 옵션은 provider의 현재 상태를 반환한다는 점에서 `ref.watch`와 유사합니다. +하지만 `ref.watch`와 달리 공급자를 수신(listen)하지 않습니다. + +따라서 `ref.read`는 Notifier의 메서드 내부와 같이 `ref.watch`를 사용할 수 없는 곳에서만 사용해야 합니다. + + + +:::caution +provider에서 `ref.read`를 사용할 때는 주의하세요. provider를 수신(listen)하지 않으므로, 해당 provider가 수신(listen)하지 않으면 상태(state)를 파괴(destroy할 수 있습니다. +::: diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/functional_ref/codegen.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/functional_ref/codegen.dart new file mode 100644 index 000000000..2d6aa1460 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/functional_ref/codegen.dart @@ -0,0 +1,18 @@ +// ignore_for_file: unused_local_variable + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +@riverpod +int other(OtherRef ref) => 0; + +/* SNIPPET START */ +@riverpod +int example(ExampleRef ref) { + // 다른 provider를 읽으려면 여기에서 "Ref"를 사용할 수 있습니다. + final otherValue = ref.watch(otherProvider); + + return 0; +} +/* SNIPPET END */ diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/functional_ref/codegen.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/functional_ref/codegen.g.dart new file mode 100644 index 000000000..d7bab87bb --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/functional_ref/codegen.g.dart @@ -0,0 +1,40 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$otherHash() => r'b23696171643dfbab23d167ed9b5ab0639e6a86c'; + +/// See also [other]. +@ProviderFor(other) +final otherProvider = AutoDisposeProvider.internal( + other, + name: r'otherProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$otherHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef OtherRef = AutoDisposeProviderRef; +String _$exampleHash() => r'4429d7d3bb2b31fea4cc42c2f2af02d3f3d10693'; + +/// See also [example]. +@ProviderFor(example) +final exampleProvider = AutoDisposeProvider.internal( + example, + name: r'exampleProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$exampleHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ExampleRef = AutoDisposeProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/functional_ref/index.ts b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/functional_ref/index.ts new file mode 100644 index 000000000..9d50c6614 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/functional_ref/index.ts @@ -0,0 +1,4 @@ +import raw from '!!raw-loader!./raw.dart'; +import codegen from '!!raw-loader!./codegen.dart'; + +export default { raw, codegen }; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/functional_ref/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/functional_ref/raw.dart new file mode 100644 index 000000000..19962d9cd --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/functional_ref/raw.dart @@ -0,0 +1,14 @@ +// ignore_for_file: unused_local_variable + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +final otherProvider = Provider((ref) => 0); + +/* SNIPPET START */ +final provider = Provider((ref) { + // "Ref" can be used here to read other providers + final otherValue = ref.watch(otherProvider); + + return 0; +}); +/* SNIPPET END */ diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/listen_example/codegen.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/listen_example/codegen.dart new file mode 100644 index 000000000..25c6a2f37 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/listen_example/codegen.dart @@ -0,0 +1,17 @@ +// ignore_for_file: unused_local_variable, avoid_print + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +final otherProvider = Provider((ref) => 0); + +/* SNIPPET START */ +@riverpod +int example(ExampleRef ref) { + ref.listen(otherProvider, (previous, next) { + print('Changed from: $previous, next: $next'); + }); + + return 0; +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/listen_example/codegen.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/listen_example/codegen.g.dart new file mode 100644 index 000000000..0e0b3489f --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/listen_example/codegen.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$exampleHash() => r'd614303f372e06e6ab96035affc4c07a53b28741'; + +/// See also [example]. +@ProviderFor(example) +final exampleProvider = AutoDisposeProvider.internal( + example, + name: r'exampleProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$exampleHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ExampleRef = AutoDisposeProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/listen_example/index.ts b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/listen_example/index.ts new file mode 100644 index 000000000..9d50c6614 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/listen_example/index.ts @@ -0,0 +1,4 @@ +import raw from '!!raw-loader!./raw.dart'; +import codegen from '!!raw-loader!./codegen.dart'; + +export default { raw, codegen }; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/listen_example/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/listen_example/raw.dart new file mode 100644 index 000000000..12b578dd0 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/listen_example/raw.dart @@ -0,0 +1,13 @@ +// ignore_for_file: unused_local_variable, avoid_print +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +final otherProvider = Provider((ref) => 0); + +/* SNIPPET START */ +final provider = Provider((ref) { + ref.listen(otherProvider, (previous, next) { + print('Changed from: $previous, next: $next'); + }); + + return 0; +}); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/notifier_ref/codegen.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/notifier_ref/codegen.dart new file mode 100644 index 000000000..1454617d1 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/notifier_ref/codegen.dart @@ -0,0 +1,21 @@ +// ignore_for_file: unused_local_variable + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +@riverpod +int other(OtherRef ref) => 0; + +/* SNIPPET START */ +@riverpod +class Example extends _$Example { + @override + int build() { + // 다른 provider를 읽으려면 여기에서 "Ref"를 사용할 수 있습니다. + final otherValue = ref.watch(otherProvider); + + return 0; + } +} +/* SNIPPET END */ diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/notifier_ref/codegen.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/notifier_ref/codegen.g.dart new file mode 100644 index 000000000..ca4e51873 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/notifier_ref/codegen.g.dart @@ -0,0 +1,40 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$otherHash() => r'b23696171643dfbab23d167ed9b5ab0639e6a86c'; + +/// See also [other]. +@ProviderFor(other) +final otherProvider = AutoDisposeProvider.internal( + other, + name: r'otherProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$otherHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef OtherRef = AutoDisposeProviderRef; +String _$exampleHash() => r'893db991b377b8e314e60c429043e5e81f1fd526'; + +/// See also [Example]. +@ProviderFor(Example) +final exampleProvider = AutoDisposeNotifierProvider.internal( + Example.new, + name: r'exampleProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$exampleHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$Example = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/notifier_ref/index.ts b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/notifier_ref/index.ts new file mode 100644 index 000000000..9d50c6614 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/notifier_ref/index.ts @@ -0,0 +1,4 @@ +import raw from '!!raw-loader!./raw.dart'; +import codegen from '!!raw-loader!./codegen.dart'; + +export default { raw, codegen }; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/notifier_ref/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/notifier_ref/raw.dart new file mode 100644 index 000000000..d292fea5e --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/notifier_ref/raw.dart @@ -0,0 +1,19 @@ +// ignore_for_file: unused_local_variable + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +final otherProvider = Provider((ref) => 0); + +/* SNIPPET START */ +final provider = NotifierProvider(MyNotifier.new); + +class MyNotifier extends Notifier { + @override + int build() { + // "Ref" can be used here to read other providers + final otherValue = ref.watch(otherProvider); + + return 0; + } +} +/* SNIPPET END */ diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/read_example/codegen.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/read_example/codegen.dart new file mode 100644 index 000000000..fc46f5927 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/read_example/codegen.dart @@ -0,0 +1,23 @@ +// ignore_for_file: unused_local_variable + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +final otherProvider = Provider((ref) => 0); + +/* SNIPPET START */ +@riverpod +class MyNotifier extends _$MyNotifier { + @override + int build() { + // Bad! 여기서는 reactive가 아니므로 'read'를 사용하지 마세요. + ref.read(otherProvider); + + return 0; + } + + void increment() { + ref.read(otherProvider); // 여기서 'read'를 사용해도 괜찮습니다. + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/read_example/codegen.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/read_example/codegen.g.dart new file mode 100644 index 000000000..9c655cd8a --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/read_example/codegen.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$myNotifierHash() => r'353efe22dd5a91b2d036286211ac9e60c9de5f6d'; + +/// See also [MyNotifier]. +@ProviderFor(MyNotifier) +final myNotifierProvider = + AutoDisposeNotifierProvider.internal( + MyNotifier.new, + name: r'myNotifierProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$myNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$MyNotifier = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/read_example/index.ts b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/read_example/index.ts new file mode 100644 index 000000000..9d50c6614 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/read_example/index.ts @@ -0,0 +1,4 @@ +import raw from '!!raw-loader!./raw.dart'; +import codegen from '!!raw-loader!./codegen.dart'; + +export default { raw, codegen }; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/read_example/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/read_example/raw.dart new file mode 100644 index 000000000..e269310eb --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/read_example/raw.dart @@ -0,0 +1,22 @@ +// ignore_for_file: unused_local_variable + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +final otherProvider = Provider((ref) => 0); + +/* SNIPPET START */ +final notifierProvider = NotifierProvider(MyNotifier.new); + +class MyNotifier extends Notifier { + @override + int build() { + // Bad! Do not use "read" here as it is not reactive + ref.read(otherProvider); + + return 0; + } + + void increment() { + ref.read(otherProvider); // Using "read" here is fine + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_example/codegen.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_example/codegen.dart new file mode 100644 index 000000000..2521721cb --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_example/codegen.dart @@ -0,0 +1,43 @@ +// ignore_for_file: unused_local_variable + +import 'dart:convert'; + +import 'package:http/http.dart' as http; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +final otherProvider = Provider((ref) => 0); + +const someStream = Stream<({double longitude, double latitude})>.empty(); + +/* SNIPPET START */ +@riverpod +Stream<({double longitude, double latitude})> location(LocationRef ref) { + // TO-DO: 현재 위치를 가져오는 Stream을 반환합니다. + return someStream; +} + +@riverpod +Future> restaurantsNearMe(RestaurantsNearMeRef ref) async { + // "ref.watch"를 사용하여 최신 위치를 가져옵니다. + // 공급자 뒤에 ".future"를 지정하면 코드가 적어도 하나의 위치를 사용할 수 있을 때까지 기다립니다. + final location = await ref.watch(locationProvider.future); + + // 이제 해당 위치를 기반으로 네트워크 요청을 할 수 있습니다. + // 예를 들어 Google 지도 API를 사용할 수 있습니다: + // https://developers.google.com/maps/documentation/places/web-service/search-nearby + final response = await http.get( + Uri.https('maps.googleapis.com', 'maps/api/place/nearbysearch/json', { + 'location': '${location.latitude},${location.longitude}', + 'radius': '1500', + 'type': 'restaurant', + 'key': '', + }), + ); + // JSON에서 레스토랑 이름 가져오기 + final json = jsonDecode(response.body) as Map; + final results = (json['results'] as List).cast>(); + return results.map((e) => e['name']! as String).toList(); +} +/* SNIPPET END */ diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_example/codegen.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_example/codegen.g.dart new file mode 100644 index 000000000..910b079a1 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_example/codegen.g.dart @@ -0,0 +1,44 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$locationHash() => r'22e666f1e1ce04ce03d8f8d5652e25b54c1d1af3'; + +/// See also [location]. +@ProviderFor(location) +final locationProvider = + AutoDisposeStreamProvider<({double longitude, double latitude})>.internal( + location, + name: r'locationProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$locationHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef LocationRef + = AutoDisposeStreamProviderRef<({double longitude, double latitude})>; +String _$restaurantsNearMeHash() => r'dd49cc1e6f16abb34dd15286d171e322c06b93b8'; + +/// See also [restaurantsNearMe]. +@ProviderFor(restaurantsNearMe) +final restaurantsNearMeProvider = + AutoDisposeFutureProvider>.internal( + restaurantsNearMe, + name: r'restaurantsNearMeProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$restaurantsNearMeHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef RestaurantsNearMeRef = AutoDisposeFutureProviderRef>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_example/index.ts b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_example/index.ts new file mode 100644 index 000000000..9d50c6614 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_example/index.ts @@ -0,0 +1,4 @@ +import raw from '!!raw-loader!./raw.dart'; +import codegen from '!!raw-loader!./codegen.dart'; + +export default { raw, codegen }; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_example/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_example/raw.dart new file mode 100644 index 000000000..f4e85466e --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_example/raw.dart @@ -0,0 +1,41 @@ +// ignore_for_file: unused_local_variable + +import 'dart:convert'; + +import 'package:http/http.dart' as http; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +final otherProvider = Provider((ref) => 0); + +const someStream = Stream<({double longitude, double latitude})>.empty(); + +/* SNIPPET START */ +final locationProvider = + StreamProvider<({double longitude, double latitude})>((ref) { + // TO-DO: Return a stream which obtains the current location + return someStream; +}); + +final restaurantsNearMeProvider = FutureProvider>((ref) async { + // We use "ref.watch" to obtain the latest location. + // By specifying that ".future" after the provider, our code will wait + // for at least one location to be available. + final location = await ref.watch(locationProvider.future); + + // We can now make a network request based on that location. + // For example, we could use the Google Map API: + // https://developers.google.com/maps/documentation/places/web-service/search-nearby + final response = await http.get( + Uri.https('maps.googleapis.com', 'maps/api/place/nearbysearch/json', { + 'location': '${location.latitude},${location.longitude}', + 'radius': '1500', + 'type': 'restaurant', + 'key': '', + }), + ); + // Obtain the restaurant names from the JSON + final json = jsonDecode(response.body) as Map; + final results = (json['results'] as List).cast>(); + return results.map((e) => e['name']! as String).toList(); +}); +/* SNIPPET END */ diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_placement/codegen.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_placement/codegen.dart new file mode 100644 index 000000000..736d1ecc7 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_placement/codegen.dart @@ -0,0 +1,37 @@ +// ignore_for_file: unused_local_variable + +import 'package:flutter/foundation.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +final otherProvider = Provider((ref) => 0); + +/* SNIPPET START */ +@riverpod +int example(ExampleRef ref) { + ref.watch(otherProvider); // Good! + ref.onDispose(() => ref.watch(otherProvider)); // Bad! + + final someListenable = ValueNotifier(0); + someListenable.addListener(() { + ref.watch(otherProvider); // Bad! + }); + + return 0; +} + +@riverpod +class MyNotifier extends _$MyNotifier { + @override + int build() { + ref.watch(otherProvider); // Good! + ref.onDispose(() => ref.watch(otherProvider)); // Bad! + + return 0; + } + + void increment() { + ref.watch(otherProvider); // Bad! + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_placement/codegen.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_placement/codegen.g.dart new file mode 100644 index 000000000..b1dcd7086 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_placement/codegen.g.dart @@ -0,0 +1,41 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$exampleHash() => r'd4d63f5cf1aaec5b7c6a19e6fee18ddf070147ec'; + +/// See also [example]. +@ProviderFor(example) +final exampleProvider = AutoDisposeProvider.internal( + example, + name: r'exampleProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$exampleHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ExampleRef = AutoDisposeProviderRef; +String _$myNotifierHash() => r'ad79fdb5b0e72a800fa03efc1e7157f0d1524844'; + +/// See also [MyNotifier]. +@ProviderFor(MyNotifier) +final myNotifierProvider = + AutoDisposeNotifierProvider.internal( + MyNotifier.new, + name: r'myNotifierProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$myNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$MyNotifier = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_placement/index.ts b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_placement/index.ts new file mode 100644 index 000000000..9d50c6614 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_placement/index.ts @@ -0,0 +1,4 @@ +import raw from '!!raw-loader!./raw.dart'; +import codegen from '!!raw-loader!./codegen.dart'; + +export default { raw, codegen }; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_placement/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_placement/raw.dart new file mode 100644 index 000000000..34fa8f6c9 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_placement/raw.dart @@ -0,0 +1,35 @@ +// ignore_for_file: unused_local_variable + +import 'package:flutter/foundation.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +final otherProvider = Provider((ref) => 0); + +/* SNIPPET START */ +final provider = Provider((ref) { + ref.watch(otherProvider); // Good! + ref.onDispose(() => ref.watch(otherProvider)); // Bad! + + final someListenable = ValueNotifier(0); + someListenable.addListener(() { + ref.watch(otherProvider); // Bad! + }); + + return 0; +}); + +final notifierProvider = NotifierProvider(MyNotifier.new); + +class MyNotifier extends Notifier { + @override + int build() { + ref.watch(otherProvider); // Good! + ref.onDispose(() => ref.watch(otherProvider)); // Bad! + + return 0; + } + + void increment() { + ref.watch(otherProvider); // Bad! + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/do_dont.mdx b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/do_dont.mdx new file mode 100644 index 000000000..acabeda84 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/do_dont.mdx @@ -0,0 +1,142 @@ +--- +title: 권장사항(DO/DON'T) +--- + +import { Link } from "../../../../../src/components/Link"; +import { AutoSnippet, When } from "../../../../../src/components/CodeSnippet"; + +코드의 유지보수성을 높이기 위해 Riverpod를 사용할 때 따라야 할 모범 사례 목록은 다음과 같습니다. + +이 목록은 전체 목록이 아니며 변경될 수 있습니다. +제안 사항이 있으면 언제든지 [이슈를 열어주세요](https://github.com/rrousselGit/riverpod/issues/new?assignees=rrousselGit&labels=documentation%2C+needs+triage&projects=&template=example_request.md&title=). + +이 목록의 항목은 특별한 순서가 정해져 있지 않습니다. + +이러한 권장 사항의 상당 부분은 [riverpod_lint](https://pub.dev/packages/riverpod_lint)로 적용할 수 있습니다. +설치 지침은 를 참조하세요. + +## AVOID 위젯에서 provider를 초기화하지 마세요 + +Providers는 스스로 초기화해야 합니다. +위젯과 같은 외부 요소에 의해 초기화되어서는 안 됩니다. + +그렇게 하지 않으면 경합 상태(race conditions) 및 예기치 않은 동작(unexpected behaviors)이 발생할 수 있습니다. + +**DON'T** + +```dart +class WidgetState extends State { + @override + void initState() { + super.initState(); + // Bad: provider가 자체적으로 초기화해야 합니다. + ref.read(provider).init(); + } +} +``` + +**CONSIDER** + +이 문제에 대한 "모든 것에 맞는" 해결책은 없습니다. +초기화 로직이 provider 외부의 요인에 따라 달라지는 경우 이러한 로직을 배치하는 올바른 위치는 탐색(navigation)을 트리거하는 버튼의 `onPressed` 메서드에 있는 경우가 많습니다: + +```dart +ElevatedButton( + onPressed: () { + ref.read(provider).init(); + Navigator.of(context).push(...); + }, + child: Text('Navigate'), +) +``` + +## AVOID 로컬 위젯 상태에 provider를 사용하지 마세요. + +Provider는 공유 비즈니스 상태(shared business state)용으로 설계되었습니다. +로컬 위젯 상태와 같은 용도로는 사용하기에 적합하지 않습니다: + +- 양식(form) 상태 저장 +- 현재 선택된 항목 +- 애니메이션 +- 일반적으로 Flutter가 "controller"(예: `TextEditingController`)로 처리하는 모든 것 + +로컬 위젯 상태를 처리하는 방법을 찾고 있다면 대신 [flutter_hooks](https://pub.dev/packages/flutter_hooks)를 사용하는 것이 좋습니다. + +이를 권장하지 않는 이유 중 하나는 이러한 상태가 경로로 범위가 지정(scoped to a route)되는 경우가 많기 때문입니다. +그렇게 하지 않으면 새 페이지가 이전 페이지의 상태를 재정의하기 때문에 앱의 뒤로 가기 버튼이 손상될 수 있습니다. + +## DON'T provider를 초기화하는 동안 부가작업(side effects)을 수행하지 마세요. + +provider는 일반적으로 "read" 작업을 나타내는 데 사용해야 합니다. +양식 제출과 같은 "write" 작업에는 사용하지 않아야 합니다. + +이러한 작업에 provider를 사용하면, 이전에 수행된 부가작업을 건너뛰는 등 예기치 않은 동작이 발생할 수 있습니다. + +부가작업의 로딩/오류 상태를 처리하는 방법을 알아보려면 을 참조하세요. + +**DON'T**: + +```dart +final submitProvider = FutureProvider((ref) async { + final formState = ref.watch(formState); + + // Bad: 공급자는 'write' 작업에 사용해서는 안 됩니다. + return http.post('https://my-api.com', body: formState.toJson()); +}); +``` + +## PREFER 정적으로 알려진 providers를 사용하여 ref.watch/read/listen (및 유사한 API)를 호출하세요. + +Riverpod은 린트 규칙을 활성화할 것을 강력히 권장합니다(`riverpod_lint`를 통해). +하지만 린트를 효과적으로 사용하려면 코드를 정적으로 분석할 수 있는 방식으로 작성해야 합니다. + +그렇게 하지 않으면 버그를 발견하기가 더 어려워지거나 린트로 오탐이 발생할 수 있습니다. + +**Do**: + +```dart +final provider = Provider((ref) => 42); + +... + +// OK provider가 정적으로 알려져 있으므로 확인 가능 +ref.watch(provider); +``` + +**Don't**: + +```dart +class Example extends ConsumerWidget { + Example({required this.provider}); + final Provider provider; + + @override + Widget build(context, ref) { + // Bad 정적 분석이 `provider`가 무엇인지 알 수 없으므로 나쁨 + ref.watch(provider); + } +} +``` + +## AVOID 동적으로 providers 생성하지 않기 + +공급자는 최상위 레벨(top-level)의 final 변수만 사용해야 합니다. + +**Do**: + +```dart +final provider = Provider((ref) => 'Hello world'); +``` + +**Don't**: + +```dart +class Example { + // 지원되지 않는 작업입니다. 메모리 누수 및 예기치 않은 동작이 발생할 수 있습니다. + final provider = Provider((ref) => 'Hello world'); +} +``` + +:::info +provider를 static final 변수로 생성하는 것은 허용되지만, 코드 생성기에서는 지원되지 않습니다. +::: diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/eager_initialization.mdx b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/eager_initialization.mdx new file mode 100644 index 000000000..c934ddb60 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/eager_initialization.mdx @@ -0,0 +1,58 @@ +--- +title: Providers의 빠른 초기화(Eager initialization) +version: 1 +--- + +import { Link } from "../../../../../src/components/Link"; +import { AutoSnippet } from "../../../../../src/components/CodeSnippet"; +import consumerExample from "!!raw-loader!./eager_initialization/consumer_example.dart"; +import asyncConsumerExample from "!!raw-loader!./eager_initialization/async_consumer_example.dart"; +import requireValue from "./eager_initialization/require_value"; + +모든 providers는 기본적으로 느리게(Lazy) 초기화됩니다. +즉, provider는 처음 사용될 때만 초기화됩니다. +이는 애플리케이션의 특정 부분에서만 사용되는 provider에 유용합니다. + +안타깝게도 Dart의 작동 방식(tree shaking 목적)으로 인해 provider를 이른 초기화(eagerly initialized)해야 하는 것으로 플래그를 지정할 수 있는 방법은 없습니다. +그러나 한 가지 해결책은 애플리케이션의 루트에서 초기화하려는 공급자를 강제로 읽는(read) 것입니다. + +권장되는 접근 방식은 `ProviderScope` 바로 아래에 배치된 소비자(Consumer)에서 provider를 단순히 "watch"하는 것입니다: + + + +:::note +초기화 소비자(initialization consumer)를 "MyApp" 또는 공개 위젯에 넣는 것을 고려해 보세요. +이렇게 하면 메인(main)에서 로직을 제거하여 테스트에서 동일한 동작을 사용할 수 있습니다. +::: + +### FAQ + +#### provider가 변경되면 전체 애플리케이션이 다시 빌드되지 않나요? + +아니요, 그렇지 않습니다. +위의 샘플에서 열심히 초기화를 담당하는 Consumer는 별도의 위젯으로, `child`을 반환하는 것 외에는 아무것도 하지 않습니다. + +핵심은 `MaterialApp` 자체를 인스턴스화하는 것이 아니라 `child`을 반환한다는 것입니다. +즉, `_EagerInitialization`이 리빌드되더라도 `child` 변수는 변경되지 않습니다. +그리고 위젯이 변경되지 않으면 Flutter는 위젯을 다시 빌드하지 않습니다. + +따라서 다른 위젯이 해당 provider를 수신하고 있지 않는 한 `_EagerInitialization`만 리빌드됩니다. + +#### 이 접근 방식을 사용하면 로딩 및 오류 상태를 어떻게 처리할 수 있나요? + +`Consumer`에서 일반적으로 처리하는 것처럼 로딩/오류 상태를 처리할 수 있습니다. +`_EagerInitialization`은 프로바이더가 "loading" 상태인지 확인하고, 만약 그렇다면 `child` 대신 `CircularProgressIndicator`를 반환할 수 있습니다: + + + +#### 로딩/오류 상태를 처리했지만 다른 Consumer는 여전히 AsyncValue를 받습니다! 모든 위젯에서 로딩/에러 상태를 처리하지 않아도 되는 방법이 있나요? + +provider가 `AsyncValue`를 노출하지 않도록 하는 대신 위젯이 `AsyncValue.requireValue`를 사용하도록 할 수 있습니다. +이렇게 하면 패턴 매칭을 수행하지 않고도 데이터를 읽을 수 있습니다. 그리고 버그가 발생하면 명확한 메시지와 함께 예외를 던집니다. + + + +:::note +이러한 경우 로딩/오류 상태를 노출하지 않는 방법(스코핑(scoping)에 의존)이 있지만 일반적으로 그렇게 하지 않는 것이 좋습니다. +두 개의 providers를 만들고 오버라이드를 사용하는 복잡성을 감수할 가치가 없습니다. +::: diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/eager_initialization/async_consumer_example.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/eager_initialization/async_consumer_example.dart new file mode 100644 index 000000000..17bc6de79 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/eager_initialization/async_consumer_example.dart @@ -0,0 +1,28 @@ +// ignore_for_file: unused_local_variable, use_key_in_widget_constructors, unused_element + +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +final myProvider = FutureProvider((ref) => 0); + +/* SNIPPET START */ +class _EagerInitialization extends ConsumerWidget { + const _EagerInitialization({required this.child}); + final Widget child; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final result = ref.watch(myProvider); + + // 오류 상태 및 로딩 상태 처리 + if (result.isLoading) { + return const CircularProgressIndicator(); + } else if (result.hasError) { + return const Text('Oopsy!'); + } + + return child; + } +} +/* SNIPPET END */ + diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/eager_initialization/consumer_example.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/eager_initialization/consumer_example.dart new file mode 100644 index 000000000..e9df84f4b --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/eager_initialization/consumer_example.dart @@ -0,0 +1,36 @@ +// ignore_for_file: unused_local_variable, use_key_in_widget_constructors + +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +final myProvider = Provider((ref) => 0); + +/* SNIPPET START */ +void main() { + runApp(ProviderScope(child: MyApp())); +} + +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return const _EagerInitialization( + // TODO: 여기에서 앱 렌더링 + child: MaterialApp(), + ); + } +} + +class _EagerInitialization extends ConsumerWidget { + const _EagerInitialization({required this.child}); + final Widget child; + + @override + Widget build(BuildContext context, WidgetRef ref) { + // providers를 감시하여 이른 초기화(Eagerly initialize)합니다. + // "watch"를 사용하면 provider가 폐기(disposed)되지 않고 계속 살아 있습니다. + ref.watch(myProvider); + return child; + } +} +/* SNIPPET END */ + diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/eager_initialization/require_value/codegen.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/eager_initialization/require_value/codegen.dart new file mode 100644 index 000000000..7025bd12d --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/eager_initialization/require_value/codegen.dart @@ -0,0 +1,24 @@ +// ignore_for_file: unused_local_variable, use_key_in_widget_constructors + +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +/* SNIPPET START */ +// 이른 초기화된 provider +@riverpod +Future example(ExampleRef ref) async => 'Hello world'; + +class MyConsumer extends ConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + final result = ref.watch(exampleProvider); + + /// provider가 올바르게 초기화되었다면, + /// "requireValue"로 데이터를 직접 읽을 수 있습니다. + return Text(result.requireValue); + } +} +/* SNIPPET END */ diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/eager_initialization/require_value/codegen.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/eager_initialization/require_value/codegen.g.dart new file mode 100644 index 000000000..739f3ea63 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/eager_initialization/require_value/codegen.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$exampleHash() => r'd421d08db0ee9d10af5521159561135d8c5fa57c'; + +/// See also [example]. +@ProviderFor(example) +final exampleProvider = AutoDisposeFutureProvider.internal( + example, + name: r'exampleProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$exampleHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ExampleRef = AutoDisposeFutureProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/eager_initialization/require_value/index.ts b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/eager_initialization/require_value/index.ts new file mode 100644 index 000000000..9d50c6614 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/eager_initialization/require_value/index.ts @@ -0,0 +1,4 @@ +import raw from '!!raw-loader!./raw.dart'; +import codegen from '!!raw-loader!./codegen.dart'; + +export default { raw, codegen }; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/eager_initialization/require_value/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/eager_initialization/require_value/raw.dart new file mode 100644 index 000000000..0a7b0580b --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/eager_initialization/require_value/raw.dart @@ -0,0 +1,20 @@ +// ignore_for_file: unused_local_variable, use_key_in_widget_constructors + +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +/* SNIPPET START */ +// An eagerly initialized provider. +final exampleProvider = FutureProvider((ref) async => 'Hello world'); + +class MyConsumer extends ConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + final result = ref.watch(exampleProvider); + + /// If the provider was correctly eagerly initialized, then we can + /// directly read the data with "requireValue". + return Text(result.requireValue); + } +} +/* SNIPPET END */ diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/faq.mdx b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/faq.mdx new file mode 100644 index 000000000..a7c0a8d25 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/faq.mdx @@ -0,0 +1,157 @@ +--- +title: 자주 묻는 질문(FAQ) +--- + +import { Link } from "../../../../../src/components/Link"; +import { AutoSnippet, When } from "../../../../../src/components/CodeSnippet"; + +다음은 커뮤니티에서 자주 묻는 질문입니다: + +## `ref.refresh`와 `ref.invalidate`의 차이점은 무엇인가요? + +`ref`에는 provider가 강제로 재계산(recompute)하도록 하는 두 가지 메서드가 있다는 것을 알고 어떻게 다른지 궁금하셨을 것입니다. + +생각보다 간단합니다. `ref.refresh`는 `invalidate` + `read`를 위한 구문 설탕일 뿐입니다: + +```dart +T refresh(provider) { + invalidate(provider); + return read(provider); +} +``` + +provider를 다시 계산한 후 새 값을 신경 쓰지 않는다면 `invalidate`를 사용면 됩니다. +그 경우 대신 `refresh`를 사용하십시오. + +:::info +이 로직은 린트 규칙을 통해 자동으로 적용됩니다. +반환된 값을 사용하지 않고 `ref.refresh`를 사용하려고 하면 경고가 표시됩니다. +::: + +동작의 주요 차이점은 무효화(invalidating) 직후에 공급자를 읽으면 공급자가 **즉시(immediately)** 재계산(recomputes)한다는 것입니다. +반면, `invalidate`를 호출하고 바로 읽지 않으면 업데이트가 _나중에(later)_ 트리거됩니다. + +이 "나중에(later)" 업데이트는 일반적으로 다음 프레임이 시작될 때 이루어집니다. +그러나 현재 수신 중(listened)이 아닌 provider가 무효화되면, 다시 수신(listen)할 때까지 업데이트되지 않습니다. + +## Ref와 WidgetRef 사이에 공유 인터페이스가 없는 이유는 무엇인가요? + +Riverpod은 `Ref`와 `WidgetRef`를 자발적으로 분리합니다. +이는 조건부로 둘 중 하나에 의존하는 코드를 작성하지 않기 위해 의도적으로 수행됩니다. + +한 가지 문제는 `Ref`와 `WidgetRef`가 비슷해 보이지만 미묘한 차이가 있다는 것입니다. +둘 다에 의존하는 코드는 발견하기 어려운 방식으로 불안정할 수 있습니다. + +동시에 `WidgetRef`에 의존하는 것은 `BuildContext`에 의존하는 것과 같습니다. +이는 사실상 UI 레이어에 로직을 넣는 것이므로 권장하지 않습니다. + +--- + +이러한 코드는 항상 `Ref`를 사용하도록 리팩터링해야 합니다. + +이 문제에 대한 해결책은 일반적으로 로직을 `Notifier`로 옮긴 다음( 참조), 로직을 해당 `Notifier`의 메서드가 되도록 하는 것입니다. + +이렇게 하면 위젯이 이 로직을 호출하고자 할 때 다음과 같은 내용을 작성할 수 있습니다: + +```dart +ref.read(yourNotifierProvider.notifier).yourMethod(); +``` + +`yourMethod`는 다른 제공자와 상호작용하기 위해 `Notifier`의 `Ref`를 사용합니다. + +## 원시(raw) StatelessWidget을 사용하는 대신 ConsumerWidget을 확장(extend)해야 하는 이유는 무엇인가요? + +이는 `InheritedWidget` API의 안타까운 제한 때문입니다. + +몇 가지 문제가 있습니다: + +- `InheritedWidget`으로 "on change" 리스너를 구현할 수 없습니다. + 즉, `ref.listen`과 같은 것을 `BuildContext`와 함께 사용할 수 없습니다. + + `State.didChangeDependencies`가 가장 비슷하지만 신뢰할 수 없습니다. + 한 가지 문제는 특히 위젯 트리가 GlobalKey를 사용하는 경우(일부 Flutter 위젯은 이미 내부적으로 그렇게 하고 있습니다) 종속성이 변경되지 않더라도 수명 주기가 트리거될 수 있다는 것입니다. + +- `InheritedWidget`을 수신하는 위젯은 수신이 중단되지 않습니다. + 이는 일반적으로 "테마" 또는 "미디어 쿼리"와 같은 순수한 메타데이터의 경우 괜찮습니다. + + 비즈니스 로직의 경우 이는 문제가 됩니다. + 페이지가 지정된 API를 표현하기 위해 provider를 사용한다고 가정해 봅시다. + 페이지 오프셋이 변경되면 위젯이 이전에 표시된 페이지를 계속 수신하고 싶지 않을 것입니다. + +- `InheritedWidget`은 위젯이 언제 청취(listening)를 중단하는지 추적할 수 있는 방법이 없습니다. + Riverpod은 때때로 공급자가 수신 대기 중인지 여부를 추적하는 데 의존합니다. + +이 기능은 자동 폐기 메커니즘(auto dispose mechanism)과 provider에게 인수를 전달하는 기능 모두에 매우 중요합니다. +이러한 기능들이 Riverpod를 강력하게 만드는 원동력입니다. + +먼 미래에는 이러한 문제가 해결될 수도 있습니다. 이 경우 Riverpod는 `Ref` 대신 `BuildContext`를 사용하도록 마이그레이션할 것입니다. +이렇게 되면 `ConsumerWidget` 대신 `StatelessWidget`을 사용할 수 있게 됩니다. +하지만 그건 다음 기회에! + +## hooks_riverpod이 flutter_hooks을 export하지 않는 이유는? + +이는 올바른 버전 관리 관행을 존중하기 위한 것입니다. + +`hooks_riverpod`은 `flutter_hooks` 없이 사용할 수 없지만, 두 패키지는 독립적으로 버전이 관리됩니다. +한 쪽에서는 변경 사항이 발생해도 다른 쪽에서는 발생하지 않을 수 있습니다. + +## Riverpod이 업데이트를 필터링할 때 `==` 대신 `identical`을 사용하는 이유는 무엇인가요? + +Notifiers는 `==` 대신 `identical`을 사용하여 업데이트를 필터링합니다. + +이는 riverpod 사용자들이 copyWith 구현을 위해 Freezed/built_value와 같은 코드 생성기를 사용하는 것이 일반적이기 때문입니다. +이러한 패키지는 `==`를 재정의하여 객체를 심층적으로 비교합니다. +객체 심층 비교는 상당히 많은 비용이 듭니다. +"비즈니스 로직" 모델에는 많은 프로퍼티가 있는 경향이 있습니다. +더 나쁜 것은 목록, 지도 등과 같은 컬렉션도 있다는 것입니다. + +동시에 복잡한 "비즈니스" 객체를 사용할 때 대부분의 `state = newState` 호출은 항상 알림(notification)을 발생시킵니다.(그렇지 않으면 setter를 호출할 필요가 없습니다) +일반적으로 현재 상태와 새 상태가 같을 때 `state = newState`를 호출하는 주된 경우는 원시 객체(primitive objects)(lists/classes/...가 아닌 ints, enums, strings)에 대한 것입니다. +이러한 객체는 "기본적으로 표준화(canonicalized by default)"됩니다. 이러한 객체가 같으면 일반적으로 "동일(identical)"합니다. + +따라서 riverpod은 `identical`을 사용하여 업데이트를 필터링하는 것은 두 가지 모두에 좋은 기본값을 갖기 위한 시도입니다. +객체 필터링에 크게 신경 쓰지 않고 코드 생성기가 기본적으로 `==` 오버라이드를 생성하기 때문에 `==`가 비쌀 수 있는 복잡한 객체의 경우, `identical`을 사용하면 리스너에게 효율적으로 알림을 제공할 수 있습니다. +동시에 단순한 객체의 경우 `identical`은 중복 알림을 올바르게 필터링합니다. + +Last but not least, you can change this behavior by overriding the method `updateShouldNotify` on Notifiers. +마지막으로, Notifiers에서 `updateShouldNotify` 메서드를 재정의하여 이 동작을 변경할 수 있습니다. + +## 모든 providers를 한 번에 재설정(reset)하는 방법이 있나요? + +아니요, 모든 providers를 한 번에 재설정할 수 있는 방법은 없습니다. + +이는 안티 패턴으로 간주되기 때문에 일부러 그렇게 한 것입니다. +모든 공급업체를 한 번에 재설정하면 재설정하지 않으려던 공급업체도 재설정되는 경우가 많습니다. + +이는 사용자가 로그아웃할 때 애플리케이션의 상태를 초기화하려는 사용자가 주로 요청하는 기능입니다. +이 기능을 원하는 경우, 대신 사용자의 상태에 따라 달라지는 모든 것을 "user" provider를 `ref.watch`로 설정해야 합니다. + +그러면 사용자가 로그아웃하면 그에 따라 모든 providers가 자동으로 재설정되지만 다른 모든 것은 그대로 유지됩니다. + +## "Cannot use "ref" after the widget was disposed"라는 오류가 발생했는데 무엇이 문제인가요? + +"Bad state: No ProviderScope found"이라는 동일한 문제의 이전 오류 메시지가 표시될 수도 있습니다. + +이 오류는 더 이상 마운트되지 않은 위젯에서 `ref`를 사용하려고 할 때 발생합니다. +이 오류는 일반적으로 `await` 이후에 발생합니다: + +```dart +ElevatedButton( + onPressed: () async { + await future; + ref.read(...); // May throw "Cannot use "ref" after the widget was disposed" + } +) +``` + +해결책은 `BuildContext`와 마찬가지로 `ref`를 사용하기 전에 `mounted`를 확인하는 것입니다: + +```dart +ElevatedButton( + onPressed: () async { + await future; + if (!context.mounted) return; + ref.read(...); // No longer throws + } +) +``` diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request.mdx b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request.mdx new file mode 100644 index 000000000..cd73f81cb --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request.mdx @@ -0,0 +1,320 @@ +--- +title: 첫 번째 provider/네트워크 요청 만들기 +pagination_prev: introduction/getting_started +version: 1 +--- + +import { Link } from "../../../../../src/components/Link"; +import { AutoSnippet, When } from "../../../../../src/components/CodeSnippet"; +import activity from "./first_request/activity"; +import provider from "./first_request/provider"; +import consumer from "./first_request/consumer"; +import consumerWidget from "./first_request/consumer_widget"; +import consumerStatefulWidget from "./first_request/consumer_stateful_widget"; +import hookConsumerWidget from "./first_request/hook_consumer_widget"; +import Legend from "./first_request/legend/legend"; + +네트워크 요청은 모든 애플리케이션의 핵심입니다. +하지만 네트워크 요청을 할 때는 고려해야 할 사항이 많습니다: + +- 요청이 이루어지는 동안 UI는 로딩 상태를 렌더링해야 합니다. +- 오류는 정상적으로 처리되어야 합니다. +- 요청은 가능하면 캐시되어야 합니다. + +이 섹션에서는 Riverpod이 이 모든 것을 자연스럽게 처리하는 데 어떻게 도움이 되는지 살펴보겠습니다. + +## `ProviderScope` 설정하기 + +네트워크 요청을 시작하기 전에 애플리케이션의 루트에 `ProviderScope`가 추가되었는지 확인하세요. + +```dart +void main() { + runApp( + // Riverpod을 설치하려면 다른 모든 위젯 위에 이 위젯을 추가해야 합니다. + // 이 위젯은 "MyApp" 내부가 아니라 "runApp"에 직접 파라미터로 추가해야 합니다. + ProviderScope( + child: MyApp(), + ), + ); +} +``` + +이렇게 하면 전체 애플리케이션에 대해 Riverpod이 활성화됩니다. + +:::note +[riverpod_lint](https://pub.dev/packages/riverpod_lint) 설치와 같은 전체 설치 단계는 를 확인하세요 +::: + +## "provider"에서 네트워크 요청 수행하기 + +네트워크 요청을 수행하는 것을 보통 "비즈니스 로직"이라고 부릅니다. +Riverpod에서 비즈니스 로직은 "providers" 내부에 배치됩니다. +프로바이더는 초강력(super-powered) 함수입니다. +일반 함수처럼 동작하지만 다음과 같은 이점이 추가됩니다: + +- 캐시됩니다. +- 기본적인 오류/로딩 처리를 제공합니다. +- 리스너를 추가할 수 있습니다. +- 데이터가 변경될 때 자동으로 다시 실행됩니다. + +따라서 공급자는 _GET_ 네트워크 요청에 가장 적합합니다. (_POST/etc_ 요청의 경우, 참조) + +예를 들어, 지루할 때 할 수 있는 무작위 활동을 제안하는 간단한 애플리케이션을 만들어 보겠습니다. +이를 위해 [Bored API](https://boredapi.com/)를 사용하겠습니다. +특히 `/api/activity` 엔드포인트에서 _GET_ 요청을 수행하겠습니다. +그러면 JSON 객체가 반환되며, 이 객체를 Dart 클래스 인스턴스로 파싱합니다. +다음 단계는 이 활동을 UI에 표시하는 것입니다. +또한 요청이 이루어지는 동안 로딩 상태를 렌더링하고 오류를 정상적으로 처리해야 합니다. + +멋지게 들리나요? 시작해보세요! + +### 모델 정의하기 + +시작하기 전에 API에서 수신할 데이터의 모델을 정의해야 합니다. +이 모델에는 JSON 객체를 Dart 클래스 인스턴스로 파싱하는 방법도 필요합니다. + +일반적으로 JSON 디코딩을 처리할 때는 [Freezed](https://pub.dev/packages/freezed) 또는 [json_serializable](https://pub.dev/packages/json_serializable)과 같은 코드 생성기를 사용하는 것이 좋습니다. +물론 수작업으로 처리하는 것도 가능합니다. + +어쨌든, 여기 우리의 모델이 있습니다: + + + +### 공급자 만들기 + +이제 모델이 생겼으니 API 쿼리를 시작할 수 있습니다. +그러기 위해서는 첫 번째 공급자를 만들어야 합니다. + +공급자를 정의하는 구문은 다음과 같습니다: + + +((ref) { + +}); +`} + annotations={[ + { + offset: 6, + length: 4, + label: "provider 변수", + description: <> + +이 변수는 공급자와 상호 작용하는 데 사용되는 변수입니다. +변수는 final이여야하고, "top-level" (global)에 선언되어야 합니다. + + + }, + { + offset: 13, + length: 12, + label: "provider 타입", + description: <> + +일반적으로 `Provider`, `FutureProvider` 또는 `StreamProvider`중 하나입니다. +사용되는 provider 타입은은 함수의 반환값에 따라 달라집니다. +예를 들어, `Future`를 만들려면 `FutureProvider`가 필요할 것입니다. + +`FutureProvider`가 가장 많이 사용될 것입니다. + +:::tip +"어떤 provider를 선택해야 하나"라는 관점에서 생각하지 마세요. +대신 "내가 무엇을 반환하고 싶은가"라는 관점에서 생각하세요. +공급자 유형은 자연스럽게 따라올 것입니다. +::: + + + }, + { + offset: 25, + length: 13, + label: "수정자(Modifiers) (optional)", + description: <> + +종종 provider 타입 뒤에 "수정자(Modifiers)"가 표시될 수 있습니다. +수정자(Modifiers)는 선택 사항이며, 타입에 안전한 방식으로 공급자의 동작을 조정하는 데 사용됩니다. + +현재 두 가지 수정자를 사용할 수 있습니다: + +- 공급자가 더 이상 사용되지 않을 때 캐시를 자동으로 지우는 `autoDispose`. + 도 참조하세요. +- provider에 인수를 전달할 수 있는 `family`. + 도 참조하세요. + + + }, + { + offset: 48, + length: 3, + label: "Ref", + description: <> + +다른 provider와 상호작용하는 데 사용되는 객체입니다. +모든 providers는 provider 함수의 매개변수(parameter) 또는 Notifier의 속성(property)으로 하나씩 가지고 있습니다. + + + }, + { + offset: 57, + length: 17, + label: "provider 함수", + description: <> + +여기에 프로바이더의 로직을 배치합니다. +이 함수는 공급자를 처음 읽을 때 호출됩니다. +이후 읽기는 이 함수를 다시 호출하지 않고 대신 캐시된 값을 반환합니다. + + + }, +]} +/> + + + + +} +`} + annotations={[ + { + offset: 0, + length: 9, + label: "어노테이션(annotation)", + description: <> + +모든 프로바이더는 `@riverpod` 또는 `@Riverpod()`로 어노테이션해야 합니다. +이 어노테이션은 전역 함수나 클래스에 배치할 수 있습니다. +이 어노테이션을 통해 프로바이더를 구성할 수 있습니다. + +예를 들어, `@Riverpod(keepAlive: true)`를 작성하여 "auto-dispose"(나중에 살펴보겠습니다)를 비활성화할 수 있습니다. + + + }, + { + offset: 17, + length: 10, + label: "어노테이션된 함수(annotated function)", + description: <> + +어노테이션된 함수의 이름에 따라 provider와 상호작용하는 방식이 결정됩니다. +주어진 함수 `myFunction`에 대해 생성된 `myFunctionProvider` 변수가 생성됩니다. + +어노테이션된 함수는 첫 번째 매개변수로 "ref"를 지정해야 합니다. +그 외에도 함수는 제네릭을 포함하여 여러 개의 매개변수를 가질 수 있습니다. +이 함수는 원할 경우 `Future`/`Stream`을 반환할 수도 있습니다. + +이 함수는 공급자를 처음 읽을 때 호출됩니다. +이후 읽기는 함수를 다시 호출하지 않고 대신 캐시된 값을 반환합니다. + + + }, + { + offset: 28, + length: 17, + label: "Ref", + description: <> + +다른 providers와 상호작용하는 데 사용되는 객체입니다. +모든 providers에는 provider 함수의 매개변수(parameter) 또는 Notifier의 속성(property)으로 하나씩 가지고 있습니다. +이 객체의 타입은 함수/클래스의 이름에 의해 결정됩니다. + + + }, +]} +/> + + +여기서는 API에서 Activity를 _GET_하고자 합니다. +_GET_은 비동기 연산이므로 `Future`를 생성해야 합니다. + +따라서 앞서 정의한 구문을 사용하여 다음과 같이 공급자를 정의할 수 있습니다: + + + +이 코드조각에서는 UI가 임의의 액티비티를 가져오는 데 사용할 수 있는 `activityProvider`라는 이름의 공급자를 정의했습니다. +다음과 같은 것은 주목할 가치가 있습니다: + +- 네트워크 요청은 UI가 provider를 한 번 이상 읽을 때까지 실행되지 않습니다. +- 이후 읽기는 네트워크 요청을 다시 실행하지 않고 이전에 가져온 활동을 반환합니다. +- UI가 이 공급자의 사용을 중단하면 캐시가 삭제됩니다. + 그런 다음 UI가 이 공급자를 다시 사용하면 새로운 네트워크 요청이 이루어집니다. +- 오류는 catch되지 않았습니다. 이는 공급자가 기본적으로 오류를 처리하기 때문에 자발적인 조치입니다. + 네트워크 요청이나 JSON 파싱에서 에러가 발생하면 riverpod에서 에러를 catch합니다. + 그러면 UI에 오류 페이지를 렌더링하는 데 필요한 정보가 자동으로 표시됩니다. + +:::info +Provider는 "지연(lazy)"입니다. 공급자를 정의해도 네트워크 요청이 실행되지 않습니다. +대신 공급자를 처음 읽을 때 네트워크 요청이 실행됩니다. +::: + +### UI에서 네트워크 요청의 응답 렌더링하기 + +Now that we have defined a provider, we can start using it inside our UI to display the activity. + +provider와 상호 작용하려면 "ref"라는 객체가 필요합니다. +provider는 당연히 "ref" 객체에 액세스할 수 있으므로 이전에 provider 정의에서 이 객체를 보셨을 것입니다. +하지만 여기서는 provider가 아니라 위젯에 있습니다. 그렇다면 "ref"는 어떻게 얻을 수 있을까요? + +해결책은 `Consumer`라는 커스텀 위젯을 사용하는 것입니다. +`Consumer`는 `Builder`와 비슷한 위젲이지만, "ref"를 제공한다는 추가적인 이점이 있습니다. +이를 통해 UI가 공급자를 읽을 수 있습니다. +다음 예제는 `Consumer`를 사용하는 방법을 보여줍니다: + + + +이 코드 조각에서는 `Consumer`를 사용하여 `activityProvider`를 읽고 Activity를 표시했습니다. +또한 로딩/오류 상태도 우아하게 처리했습니다. +provider에서 특별한 작업을 하지 않고도 UI가 어떻게 로딩/오류 상태를 처리할 수 있었는지 주목하세요. +동시에 위젯이 다시 빌드될 경우 네트워크 요청이 올바르게 다시 실행되지 않습니다. +다른 위젯도 네트워크 요청을 다시 실행하지 않고도 동일한 provider에 액세스할 수 있습니다. + +:::info +위젯은 원하는 만큼 많은 providers를 수신(listen)할 수 있습니다. 그렇게 하려면 `ref.watch` 호출을 더 추가하기만 하면 됩니다. +::: + +:::tip +linter를 설치하세요. +그러면 IDE에서 자동으로 `Consumer`를 추가하거나 `StatelessWidget`을 `ConsumerWidget`으로 변환하는 리팩터링 옵션을 이용할 수 있습니다. + +설치 단계는 를 참고하세요. +::: + +## 더 살펴보기: `Consumer` 대신 `ConsumerWidget`을 사용하여 코드 들여쓰기 제거하기. + +이전 예제에서는 `Consumer`를 사용하여 provider를 읽었습니다. +이 접근 방식에 문제가 있는 것은 아니지만, 들여쓰기가 추가되면 코드를 읽기 어렵게 만들 수 있습니다. + +Riverpod은 동일한 결과를 얻을 수 있는 다른 방법을 제공합니다: +`StatelessWidget`/`StatefulWidget`이 `Consumer`를 반환하는 코드를 작성하는 대신 `ConsumerWidget`/`ConsumerStatefulWidget`을 정의할 수 있습니다. +`ConsumerWidget`과 `ConsumerStatefulWidget`은 사실상 `StatelessWidget`/`StatefulWidget`과 `Consumer`를 결합한 것입니다. +이들은 원래의 짝과 동일하게 동작하지만 "ref"를 제공한다는 추가적인 이점이 있습니다. + +이전 예제를 다음과 같이 `ConsumerWidget`을 사용하도록 다시 작성할 수 있습니다: + + + +`ConsumerStatefulWidget`의 경우 대신 다음과 같이 작성합니다: + + + +### Flutter_hooks 고려 사항: `HookWidget`과 `ConsumerWidget`의 결합 + +:::caution +"훅(Hooks)"에 대해 들어본 적이 없다면 이 섹션을 건너뛰셔도 됩니다. +[Flutter_hooks](https://pub.dev/packages/flutter_hooks)는 Riverpod과는 별개의 패키지이지만 Riverpod과 함께 사용되는 경우가 많습니다. +Riverpod을 처음 사용하는 경우 "훅(Hooks)" 사용을 권장하지 않습니다. 자세한 내용은 에서 확인하세요. +::: + +`flutter_hooks`를 사용하는 경우 `HookWidget`과 `ConsumerWidget`을 결합하는 방법이 궁금할 수 있습니다. +결국 둘 다 확장된(extended) 위젯 클래스를 변경해야 합니다. + +Riverpod는 이 문제에 대한 해결책으로 `HookConsumerWidget`과 `StatefulHookConsumerWidget`을 제공합니다. +`ConsumerWidget`과 `ConsumerStatefulWidget`이 `Consumer`와 `StatelessWidget`/`StatefulWidget`의 결합인 것과 유사하게, +`HookConsumerWidget`과 `StatefulHookConsumerWidget`은 `Consumer`와 `HookWidget`/`HookStatefulWidget`의 결합입니다. +따라서 동일한 위젯에서 훅(Hooks)와 providers를 모두 사용할 수 있습니다. + +이를 보여주기 위해 이전 예제를 다시 한 번 다시 작성해 보겠습니다: + + diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/activity.ts b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/activity.ts new file mode 100644 index 000000000..5cea035c7 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/activity.ts @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw/activity.dart"; +import codegen from "!!raw-loader!./codegen/activity.dart"; + +export default { + raw: raw, + hooks: raw, + codegen: codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/codegen/activity.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/codegen/activity.dart new file mode 100644 index 000000000..5a810a08f --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/codegen/activity.dart @@ -0,0 +1,24 @@ +/* SNIPPET START */ + +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'activity.freezed.dart'; +part 'activity.g.dart'; + +/// `GET /api/activity` 엔드포인트의 응답 +/// +/// `freezed`와 `json_serializable`을 사용하여 정의됩니다. +@freezed +class Activity with _$Activity { + factory Activity({ + required String key, + required String activity, + required String type, + required int participants, + required double price, + }) = _Activity; + + /// JSON 객체를 [Activity] 인스턴스로 변환합니다. + /// 이렇게 하면 API 응답을 형안정(Type-safe)하게 읽을 수 있습니다. + factory Activity.fromJson(Map json) => _$ActivityFromJson(json); +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/codegen/activity.freezed.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/codegen/activity.freezed.dart new file mode 100644 index 000000000..6ffa343c1 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/codegen/activity.freezed.dart @@ -0,0 +1,237 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'activity.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +Activity _$ActivityFromJson(Map json) { + return _Activity.fromJson(json); +} + +/// @nodoc +mixin _$Activity { + String get key => throw _privateConstructorUsedError; + String get activity => throw _privateConstructorUsedError; + String get type => throw _privateConstructorUsedError; + int get participants => throw _privateConstructorUsedError; + double get price => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $ActivityCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ActivityCopyWith<$Res> { + factory $ActivityCopyWith(Activity value, $Res Function(Activity) then) = + _$ActivityCopyWithImpl<$Res, Activity>; + @useResult + $Res call( + {String key, + String activity, + String type, + int participants, + double price}); +} + +/// @nodoc +class _$ActivityCopyWithImpl<$Res, $Val extends Activity> + implements $ActivityCopyWith<$Res> { + _$ActivityCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? key = null, + Object? activity = null, + Object? type = null, + Object? participants = null, + Object? price = null, + }) { + return _then(_value.copyWith( + key: null == key + ? _value.key + : key // ignore: cast_nullable_to_non_nullable + as String, + activity: null == activity + ? _value.activity + : activity // ignore: cast_nullable_to_non_nullable + as String, + type: null == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as String, + participants: null == participants + ? _value.participants + : participants // ignore: cast_nullable_to_non_nullable + as int, + price: null == price + ? _value.price + : price // ignore: cast_nullable_to_non_nullable + as double, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$ActivityImplCopyWith<$Res> + implements $ActivityCopyWith<$Res> { + factory _$$ActivityImplCopyWith( + _$ActivityImpl value, $Res Function(_$ActivityImpl) then) = + __$$ActivityImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String key, + String activity, + String type, + int participants, + double price}); +} + +/// @nodoc +class __$$ActivityImplCopyWithImpl<$Res> + extends _$ActivityCopyWithImpl<$Res, _$ActivityImpl> + implements _$$ActivityImplCopyWith<$Res> { + __$$ActivityImplCopyWithImpl( + _$ActivityImpl _value, $Res Function(_$ActivityImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? key = null, + Object? activity = null, + Object? type = null, + Object? participants = null, + Object? price = null, + }) { + return _then(_$ActivityImpl( + key: null == key + ? _value.key + : key // ignore: cast_nullable_to_non_nullable + as String, + activity: null == activity + ? _value.activity + : activity // ignore: cast_nullable_to_non_nullable + as String, + type: null == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as String, + participants: null == participants + ? _value.participants + : participants // ignore: cast_nullable_to_non_nullable + as int, + price: null == price + ? _value.price + : price // ignore: cast_nullable_to_non_nullable + as double, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$ActivityImpl implements _Activity { + _$ActivityImpl( + {required this.key, + required this.activity, + required this.type, + required this.participants, + required this.price}); + + factory _$ActivityImpl.fromJson(Map json) => + _$$ActivityImplFromJson(json); + + @override + final String key; + @override + final String activity; + @override + final String type; + @override + final int participants; + @override + final double price; + + @override + String toString() { + return 'Activity(key: $key, activity: $activity, type: $type, participants: $participants, price: $price)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ActivityImpl && + (identical(other.key, key) || other.key == key) && + (identical(other.activity, activity) || + other.activity == activity) && + (identical(other.type, type) || other.type == type) && + (identical(other.participants, participants) || + other.participants == participants) && + (identical(other.price, price) || other.price == price)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => + Object.hash(runtimeType, key, activity, type, participants, price); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$ActivityImplCopyWith<_$ActivityImpl> get copyWith => + __$$ActivityImplCopyWithImpl<_$ActivityImpl>(this, _$identity); + + @override + Map toJson() { + return _$$ActivityImplToJson( + this, + ); + } +} + +abstract class _Activity implements Activity { + factory _Activity( + {required final String key, + required final String activity, + required final String type, + required final int participants, + required final double price}) = _$ActivityImpl; + + factory _Activity.fromJson(Map json) = + _$ActivityImpl.fromJson; + + @override + String get key; + @override + String get activity; + @override + String get type; + @override + int get participants; + @override + double get price; + @override + @JsonKey(ignore: true) + _$$ActivityImplCopyWith<_$ActivityImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/codegen/activity.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/codegen/activity.g.dart new file mode 100644 index 000000000..55a7a5383 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/codegen/activity.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'activity.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$ActivityImpl _$$ActivityImplFromJson(Map json) => + _$ActivityImpl( + key: json['key'] as String, + activity: json['activity'] as String, + type: json['type'] as String, + participants: json['participants'] as int, + price: (json['price'] as num).toDouble(), + ); + +Map _$$ActivityImplToJson(_$ActivityImpl instance) => + { + 'key': instance.key, + 'activity': instance.activity, + 'type': instance.type, + 'participants': instance.participants, + 'price': instance.price, + }; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/codegen/provider.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/codegen/provider.dart new file mode 100644 index 000000000..19901c6c4 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/codegen/provider.dart @@ -0,0 +1,21 @@ +/* SNIPPET START */ + +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'activity.dart'; + +// 코드 생성이 작동하는 데 필요합니다. +part 'provider.g.dart'; + +/// 그러면 `activityProvider`라는 이름의 provider가 생성됩니다. +/// 이 함수의 결과를 캐시하는 공급자를 생성합니다. +@riverpod +Future activity(ActivityRef ref) async { + // package:http를 사용하여 Bored API에서 임의의 Activity를 가져옵니다. + final response = await http.get(Uri.https('boredapi.com', '/api/activity')); + // 그런 다음 dart:convert를 사용하여 JSON 페이로드를 맵 데이터 구조로 디코딩합니다. + final json = jsonDecode(response.body) as Map; + // 마지막으로 맵을 Activity 인스턴스로 변환합니다. + return Activity.fromJson(json); +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/codegen/provider.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/codegen/provider.g.dart new file mode 100644 index 000000000..78a8a08aa --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/codegen/provider.g.dart @@ -0,0 +1,29 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$activityHash() => r'636cd5510e09cbfc46f31b74a70d9e98c89e95a4'; + +/// 그러면 `activityProvider`라는 이름의 provider가 생성됩니다. +/// 이 함수의 결과를 캐시하는 공급자를 생성합니다. +/// +/// Copied from [activity]. +@ProviderFor(activity) +final activityProvider = AutoDisposeFutureProvider.internal( + activity, + name: r'activityProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$activityHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ActivityRef = AutoDisposeFutureProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/consumer.ts b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/consumer.ts new file mode 100644 index 000000000..a625b7073 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/consumer.ts @@ -0,0 +1,8 @@ +import raw from "!!raw-loader!./raw/consumer.dart"; + +export default { + raw: raw, + hooks: raw, + codegen: raw, + hooksCodegen: raw, +}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/consumer_stateful_widget.ts b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/consumer_stateful_widget.ts new file mode 100644 index 000000000..5e6ac973a --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/consumer_stateful_widget.ts @@ -0,0 +1,8 @@ +import raw from "!!raw-loader!./raw/consumer_stateful_widget.dart"; + +export default { + raw: raw, + hooks: raw, + codegen: raw, + hooksCodegen: raw, +}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/consumer_widget.ts b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/consumer_widget.ts new file mode 100644 index 000000000..56cb2331d --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/consumer_widget.ts @@ -0,0 +1,8 @@ +import raw from "!!raw-loader!./raw/consumer_widget.dart"; + +export default { + raw: raw, + hooks: raw, + codegen: raw, + hooksCodegen: raw, +}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/hook_consumer_widget.ts b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/hook_consumer_widget.ts new file mode 100644 index 000000000..300ec6596 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/hook_consumer_widget.ts @@ -0,0 +1,8 @@ +import raw from "!!raw-loader!./raw/hook_consumer_widget.dart"; + +export default { + raw: raw, + hooks: raw, + codegen: raw, + hooksCodegen: raw, +}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/legend/DocuCode.scss b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/legend/DocuCode.scss new file mode 100644 index 000000000..bda077971 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/legend/DocuCode.scss @@ -0,0 +1,18 @@ +.legend { + table, + td, + tr { + background: none !important; + border: none; + + vertical-align: top; + } + + td:first-child { + text-align: end; + } + + tr + tr { + border-top: solid 0.5px; + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/legend/legend.tsx b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/legend/legend.tsx new file mode 100644 index 000000000..048817ec0 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/legend/legend.tsx @@ -0,0 +1,88 @@ +import React from "react"; +import PropTypes from "prop-types"; +import CodeBlock from "@theme/CodeBlock"; +import "./DocuCode.scss"; + +type AnnotatedCode = { + color?: string; + code: string; +}; + +export const colors = [ + "#2196f3", + "#4caf50", + "#f44336", + "#ff9800", + "#009688", + "#e91e63", + "#00bcd4", + "#8bc34a", +]; + +const Legend = ({ annotations, code }) => { + const fullAnnotations = new Array(); + + let annotationOffset = 0; + for (var codeOffset = 0; codeOffset < code.length; ) { + if (annotationOffset >= annotations.length) { + // Out of annotations, just add the rest of the code + fullAnnotations.push({ code: code.substring(codeOffset) }); + break; + } + + const annotation = annotations[annotationOffset]; + if (codeOffset < annotation.offset) { + /// There is an unannotated gap between the last annotation and this one. + const codeLength = annotation.offset - codeOffset; + fullAnnotations.push({ + code: code.substring(codeOffset, codeOffset + codeLength), + }); + codeOffset += codeLength; + } + + if (annotation.offset >= code.length) { + throw new Error("Annotation offset out of bounds"); + } + + annotationOffset++; + codeOffset = annotation.offset + annotation.length; + fullAnnotations.push({ + color: colors[annotationOffset - 1], + code: code.substring( + annotation.offset, + annotation.offset + annotation.length + ), + }); + } + + return ( +
+
+        {fullAnnotations.map(({ code, color }) => {
+          let underlineClass = color ? `underline` : "";
+          let style = color ? { color: color } : undefined;
+
+          return (
+            
+              {code}
+            
+          );
+        })}
+      
+ + + {annotations.map((annotation, index) => ( + + + + + ))} + +
+ {annotation.label} + {annotation.description}
+
+ ); +}; + +export default Legend; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/provider.ts b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/provider.ts new file mode 100644 index 000000000..42ce35bff --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/provider.ts @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw/provider.dart"; +import codegen from "!!raw-loader!./codegen/provider.dart"; + +export default { + raw: raw, + hooks: raw, + codegen: codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/raw/activity.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/raw/activity.dart new file mode 100644 index 000000000..794d36782 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/raw/activity.dart @@ -0,0 +1,30 @@ +/* SNIPPET START */ + +/// The response of the `GET /api/activity` endpoint. +class Activity { + Activity({ + required this.key, + required this.activity, + required this.type, + required this.participants, + required this.price, + }); + + /// Convert a JSON object into an [Activity] instance. + /// This enables type-safe reading of the API response. + factory Activity.fromJson(Map json) { + return Activity( + key: json['key'] as String, + activity: json['activity'] as String, + type: json['type'] as String, + participants: json['participants'] as int, + price: json['price'] as double, + ); + } + + final String key; + final String activity; + final String type; + final int participants; + final double price; +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/raw/consumer.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/raw/consumer.dart new file mode 100644 index 000000000..718aa25e8 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/raw/consumer.dart @@ -0,0 +1,41 @@ +// ignore_for_file: omit_local_variable_types + +/* SNIPPET START */ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'activity.dart'; +import 'provider.dart'; + +/// The homepage of our application +class Home extends StatelessWidget { + const Home({super.key}); + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, ref, child) { + // Read the activityProvider. This will start the network request + // if it wasn't already started. + // By using ref.watch, this widget will rebuild whenever the + // the activityProvider updates. This can happen when: + // - The response goes from "loading" to "data/error" + // - The request was refreshed + // - The result was modified locally (such as when performing side-effects) + // ... + final AsyncValue activity = ref.watch(activityProvider); + + return Center( + /// Since network-requests are asynchronous and can fail, we need to + /// handle both error and loading states. We can use pattern matching for this. + /// We could alternatively use `if (activity.isLoading) { ... } else if (...)` + child: switch (activity) { + AsyncData(:final value) => Text('Activity: ${value.activity}'), + AsyncError() => const Text('Oops, something unexpected happened'), + _ => const CircularProgressIndicator(), + }, + ); + }, + ); + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/raw/consumer_stateful_widget.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/raw/consumer_stateful_widget.dart new file mode 100644 index 000000000..2ba511a07 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/raw/consumer_stateful_widget.dart @@ -0,0 +1,43 @@ +// ignore_for_file: omit_local_variable_types, prefer_const_constructors, unused_local_variable, todo + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'activity.dart'; +import 'provider.dart'; + +/* SNIPPET START */ + +// We extend ConsumerStatefulWidget. +// This is the equivalent of "Consumer" + "StatefulWidget". +class Home extends ConsumerStatefulWidget { + const Home({super.key}); + + @override + ConsumerState createState() => _HomeState(); +} + +// Notice how instead of "State", we are extending "ConsumerState". +// This uses the same principle as "ConsumerWidget" vs "StatelessWidget". +class _HomeState extends ConsumerState { + @override + void initState() { + super.initState(); + + // State life-cycles have access to "ref" too. + // This enables things such as adding a listener on a specific provider + // to show dialogs/snackbars. + ref.listenManual(activityProvider, (previous, next) { + // TODO show a snackbar/dialog + }); + } + + @override + Widget build(BuildContext context) { + // "ref" is not passed as parameter anymore, but is instead a property of "ConsumerState". + // We can therefore keep using "ref.watch" inside "build". + final AsyncValue activity = ref.watch(activityProvider); + + return Center(/* ... */); + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/raw/consumer_widget.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/raw/consumer_widget.dart new file mode 100644 index 000000000..1f01fa38a --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/raw/consumer_widget.dart @@ -0,0 +1,25 @@ +// ignore_for_file: omit_local_variable_types, prefer_const_constructors, unused_local_variable + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'activity.dart'; +import 'provider.dart'; + +/* SNIPPET START */ + +/// We subclassed "ConsumerWidget" instead of "StatelessWidget". +/// This is equivalent to making a "StatelessWidget" and retuning "Consumer". +class Home extends ConsumerWidget { + const Home({super.key}); + + @override + // Notice how "build" now receives an extra parameter: "ref" + Widget build(BuildContext context, WidgetRef ref) { + // We can use "ref.watch" inside our widget like we did using "Consumer" + final AsyncValue activity = ref.watch(activityProvider); + + // The rendering logic stays the same + return Center(/* ... */); + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/raw/hook_consumer_widget.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/raw/hook_consumer_widget.dart new file mode 100644 index 000000000..1fc7306dc --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/raw/hook_consumer_widget.dart @@ -0,0 +1,28 @@ +// ignore_for_file: omit_local_variable_types, unused_local_variable, prefer_const_constructors + +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +import 'activity.dart'; +import 'provider.dart'; + +/* SNIPPET START */ + +/// "HookConsumerWidget"을 서브클래스화했습니다. +/// 이것은 "StatelessWidget" + "Consumer" + "HookWidget"을 함께 결합합니다. +class Home extends HookConsumerWidget { + const Home({super.key}); + + @override + // 이제 "build"가 추가 매개 변수 "ref"를 받는 것에 주목하세요. + Widget build(BuildContext context, WidgetRef ref) { + // 위젯 내부에서 "useState"와 같은 훅을 사용할 수 있습니다. + final counter = useState(0); + + // providers도 읽을 수 있습니다. + final AsyncValue activity = ref.watch(activityProvider); + + return Center(/* ... */); + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/raw/provider.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/raw/provider.dart new file mode 100644 index 000000000..f1395604f --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/first_request/raw/provider.dart @@ -0,0 +1,15 @@ +/* SNIPPET START */ + +import 'dart:convert'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:http/http.dart' as http; +import 'activity.dart'; + +final activityProvider = FutureProvider.autoDispose((ref) async { + // Using package:http, we fetch a random activity from the Bored API. + final response = await http.get(Uri.https('boredapi.com', '/api/activity')); + // Using dart:convert, we then decode the JSON payload into a Map data structure. + final json = jsonDecode(response.body) as Map; + // Finally, we convert the Map into an Activity instance. + return Activity.fromJson(json); +}); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/passing_args.mdx b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/passing_args.mdx new file mode 100644 index 000000000..5bf4dc7c0 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/passing_args.mdx @@ -0,0 +1,137 @@ +--- +title: 요청에 인자 전달하기 +version: 1 +--- + +import { Link } from "../../../../../src/components/Link"; +import { AutoSnippet, When } from "../../../../../src/components/CodeSnippet"; +import noArgProvider from "./passing_args/no_arg_provider"; +import family from "!!raw-loader!./passing_args/raw/family.dart"; +import codegenFamily from "!!raw-loader!./passing_args/codegen/family.dart"; +import consumerProvider from "!!raw-loader!./passing_args/raw/consumer_provider.dart"; +import consumerFamily from "!!raw-loader!./passing_args/raw/consumer_family.dart"; +import consumerListFamily from "!!raw-loader!./passing_args/raw/consumer_list_family.dart"; +import multipleConsumerFamily from "!!raw-loader!./passing_args/raw/multiple_consumer_family.dart"; +import tupleFamily from "!!raw-loader!./passing_args/raw/tuple_family.dart"; +import consumerTupleFamily from "!!raw-loader!./passing_args/raw/consumer_tuple_family.dart"; + +이전 글에서 "provider"를 정의하여 간단한 _GET_ HTTP 요청을 만드는 방법을 살펴봤습니다. +하지만 HTTP 요청은 외부 매개변수(external parameters)에 의존하는 경우가 많습니다. + +예를 들어, 이전에는 사용자에게 무작위 액티비티를 제안하기 위해 [Bored API](https://boredapi.com/)를 사용했습니다. +하지만 사용자가 원하는 액티비티 타입을 필터링하거나 가격 요구 사항 등을 원할 수도 있습니다. +이러한 매개변수는 미리 알 수 없습니다. +따라서 이러한 매개변수를 UI에서 providers에 전달할 방법이 필요합니다. + +## 인수를 허용하도록 providers 업데이트 + +참고로 이전에는 다음과 같이 provider를 정의했습니다: + + + + + +코드 생성(code-generation)에 의존하지 않을 때는 인자 전달(passing arguments)을 지원하기 위해 provider 정의 구문을 약간 조정해야 합니다. +이는 "family"라는 "수정자(modifier)"에 의존하여 수행됩니다. + +간단히 말해, provider 타입 뒤에 `.family`를 추가하고 인수 타입(argument type)에 해당하는 추가 타입 매개 변수를 추가해야 합니다. +예를 들어, 원하는 액티비티 타입에 해당하는 `String` 인수를 받도록 provider를 업데이트할 수 있습니다: + + + + + + + +provider에게 매개변수(parameters)를 전달하려면 어노테이션이 달린 함수 자체에 매개변수(parameters)를 추가하기만 하면 됩니다. +예를 들어, 원하는 액티비티 타입에 해당하는 `String` 인수를 받도록 provider를 업데이트할 수 있습니다: + + + + + +:::caution +provider에게 인수(arguments)를 전달할 때는 provider에서 "autoDispose"를 활성화하는 것이 좋습니다. +그렇지 않으면 메모리 누수(memory leaks)가 발생할 수 있습니다. +자세한 내용은 를 참조하세요. +::: + +## 인수(arguments)를 전달하도록 UI 업데이트 + +이전에는 위젯이 다음과 같이 provider를 소비(consume)했습니다: + + + +하지만 이제 provider가 인수(arguments)를 받으므로 인수를 사용하는 구문이 약간 달라졌습니다. +이제 provider는 요청된 매개 변수를 사용하여 호출해야 하는 함수입니다. +이와 같이 하드코딩된 타입의 액티비티을 전달하도록 UI를 업데이트할 수 있습니다: + + + + + +provider에게 전달된 매개변수(parameters)는 어노테이션이 달린 함수의 매개변수에서 "ref" 매개변수를 뺀 값에 해당합니다. + + + +:::info +서로 다른 인수(arguments)를 가진 동일한 provider를 동시에 수신(listen)하는 것은 전적으로 가능합니다. +예를 들어, 우리의 UI는 "오락" 액티비티와 "요리" 액티비티을 모두 렌더링할 수 있습니다: + + + + + +이것이 수정자(modifier)를 "family"라고 부르는 이유입니다: +provider에게 매개 변수를 전달하면, 내부적으로 동일한 로직을 가진 상태 그룹(group of states)으로 공급자를 효과적으로 변환할 수 있기 때문입니다. + + +::: + +## Caching considerations and parameter restrictions + +매개변수(parameters)를 providers에게 전달할 때 계산은 여전히 캐시됩니다. +차이점은 이제 계산이 인수별로 캐시(cached per-argument)된다는 점입니다. + +즉, 두 개의 위젯이 동일한 매개변수로 동일한 provider를 사용하는 경우 네트워크 요청은 한 번만 이루어집니다. +그러나 두 위젯이 서로 다른 매개변수를 가진 동일한 provider를 사용하는 경우 두 번의 네트워크 요청이 이루어집니다. + +이를 위해 Riverpod은 매개변수의 `==` 연산자에 의존합니다. +따라서 provider에게 전달되는 매개변수가 일관된 동일성(consistent equality)을 갖는 것이 중요합니다. + +:::caution +흔히 저지르는 실수는 새 객체가 `==`를 재정의하지 않는데도 새 객체를 프로바이더의 매개변수로 직접 인스턴스화하는 것입니다. +예를 들어, `List`를 이렇게 전달하고 싶은 유혹을 받을 수 있습니다: + + + +이 코드의 문제점은 `['recreational', 'cooking'] == ['recreational', 'cooking']`가 `false`라는 것입니다. +따라서 Riverpod은 두 매개변수가 다르다고 판단하고 새로운 네트워크 요청을 시도합니다. +이렇게 되면 네트워크 요청이 무한 반복되어 사용자에게 진행률 표시기가 영구적으로 표시됩니다. + +이 문제를 해결하려면 `const` 목록(`const ['레크리에이션', '요리']`)을 사용하거나 `==`를 재정의하는 사용자 정의 목록 구현을 사용할 수 있습니다. + +이 실수를 발견하는 데 도움이 되려면 [riverpod_lint](https://pub.dev/packages/riverpod_lint)를 사용하여 +[provider_parameters](https://github.com/rrousselGit/riverpod/tree/master/packages/riverpod_lint#provider_parameters) 린트 규칙을 활성화하는 것이 좋습니다. +그러면 위와 같은 스니펫에 경고가 표시됩니다. +설치 단계는 를 참조하세요. +::: + + + +이를 염두에 두고 provider에 여러 매개 변수(multiple parameters)를 전달하는 방법이 궁금할 수 있습니다. +권장되는 해결책은 다음과 같습니다: + +- 코드 생성(code-generation)으로 전환하여 원하는 수의 매개변수를 전달할 수 있습니다. +- Dart 3의 레코드(records) 사용 + +다트 3의 레코드(records)가 유용한 이유는 `==`를 자연스럽게 재정의하고 편리한 구문을 가지고 있기 때문입니다. +예를 들어, 액티비티 타입과 최대 가격을 모두 허용하도록 공급자를 업데이트할 수 있습니다: + + + +그런 다음 이 provider를 다음과 같이 소비(consume)할 수 있습니다: + + + + diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/passing_args/codegen/family.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/passing_args/codegen/family.dart new file mode 100644 index 000000000..4c9d1e874 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/passing_args/codegen/family.dart @@ -0,0 +1,29 @@ +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import '../../first_request/codegen/activity.dart'; + +part 'family.g.dart'; + +/* SNIPPET START */ +@riverpod +Future activity( + ActivityRef ref, + // provider에 인수를 추가할 수 있습니다. + // 매개변수 타입은 원하는 대로 지정할 수 있습니다. + String activityType, +) async { + // "activityType" 인수를 사용하여 URL을 작성할 수 있습니다. + // "https://boredapi.com/api/activity?type="을 가리키게 됩니다. + final response = await http.get( + Uri( + scheme: 'https', + host: 'boredapi.com', + path: '/api/activity', + // 쿼리 매개변수를 수동으로 인코딩할 필요 없이 "Uri" 클래스가 자동으로 인코딩합니다. + queryParameters: {'type': activityType}, + ), + ); + final json = jsonDecode(response.body) as Map; + return Activity.fromJson(json); +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/passing_args/codegen/family.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/passing_args/codegen/family.g.dart new file mode 100644 index 000000000..f025f6159 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/passing_args/codegen/family.g.dart @@ -0,0 +1,159 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'family.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$activityHash() => r'cb76e67cd45f1823d3ed497a235be53819ce2eaf'; + +/// Copied from Dart SDK +class _SystemHash { + _SystemHash._(); + + static int combine(int hash, int value) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + value); + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); + return hash ^ (hash >> 6); + } + + static int finish(int hash) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + // ignore: parameter_assignments + hash = hash ^ (hash >> 11); + return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); + } +} + +/// See also [activity]. +@ProviderFor(activity) +const activityProvider = ActivityFamily(); + +/// See also [activity]. +class ActivityFamily extends Family> { + /// See also [activity]. + const ActivityFamily(); + + /// See also [activity]. + ActivityProvider call( + String activityType, + ) { + return ActivityProvider( + activityType, + ); + } + + @override + ActivityProvider getProviderOverride( + covariant ActivityProvider provider, + ) { + return call( + provider.activityType, + ); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'activityProvider'; +} + +/// See also [activity]. +class ActivityProvider extends AutoDisposeFutureProvider { + /// See also [activity]. + ActivityProvider( + String activityType, + ) : this._internal( + (ref) => activity( + ref as ActivityRef, + activityType, + ), + from: activityProvider, + name: r'activityProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$activityHash, + dependencies: ActivityFamily._dependencies, + allTransitiveDependencies: ActivityFamily._allTransitiveDependencies, + activityType: activityType, + ); + + ActivityProvider._internal( + super._createNotifier, { + required super.name, + required super.dependencies, + required super.allTransitiveDependencies, + required super.debugGetCreateSourceHash, + required super.from, + required this.activityType, + }) : super.internal(); + + final String activityType; + + @override + Override overrideWith( + FutureOr Function(ActivityRef provider) create, + ) { + return ProviderOverride( + origin: this, + override: ActivityProvider._internal( + (ref) => create(ref as ActivityRef), + from: from, + name: null, + dependencies: null, + allTransitiveDependencies: null, + debugGetCreateSourceHash: null, + activityType: activityType, + ), + ); + } + + @override + AutoDisposeFutureProviderElement createElement() { + return _ActivityProviderElement(this); + } + + @override + bool operator ==(Object other) { + return other is ActivityProvider && other.activityType == activityType; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, activityType.hashCode); + + return _SystemHash.finish(hash); + } +} + +mixin ActivityRef on AutoDisposeFutureProviderRef { + /// The parameter `activityType` of this provider. + String get activityType; +} + +class _ActivityProviderElement + extends AutoDisposeFutureProviderElement with ActivityRef { + _ActivityProviderElement(super.provider); + + @override + String get activityType => (origin as ActivityProvider).activityType; +} +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/passing_args/codegen/provider.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/passing_args/codegen/provider.dart new file mode 100644 index 000000000..1454fb399 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/passing_args/codegen/provider.dart @@ -0,0 +1,32 @@ +// ignore_for_file: avoid_print + +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import '../../first_request/codegen/activity.dart'; + +// Necessary for code-generation to work +part 'provider.g.dart'; + +FutureOr fetchActivity() => throw UnimplementedError(); + +/* SNIPPET START */ +// "함수형" provider +@riverpod +Future activity(ActivityRef ref) async { + // TODO: 네트워크 요청을 수행하여 액티비티를 가져옵니다 + return fetchActivity(); +} + +// 또는, "notifier" +@riverpod +class ActivityNotifier2 extends _$ActivityNotifier2 { + /// Notifier 인자(arguments)는 빌드 메서드에 지정됩니다. + /// 원하는 개수만큼 지정할 수 있고, 이름도 지정할 수 있으며, 선택적/명명할 수도 있습니다. + @override + Future build(String activityType) async { + // 인수는 "this."으로도 사용할 수 있습니다. + print(this.activityType); + + // TODO: 네트워크 요청을 수행하여 액티비티를 가져옵니다 + return fetchActivity(); + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/passing_args/codegen/provider.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/passing_args/codegen/provider.g.dart new file mode 100644 index 000000000..00390f801 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/passing_args/codegen/provider.g.dart @@ -0,0 +1,191 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$activityHash() => r'2f9496c5d70de9314c67e5c48ac44d8b149bc471'; + +/// See also [activity]. +@ProviderFor(activity) +final activityProvider = AutoDisposeFutureProvider.internal( + activity, + name: r'activityProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$activityHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ActivityRef = AutoDisposeFutureProviderRef; +String _$activityNotifier2Hash() => r'9e67c655d53a9f98c3b012a0534421385dde0339'; + +/// Copied from Dart SDK +class _SystemHash { + _SystemHash._(); + + static int combine(int hash, int value) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + value); + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); + return hash ^ (hash >> 6); + } + + static int finish(int hash) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + // ignore: parameter_assignments + hash = hash ^ (hash >> 11); + return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); + } +} + +abstract class _$ActivityNotifier2 + extends BuildlessAutoDisposeAsyncNotifier { + late final String activityType; + + FutureOr build( + String activityType, + ); +} + +/// See also [ActivityNotifier2]. +@ProviderFor(ActivityNotifier2) +const activityNotifier2Provider = ActivityNotifier2Family(); + +/// See also [ActivityNotifier2]. +class ActivityNotifier2Family extends Family> { + /// See also [ActivityNotifier2]. + const ActivityNotifier2Family(); + + /// See also [ActivityNotifier2]. + ActivityNotifier2Provider call( + String activityType, + ) { + return ActivityNotifier2Provider( + activityType, + ); + } + + @override + ActivityNotifier2Provider getProviderOverride( + covariant ActivityNotifier2Provider provider, + ) { + return call( + provider.activityType, + ); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'activityNotifier2Provider'; +} + +/// See also [ActivityNotifier2]. +class ActivityNotifier2Provider + extends AutoDisposeAsyncNotifierProviderImpl { + /// See also [ActivityNotifier2]. + ActivityNotifier2Provider( + String activityType, + ) : this._internal( + () => ActivityNotifier2()..activityType = activityType, + from: activityNotifier2Provider, + name: r'activityNotifier2Provider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$activityNotifier2Hash, + dependencies: ActivityNotifier2Family._dependencies, + allTransitiveDependencies: + ActivityNotifier2Family._allTransitiveDependencies, + activityType: activityType, + ); + + ActivityNotifier2Provider._internal( + super._createNotifier, { + required super.name, + required super.dependencies, + required super.allTransitiveDependencies, + required super.debugGetCreateSourceHash, + required super.from, + required this.activityType, + }) : super.internal(); + + final String activityType; + + @override + FutureOr runNotifierBuild( + covariant ActivityNotifier2 notifier, + ) { + return notifier.build( + activityType, + ); + } + + @override + Override overrideWith(ActivityNotifier2 Function() create) { + return ProviderOverride( + origin: this, + override: ActivityNotifier2Provider._internal( + () => create()..activityType = activityType, + from: from, + name: null, + dependencies: null, + allTransitiveDependencies: null, + debugGetCreateSourceHash: null, + activityType: activityType, + ), + ); + } + + @override + AutoDisposeAsyncNotifierProviderElement + createElement() { + return _ActivityNotifier2ProviderElement(this); + } + + @override + bool operator ==(Object other) { + return other is ActivityNotifier2Provider && + other.activityType == activityType; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, activityType.hashCode); + + return _SystemHash.finish(hash); + } +} + +mixin ActivityNotifier2Ref on AutoDisposeAsyncNotifierProviderRef { + /// The parameter `activityType` of this provider. + String get activityType; +} + +class _ActivityNotifier2ProviderElement + extends AutoDisposeAsyncNotifierProviderElement + with ActivityNotifier2Ref { + _ActivityNotifier2ProviderElement(super.provider); + + @override + String get activityType => (origin as ActivityNotifier2Provider).activityType; +} +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/passing_args/no_arg_provider.ts b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/passing_args/no_arg_provider.ts new file mode 100644 index 000000000..42ce35bff --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/passing_args/no_arg_provider.ts @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw/provider.dart"; +import codegen from "!!raw-loader!./codegen/provider.dart"; + +export default { + raw: raw, + hooks: raw, + codegen: codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/consumer_family.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/consumer_family.dart new file mode 100644 index 000000000..ed154e255 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/consumer_family.dart @@ -0,0 +1,24 @@ +// ignore_for_file: omit_local_variable_types, unused_local_variable, prefer_final_locals + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../first_request/raw/activity.dart'; +import 'family.dart'; + +class Example extends ConsumerWidget { + const Example({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { +/* SNIPPET START */ + AsyncValue activity = ref.watch( + // 이제 provider는 액티비티 타입을 기대하는 함수입니다. + // 단순화를 위해 지금은 상수 문자열을 전달하겠습니다. + activityProvider('recreational'), + ); +/* SNIPPET END */ + + return Container(); + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/consumer_list_family.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/consumer_list_family.dart new file mode 100644 index 000000000..3bd3fdf27 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/consumer_list_family.dart @@ -0,0 +1,23 @@ +// ignore_for_file: omit_local_variable_types, unused_local_variable, prefer_final_locals + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +final activityProvider = Provider.family((ref, id) { + throw UnimplementedError(); +}); + +class Example extends ConsumerWidget { + const Example({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { +/* SNIPPET START */ + // 대신 문자열 목록을 허용하도록 activityProvider를 업데이트할 수 있습니다. + // 그런 다음 watch를 호출하여 해당 목록을 직접 만들 수 있습니다. + ref.watch(activityProvider(['recreational', 'cooking'])); +/* SNIPPET END */ + + return Container(); + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/consumer_provider.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/consumer_provider.dart new file mode 100644 index 000000000..337eab2d1 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/consumer_provider.dart @@ -0,0 +1,20 @@ +// ignore_for_file: omit_local_variable_types, unused_local_variable, prefer_final_locals + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../first_request/raw/activity.dart'; +import 'provider.dart'; + +class Example extends ConsumerWidget { + const Example({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { +/* SNIPPET START */ + AsyncValue activity = ref.watch(activityProvider); +/* SNIPPET END */ + + return Container(); + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/consumer_tuple_family.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/consumer_tuple_family.dart new file mode 100644 index 000000000..e9ce32d48 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/consumer_tuple_family.dart @@ -0,0 +1,23 @@ +// ignore_for_file: omit_local_variable_types, unused_local_variable, prefer_final_locals + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'tuple_family.dart'; + +class Example extends ConsumerWidget { + const Example({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { +/* SNIPPET START */ + ref.watch( + // Record를 사용하여 매개 변수를 전달할 수 있습니다. + // Record가 ==를 재정의하므로 watch 호출에서 직접 Record를 생성해도 괜찮습니다. + activityProvider((type: 'recreational', maxPrice: 40)), + ); +/* SNIPPET END */ + + return Container(); + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/family.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/family.dart new file mode 100644 index 000000000..b8229619a --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/family.dart @@ -0,0 +1,40 @@ +// ignore_for_file: unnecessary_this, avoid_print + +import 'dart:async'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../../first_request/raw/activity.dart'; + +FutureOr fetchActivity(String activityType) => throw UnimplementedError(); + +/* SNIPPET START */ +// "함수형" provider +final activityProvider = FutureProvider.autoDispose + // ".family" 수정자(modifier)를 사용합니다. + // "String" 제네릭 타입은 인수 타입에 해당합니다. + // 이제 provider는 "ref" 외에 액티비티 타입이라는 추가 인수를 받습니다. + .family((ref, activityType) async { + // TODO: "activityType"을 사용하여 액티비티를 가져오기 위한 네트워크 요청을 수행합니다. + return fetchActivity(activityType); +}); + +// "notifier" provider +final activityProvider2 = AsyncNotifierProvider.autoDispose + // 여기서도 ".family" 수정자를 사용하고 인수를 "String" 유형으로 지정합니다. + .family( + ActivityNotifier.new, +); + +// notifiers에 '.family'를 사용할 때는 notifier 서브클래스를 변경해야 합니다: +// AsyncNotifier -> FamilyAsyncNotifier +// AutoDisposeAsyncNotifier -> AutoDisposeFamilyAsyncNotifier +class ActivityNotifier extends AutoDisposeFamilyAsyncNotifier { + /// Family 인자는 빌드 메서드에 전달되며 this.arg로 액세스할 수 있습니다. + @override + Future build(String activityType) async { + // 인수는 "this.arg"로도 사용할 수 있습니다. + print(this.arg); + + // TODO: 액티비티를 가져오기 위한 네트워크 요청을 수행합니다. + return fetchActivity(activityType); + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/multiple_consumer_family.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/multiple_consumer_family.dart new file mode 100644 index 000000000..3a8160027 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/multiple_consumer_family.dart @@ -0,0 +1,37 @@ +// ignore_for_file: omit_local_variable_types, unused_local_variable, prefer_final_locals + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../first_request/raw/activity.dart'; +import 'family.dart'; + +class Example extends ConsumerWidget { + const Example({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + AsyncValue activity = ref.watch( + // 이제 provider는 액티비티 타입을 기대하는 함수입니다. + // 단순화를 위해 지금은 상수 문자열을 전달하겠습니다. + activityProvider('recreational'), + ); + /* SNIPPET START */ + return Consumer( + builder: (context, ref, child) { + final recreational = ref.watch(activityProvider('recreational')); + final cooking = ref.watch(activityProvider('cooking')); + + // 그러면 두 활동을 모두 렌더링할 수 있습니다. + // 두 요청이 모두 병렬로 발생하고 올바르게 캐시됩니다. + return Column( + children: [ + Text(recreational.valueOrNull?.activity ?? ''), + Text(cooking.valueOrNull?.activity ?? ''), + ], + ); + }, + ); + /* SNIPPET END */ + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/provider.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/provider.dart new file mode 100644 index 000000000..b1c7e862b --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/provider.dart @@ -0,0 +1,26 @@ +import 'dart:async'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../../first_request/raw/activity.dart'; + +/* SNIPPET START */ + +FutureOr fetchActivity() => throw UnimplementedError(); + +// "함수형" provider +final activityProvider = FutureProvider.autoDispose((ref) async { + // TODO: 액티비티를 가져오기 위한 네트워크 요청을 수행합니다. + return fetchActivity(); +}); + +// 또는, "notifier" +final activityProvider2 = AsyncNotifierProvider( + ActivityNotifier.new, +); + +class ActivityNotifier extends AsyncNotifier { + @override + Future build() async { + // TODO: 액티비티를 가져오기 위한 네트워크 요청을 수행합니다. + return fetchActivity(); + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/tuple_family.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/tuple_family.dart new file mode 100644 index 000000000..d0a6edaf6 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/tuple_family.dart @@ -0,0 +1,34 @@ +// ignore_for_file: omit_local_variable_types, unused_local_variable, prefer_final_locals + +import 'dart:convert'; + +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:http/http.dart' as http; + +import '../../first_request/raw/activity.dart'; + +/* SNIPPET START */ + +// provider에게 전달할 매개변수를 나타내는 record를 정의합니다. +// typedef는 선택 사항이지만 코드를 더 읽기 쉽게 만들 수 있습니다. + +typedef ActivityParameters = ({String type, int maxPrice}); + +final activityProvider = FutureProvider.autoDispose + // 이제 새로 정의된 record를 인수 유형으로 사용합니다. + .family((ref, arguments) async { + final response = await http.get( + Uri( + scheme: 'https', + host: 'boredapi.com', + path: '/api/activity', + queryParameters: { + // 마지막으로 인수를 사용하여 쿼리 매개변수를 업데이트할 수 있습니다. + 'type': arguments.type, + 'price': arguments.maxPrice, + }, + ), + ); + final json = jsonDecode(response.body) as Map; + return Activity.fromJson(json); +}); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/provider_observer.mdx b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/provider_observer.mdx new file mode 100644 index 000000000..419ff377a --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/provider_observer.mdx @@ -0,0 +1,48 @@ +--- +title: 로깅 및 오류 보고 +--- + +import { Link } from "../../../../../src/components/Link"; +import { AutoSnippet, When } from "../../../../../src/components/CodeSnippet"; +import providerObserver from "!!raw-loader!./provider_observer/provider_observer.dart"; + +Riverpod은 기본적으로 provider 트리에서 발생하는 모든 이벤트를 수신하는 방법을 제공합니다. +이 기능은 모든 이벤트를 기록하거나 원격 서비스에 오류를 보고하는 데 사용할 수 있습니다. + +이는 `ProviderObserver` 클래스를 사용하고 이를 `ProviderScope`/`ProviderContainer`에 전달하면 됩니다. + +## ProviderObserver 정의하기 + +`ProviderObserver`는 확장(extend)해야 하는 클래스입니다. +이벤트를 수신하기 위해 재정의할 수 있는 다양한 메서드를 제공합니다: + +- `didAddProvider`, provider가 트리에 추가(added)될때 호출 +- `didUpdateProvider`, provider가 갱신(updated)될때 호출 +- `didDisposeProvider`, provider가 폐기(disposed)될때 호출 +- `providerDidFail`, synchronous provider가 에러를 던질때 호출 + + + +## ProviderObserver 사용하기 + +이제 옵저버(observer)를 정의했으니 이를 사용해야 합니다. +그러기 위해서는 `ProviderScope` 또는 `ProviderContainer`에 전달해야 합니다: + +```dart +runApp( + ProviderScope( + observers: [ + MyObserver(), + ], + child: MyApp(), + ) +); +``` + +```dart +final container = ProviderContainer( + observers: [ + MyObserver(), + ], +); +``` diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/provider_observer/provider_observer.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/provider_observer/provider_observer.dart new file mode 100644 index 000000000..dd75afb01 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/provider_observer/provider_observer.dart @@ -0,0 +1,43 @@ +// ignore_for_file: avoid_print + +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +/* SNIPPET START */ +class MyObserver extends ProviderObserver { + @override + void didAddProvider( + ProviderBase provider, + Object? value, + ProviderContainer container, + ) { + print('Provider $provider was initialized with $value'); + } + + @override + void didDisposeProvider( + ProviderBase provider, + ProviderContainer container, + ) { + print('Provider $provider was disposed'); + } + + @override + void didUpdateProvider( + ProviderBase provider, + Object? previousValue, + Object? newValue, + ProviderContainer container, + ) { + print('Provider $provider updated from $previousValue to $newValue'); + } + + @override + void providerDidFail( + ProviderBase provider, + Object error, + StackTrace stackTrace, + ProviderContainer container, + ) { + print('Provider $provider threw $error at $stackTrace'); + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects.mdx b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects.mdx new file mode 100644 index 000000000..7526dd86a --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects.mdx @@ -0,0 +1,407 @@ +--- +title: 부가 작업 수행(Performing side effects) +version: 1 +--- + +import { Link } from "../../../../../src/components/Link"; +import { AutoSnippet, When } from "../../../../../src/components/CodeSnippet"; +import Legend, { colors } from "./first_request/legend/legend"; +import todoListProvider from "./side_effects/todo_list_provider"; +import todoListNotifier from "./side_effects/todo_list_notifier"; +import todoListNotifierAddTodo from "./side_effects/todo_list_notifier_add_todo"; +import consumerAddTodoCall from "!!raw-loader!./side_effects/raw/consumer_add_todo_call.dart"; +import restAddTodo from "!!raw-loader!./side_effects/raw/rest_add_todo.dart"; +import invalidateSelfAddTodo from "!!raw-loader!./side_effects/raw/invalidate_self_add_todo.dart"; +import manualAddTodo from "!!raw-loader!./side_effects/raw/manual_add_todo.dart"; +import mutableManualAddTodo from "!!raw-loader!./side_effects/raw/mutable_manual_add_todo.dart"; +import renderAddTodo from "./side_effects/render_add_todo"; + +지금까지는 데이터를 가져오는 방법(일명 _GET_ HTTP 요청 수행)만 살펴봤습니다. +하지만 _POST_ 요청과 같은 부수적 효과(side-effect)는 어떨까요? + +애플리케이션은 종종 CRUD(생성, 읽기, 업데이트, 삭제) API를 구현합니다. +이 경우 일반적으로 업데이트 요청(일반적으로 _POST_)은 로컬 캐시를 업데이트해야 합니다. +로컬 캐시도 업데이트하여 UI에 새 상태가 반영되도록 하는 것이 일반적입니다. + +문제는 consumer 내에서 provider의 상태를 어떻게 업데이트할 수 있느냐는 것입니다. +당연히 providers는 자신의 상태(state)를 수정할 수 있는 방법을 노출하지 않습니다. +이는 상태가 통제된 방식으로만 수정되도록 하고 관심사 분리(separation of concerns)를 보장하기 위한 설계입니다. +대신 providers는 자신의 상태를 수정할 수 있는 방법을 명시적으로 노출해야 합니다. + +이를 위해 새로운 개념을 사용할 것입니다: Notifiers. +이 새로운 개념을 보여드리기 위해 좀 더 발전된 예를 사용하겠습니다: 할일 목록(to-do list)입니다. + +## Notifier 정의하기 + +이 시점에서 우리가 이미 알고 있는 것부터 시작하겠습니다: 간단한 _GET_ 요청입니다. +앞서 에서 보았듯이, 글쓰기를 통해 할일 목록을 가져올 수 있습니다: + + + +이제 할 일 목록을 가져왔으니 새 할 일을 추가하는 방법을 살펴봅시다. +이를 위해서는 상태(state) 수정을 위한 공개 API를 노출하도록 provider를 수정해야 합니다. +이 작업은 provider를 "notifier"라고 부르는 것으로 변환하여 수행합니다. + +Notifiers는 providers의 "상태저장 위젯(stateful widget)"입니다. +provider를 정의하는 문법을 약간 수정해야 합니다. +이 새로운 문법은 다음과 같습니다: + + +(MyNotifier.new); + +class MyNotifier extends SomeNotifier { + @override + Result build() { + + } + + +}`} + annotations={[ + { + offset: 6, + length: 4, + label: "provider 변수(variable)", + description: <> + +이 변수는 provider와 상호 작용하는 데 사용됩니다. +변수는 final이고 "top-level"(global)이어야 합니다. + + + }, + { + offset: 13, + length: 20, + label: "provider 타입(type)", + description: <> + +일반적으로 `NotifierProvider`, `AsyncNotifierProvider` 또는 `StreamNotifierProvider` 중 하나입니다. +사용되는 provider 유형은 함수의 반환값(return value)에 따라 달라집니다. +예를 들어, `Future`를 만들려면 `AsyncNotifierProvider`가 필요할 것입니다. + +가장 많이 사용하는 provider는 `AsyncNotifierProvider`입니다. + +:::tip +"어떤 provider를 선택해야 하나"라는 관점에서 생각하지 마세요. +대신 "내가 무엇을 반환하고 싶은가"라는 관점에서 생각하세요. +provider 타입은 자연스럽게 따라올 것입니다. +::: + + + }, + { + offset: 33, + length: 13, + label: "수정자(Modifiers) (옵션)", + description: <> + +종종 provider 유형 뒤에 "수정자(modifier)"가 표시될 수 있습니다. +수정자(modifier)는 선택 사항이며, 타입에 안전한(type-safe) 방식으로 provider의 동작을 조정하는 데 사용됩니다. + +현재 두 가지 수정자(modifier)를 사용할 수 있습니다: + +- `autoDispose`를 설정하면 provider가 더 이상 사용되지 않을 때 자동으로 캐시를 지웁니다. + 도 참조하세요. +- provider에게 인자(arguments)를 전달할 수 있는 `family`. + 도 참조하세요. + + + }, + { + offset: 67, + length: 14, + label: "Notifier의 생성자 (Constructor)", + description: <> + +"notifier providers"의 매개변수는 "notifier"를 인스턴스화할 것으로 예상되는 함수입니다. +일반적으로 "생성자 분리(constructor tear-off)"형식이여야 합니다. + + + }, + { + offset: 86, + length: 16, + label: "Notifier", + description: <> + +`NotifierProvider`가 "StatefulWidget" 클래스인 경우, 이 부분은 `State` 클래스입니다. + +이 클래스는 provider의 상태(state)를 수정하는 방법을 노출하는 역할을 담당합니다. +이 클래스의 공개 메서드는 `ref.read(yourProvider.notifier).yourMethod()`를 사용하여 소비자(consumers)가 액세스할 수 있습니다. + +:::note +UI에서 상태(state)가 변경되었음을 알 수 없기 때문에, Notifiers에는 내장된 `state` 외에 다른 공용 프로퍼티가 없어야 합니다. +::: + + + }, + { + offset: 111, + length: 12, + label: "Notifier 타입(type)", + description: <> + +Notifier가 확장(extend)하는 기반(base) 클래스는 provider + modifiers의 클래스와 일치해야 합니다. +몇 가지 예를 들면 다음과 같습니다: + +- NotifierProvider -> Notifier +- AsyncNotifierProvider -> AsyncNotifier +- AsyncNotifierProvider. + autoDispose -> + AutoDispose + + AsyncNotifier +- AsyncNotifierProvider. + autoDispose. + family + -> AutoDispose + Family + AsyncNotifier + +이 작업을 더 간단하게 하려면 올바른 유형을 자동으로 추론하는 코드 생성기(code generator)를 사용하는 것이 좋습니다. + + + }, + { + offset: 136, + length: 54, + label: "build 메소드(method)", + description: <> + +모든 notifiers는 `build` 메서드를 재정의(override)해야 합니다. +이 메서드는 일반적으로 notifier가 아닌 provider(non-notifier provider)에서 로직을 넣는 위치에 해당합니다. + +이 메서드는 직접 호출해서는 안 됩니다. + + + }, +]} +/> + + + + + + + } + + +}`} + annotations={[ + { + offset: 0, + length: 9, + label: "어노테이션(annotation)", + description: <> + +모든 providers는 `@riverpod` 또는 `@Riverpod()`로 어노테이션해야 합니다. +이 어노테이션은 전역 함수나 클래스에 배치할 수 있습니다. +이 어노테이션을 통해 프로바이더를 구성할 수 있습니다. + +예를 들어, `@Riverpod(keepAlive: true)`를 작성하여 "auto-dispose"(나중에 살펴볼 것임)를 비활성화할 수 있습니다. + + + }, + { + offset: 10, + length: 16, + label: "Notifier", + description: <> + +`@riverpod` 어노테이션이 클래스에 배치되면 해당 클래스를 "Notifier"라고 부릅니다. +클래스는 `_$NotifierName`을 확장해야 하며, 여기서 `NotifierName`은 클래스 이름입니다. + +Notifiers는 provider의 상태(state)를 수정하는 메서드를 노출할 책임이 있습니다. +이 클래스의 공개 메서드는 `ref.read(yourProvider.notifier).yourMethod()`를 사용하여 consumer가 액세스할 수 있습니다. + +:::note +UI에서 상태가 변경되었음을 알 수 있는 수단이 없기 때문에, Notifiers에는 기본 제공 'state' 외에 공개 속성이 없어야 합니다. +::: + + + }, + { + offset: 52, + length: 54, + label: "The build method", + description: <> + +모든 notifiers는 `build` 메서드를 재정의(override)해야 합니다. +이 메서드는 일반적으로 notifier가 아닌 provider(non-notifier provider)에서 로직을 넣는 위치에 해당합니다. + +이 메서드는 직접 호출해서는 안 됩니다. + + + }, +]} +/> + + +참고로, 이 새로운 문법을 이전에 보았던 문법과 비교하려면 를 확인하면 됩니다. + +:::info +`bbuild` 이외의 메서드가 없는 Notifier는 앞서 본 문법을 사용하는 것과 동일합니다. +에 표시된 문법은 UI에서 수정할 방법이 없는 notifiers에 대한 간략한 표현이라고 볼 수 있습니다. +::: + +이제 문법을 살펴봤으니 이전에 정의한 provider를 notifier으로 변환하는 방법을 살펴보겠습니다: +Now that we've seen the syntax, let's see how to convert our previously defined provider to a notifier: + + + +위젯 내에서 provider를 읽는 방법은 변경되지 않았습니다. +이전 구문과 마찬가지로 `ref.watch(todoListProvider)`를 계속 사용할 수 있습니다. + +:::caution +notifier의 생성자에 로직을 넣지 마세요. +`ref` 및 기타 프로퍼티는 아직 사용할 수 없으므로 Notifier에는 생성자가 없어야 합니다. +대신 `build` 메서드에 로직을 넣으세요. + +```dart +class MyNotifier extends ... { + MyNotifier() { + // ❌ 이렇게 하지 마세요. + // 이 경우 예외가 발생합니다. + state = AsyncValue.data(42); + } + + @override + Result build() { + // ✅ 대신 이렇게 하세요. + state = AsyncValue.data(42); + } +} +``` + +::: + +## _POST_ 요청을 수행하는 메서드 노출하기 + +이제 Notifier가 생겼으니 부가 작업(side-effect)을 수행할 수 있는 메서드를 추가할 수 있습니다. +그러한 부가 작업 중 하나는 클라이언트가 새 할 일을 _POST_하도록 하는 것입니다. +notifier에 `addTodo` 메서드를 추가하면 그렇게 할 수 있습니다: + + + +그런 다음 에서 보았던 것과 동일한 `Consumer`/`ConsumerWidget`을 사용하여 UI에서 이 메서드를 호출할 수 있습니다: + + + +:::info +메서드를 호출할 때 `ref.watch` 대신 `ref.read`를 사용하고 있는 것을 주목하세요. +`ref.watch`도 기술적으로는 작동할 수 있지만, "onPressed"와 같은 이벤트 핸들러에서 로직이 수행될 때는 `ref.read`를 사용하는 것이 좋습니다. +::: + +이제 버튼을 누르면 _POST_ 요청을 하는 버튼이 생겼습니다. +그러나 현재로서는 새 할 일 목록을 반영하도록 UI가 업데이트되지 않습니다. +로컬 캐시가 서버의 상태와 일치하기를 원할 것입니다. + +장단점이 있는 몇 가지 방법이 있습니다. + +### API 응답에 맞춰 로컬 캐시 업데이트하기 + +일반적인 백엔드 관행은 _POST_ 요청이 리소스의 새 상태를 반환하도록 하는 것입니다. +특히, 저희 API는 새 할 일을 추가한 후 새 할 일 목록을 반환합니다. +이를 수행하는 한 가지 방법은 `state = AsyncData(response)`를 작성하는 것입니다: + + + +:::tip 장점 + +- UI는 가능한 가장 최신 상태로 유지됩니다. + 다른 사용자가 할 일을 추가하면 우리도 볼 수 있습니다. +- 서버가 진실의 원천입니다. + 이 접근 방식을 사용하면 클라이언트는 할 일 목록에서 새 할 일을 어디에 삽입해야 하는지 알 필요가 없습니다. +- 단 한 번의 네트워크 요청만 필요합니다. + +::: + +:::danger 단점 + +- 이 접근 방식은 서버가 특정 방식으로 구현된 경우에만 작동합니다. + 서버가 새 상태를 반환하지 않으면 이 접근 방식은 작동하지 않습니다. +- 필터/소팅이 있는 경우와 같이 연결된 _GET_ 요청이 더 복잡한 경우에는 여전히 작동하지 않을 수 있습니다. + +::: + +### ref.invalidateSelf()`를 사용하여 provider를 새로고침 + +한 가지 옵션은 provider가 _GET_ 요청을 다시 실행하도록 하는 것입니다. +이는 _POST_ 요청 뒤에 `ref.invalidateSelf()`를 호출하여 수행할 수 있습니다: + + + +:::tip 장점 + +- UI는 가능한 가장 최신 상태로 유지됩니다. + 다른 사용자가 할 일을 추가하면 우리도 볼 수 있습니다. +- 서버가 진실의 원천입니다. + 이 접근 방식을 사용하면 클라이언트는 할 일 목록에서 새 할 일을 어디에 삽입해야 하는지 알 필요가 없습니다. +- 이 접근 방식은 서버 구현에 관계없이 작동합니다. + 필터/소팅이 포함된 경우와 같이 _GET_ 요청이 더 복잡한 경우에 특히 유용할 수 있습니다. + +::: + +:::danger 단점 + +- 이 접근 방식은 비효율적일 수 있는 추가 _GET_ 요청을 수행합니다. + +::: + +### 로컬 캐시 수동 업데이트 + +또 다른 옵션은 로컬 캐시를 수동으로 업데이트하는 것입니다. +여기에는 백엔드의 동작을 모방하는 작업이 포함됩니다. +예를 들어, 백엔드가 새 항목을 처음에 삽입하는지 아니면 마지막에 삽입하는지 알아야 합니다. + + + +:::info +이 예제에서는 불변(immutable) 상태를 사용합니다. 이는 필수는 아니지만 권장 사항입니다. +자세한 내용은 를 참조하세요. +대신 변경 가능한 상태를 사용하려는 경우 다른 방법을 사용할 수 있습니다: + + + +::: + +:::tip 장점 + +- 이 접근 방식은 서버 구현에 관계없이 작동합니다. +- 네트워크 요청은 단 한 번만 필요합니다. + +::: + +:::danger 단점 + +- 로컬 캐시가 서버의 상태와 일치하지 않을 수 있습니다. + 다른 사용자가 할 일을 추가한 경우 이를 볼 수 없습니다. +- 이 접근 방식은 백엔드의 로직을 효과적으로 복제하고 구현하기가 더 복잡할 수 있습니다. + +::: + +## 더 알아보기: 스피너(spinner) 표시 및 오류 처리(error handling) + +지금까지 살펴본 바에 따르면 버튼을 누르면 _POST_ 요청을 하고 요청이 완료되면 변경 사항을 반영하여 UI가 업데이트되는 버튼이 있습니다. +하지만 현재로서는 요청이 수행되고 있다는 표시도 없고, 요청이 실패할 경우 어떤 정보도 표시되지 않습니다. + +한 가지 방법은 로컬 위젯 상태에 `addTodo`가 반환한 Future를 저장한 다음 해당 Future를 수신하여 스피너 또는 오류 메시지를 표시하는 것입니다. +이 시나리오에서는 [flutter_hooks](https://pub.dev/packages/flutter_hooks)가 유용합니다. +물론 `StatefulWidget`을 대신 사용할 수도 있습니다. + +다음 스니펫은 작업이 보류 중인 동안 진행률 표시기를 보여줍니다. +그리고 실패하면 버튼을 빨간색으로 렌더링합니다: + +![A button which turns red when the operation failed](/img/essentials/side_effects/spinner.gif) + + diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier.dart new file mode 100644 index 000000000..2f3d450ed --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier.dart @@ -0,0 +1,28 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'todo_list_notifier.freezed.dart'; +part 'todo_list_notifier.g.dart'; + +@freezed +class Todo with _$Todo { + factory Todo({ + required String description, + @Default(false) bool completed, + }) = _Todo; + + factory Todo.fromJson(Map json) => _$TodoFromJson(json); +} + +/* SNIPPET START */ +@riverpod +class TodoList extends _$TodoList { + @override + Future> build() async { + // 이전에 FutureProvider에 있던 로직이 이제 빌드 메서드에 있습니다. + return [ + Todo(description: 'Learn Flutter', completed: true), + Todo(description: 'Learn Riverpod'), + ]; + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier.freezed.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier.freezed.dart new file mode 100644 index 000000000..3326f9ffa --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier.freezed.dart @@ -0,0 +1,166 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'todo_list_notifier.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +Todo _$TodoFromJson(Map json) { + return _Todo.fromJson(json); +} + +/// @nodoc +mixin _$Todo { + String get description => throw _privateConstructorUsedError; + bool get completed => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $TodoCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $TodoCopyWith<$Res> { + factory $TodoCopyWith(Todo value, $Res Function(Todo) then) = + _$TodoCopyWithImpl<$Res, Todo>; + @useResult + $Res call({String description, bool completed}); +} + +/// @nodoc +class _$TodoCopyWithImpl<$Res, $Val extends Todo> + implements $TodoCopyWith<$Res> { + _$TodoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? description = null, + Object? completed = null, + }) { + return _then(_value.copyWith( + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + completed: null == completed + ? _value.completed + : completed // ignore: cast_nullable_to_non_nullable + as bool, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$TodoImplCopyWith<$Res> implements $TodoCopyWith<$Res> { + factory _$$TodoImplCopyWith( + _$TodoImpl value, $Res Function(_$TodoImpl) then) = + __$$TodoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String description, bool completed}); +} + +/// @nodoc +class __$$TodoImplCopyWithImpl<$Res> + extends _$TodoCopyWithImpl<$Res, _$TodoImpl> + implements _$$TodoImplCopyWith<$Res> { + __$$TodoImplCopyWithImpl(_$TodoImpl _value, $Res Function(_$TodoImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? description = null, + Object? completed = null, + }) { + return _then(_$TodoImpl( + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + completed: null == completed + ? _value.completed + : completed // ignore: cast_nullable_to_non_nullable + as bool, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$TodoImpl implements _Todo { + _$TodoImpl({required this.description, this.completed = false}); + + factory _$TodoImpl.fromJson(Map json) => + _$$TodoImplFromJson(json); + + @override + final String description; + @override + @JsonKey() + final bool completed; + + @override + String toString() { + return 'Todo(description: $description, completed: $completed)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$TodoImpl && + (identical(other.description, description) || + other.description == description) && + (identical(other.completed, completed) || + other.completed == completed)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, description, completed); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$TodoImplCopyWith<_$TodoImpl> get copyWith => + __$$TodoImplCopyWithImpl<_$TodoImpl>(this, _$identity); + + @override + Map toJson() { + return _$$TodoImplToJson( + this, + ); + } +} + +abstract class _Todo implements Todo { + factory _Todo({required final String description, final bool completed}) = + _$TodoImpl; + + factory _Todo.fromJson(Map json) = _$TodoImpl.fromJson; + + @override + String get description; + @override + bool get completed; + @override + @JsonKey(ignore: true) + _$$TodoImplCopyWith<_$TodoImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier.g.dart new file mode 100644 index 000000000..9d12d50c6 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier.g.dart @@ -0,0 +1,42 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'todo_list_notifier.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$TodoImpl _$$TodoImplFromJson(Map json) => _$TodoImpl( + description: json['description'] as String, + completed: json['completed'] as bool? ?? false, + ); + +Map _$$TodoImplToJson(_$TodoImpl instance) => + { + 'description': instance.description, + 'completed': instance.completed, + }; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$todoListHash() => r'c939d438b07da6065dbbcfab86c27ef363bdb76c'; + +/// See also [TodoList]. +@ProviderFor(TodoList) +final todoListProvider = + AutoDisposeAsyncNotifierProvider>.internal( + TodoList.new, + name: r'todoListProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$todoListHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$TodoList = AutoDisposeAsyncNotifier>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier_add_todo.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier_add_todo.dart new file mode 100644 index 000000000..589d4b64e --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier_add_todo.dart @@ -0,0 +1,26 @@ +// ignore_for_file: avoid_print + +import 'dart:convert'; + +import 'package:http/http.dart' as http; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import 'todo_list_notifier.dart'; + +part 'todo_list_notifier_add_todo.g.dart'; + +/* SNIPPET START */ +@riverpod +class TodoList extends _$TodoList { + @override + Future> build() async => [/* ... */]; + + Future addTodo(Todo todo) async { + await http.post( + Uri.https('your_api.com', '/todos'), + // 할 일 개체를 직렬화하여 서버에 POST합니다. + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(todo.toJson()), + ); + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier_add_todo.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier_add_todo.g.dart new file mode 100644 index 000000000..a6c2ce847 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier_add_todo.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'todo_list_notifier_add_todo.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$todoListHash() => r'4008395aaca8f55312f668c0b2a32e7599f82349'; + +/// See also [TodoList]. +@ProviderFor(TodoList) +final todoListProvider = + AutoDisposeAsyncNotifierProvider>.internal( + TodoList.new, + name: r'todoListProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$todoListHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$TodoList = AutoDisposeAsyncNotifier>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_provider.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_provider.dart new file mode 100644 index 000000000..dd8082be4 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_provider.dart @@ -0,0 +1,23 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'todo_list_provider.freezed.dart'; +part 'todo_list_provider.g.dart'; + +@freezed +class Todo with _$Todo { + factory Todo({ + required String description, + @Default(false) bool completed, + }) = _Todo; +} + +/* SNIPPET START */ +@riverpod +Future> todoList(TodoListRef ref) async { + // 네트워크 요청을 시뮬레이션합니다. 이는 일반적으로 실제 API로부터 수신됩니다. + return [ + Todo(description: 'Learn Flutter', completed: true), + Todo(description: 'Learn Riverpod'), + ]; +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_provider.freezed.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_provider.freezed.dart new file mode 100644 index 000000000..26a2d640c --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_provider.freezed.dart @@ -0,0 +1,148 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'todo_list_provider.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +/// @nodoc +mixin _$Todo { + String get description => throw _privateConstructorUsedError; + bool get completed => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $TodoCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $TodoCopyWith<$Res> { + factory $TodoCopyWith(Todo value, $Res Function(Todo) then) = + _$TodoCopyWithImpl<$Res, Todo>; + @useResult + $Res call({String description, bool completed}); +} + +/// @nodoc +class _$TodoCopyWithImpl<$Res, $Val extends Todo> + implements $TodoCopyWith<$Res> { + _$TodoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? description = null, + Object? completed = null, + }) { + return _then(_value.copyWith( + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + completed: null == completed + ? _value.completed + : completed // ignore: cast_nullable_to_non_nullable + as bool, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$TodoImplCopyWith<$Res> implements $TodoCopyWith<$Res> { + factory _$$TodoImplCopyWith( + _$TodoImpl value, $Res Function(_$TodoImpl) then) = + __$$TodoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String description, bool completed}); +} + +/// @nodoc +class __$$TodoImplCopyWithImpl<$Res> + extends _$TodoCopyWithImpl<$Res, _$TodoImpl> + implements _$$TodoImplCopyWith<$Res> { + __$$TodoImplCopyWithImpl(_$TodoImpl _value, $Res Function(_$TodoImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? description = null, + Object? completed = null, + }) { + return _then(_$TodoImpl( + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + completed: null == completed + ? _value.completed + : completed // ignore: cast_nullable_to_non_nullable + as bool, + )); + } +} + +/// @nodoc + +class _$TodoImpl implements _Todo { + _$TodoImpl({required this.description, this.completed = false}); + + @override + final String description; + @override + @JsonKey() + final bool completed; + + @override + String toString() { + return 'Todo(description: $description, completed: $completed)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$TodoImpl && + (identical(other.description, description) || + other.description == description) && + (identical(other.completed, completed) || + other.completed == completed)); + } + + @override + int get hashCode => Object.hash(runtimeType, description, completed); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$TodoImplCopyWith<_$TodoImpl> get copyWith => + __$$TodoImplCopyWithImpl<_$TodoImpl>(this, _$identity); +} + +abstract class _Todo implements Todo { + factory _Todo({required final String description, final bool completed}) = + _$TodoImpl; + + @override + String get description; + @override + bool get completed; + @override + @JsonKey(ignore: true) + _$$TodoImplCopyWith<_$TodoImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_provider.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_provider.g.dart new file mode 100644 index 000000000..455d9a64b --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_provider.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'todo_list_provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$todoListHash() => r'26b30307668c8feefa7cbe3c400b73e6edccbc39'; + +/// See also [todoList]. +@ProviderFor(todoList) +final todoListProvider = AutoDisposeFutureProvider>.internal( + todoList, + name: r'todoListProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$todoListHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef TodoListRef = AutoDisposeFutureProviderRef>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/consumer_add_todo_call.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/consumer_add_todo_call.dart new file mode 100644 index 000000000..f869a1175 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/consumer_add_todo_call.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../raw/todo_list_notifier.dart' show Todo; +import '../raw/todo_list_notifier_add_todo.dart'; + +/* SNIPPET START */ +class Example extends ConsumerWidget { + const Example({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return ElevatedButton( + onPressed: () { + // "ref.read"를 "myProvider.notifier"와 사용하면 + // notifier의 클래스 인스턴스를 얻을 수 있습니다. + // 이를 통해 "addTodo" 메서드를 호출할 수 있습니다. + ref + .read(todoListProvider.notifier) + .addTodo(Todo(description: 'This is a new todo')); + }, + child: const Text('Add todo'), + ); + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/invalidate_self_add_todo.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/invalidate_self_add_todo.dart new file mode 100644 index 000000000..e83b3708f --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/invalidate_self_add_todo.dart @@ -0,0 +1,38 @@ +// ignore_for_file: avoid_print, prefer_final_locals, omit_local_variable_types + +import 'dart:convert'; + +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:http/http.dart' as http; + +import 'todo_list_notifier.dart'; + +final todoListProvider = + AsyncNotifierProvider.autoDispose>( + TodoList.new, +); + +class TodoList extends AutoDisposeAsyncNotifier> { + @override + Future> build() async => [/* ... */]; + + /* SNIPPET START */ + Future addTodo(Todo todo) async { + // API 응답은 신경 쓰지 않습니다. + await http.post( + Uri.https('your_api.com', '/todos'), + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(todo.toJson()), + ); + + // 포스트 요청이 완료되면 로컬 캐시를 더티(dirty)로 표시할 수 있습니다. + // 이렇게 하면 notifier의 "build"가 비동기적으로 다시 호출되고, 이 때 리스너(listener)에게 알림이 전송됩니다. + + ref.invalidateSelf(); + + // (선택 사항) 그런 다음 새 상태가 계산될 때까지 기다릴 수 있습니다. + // 이렇게 하면 새 상태를 사용할 수 있을 때까지 "addTodo"가 완료되지 않습니다. + await future; + } +/* SNIPPET END */ +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/manual_add_todo.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/manual_add_todo.dart new file mode 100644 index 000000000..54e454954 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/manual_add_todo.dart @@ -0,0 +1,39 @@ +// ignore_for_file: avoid_print, prefer_final_locals, omit_local_variable_types + +import 'dart:convert'; + +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:http/http.dart' as http; + +import 'todo_list_notifier.dart'; + +final todoListProvider = + AsyncNotifierProvider.autoDispose>( + TodoList.new, +); + +class TodoList extends AutoDisposeAsyncNotifier> { + @override + Future> build() async => [/* ... */]; + + /* SNIPPET START */ + Future addTodo(Todo todo) async { + // API 응답은 신경 쓰지 않습니다. + await http.post( + Uri.https('your_api.com', '/todos'), + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(todo.toJson()), + ); + + // 그런 다음 로컬 캐시를 수동으로 업데이트할 수 있습니다. 이를 위해서는 이전 상태를 가져와야 합니다. + // 주의: 이전 상태가 여전히 로딩 중이거나 오류 상태일 수 있습니다. + // 이 문제를 우아하게 처리하는 방법은 `this.state` 대신 `this.future`를 읽어서 로딩 상태를 기다리게 하고 + // 상태가 오류 상태인 경우 오류를 발생시키는 것입니다. + final previousState = await future; + + // 그런 다음 새 상태 객체를 생성하여 상태를 업데이트할 수 있습니다. + // 그러면 모든 리스너에게 알림이 전송됩니다. + state = AsyncData([...previousState, todo]); + } +/* SNIPPET END */ +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/mutable_manual_add_todo.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/mutable_manual_add_todo.dart new file mode 100644 index 000000000..aece4d511 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/mutable_manual_add_todo.dart @@ -0,0 +1,34 @@ +// ignore_for_file: avoid_print, prefer_final_locals, omit_local_variable_types + +import 'dart:convert'; + +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:http/http.dart' as http; + +import 'todo_list_notifier.dart'; + +final todoListProvider = AsyncNotifierProvider.autoDispose>( + TodoList.new, +); + +class TodoList extends AutoDisposeAsyncNotifier> { + @override + Future> build() async => [/* ... */]; + + Future addTodo(Todo todo) async { + // API 응답은 신경 쓰지 않습니다. + await http.post( + Uri.https('your_api.com', '/todos'), + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(todo.toJson()), + ); + + /* SNIPPET START */ + final previousState = await future; + // 이전 할 일 목록을 변경합니다. (Mutable) + previousState.add(todo); + // 리스너에게 수동으로 알림을 전송합니다. + ref.notifyListeners(); +/* SNIPPET END */ + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/render_add_todo.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/render_add_todo.dart new file mode 100644 index 000000000..9e97a21a1 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/render_add_todo.dart @@ -0,0 +1,75 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +import 'rest_add_todo.dart'; +import 'todo_list_notifier.dart' show Todo; + +void main() { + runApp( + const ProviderScope(child: MyApp()), + ); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return const MaterialApp(home: Example()); + } +} + +/* SNIPPET START */ +class Example extends ConsumerStatefulWidget { + const Example({super.key}); + + @override + ConsumerState createState() => _ExampleState(); +} + +class _ExampleState extends ConsumerState { + // pending중인 addTodo 작업입니다. 또는 pending중인 작업이 없으면 null입니다. + Future? _pendingAddTodo; + + @override + Widget build(BuildContext context) { + return FutureBuilder( + // pending중인 작업을 수신하여 그에 따라 UI를 업데이트합니다. + future: _pendingAddTodo, + builder: (context, snapshot) { + // 오류 상태가 있는지 여부를 계산합니다. + // 연결 상태(connectionState) 확인은 연산을 다시 시도할 때 처리합니다. + final isErrored = snapshot.hasError && snapshot.connectionState != ConnectionState.waiting; + + return Row( + children: [ + ElevatedButton( + style: ButtonStyle( + // 오류가 있는 경우 버튼이 빨간색으로 표시됩니다. + backgroundColor: MaterialStateProperty.all( + isErrored ? Colors.red : null, + ), + ), + onPressed: () { + // addTodo가 반환한 future를 변수에 보관합니다. + final future = ref.read(todoListProvider.notifier).addTodo(Todo(description: 'This is a new todo')); + + // 해당 future를 로컬 상태에 저장합니다. + setState(() { + _pendingAddTodo = future; + }); + }, + child: const Text('Add todo'), + ), + // 작업이 pending중이므로 진행률 표시기(progress indicator)를 표시합니다. + if (snapshot.connectionState == ConnectionState.waiting) ...[ + const SizedBox(width: 8), + const CircularProgressIndicator(), + ] + ], + ); + }, + ); + } +} +/* SNIPPET END */ \ No newline at end of file diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/render_add_todo_hooks.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/render_add_todo_hooks.dart new file mode 100644 index 000000000..e5412a8cd --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/render_add_todo_hooks.dart @@ -0,0 +1,68 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +import 'rest_add_todo.dart'; +import 'todo_list_notifier.dart' show Todo; + +void main() { + runApp( + const ProviderScope(child: MyApp()), + ); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return const MaterialApp(home: Example()); + } +} + +/* SNIPPET START */ +class Example extends HookConsumerWidget { + const Example({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + // pending중인 addTodo 작업입니다. 또는 pending중인 작업이 없으면 null입니다. + final pendingAddTodo = useState?>(null); + // pending인 작업을 수신하여 그에 따라 UI를 업데이트합니다. + final snapshot = useFuture(pendingAddTodo.value); + + // 오류 상태가 있는지 여부를 계산합니다. + // 연결 상태(connectionState) 확인은 작업이 다시 시도될 때 처리하기 위해 여기에 있습니다. + final isErrored = snapshot.hasError && + snapshot.connectionState != ConnectionState.waiting; + + return Row( + children: [ + ElevatedButton( + style: ButtonStyle( + // 오류가 있는 경우 버튼을 빨간색으로 표시합니다. + backgroundColor: MaterialStateProperty.all( + isErrored ? Colors.red : null, + ), + ), + onPressed: () async { + // addTodo가 반환한 future를 변수에 보관합니다. + final future = ref + .read(todoListProvider.notifier) + .addTodo(Todo(description: 'This is a new todo')); + + // 해당 미래를 로컬 상태에 저장합니다. + pendingAddTodo.value = future; + }, + child: const Text('Add todo'), + ), + // 작업이 pending중이므로 진행률 표시기(progress indicator)를 표시하겠습니다. + if (snapshot.connectionState == ConnectionState.waiting) ...[ + const SizedBox(width: 8), + const CircularProgressIndicator(), + ] + ], + ); + } +} +/* SNIPPET END */ \ No newline at end of file diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/rest_add_todo.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/rest_add_todo.dart new file mode 100644 index 000000000..392756b3d --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/rest_add_todo.dart @@ -0,0 +1,39 @@ +// ignore_for_file: avoid_print, prefer_final_locals, omit_local_variable_types + +import 'dart:convert'; + +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:http/http.dart' as http; + +import 'todo_list_notifier.dart'; + +final todoListProvider = + AsyncNotifierProvider.autoDispose>( + TodoList.new, +); + +class TodoList extends AutoDisposeAsyncNotifier> { + @override + Future> build() async => [/* ... */]; + + /* SNIPPET START */ + Future addTodo(Todo todo) async { + // POST 요청은 새 애플리케이션 상태와 일치하는 List를 반환합니다. + final response = await http.post( + Uri.https('your_api.com', '/todos'), + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(todo.toJson()), + ); + + // API 응답을 디코딩하여 List로 변환합니다. + List newTodos = (jsonDecode(response.body) as List) + .cast>() + .map(Todo.fromJson) + .toList(); + + // 로컬 캐시를 새 상태와 일치하도록 업데이트합니다. + // 그러면 모든 리스너에게 알림이 전송됩니다. + state = AsyncData(newTodos); + } +/* SNIPPET END */ +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/todo_list_notifier.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/todo_list_notifier.dart new file mode 100644 index 000000000..9a6d5f2e5 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/todo_list_notifier.dart @@ -0,0 +1,44 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +class Todo { + Todo({ + required this.description, + this.completed = false, + }); + + factory Todo.fromJson(Map json) { + return Todo( + description: json['description']! as String, + completed: json['completed']! as bool, + ); + } + + final String description; + final bool completed; + + Map toJson() => { + 'description': description, + 'completed': completed, + }; +} + +/* SNIPPET START */ +// We now use AsyncNotifierProvider instead of FutureProvider +final todoListProvider = + AsyncNotifierProvider.autoDispose>( + TodoList.new, +); + +// We use an AsyncNotifier because our logic is asynchronous. +// More specifically, we'll need AutoDisposeAsyncNotifier because +// of the "autoDispose" modifier. +class TodoList extends AutoDisposeAsyncNotifier> { + @override + Future> build() async { + // The logic we previously had in our FutureProvider is now in the build method. + return [ + Todo(description: 'Learn Flutter', completed: true), + Todo(description: 'Learn Riverpod'), + ]; + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/todo_list_notifier_add_todo.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/todo_list_notifier_add_todo.dart new file mode 100644 index 000000000..2023bdc99 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/todo_list_notifier_add_todo.dart @@ -0,0 +1,28 @@ +// ignore_for_file: avoid_print + +import 'dart:convert'; + +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:http/http.dart' as http; + +import 'todo_list_notifier.dart'; + +final todoListProvider = + AsyncNotifierProvider.autoDispose>( + TodoList.new, +); + +/* SNIPPET START */ +class TodoList extends AutoDisposeAsyncNotifier> { + @override + Future> build() async => [/* ... */]; + + Future addTodo(Todo todo) async { + await http.post( + Uri.https('your_api.com', '/todos'), + // We serialize our Todo object and POST it to the server. + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(todo.toJson()), + ); + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/todo_list_provider.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/todo_list_provider.dart new file mode 100644 index 000000000..51c621c40 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/todo_list_provider.dart @@ -0,0 +1,27 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +/* SNIPPET START */ +class Todo { + Todo({ + required this.description, + this.completed = false, + }); + + factory Todo.fromJson(Map json) { + return Todo( + description: json['description'] as String, + completed: json['completed'] as bool, + ); + } + + final String description; + final bool completed; +} + +final todoListProvider = FutureProvider.autoDispose>((ref) async { + // Simulate a network request. This would normally come from a real API + return [ + Todo(description: 'Learn Flutter', completed: true), + Todo(description: 'Learn Riverpod'), + ]; +}); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/render_add_todo.ts b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/render_add_todo.ts new file mode 100644 index 000000000..798feb107 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/render_add_todo.ts @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw/render_add_todo.dart"; +import hooks from "!!raw-loader!./raw/render_add_todo_hooks.dart"; + +export default { + raw: raw, + hooks: hooks, + codegen: raw, + hooksCodegen: hooks, +}; \ No newline at end of file diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/todo_list_notifier.ts b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/todo_list_notifier.ts new file mode 100644 index 000000000..b32ae3b9b --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/todo_list_notifier.ts @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw/todo_list_notifier.dart"; +import codegen from "!!raw-loader!./codegen/todo_list_notifier.dart"; + +export default { + raw: raw, + hooks: raw, + codegen: codegen, + hooksCodegen: codegen, +}; \ No newline at end of file diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/todo_list_notifier_add_todo.ts b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/todo_list_notifier_add_todo.ts new file mode 100644 index 000000000..62fb0e0fc --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/todo_list_notifier_add_todo.ts @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw/todo_list_notifier_add_todo.dart"; +import codegen from "!!raw-loader!./codegen/todo_list_notifier_add_todo.dart"; + +export default { + raw: raw, + hooks: raw, + codegen: codegen, + hooksCodegen: codegen, +}; \ No newline at end of file diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/todo_list_provider.ts b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/todo_list_provider.ts new file mode 100644 index 000000000..de7789c0b --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/side_effects/todo_list_provider.ts @@ -0,0 +1,7 @@ +import raw from "!!raw-loader!./raw/todo_list_provider.dart"; +import codegen from "!!raw-loader!./codegen/todo_list_provider.dart"; + +export default { + raw: raw, + codegen: codegen, +}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing.mdx b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing.mdx new file mode 100644 index 000000000..fe268049e --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing.mdx @@ -0,0 +1,148 @@ +--- +title: providers 테스트하기 +--- + +import { AutoSnippet, When } from "../../../../../src/components/CodeSnippet"; +import createContainer from "!!raw-loader!./testing/create_container.dart"; +import unitTest from "!!raw-loader!./testing/unit_test.dart"; +import widgetTest from "!!raw-loader!./testing/widget_test.dart"; +import fullWidgetTest from "!!raw-loader!./testing/full_widget_test.dart"; +import widgetContainerOf from "!!raw-loader!./testing/widget_container_of.dart"; +import providerToMock from "./testing/provider_to_mock"; +import mockProvider from "!!raw-loader!./testing/mock_provider.dart"; +import autoDisposeListen from "!!raw-loader!./testing/auto_dispose_listen.dart"; +import listenProvider from "!!raw-loader!./testing/listen_provider.dart"; +import awaitFuture from "!!raw-loader!./testing/await_future.dart"; +import notifierMock from "./testing/notifier_mock"; + +Riverpod API의 핵심은 provider를 개별적으로 테스트할 수 있는 기능입니다. + +적절한 테스트 스위트를 위해서는 몇 가지 극복해야 할 과제가 있습니다: + +- 테스트는 상태를 공유해서는 안 됩니다. 즉, 새 테스트가 이전 테스트의 영향을 받지 않아야 합니다. +- 테스트는 원하는 상태를 얻기 위해 특정 기능을 모의할 수 있는 기능을 제공해야 합니다. +- 테스트 환경은 가능한 한 실제 환경과 유사해야 합니다. + +다행히도 Riverpod를 사용하면 이러한 목표를 모두 쉽게 달성할 수 있습니다. + +## 테스트 설정하기 + +Riverpod로 테스트를 정의할 때는 크게 두 가지 시나리오가 있습니다: + +- 일반적으로 Flutter 종속성이 없는 단위 테스트. + 이는 provider의 동작을 단독으로 테스트할 때 유용할 수 있습니다. +- 위젯 테스트: 일반적으로 Flutter 종속성이 있는 위젯 테스트. + 공급자를 사용하는 위젯의 동작을 테스트하는 데 유용할 수 있습니다. + +### 단위 테스트 + +단위 테스트는 [package:test](https://pub.dev/packages/test)의 `test` 함수를 사용하여 정의합니다. + +다른 테스트와 가장 큰 차이점은 `ProviderContainer` 객체를 생성한다는 점입니다. +이 객체를 사용하면 테스트가 provider와 상호 작용할 수 있습니다. + +`ProviderContainer` 객체를 생성하고 폐기하기 위한 테스트 유틸리티를 만드는 것이 좋습니다: + + + +그런 다음 이 유틸리티를 사용하여 `test`를 정의할 수 있습니다: + + + +이제 ProviderContainer가 생겼으니 이를 사용하여 provider를 읽을 수 있습니다: + +- provider의 현재 값을 읽기위해 `container.read` 사용. +- provider를 청취하고, 변경을 통지받기 위해 `container.listen` 사용. + +:::caution +provider가 자동으로 폐기될 때 `container.read`를 사용할 때는 주의하세요. +provider가 리스닝되지 않으면 테스트 도중에 provider의 상태가 파괴될 가능성이 있습니다. + +이 경우 `container.listen`을 사용하는 것을 고려해 보세요. +이 반환값은 어쨌든 provider의 현재 값을 읽을 수 있게 해주지만, +테스트 도중에 provider가 폐기되지 않도록 보장합니다: + + +::: + +### 위젯 테스트 + +위젯 테스트는 [package:flutter_test](https://pub.dev/packages/flutter_test)의 `testWidgets` 함수를 사용하여 정의합니다. + +이 경우 일반적인 위젯 테스트와 가장 큰 차이점은 `tester.pumpWidget`의 루트에 `ProviderScope` 위젯을 추가해야 한다는 점입니다: + + + +이는 Flutter 앱에서 Riverpod을 활성화할 때 하는 작업과 유사합니다. + +그런 다음 `tester`를 사용하여 위젯과 상호 작용할 수 있습니다. +또는 provider와 상호 작용하고 싶다면 `ProviderContainer`를 얻을 수 있습니다. +이는 `ProviderScope.containerOf(buildContext)`를 사용하여 얻을 수 있습니다. +따라서 `tester`를 사용하면 다음과 같이 작성할 수 있습니다: + + + +그런 다음 이를 사용하여 provider를 읽을 수 있습니다. 다음은 전체 예제입니다: + + + +## provider 모킹하기(Mocking) + +지금까지 테스트를 설정하는 방법과 provider와의 기본적인 상호 작용에 대해 살펴보았습니다. +하지만 경우에 따라서는 provider를 모킹(mock)하고 싶을 수도 있습니다. + +멋진 부분: 추가 설정 없이 모든 공급자를 기본적으로 모킹할 수 있습니다. +이는 `ProviderScope` 또는 `ProviderContainer`에 `overrides` 매개변수를 지정하면 가능합니다. + +다음 provider를 살펴봅시다: + + + +다음을 사용하여 모킹해 볼 수 있습니다: + + + +## provider 변경 사항 감시(Spying) + +테스트에서 `ProviderContainer`를 얻었으므로 이를 사용하여 provider를 "listen"할 수 있습니다: + + + +그런 다음 이를 [mockito](https://pub.dev/packages/mockito) 또는 [mocktail](https://pub.dev/packages/mocktail)과 같은 패키지와 결합하여 해당 패키지의 `verify` API를 사용할 수 있습니다. +또는 더 간단하게는 목록에 모든 변경 사항을 추가하고 어설트(assert)할 수 있습니다. + +## 비동기 provider를 기다리기 + +Riverpod에서는 provider가 Future/Stream을 반환하는 경우가 매우 흔합니다. +이 경우 테스트에서 해당 비동기 연산이 완료될 때까지 기다려야 할 가능성이 있습니다. + +이를 위한 한 가지 방법은 프로바이더의 '.future'를 읽는 것입니다: + + + +## Notifiers 모킹하기 + +일반적으로 Notifiers를 모의하는 것은 권장하지 않습니다. +그 대신에, Notifier의 로직에 어느 정도 추상화 수준을 도입하여 그 추상화를 모킹할 수 있도록 해야 합니다. +예를 들어, Notifier을 모킹하는 대신 Notifier가 데이터를 가져오는 데 사용하는 "repository"를 모킹할 수 있습니다. + +Notifier를 모킹하려는 경우, 모킹을 만들 때 특별히 고려해야 할 사항이 있습니다: +모의 클래스는 반드시 원래 Notifier 베이스 클래스를 서브 클래싱해야 합니다: +인터페이스를 손상시킬 수 있으므로 Notifier를 "implement"할 수 없습니다. + +따라서 Notifier를 모킹할 때는 다음과 같은 mockito 코드를 작성하지 마세요: + +```dart +class MyNotifierMock with Mock implements MyNotifier {} +``` + +대신 다음과 같이 작성하세요: + + + + + +이 기능을 사용하려면 목(Mock)을 모킹하려는 Notifier와 동일한 파일에 배치해야 합니다. +그렇지 않으면 `_$MyNotifier` 클래스에 액세스할 수 없습니다. + + diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/auto_dispose_listen.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/auto_dispose_listen.dart new file mode 100644 index 000000000..f16a2d6c8 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/auto_dispose_listen.dart @@ -0,0 +1,24 @@ +// ignore_for_file: unused_local_variable, avoid_print + +import 'package:flutter_test/flutter_test.dart'; +import 'package:riverpod/riverpod.dart'; + +import 'create_container.dart'; + +final provider = Provider((_) => 'Hello world'); + +void main() { + test('Some description', () { + final container = createContainer(); + /* SNIPPET START */ + final subscription = container.listen(provider, (_, __) {}); + + expect( + // `container.read(provider)`와 동일합니다. + // 그러나 "subscription"이 disposed되지 않는 한 provider는 disposed되지 않습니다. + subscription.read(), + 'Some value', + ); + /* SNIPPET END */ + }); +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/await_future.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/await_future.dart new file mode 100644 index 000000000..10bc7e238 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/await_future.dart @@ -0,0 +1,29 @@ +// ignore_for_file: unused_local_variable + +import 'package:flutter_test/flutter_test.dart'; +import 'package:riverpod/riverpod.dart'; + +import 'create_container.dart'; + +final provider = FutureProvider((_) async => 42); + +void main() { + test('Some description', () async { + // 이 테스트에 대한 ProviderContainer를 생성합니다. + // DO NOT: 테스트 간에 ProviderContainer를 공유하지 마세요. + final container = createContainer(); + + /* SNIPPET START */ + // TODO: 컨테이너를 사용하여 애플리케이션을 테스트합니다. + // 기대는 비동기적이므로 "expectLater"를 사용해야 합니다. + await expectLater( + // "provider"대신 "provider.future"를 읽습니다. + // 이는 비동기 provider에서 가능하며, provider의 value로 resolve되는 future를 반환합니다. + container.read(provider.future), + // future가 예상 값으로 resolve되는지 확인할 수 있습니다. + // 또는 오류에 "throwsA"를 사용할 수 있습니다. + completion('some value'), + ); + /* SNIPPET END */ + }); +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/create_container.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/create_container.dart new file mode 100644 index 000000000..c0aab45e6 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/create_container.dart @@ -0,0 +1,22 @@ +import 'package:riverpod/riverpod.dart'; +import 'package:test/test.dart'; + +/// [ProviderContainer]를 생성하고 +/// 테스트가 끝나면 자동으로 폐기하는 테스트 유틸리티입니다. +ProviderContainer createContainer({ + ProviderContainer? parent, + List overrides = const [], + List? observers, +}) { + // ProviderContainer를 생성하고 선택적으로 매개변수 지정을 허용합니다. + final container = ProviderContainer( + parent: parent, + overrides: overrides, + observers: observers, + ); + + // 테스트가 끝나면 container를 폐기합니다. + addTearDown(container.dispose); + + return container; +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/full_widget_test.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/full_widget_test.dart new file mode 100644 index 000000000..325292aa7 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/full_widget_test.dart @@ -0,0 +1,33 @@ +// ignore_for_file: unused_local_variable + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; + +final provider = Provider((_) => 'some value'); + +class YourWidgetYouWantToTest extends StatelessWidget { + const YourWidgetYouWantToTest({super.key}); + + @override + Widget build(BuildContext context) => const Placeholder(); +} + +/* SNIPPET START */ +void main() { + testWidgets('Some description', (tester) async { + await tester.pumpWidget( + const ProviderScope(child: YourWidgetYouWantToTest()), + ); + + final element = tester.element(find.byType(YourWidgetYouWantToTest)); + final container = ProviderScope.containerOf(element); + + // TODO providers와 상호작용합니다. + expect( + container.read(provider), + 'some value', + ); + }); +} +/* SNIPPET END */ diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/listen_provider.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/listen_provider.dart new file mode 100644 index 000000000..d2e840d9b --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/listen_provider.dart @@ -0,0 +1,22 @@ +// ignore_for_file: unused_local_variable, avoid_print + +import 'package:flutter_test/flutter_test.dart'; +import 'package:riverpod/riverpod.dart'; + +import 'create_container.dart'; + +final provider = Provider((_) => 'Hello world'); + +void main() { + test('Some description', () { + final container = createContainer(); + /* SNIPPET START */ + container.listen( + provider, + (previous, next) { + print('The provider changed from $previous to $next'); + }, + ); + /* SNIPPET END */ + }); +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/mock_provider.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/mock_provider.dart new file mode 100644 index 000000000..d939aa2e7 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/mock_provider.dart @@ -0,0 +1,45 @@ +// ignore_for_file: unused_local_variable + +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'create_container.dart'; +import 'full_widget_test.dart'; +import 'provider_to_mock/raw.dart'; + +void main() { + testWidgets('Some description', (tester) async { + await tester.pumpWidget( + const ProviderScope(child: YourWidgetYouWantToTest()), + ); + /* SNIPPET START */ + // 단위 테스트에서는 이전의 "createContainer" 유틸리티를 재사용합니다. + final container = createContainer( + // 모킹할 provider 목록을 지정할 수 있습니다: + overrides: [ + // 이 경우 "exampleProvider"를 모킹하고 있습니다. + exampleProvider.overrideWith((ref) { + // 이 함수는 provider의 일반적인 초기화 함수입니다. + // 일반적으로 "ref.watch"를 호출하여 초기 상태를 반환하는 곳입니다. + + // 기본값인 "Hello world"를 사용자 정의 값으로 바꾸어 보겠습니다. + // 그러면 `exampleProvider`와 상호 작용하면 이 값이 반환됩니다. + return 'Hello from tests'; + }), + ], + ); + + // ProviderScope를 사용하여 위젯 테스트에서도 동일한 작업을 수행할 수 있습니다: + await tester.pumpWidget( + ProviderScope( + // ProviderScopes에는 정확히 동일한 "overrides" 매개변수가 있습니다. + overrides: [ + // 이전과 동일 + exampleProvider.overrideWith((ref) => 'Hello from tests'), + ], + child: const YourWidgetYouWantToTest(), + ), + ); + /* SNIPPET END */ + }); +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/notifier_mock/codegen.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/notifier_mock/codegen.dart new file mode 100644 index 000000000..ccb009f3f --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/notifier_mock/codegen.dart @@ -0,0 +1,17 @@ +// ignore_for_file: prefer_mixin + +import 'package:mockito/mockito.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +/* SNIPPET START */ +@riverpod +class MyNotifier extends _$MyNotifier { + @override + int build() => throw UnimplementedError(); +} + +// Mock 클래스는 notifier가 사용하는 것에 해당하는 Notifier base-class를 서브클래싱해야 합니다. +class MyNotifierMock extends _$MyNotifier with Mock implements MyNotifier {} +/* SNIPPET END */ diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/notifier_mock/codegen.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/notifier_mock/codegen.g.dart new file mode 100644 index 000000000..7efc9fc64 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/notifier_mock/codegen.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$myNotifierHash() => r'912fa35c2296626fc0825bcbcfc6b6c85958be02'; + +/// See also [MyNotifier]. +@ProviderFor(MyNotifier) +final myNotifierProvider = + AutoDisposeNotifierProvider.internal( + MyNotifier.new, + name: r'myNotifierProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$myNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$MyNotifier = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/notifier_mock/index.ts b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/notifier_mock/index.ts new file mode 100644 index 000000000..9d50c6614 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/notifier_mock/index.ts @@ -0,0 +1,4 @@ +import raw from '!!raw-loader!./raw.dart'; +import codegen from '!!raw-loader!./codegen.dart'; + +export default { raw, codegen }; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/notifier_mock/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/notifier_mock/raw.dart new file mode 100644 index 000000000..e7b1c6f0e --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/notifier_mock/raw.dart @@ -0,0 +1,15 @@ +// ignore_for_file: prefer_mixin + +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:mockito/mockito.dart'; + +/* SNIPPET START */ +class MyNotifier extends Notifier { + @override + int build() => throw UnimplementedError(); +} + +// Your mock needs to subclass the Notifier base-class corresponding +// to whatever your notifier uses +class MyNotifierMock extends Notifier with Mock implements MyNotifier {} +/* SNIPPET END */ diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/provider_to_mock/codegen.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/provider_to_mock/codegen.dart new file mode 100644 index 000000000..2aea771eb --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/provider_to_mock/codegen.dart @@ -0,0 +1,9 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +/* SNIPPET START */ +// 이른 초기화된 provider. +@riverpod +Future example(ExampleRef ref) async => 'Hello world'; +/* SNIPPET END */ diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/provider_to_mock/codegen.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/provider_to_mock/codegen.g.dart new file mode 100644 index 000000000..739f3ea63 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/provider_to_mock/codegen.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$exampleHash() => r'd421d08db0ee9d10af5521159561135d8c5fa57c'; + +/// See also [example]. +@ProviderFor(example) +final exampleProvider = AutoDisposeFutureProvider.internal( + example, + name: r'exampleProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$exampleHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ExampleRef = AutoDisposeFutureProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/provider_to_mock/index.ts b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/provider_to_mock/index.ts new file mode 100644 index 000000000..9d50c6614 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/provider_to_mock/index.ts @@ -0,0 +1,4 @@ +import raw from '!!raw-loader!./raw.dart'; +import codegen from '!!raw-loader!./codegen.dart'; + +export default { raw, codegen }; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/provider_to_mock/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/provider_to_mock/raw.dart new file mode 100644 index 000000000..aad01febe --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/provider_to_mock/raw.dart @@ -0,0 +1,6 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +/* SNIPPET START */ +// An eagerly initialized provider. +final exampleProvider = FutureProvider((ref) async => 'Hello world'); +/* SNIPPET END */ diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/unit_test.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/unit_test.dart new file mode 100644 index 000000000..06caf0b96 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/unit_test.dart @@ -0,0 +1,23 @@ +// ignore_for_file: unused_local_variable + +import 'package:flutter_test/flutter_test.dart'; +import 'package:riverpod/riverpod.dart'; + +import 'create_container.dart'; + +final provider = Provider((_) => 42); + +/* SNIPPET START */ +void main() { + test('Some description', () { + // 이 테스트에 대한 ProviderContainer를 생성합니다. + // DO NOT 테스트 간에 ProviderContainer를 공유하지 마세요. + final container = createContainer(); + + // TODO: use the container to test your application. + expect( + container.read(provider), + equals('some value'), + ); + }); +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/widget_container_of.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/widget_container_of.dart new file mode 100644 index 000000000..61b2ca36b --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/widget_container_of.dart @@ -0,0 +1,15 @@ +// ignore_for_file: unused_local_variable + +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'widget_test.dart'; + +void main() { + testWidgets('Some description', (tester) async { + /* SNIPPET START */ + final element = tester.element(find.byType(YourWidgetYouWantToTest)); + final container = ProviderScope.containerOf(element); + /* SNIPPET END */ + }); +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/widget_test.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/widget_test.dart new file mode 100644 index 000000000..b4afc835c --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/testing/widget_test.dart @@ -0,0 +1,22 @@ +// ignore_for_file: unused_local_variable + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; + +class YourWidgetYouWantToTest extends StatelessWidget { + const YourWidgetYouWantToTest({super.key}); + + @override + Widget build(BuildContext context) => const Placeholder(); +} + +/* SNIPPET START */ +void main() { + testWidgets('Some description', (tester) async { + await tester.pumpWidget( + const ProviderScope(child: YourWidgetYouWantToTest()), + ); + }); +} +/* SNIPPET END */ diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync.mdx b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync.mdx new file mode 100644 index 000000000..39db8dcfe --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync.mdx @@ -0,0 +1,100 @@ +--- +title: 웹소켓 및 동기 실행 +--- + +import { + trimSnippet, + AutoSnippet, + When, +} from "../../../../../src/components/CodeSnippet"; +import syncDefinition from "./websockets_sync/sync_definition"; +import streamProvider from "./websockets_sync/stream_provider"; +import syncConsumer from "!!raw-loader!./websockets_sync/sync_consumer.dart"; +import rawUsage from "!!raw-loader!./websockets_sync/raw_usage.dart"; +import pipeChangeNotifier from "!!raw-loader!./websockets_sync/pipe_change_notifier.dart"; +import sharedPipeChangeNotifier from "!!raw-loader!./websockets_sync/shared_pipe_change_notifier.dart"; +import changeNotifierProvider from "!!raw-loader!./websockets_sync/change_notifier_provider.dart"; + +지금까지는 `Future`를 생성하는 방법만 다루었습니다. +이는 의도적으로 `Future`가 Riverpod 애플리케이션을 빌드하는 방법의 핵심이기 때문입니다. +_하지만_ Riverpod는 필요한 경우 다른 형식도 지원합니다. + +특히 providers는 `Future` 대신 자유롭게 객체를 반환할 수 있습니다: + +- 'Repository' 생성 등 객체를 동기적으로 반환할 수 있습니다. +- 웹소켓을 수신하기 위해 `Stream`을 반환합니다. + +`Future`를 반환하는 것과 `Stream` 또는 객체를 반환하는 것은 전반적으로 매우 유사합니다. +이 페이지는 이러한 사용 사례에 대한 미묘한 차이점과 다양한 팁을 설명하는 페이지라고 생각하시면 됩니다. + +## 동기적으로 객체 반환하기 + +객체를 동기적으로 생성하려면 provice가 Future를 반환하지 않는지 확인하세요: + + + +provider가 객체를 동기적으로 생성하면 객체가 소비되는 방식에 영향을 미칩니다. +특히 동기식 값은 "AsyncValue"로 래핑되지 않습니다: + + + +이 차이로 인해 provice가 에러를 발생시키면 값을 읽으려고(read) 하면 에러가 다시 발생(rethrow)합니다. +또는 `ref.listen`을 사용할 경우 "onError" 콜백이 호출됩니다. + +### 수신 가능(Listenable) 객체 고려 사항 + + + +`ChangeNotifier` 또는 `StateNotifier`과 같은 수신가능 객체는 지원되지 않습니다. +호환성상의 이유로 이러한 객체 중 하나와 상호 작용해야 하는 경우, 한 가지 우회 방법은 해당 알림 메커니즘(notification mechanism)을 Riverpod로 연결(pipe)하는 것입니다. + + + +:::info +이러한 로직이 여러 번 필요한 경우, 공유된 로직에 주목할 가치가 있습니다! "ref" 객체는 컴포저블(composable)하게 설계되었습니다. +이를 통해 공급자에서 dispose/listening 로직을 추출할 수 있습니다: + + +::: + + + + + +코드 생성(code-generation)을 사용하지 않을 경우, +Riverpod은 `ChangeNotifier`와 `StateNotifier`를 즉시 지원하는 "legacy" 프로바이더를 제공합니다: `ChangeNotifierProvider` and `StateNotifierProvider`. +이들을 사용하는 것은 다른 종류의 provider를 사용하는 것과 비슷합니다. 가장 큰 차이점은 둘 다 반환된 객체를 자동으로 수신(listen)하고 폐기(dispose)한다는 점입니다. + +이러한 providers는 새로운 비즈니스 로직에는 권장되지 않습니다. +그러나 `pkg:provider`에서 Riverpod로 마이그레이션할 때와 같이 레거시 코드와 상호 작용할 때는 유용할 수 있습니다. + + + + + +## 스트림 수신하기(Listening) + +최신 애플리케이션의 일반적인 사용 사례는 websocket과 상호 작용하는 것입니다(예: Firebase 또는 GraphQL 구독). +이러한 API와의 상호 작용은 종종 `Stream`을 수신하여 수행됩니다. + +이를 돕기 위해 Riverpod은 `Stream` 객체를 자연스럽게 지원합니다. +`Future` 객체와 마찬가지로 이 객체는 `AsyncValue`로 변환됩니다: + + + +:::info +Riverpod은 RX의 `BehaviorSubject`와 같은 같은 커스텀 `Stream` 구현을 인식하지 못합니다. +따라서 생성시 이미 사용 가능하더라도 `BehaviorSubject`를 반환하면 `value`가 위젯에 동기적으로 노출되지 않습니다. +::: + +## `Stream`/`Future`를 `AsyncValue`로 변환하지 않기 + +기본적으로 Riverpod는 `Stream`과 `Future`를 `AsyncValue`로 변환합니다. +거의 필요하지 않지만, 반환 유형을 `Raw` typedef로 감싸서 이 동작을 비활성화할 수 있습니다. + +:::caution +일반적으로 `AsyncValue` 변환을 비활성화하는 것은 권장하지 않습니다. +자신이 무엇을 하고 있는지 알고 있는 경우에만 그렇게 하세요. +::: + + diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/change_notifier_provider.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/change_notifier_provider.dart new file mode 100644 index 000000000..b285c78d0 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/change_notifier_provider.dart @@ -0,0 +1,11 @@ +// ignore_for_file: omit_local_variable_types + +import 'package:flutter/widgets.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +/* SNIPPET START */ +final myProvider = ChangeNotifierProvider>((ref) { + // ValueNotifier를 수신하고 처리합니다. + // 그러면 위젯은 이 provider를 "ref.watch"하여 업데이트를 수신할 수 있습니다. + return ValueNotifier(0); +}); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/pipe_change_notifier.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/pipe_change_notifier.dart new file mode 100644 index 000000000..63c17b4f7 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/pipe_change_notifier.dart @@ -0,0 +1,21 @@ +// ignore_for_file: omit_local_variable_types + +import 'package:flutter/widgets.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'pipe_change_notifier.g.dart'; + +/* SNIPPET START */ +/// 값이 변경될 때마다 ValueNotifier를 생성하고 리스너를 업데이트하는 provider입니다. +@riverpod +ValueNotifier myListenable(MyListenableRef ref) { + final notifier = ValueNotifier(0); + + // provider가 dispose되면 notifier를 dispose합니다. + ref.onDispose(notifier.dispose); + + // ValueNotifier가 업데이트될 때마다 이 provider의 리스너에게 알립니다. + notifier.addListener(ref.notifyListeners); + + return notifier; +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/pipe_change_notifier.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/pipe_change_notifier.g.dart new file mode 100644 index 000000000..0a79f2e56 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/pipe_change_notifier.g.dart @@ -0,0 +1,28 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'pipe_change_notifier.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$myListenableHash() => r'4cc07df2f47050c4aa761e5467f341ab6c312d09'; + +/// 값이 변경될 때마다 ValueNotifier를 생성하고 리스너를 업데이트하는 provider입니다. +/// +/// Copied from [myListenable]. +@ProviderFor(myListenable) +final myListenableProvider = AutoDisposeProvider>.internal( + myListenable, + name: r'myListenableProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$myListenableHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef MyListenableRef = AutoDisposeProviderRef>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/raw_usage.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/raw_usage.dart new file mode 100644 index 000000000..f5b829c33 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/raw_usage.dart @@ -0,0 +1,28 @@ +// ignore_for_file: omit_local_variable_types, prefer_final_locals, use_key_in_widget_constructors + +import 'package:flutter/widgets.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'raw_usage.g.dart'; + +/* SNIPPET START */ +@riverpod +Raw> rawStream(RawStreamRef ref) { + // "Raw"는 typedef입니다. "Raw" 생성자로 반환값을 Wrap할 필요가 없습니다. + return const Stream.empty(); +} + +class Consumer extends ConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + // 값이 더 이상 AsyncValue로 변환되지 않고, 생성된 스트림이 그대로 반환됩니다. + Stream stream = ref.watch(rawStreamProvider); + return StreamBuilder( + stream: stream, + builder: (context, snapshot) { + return Text('${snapshot.data}'); + }, + ); + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/raw_usage.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/raw_usage.g.dart new file mode 100644 index 000000000..5b898587c --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/raw_usage.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'raw_usage.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$rawStreamHash() => r'7e7c2e8f4f08d33a4d86d60449e143c419ca4822'; + +/// See also [rawStream]. +@ProviderFor(rawStream) +final rawStreamProvider = AutoDisposeProvider>>.internal( + rawStream, + name: r'rawStreamProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$rawStreamHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef RawStreamRef = AutoDisposeProviderRef>>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/shared_pipe_change_notifier.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/shared_pipe_change_notifier.dart new file mode 100644 index 000000000..f22edc35e --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/shared_pipe_change_notifier.dart @@ -0,0 +1,29 @@ +// ignore_for_file: omit_local_variable_types + +import 'package:flutter/widgets.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'shared_pipe_change_notifier.g.dart'; + +/* SNIPPET START */ +extension on Ref { + // 이전 로직을 Ref 확장(extension)으로 옮길 수 있습니다. + // 이렇게 하면 provider 간에 로직을 재사용할 수 있습니다. + T disposeAndListenChangeNotifier(T notifier) { + onDispose(notifier.dispose); + notifier.addListener(notifyListeners); + // 사용 편의성을 높이기 위해 Notifier을 반환합니다. + return notifier; + } +} + +@riverpod +ValueNotifier myListenable(MyListenableRef ref) { + return ref.disposeAndListenChangeNotifier(ValueNotifier(0)); +} + +@riverpod +ValueNotifier anotherListenable(AnotherListenableRef ref) { + return ref.disposeAndListenChangeNotifier(ValueNotifier(42)); +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/shared_pipe_change_notifier.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/shared_pipe_change_notifier.g.dart new file mode 100644 index 000000000..66cb3d39c --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/shared_pipe_change_notifier.g.dart @@ -0,0 +1,42 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'shared_pipe_change_notifier.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$myListenableHash() => r'7096094cd24ed50dbabb9fb9ab64b340176c04bf'; + +/// See also [myListenable]. +@ProviderFor(myListenable) +final myListenableProvider = AutoDisposeProvider>.internal( + myListenable, + name: r'myListenableProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$myListenableHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef MyListenableRef = AutoDisposeProviderRef>; +String _$anotherListenableHash() => r'38bfe5dbf5f148819b3671ad69d15c8e05264c23'; + +/// See also [anotherListenable]. +@ProviderFor(anotherListenable) +final anotherListenableProvider = + AutoDisposeProvider>.internal( + anotherListenable, + name: r'anotherListenableProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$anotherListenableHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef AnotherListenableRef = AutoDisposeProviderRef>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/stream_provider/codegen.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/stream_provider/codegen.dart new file mode 100644 index 000000000..ca81a540e --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/stream_provider/codegen.dart @@ -0,0 +1,34 @@ +// ignore_for_file: omit_local_variable_types, prefer_final_locals, use_key_in_widget_constructors + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +/* SNIPPET START */ +@riverpod +Stream streamExample(StreamExampleRef ref) async* { + // 1초마다 0에서 41 사이의 숫자를 yield합니다. + // 이 값은 Firestore나 GraphQL 등의 스트림으로 대체할 수 있습니다. + for (var i = 0; i < 42; i++) { + yield i; + await Future.delayed(const Duration(seconds: 1)); + } +} + +class Consumer extends ConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + // 스트림을 수신하고 AsyncValue로 변환합니다. + AsyncValue value = ref.watch(streamExampleProvider); + + // 로딩/오류 상태를 처리하고 데이터를 표시하는 데 AsyncValue를 사용할 수 있습니다. + return switch (value) { + AsyncValue(:final error?) => Text('Error: $error'), + AsyncValue(:final valueOrNull?) => Text('$valueOrNull'), + _ => const CircularProgressIndicator(), + }; + } +} +/* SNIPPET END */ \ No newline at end of file diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/stream_provider/codegen.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/stream_provider/codegen.g.dart new file mode 100644 index 000000000..fec43b43a --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/stream_provider/codegen.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$streamExampleHash() => r'ca9993b22f6d587b20c041133cacd28d01933074'; + +/// See also [streamExample]. +@ProviderFor(streamExample) +final streamExampleProvider = AutoDisposeStreamProvider.internal( + streamExample, + name: r'streamExampleProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$streamExampleHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef StreamExampleRef = AutoDisposeStreamProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/stream_provider/index.ts b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/stream_provider/index.ts new file mode 100644 index 000000000..4ee159de8 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/stream_provider/index.ts @@ -0,0 +1,4 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./codegen.dart"; + +export default { raw, codegen }; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/stream_provider/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/stream_provider/raw.dart new file mode 100644 index 000000000..c4beebb71 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/stream_provider/raw.dart @@ -0,0 +1,30 @@ +// ignore_for_file: omit_local_variable_types, prefer_final_locals, use_key_in_widget_constructors + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +/* SNIPPET START */ +final streamExampleProvider = StreamProvider.autoDispose((ref) async* { + // Every 1 second, yield a number from 0 to 41. + // This could be replaced with a Stream from Firestore or GraphQL or anything else. + for (var i = 0; i < 42; i++) { + yield i; + await Future.delayed(const Duration(seconds: 1)); + } +}); + +class Consumer extends ConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + // The stream is listened to and converted to an AsyncValue. + AsyncValue value = ref.watch(streamExampleProvider); + + // We can use the AsyncValue to handle loading/error states and show the data. + return switch (value) { + AsyncValue(:final error?) => Text('Error: $error'), + AsyncValue(:final valueOrNull?) => Text('$valueOrNull'), + _ => const CircularProgressIndicator(), + }; + } +} +/* SNIPPET END */ \ No newline at end of file diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_consumer.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_consumer.dart new file mode 100644 index 000000000..4864c30da --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_consumer.dart @@ -0,0 +1,19 @@ +// ignore_for_file: omit_local_variable_types, prefer_final_locals + +import 'package:flutter/widgets.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'sync_definition/raw.dart'; + +void main() { +/* SNIPPET START */ + Consumer( + builder: (context, ref, child) { + // 값은 "AsyncValue"로 래핑되지 않습니다. + int value = ref.watch(synchronousExampleProvider); + + return Text('$value'); + }, + ); +/* SNIPPET END */ +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_definition/codegen.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_definition/codegen.dart new file mode 100644 index 000000000..b18b8f76e --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_definition/codegen.dart @@ -0,0 +1,10 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +/* SNIPPET START */ +@riverpod +int synchronousExample(SynchronousExampleRef ref) { + return 0; +} +/* SNIPPET END */ \ No newline at end of file diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_definition/codegen.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_definition/codegen.g.dart new file mode 100644 index 000000000..9d331d63e --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_definition/codegen.g.dart @@ -0,0 +1,28 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$synchronousExampleHash() => + r'98df96e07d554683041f668c06b36f183ff534c1'; + +/// See also [synchronousExample]. +@ProviderFor(synchronousExample) +final synchronousExampleProvider = AutoDisposeProvider.internal( + synchronousExample, + name: r'synchronousExampleProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$synchronousExampleHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef SynchronousExampleRef = AutoDisposeProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_definition/index.ts b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_definition/index.ts new file mode 100644 index 000000000..4ee159de8 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_definition/index.ts @@ -0,0 +1,4 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./codegen.dart"; + +export default { raw, codegen }; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_definition/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_definition/raw.dart new file mode 100644 index 000000000..9c64294a0 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_definition/raw.dart @@ -0,0 +1,7 @@ +import 'package:riverpod/riverpod.dart'; + +/* SNIPPET START */ +final synchronousExampleProvider = Provider.autoDispose((ref) { + return 0; +}); +/* SNIPPET END */ \ No newline at end of file diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/family/family.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/family/family.dart new file mode 100644 index 000000000..4d49c552a --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/family/family.dart @@ -0,0 +1,11 @@ +import 'dart:math'; + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'family.g.dart'; +/* SNIPPET START */ + +@riverpod +int random(RandomRef ref, {required int seed, required int max}) { + return Random(seed).nextInt(max); +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/family/family.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/family/family.g.dart new file mode 100644 index 000000000..528729ff0 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/family/family.g.dart @@ -0,0 +1,174 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'family.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$randomHash() => r'517b12aad4df7b31f8872b89af74e7880377b2ea'; + +/// Copied from Dart SDK +class _SystemHash { + _SystemHash._(); + + static int combine(int hash, int value) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + value); + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); + return hash ^ (hash >> 6); + } + + static int finish(int hash) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + // ignore: parameter_assignments + hash = hash ^ (hash >> 11); + return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); + } +} + +/// See also [random]. +@ProviderFor(random) +const randomProvider = RandomFamily(); + +/// See also [random]. +class RandomFamily extends Family { + /// See also [random]. + const RandomFamily(); + + /// See also [random]. + RandomProvider call({ + required int seed, + required int max, + }) { + return RandomProvider( + seed: seed, + max: max, + ); + } + + @override + RandomProvider getProviderOverride( + covariant RandomProvider provider, + ) { + return call( + seed: provider.seed, + max: provider.max, + ); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'randomProvider'; +} + +/// See also [random]. +class RandomProvider extends AutoDisposeProvider { + /// See also [random]. + RandomProvider({ + required int seed, + required int max, + }) : this._internal( + (ref) => random( + ref as RandomRef, + seed: seed, + max: max, + ), + from: randomProvider, + name: r'randomProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$randomHash, + dependencies: RandomFamily._dependencies, + allTransitiveDependencies: RandomFamily._allTransitiveDependencies, + seed: seed, + max: max, + ); + + RandomProvider._internal( + super._createNotifier, { + required super.name, + required super.dependencies, + required super.allTransitiveDependencies, + required super.debugGetCreateSourceHash, + required super.from, + required this.seed, + required this.max, + }) : super.internal(); + + final int seed; + final int max; + + @override + Override overrideWith( + int Function(RandomRef provider) create, + ) { + return ProviderOverride( + origin: this, + override: RandomProvider._internal( + (ref) => create(ref as RandomRef), + from: from, + name: null, + dependencies: null, + allTransitiveDependencies: null, + debugGetCreateSourceHash: null, + seed: seed, + max: max, + ), + ); + } + + @override + AutoDisposeProviderElement createElement() { + return _RandomProviderElement(this); + } + + @override + bool operator ==(Object other) { + return other is RandomProvider && other.seed == seed && other.max == max; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, seed.hashCode); + hash = _SystemHash.combine(hash, max.hashCode); + + return _SystemHash.finish(hash); + } +} + +mixin RandomRef on AutoDisposeProviderRef { + /// The parameter `seed` of this provider. + int get seed; + + /// The parameter `max` of this provider. + int get max; +} + +class _RandomProviderElement extends AutoDisposeProviderElement + with RandomRef { + _RandomProviderElement(super.provider); + + @override + int get seed => (origin as RandomProvider).seed; + @override + int get max => (origin as RandomProvider).max; +} +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/family/index.tsx b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/family/index.tsx new file mode 100644 index 000000000..fa391f61a --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/family/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./family.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/family/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/family/raw.dart new file mode 100644 index 000000000..68b84d40d --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/family/raw.dart @@ -0,0 +1,27 @@ +import 'dart:math'; + +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +@immutable +abstract class Equatable { + const Equatable(); + + List get props; +} + +/* SNIPPET START */ +class ParamsType extends Equatable { + const ParamsType({required this.seed, required this.max}); + + final int seed; + final int max; + + @override + List get props => [seed, max]; +} + +final randomProvider = + Provider.family.autoDispose((ref, params) { + return Random(params.seed).nextInt(params.max); +}); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/helpers/item.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/helpers/item.dart new file mode 100644 index 000000000..1082ef6f3 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/helpers/item.dart @@ -0,0 +1,12 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +import 'json.dart'; + +part 'item.freezed.dart'; +part 'item.g.dart'; + +@freezed +class Item with _$Item { + const factory Item({required int id}) = _Item; + factory Item.fromJson(Json json) => _$ItemFromJson(json); +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/helpers/item.freezed.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/helpers/item.freezed.dart new file mode 100644 index 000000000..e578c8154 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/helpers/item.freezed.dart @@ -0,0 +1,146 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'item.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +Item _$ItemFromJson(Map json) { + return _Item.fromJson(json); +} + +/// @nodoc +mixin _$Item { + int get id => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $ItemCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ItemCopyWith<$Res> { + factory $ItemCopyWith(Item value, $Res Function(Item) then) = + _$ItemCopyWithImpl<$Res, Item>; + @useResult + $Res call({int id}); +} + +/// @nodoc +class _$ItemCopyWithImpl<$Res, $Val extends Item> + implements $ItemCopyWith<$Res> { + _$ItemCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + }) { + return _then(_value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$ItemImplCopyWith<$Res> implements $ItemCopyWith<$Res> { + factory _$$ItemImplCopyWith( + _$ItemImpl value, $Res Function(_$ItemImpl) then) = + __$$ItemImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({int id}); +} + +/// @nodoc +class __$$ItemImplCopyWithImpl<$Res> + extends _$ItemCopyWithImpl<$Res, _$ItemImpl> + implements _$$ItemImplCopyWith<$Res> { + __$$ItemImplCopyWithImpl(_$ItemImpl _value, $Res Function(_$ItemImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + }) { + return _then(_$ItemImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$ItemImpl implements _Item { + const _$ItemImpl({required this.id}); + + factory _$ItemImpl.fromJson(Map json) => + _$$ItemImplFromJson(json); + + @override + final int id; + + @override + String toString() { + return 'Item(id: $id)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ItemImpl && + (identical(other.id, id) || other.id == id)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, id); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$ItemImplCopyWith<_$ItemImpl> get copyWith => + __$$ItemImplCopyWithImpl<_$ItemImpl>(this, _$identity); + + @override + Map toJson() { + return _$$ItemImplToJson( + this, + ); + } +} + +abstract class _Item implements Item { + const factory _Item({required final int id}) = _$ItemImpl; + + factory _Item.fromJson(Map json) = _$ItemImpl.fromJson; + + @override + int get id; + @override + @JsonKey(ignore: true) + _$$ItemImplCopyWith<_$ItemImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/helpers/item.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/helpers/item.g.dart new file mode 100644 index 000000000..3c653e18c --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/helpers/item.g.dart @@ -0,0 +1,18 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'item.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$ItemImpl _$$ItemImplFromJson(Map json) => _$ItemImpl( + id: json['id'] as int, + ); + +Map _$$ItemImplToJson(_$ItemImpl instance) => + { + 'id': instance.id, + }; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/helpers/json.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/helpers/json.dart new file mode 100644 index 000000000..17cfb1c01 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/helpers/json.dart @@ -0,0 +1 @@ +typedef Json = Map; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/async_values/async_values.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/async_values/async_values.dart new file mode 100644 index 000000000..802a23150 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/async_values/async_values.dart @@ -0,0 +1,29 @@ +import 'package:collection/collection.dart'; +import 'package:dio/dio.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../../helpers/item.dart'; +import '../../helpers/json.dart'; + +part 'async_values.g.dart'; + +/* SNIPPET START */ + +@riverpod +Future> itemsApi(ItemsApiRef ref) async { + final client = Dio(); + final result = await client.get>('your-favorite-api'); + final parsed = [...result.data!.map((e) => Item.fromJson(e as Json))]; + return parsed; +} + +@riverpod +List evenItems(EvenItemsRef ref) { + final asyncValue = ref.watch(itemsApiProvider); + if (asyncValue.isReloading) return []; + if (asyncValue.hasError) return const [Item(id: -1)]; + + final items = asyncValue.requireValue; + + return [...items.whereIndexed((index, element) => index.isEven)]; +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/async_values/async_values.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/async_values/async_values.g.dart new file mode 100644 index 000000000..09f07382c --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/async_values/async_values.g.dart @@ -0,0 +1,40 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'async_values.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$itemsApiHash() => r'b32ccb7b85305e361d8ed752cbe11d9524c96190'; + +/// See also [itemsApi]. +@ProviderFor(itemsApi) +final itemsApiProvider = AutoDisposeFutureProvider>.internal( + itemsApi, + name: r'itemsApiProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$itemsApiHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ItemsApiRef = AutoDisposeFutureProviderRef>; +String _$evenItemsHash() => r'55ae98f9b6108203dfc4a139f1ade9fbd8ba8ddd'; + +/// See also [evenItems]. +@ProviderFor(evenItems) +final evenItemsProvider = AutoDisposeProvider>.internal( + evenItems, + name: r'evenItemsProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$evenItemsHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef EvenItemsRef = AutoDisposeProviderRef>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/async_values/index.tsx b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/async_values/index.tsx new file mode 100644 index 000000000..526f2dffe --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/async_values/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./async_values.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/async_values/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/async_values/raw.dart new file mode 100644 index 000000000..1ca8987ef --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/async_values/raw.dart @@ -0,0 +1,25 @@ +import 'package:collection/collection.dart'; +import 'package:dio/dio.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../../helpers/item.dart'; +import '../../helpers/json.dart'; + +/* SNIPPET START */ + +final itemsApiProvider = FutureProvider.autoDispose((ref) async { + final client = Dio(); + final result = await client.get>('your-favorite-api'); + final parsed = [...result.data!.map((e) => Item.fromJson(e as Json))]; + return parsed; +}); + +final evenItemsProvider = Provider.autoDispose((ref) { + final asyncValue = ref.watch(itemsApiProvider); + if (asyncValue.isLoading) return []; + if (asyncValue.hasError) return const [Item(id: -1)]; + + final items = asyncValue.requireValue; + + return [...items.whereIndexed((index, element) => index.isEven)]; +}); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/auto_dispose/auto_dispose.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/auto_dispose/auto_dispose.dart new file mode 100644 index 000000000..9d739eb38 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/auto_dispose/auto_dispose.dart @@ -0,0 +1,29 @@ +import 'dart:math'; + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'auto_dispose.g.dart'; + +/* SNIPPET START */ + +// 코드 생성 시 .autoDispose가 기본값 +@riverpod +int diceRoll(DiceRollRef ref) { + // 이 provider는 .autoDispose이므로 + // 리스닝을 해제하면 현재 노출된 상태가 폐기됩니다. + // 그런 다음 이 provider를 다시 수신할 때마다 + // 새로운 주사위를 굴려서 다시 노출합니다. + final dice = Random().nextInt(10); + return dice; +} + +@riverpod +int cachedDiceRoll(CachedDiceRollRef ref) { + final coin = Random().nextInt(10); + if (coin > 5) throw Exception('Way too large.'); + // 위의 조건은 실패할 수 있습니다; + // 그렇지 않은 경우, 다음 명령어는 아무도 더 이상 수신하지 않더라도 + // 캐시된 상태를 유지하도록 provider에게 지시합니다. + ref.keepAlive(); + return coin; +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/auto_dispose/auto_dispose.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/auto_dispose/auto_dispose.g.dart new file mode 100644 index 000000000..376279010 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/auto_dispose/auto_dispose.g.dart @@ -0,0 +1,41 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'auto_dispose.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$diceRollHash() => r'dfd5ac8b74351a0076da9d131c10277f53ff11b9'; + +/// See also [diceRoll]. +@ProviderFor(diceRoll) +final diceRollProvider = AutoDisposeProvider.internal( + diceRoll, + name: r'diceRollProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$diceRollHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef DiceRollRef = AutoDisposeProviderRef; +String _$cachedDiceRollHash() => r'fc31fcb804f10360d75362e56329976343ee7abb'; + +/// See also [cachedDiceRoll]. +@ProviderFor(cachedDiceRoll) +final cachedDiceRollProvider = AutoDisposeProvider.internal( + cachedDiceRoll, + name: r'cachedDiceRollProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$cachedDiceRollHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef CachedDiceRollRef = AutoDisposeProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/auto_dispose/index.tsx b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/auto_dispose/index.tsx new file mode 100644 index 000000000..6c57cfffd --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/auto_dispose/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./auto_dispose.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/auto_dispose/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/auto_dispose/raw.dart new file mode 100644 index 000000000..d5961ad90 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/auto_dispose/raw.dart @@ -0,0 +1,24 @@ +import 'dart:math'; + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +/* SNIPPET START */ + +final diceRollProvider = Provider.autoDispose((ref) { + // Since this provider is .autoDispose, un-listening to it will dispose + // its current exposed state. + // Then, whenever this provider is listened to again, + // a new dice will be rolled and exposed again. + final dice = Random().nextInt(10); + return dice.isEven; +}); + +final cachedDiceRollProvider = Provider.autoDispose((ref) { + final coin = Random().nextInt(10); + if (coin > 5) throw Exception('Way too large.'); + // The above condition might fail; + // If it doesn't, the following instruction tells the Provider + // to keep its cached state, *even when no one listens to it anymore*. + ref.keepAlive(); + return coin.isEven; +}); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/combine/combine.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/combine/combine.dart new file mode 100644 index 000000000..ecd1915da --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/combine/combine.dart @@ -0,0 +1,19 @@ +import 'dart:math'; + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'combine.g.dart'; + +/* SNIPPET START */ + +@riverpod +int number(NumberRef ref) { + return Random().nextInt(10); +} + +@riverpod +int doubled(DoubledRef ref) { + final number = ref.watch(numberProvider); + + return number * 2; +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/combine/combine.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/combine/combine.g.dart new file mode 100644 index 000000000..7df3df2f7 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/combine/combine.g.dart @@ -0,0 +1,40 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'combine.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$numberHash() => r'725e25be57b9cc2bd914752f156e26a214596b63'; + +/// See also [number]. +@ProviderFor(number) +final numberProvider = AutoDisposeProvider.internal( + number, + name: r'numberProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$numberHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef NumberRef = AutoDisposeProviderRef; +String _$doubledHash() => r'ddc640c876bdbe49fe72fe1632b5ff48687c9279'; + +/// See also [doubled]. +@ProviderFor(doubled) +final doubledProvider = AutoDisposeProvider.internal( + doubled, + name: r'doubledProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$doubledHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef DoubledRef = AutoDisposeProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/combine/index.tsx b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/combine/index.tsx new file mode 100644 index 000000000..2ff7dfbaa --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/combine/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./combine.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/combine/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/combine/raw.dart new file mode 100644 index 000000000..ad33636e7 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/combine/raw.dart @@ -0,0 +1,15 @@ +import 'dart:math'; + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +/* SNIPPET START */ + +final numberProvider = Provider.autoDispose((ref) { + return Random().nextInt(10); +}); + +final doubledProvider = Provider.autoDispose((ref) { + final number = ref.watch(numberProvider); + + return number * 2; +}); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/motivation.mdx b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/motivation.mdx new file mode 100644 index 000000000..a8e75cd4f --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/motivation.mdx @@ -0,0 +1,192 @@ +--- +title: 동기부여(Motivation) +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; +import CodeBlock from "@theme/CodeBlock"; +import sameType from "./same_type"; +import combine from "./combine"; +import asyncValues from "./async_values"; +import autoDispose from "./auto_dispose"; +import override from "./override"; +import sideEffects from "./side_effects"; +import { + trimSnippet, + AutoSnippet, + When, +} from "../../../../../../src/components/CodeSnippet"; + +이 심층 글은 Riverpod의 존재 이유를 보여주기 위해 작성되었습니다. + +특히 이 섹션에서는 아래에 답해야 합니다: + - Provider가 널리 사용되는데 왜 Riverpod로 마이그레이션해야 하나요? + - 어떤 구체적인 이점을 얻을 수 있나요? + - 어떻게 Riverpod로 마이그레이션할 수 있나요? + - 점진적으로 마이그레이션할 수 있나요? + - 기타 등등 + +이 섹션이 끝날 때쯤이면 Provider보다 Riverpod을 선호해야 한다는 확신이 들 것입니다. + +**Riverpod은 실제로 Provider와 비교할 때 더 현대적이고 권장되며 신뢰할 수 있는 접근 방식입니다.** + +Riverpod은 더 나은 상태 관리 기능, 더 나은 캐싱 전략, 간소화된 리액티비티 모델을 제공합니다. +반면, Provider는 현재 많은 부분에서 부족하고 앞으로 나아갈 방법이 없습니다. + +## Provider의 제약사항 + +Provider는 InheritedWidget API의 제약을 받기 때문에 근본적인 문제가 있습니다. +본질적으로 Provider는 "더 단순한 `InheritedWidget`"입니다; +Provider는 단지 InheritedWidget 래퍼일 뿐이므로 이에 의해 제한을 받습니다. + +### Provider는 동일한 "타입"의 providers를 두 개(또는 그 이상) 보유할 수 었습니다. + +두 개의 `Provider`를 선언하면 불안정한 동작이 발생합니다.: +`InheritedWidget`API는 *둘 중 하나*만 가져옵니다: 가장 가까운 `Provider` 조상(ancestor) + +[해결방법]은 Provider의 문서에 설명되어 있지만, Riverpod은 이 문제가 없습니다. + +이 제약을 제거하면 다음과 같이 로직을 작은 조각으로 자유롭게 분할할 수 있습니다: + + + +### Providers는 한 번에 하나의 값만 합리적으로 반환합니다 + +외부 RESTful API를 읽을 때, 새 호출이 다음 값을 로드하는 동안 마지막으로 읽은 값을 표시하는 것은 매우 일반적입니다. +Riverpod은 `AsyncValue`의 API를 통해 한 번에 두 개의 값(즉, 이전 데이터 값과 새로 들어오는 새 로딩 값)을 전송함으로써 이러한 동작을 허용합니다: + + + +이전 코드 조각에서 `evenItemsProvider`를 보면 다음과 같은 효과가 나타납니다: +1. 처음에, 요청이 이루어지고 빈 목록을 얻습니다; +2. 그런 다음 오류가 발생한다고 가정합니다. `[Item(id: -1)]`을 얻습니다; +3. 그런 다음 pull-to-refresh 로직으로 요청을 다시 시도합니다(예: `ref.invalidate`를 통해); +4. 첫 번째 공급자를 다시 로드하는 동안 두 번째 공급자는 여전히 `[Item(id: -1)]`을 노출합니다; +5. 이번에는 일부 파싱된 데이터가 올바르게 수신됩니다: 짝수 항목이 올바르게 반환됩니다. + +프로바이더를 사용하면 위의 기능을 원격으로 구현할 수 없으며 해결 방법도 쉽지 않습니다. + +### providers를 결합하는 것은 어렵고 에러가 발생하기 쉽습니다 + +Provider를 사용하면 provider의 `create`안에서 `context.watch`를 사용하고 싶을 수 있습니다. +이는 종속성이 변경되지 않은 경우(예: 위젯 트리에 GlobalKey가 포함되어 있는 경우)에도 `didChangeDependencies`가 트리거될 수 있기 때문에 신뢰할 수 없습니다. + +그럼에도 불구하고, Provider는 `ProxyProvider`라는 Ad-hoc 솔루션을 가지고 있지만, 이는 지루하고 오류가 발생하기 쉽다고 여겨집니다. + +상태 결합은 [ref.watch] 및 [ref.listen]와 같은 간단하지만 강력한 유틸리티를 사용하여 오버헤드 없이 반응형으로 값을 결합하고 캐시할 수 있기 때문에 Riverpod의 핵심 메커니즘입니다. + + + +Riverpod에서는 종속성을 읽을 수 있고 API가 동일하게 유지되므로 값을 결합하는 것이 자연스럽게 느껴집니다. + +### 안정성 부족 +Provider를 상요하면, 리팩토링 또는 대규모 변경 중에 `ProviderNotFoundException`을 종종 마주치게 됩니다. +사실, 이 런타임 예외는 Riverpod이 처음 만들어진 주요 이유 중 하나였습니다. + +이보다 훨씬 더 많은 유틸리티를 제공하지만, Riverpod은 이 예외를 던질 수 없습니다. + +### 상태를 폐기(Disposing)하는 것은 어렵습니다 + +`InheritedWidget`은 [Comsumer가 더이상 Listen하지 않을때 반응(React)할 수 없습니다]. +이로 인해 더 이상 사용되지 않을때 Provider의 상태를 자동으로 파기(Dispose)할 수 없습니다. +프로파이더를 사용하면 우리는 범위 제한(Scoping) provider에 의존하여 상태가 더 이상 사용되지 않을 때 상태를 파기(Dispose)해야 합니다. +하지만 페이지 간에 상태가 공유되는 경우 까다로워지기 때문에 이것이 쉽지 않습니다. + +Riverpod은 [autodispose]와 [keepAlive]와 같은 쉽게 이해할 수 있는 API로 이 문제를 해결합니다. +이 두 API는 유연하고 창의적인 캐싱 전략(예: 시간 기반 캐싱)을 가능하게 합니다: + + + +안타깝께도 원시 `InheritedWidget`으로는 이를 구현할 방법이 없으므로 Provider로 구현할 수 없습니다. + +### 신뢰할 수 있는 매개변수화 매커니즘 부족 +Riverpod은 사용자가 [.family 수정자(modifier)]를 사용하여 "매개변수화된(parameterized)" 공급자를 선언할 수 있습니다. +실제로 `.family`는 Riverpod의 가장 강력한 기능 중 하나이며, Riverpod의 혁신의 핵심입니다. +예를 들어, 엄청한 [로직의 단순화]을 가능하게 합니다. + +Provider를 사용해 비슷한 기능을 구현하려면, 이러한 매개변수에 대한 사용 편의성*과* 유형 안전성을 포기해야 합니다. + +또한, [이 두 기능은 서로 밀접하게 연관되어 있기 때문]에 Provider로 유사한 '.autoDispose' 메커니즘을 구현할 수 없다는 것은 본질적으로 '.family'의 동등한 구현을 막는 것입니다. + +마지막으로, 앞에서 살펴본 것처럼 위젯이 `InheritedWidget`을 수신(listen)하기 위해 *절대로* 멈추지 않는다는 것을 알 수 있습니다. +이는 일부 provider 상태가 "동적으로 마운트(dynamically mounted)"된 경우, 예를 들어 빌드에 매개변수를 사용하여 provider를 빌드할 때 심각한 메모리 누수가 발생한다는 것을 의미합니다, 이것이 바로 '.family'가 하는 일입니다. +따라서 현재로서는 Provider에 해당하는 `.family`를 얻는 것은 근본적으로 불가능합니다. + +### 지루한 테스트 +테스트를 작성하려면 각 테스트 내에서 providers를 *다시 정의해야만* 합니다. + +Riverpod을 사용하면 기본적으로 테스트 내부에서 providers를 사용할 수 있습니다. +또한, Riverpod은 providers를 모킹(mocking)할 때 중요한 "재정의(overriding)" 유틸리티 모음을 편리하게 제공합니다. + +위의 결합된 상태 스니펫을 테스트하는 것은 다음과 같이 간단합니다: + + + +테스트에 대한 자세한 내용은 [테스팅]을 참조하세요. + + +### 부수 기능 트리거(Triggering side effects)는 간단하지 않습니다. + +`InheritedWidget`에는 `onChange`콜백이 없으므로, provider는 콜백을 가질 수 없습니다. +리는 snackbars, modals 등과 같은 네비게이션에 문제가 있습니다. + +대신 Riverpod은 간단한 `ref.listen`을 제공합니다. 이는 [Flutter와 잘 통합]됩니다. + + + +## Riverpod을 향해 + +개념적으로 Riverpod과 Provider는 상당히 유사합니다. +두 패키지 모두 비슷한 역할을 수행합니다. 둘 다 시도합니다: + +- 일부 상태 저장 객체를 캐시하고 폐기합니다; +- 테스트 중에 해당 객체를 모킹하는 방법을 제공합니다; +- 위젯이 이러한 객체를 간단한 방법으로 수신할 수 있는 방법을 제공합니다. + +Riverpod을 Provider가 몇 년 동안 계속 발전했다면 어땠을지로 생각해 볼 수 있습니다. + +### 왜 별도의 패키지인가요? + +원래는 앞서 언급한 문제를 해결하기 위한 방법으로 'Provider'의 주요 버전이 출시될 예정이었습니다. +그러나 새로운 `ConsumerWidget` API로 인해 "너무 많은 것을 깨뜨리고" 심지어 논란의 여지가 있었기 때문에 이를 포기하기로 결정했습니다. +Provider는 여전히 가장 많이 사용되는 Flutter 패키지 중 하나이기 때문에 별도의 패키지를 만들기로 결정했고, 그렇게 해서 Riverpod이 탄생했습니다. + +별도의 패키지 생성 활성화: + - 두 가지 접근 방식을 *동시에* 임시로 사용할 수 있도록 하여 원하는 사람은 누구나 쉽게 마이그레이션할 수 있습니다; + - 원칙적으로 Riverpod이 마음에 들지 않거나 아직 신뢰할 수 없다고 판단되는 경우 Provider를 계속 사용할 수 있도록 허용; + - Provider의 다양한 기술적 한계에 대한 생산적인 솔루션을 찾기 위해 Riverpod이 실험할 수 있도록 합니다. + +실제로 Riverpod은 Provider의 정신적 후계자 역할을 하도록 설계되었습니다. 따라서 'Riverpod'라는 이름은 'Riverpod'의 아나그램입니다. + +### 획기적인 변화 +Riverpod의 유일한 단점은 작동하려면 위젯 유형을 변경해야 한다는 것입니다: + +- Riverpod을 사용하면 `StatelessWidget` 대신 `ConsumerWidget`을 확장(extend)해야 합니다. +- Riverpod을 사용하면 `StatefulWidget` 대신 `ConsumerStatefulWidget`을 확장(extend)해야 합니다. + +그러나 이러한 불편함은 큰 틀에서 보면 상당히 사소한 것입니다. 그리고 언젠가는 이 요구 사항이 사라질 수도 있습니다. + +### 적합한 라이브러리 선택 +아마 스스로에게 물어보셨을 것입니다: +*"그렇다면 Provider 사용자로서 Provider를 사용해야 하나요, 아니면 Riverpod을 사용해야 하나요?"*. + +저희는 이 질문에 명확하게 답해드리고자 합니다: + + 아마도 Riverpod을 사용해야 할 것입니다. + +Riverpod이 전반적으로 더 잘 설계되어 있으며 로직을 대폭 간소화할 수 있습니다. + + +[ref.watch]: /docs/concepts/reading#using-refwatch-to-observe-a-provider +[ref.listen]: /docs/concepts/reading#using-reflisten-to-react-to-a-provider-change +[autodispose]: /docs/concepts/modifiers/auto_dispose +[해결방법]: https://pub.dev/packages/provider#can-i-obtain-two-different-providers-using-the-same-type +[.family 수정자(modifier)]: /docs/concepts/modifiers/family +[keepAlive]: /docs/concepts/modifiers/auto_dispose#refkeepalive +[이 두 기능은 서로 밀접하게 연관되어 있기 때문]: /docs/concepts/modifiers/family#prefer-using-autodispose-when-the-parameter-is-not-constant +[로직의 단순화]: /docs/concepts/modifiers/family#usage +[we have to]: https://github.com/flutter/flutter/issues/128432 +[it turns out]: https://github.com/flutter/flutter/issues/106549 +[Comsumer가 더이상 Listen하지 않을때 반응(React)할 수 없습니다]: https://github.com/flutter/flutter/issues/106546 +[테스팅]: /docs/cookbooks/testing +[Flutter와 잘 통합]: /docs/concepts/reading#using-reflisten-to-react-to-a-provider-change diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/override/index.tsx b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/override/index.tsx new file mode 100644 index 000000000..43ec56b51 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/override/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./override.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/override/override.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/override/override.dart new file mode 100644 index 000000000..860020903 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/override/override.dart @@ -0,0 +1,16 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +import '../combine/combine.dart'; + +/* SNIPPET START */ + +void main() { + test('it doubles the value correctly', () async { + final container = ProviderContainer( + overrides: [numberProvider.overrideWith((ref) => 9)], + ); + final doubled = container.read(doubledProvider); + expect(doubled, 9 * 2); + }); +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/override/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/override/raw.dart new file mode 100644 index 000000000..860020903 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/override/raw.dart @@ -0,0 +1,16 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +import '../combine/combine.dart'; + +/* SNIPPET START */ + +void main() { + test('it doubles the value correctly', () async { + final container = ProviderContainer( + overrides: [numberProvider.overrideWith((ref) => 9)], + ); + final doubled = container.read(doubledProvider); + expect(doubled, 9 * 2); + }); +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/same_type/index.tsx b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/same_type/index.tsx new file mode 100644 index 000000000..8569e8316 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/same_type/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./same_type.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/same_type/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/same_type/raw.dart new file mode 100644 index 000000000..dacfe9b9d --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/same_type/raw.dart @@ -0,0 +1,15 @@ +import 'package:collection/collection.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../../helpers/item.dart'; + +/* SNIPPET START */ + +final itemsProvider = Provider.autoDispose( + (ref) => [], // ... +); + +final evenItemsProvider = Provider.autoDispose((ref) { + final items = ref.watch(itemsProvider); + return [...items.whereIndexed((index, element) => index.isEven)]; +}); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/same_type/same_type.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/same_type/same_type.dart new file mode 100644 index 000000000..94a4ab086 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/same_type/same_type.dart @@ -0,0 +1,19 @@ +import 'package:collection/collection.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../../helpers/item.dart'; + +part 'same_type.g.dart'; + +/* SNIPPET START */ + +@riverpod +List items(ItemsRef ref) { + return []; // ... +} + +@riverpod +List evenItems(EvenItemsRef ref) { + final items = ref.watch(itemsProvider); + return [...items.whereIndexed((index, element) => index.isEven)]; +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/same_type/same_type.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/same_type/same_type.g.dart new file mode 100644 index 000000000..db84c17ad --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/same_type/same_type.g.dart @@ -0,0 +1,40 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'same_type.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$itemsHash() => r'f0a8fa6874f4868db9ead31e82c75d976f9d2033'; + +/// See also [items]. +@ProviderFor(items) +final itemsProvider = AutoDisposeProvider>.internal( + items, + name: r'itemsProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$itemsHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ItemsRef = AutoDisposeProviderRef>; +String _$evenItemsHash() => r'82b4525e91604745f2b4664531b32d4aff5717d4'; + +/// See also [evenItems]. +@ProviderFor(evenItems) +final evenItemsProvider = AutoDisposeProvider>.internal( + evenItems, + name: r'evenItemsProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$evenItemsHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef EvenItemsRef = AutoDisposeProviderRef>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/side_effects/index.tsx b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/side_effects/index.tsx new file mode 100644 index 000000000..f4797a94f --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/side_effects/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./side_effects.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/side_effects/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/side_effects/raw.dart new file mode 100644 index 000000000..61f016870 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/side_effects/raw.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +import '../auto_dispose/auto_dispose.dart'; + +/* SNIPPET START */ + +class DiceRollWidget extends ConsumerWidget { + const DiceRollWidget({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + ref.listen(diceRollProvider, (previous, next) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Dice roll! We got: $next')), + ); + }); + return TextButton.icon( + onPressed: () => ref.invalidate(diceRollProvider), + icon: const Icon(Icons.casino), + label: const Text('Roll a dice'), + ); + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/side_effects/side_effects.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/side_effects/side_effects.dart new file mode 100644 index 000000000..61f016870 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/motivation/side_effects/side_effects.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +import '../auto_dispose/auto_dispose.dart'; + +/* SNIPPET START */ + +class DiceRollWidget extends ConsumerWidget { + const DiceRollWidget({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + ref.listen(diceRollProvider, (previous, next) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Dice roll! We got: $next')), + ); + }); + return TextButton.icon( + onPressed: () => ref.invalidate(diceRollProvider), + icon: const Icon(Icons.casino), + label: const Text('Roll a dice'), + ); + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/provider_vs_riverpod.mdx b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/provider_vs_riverpod.mdx new file mode 100644 index 000000000..16380ac37 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/provider_vs_riverpod.mdx @@ -0,0 +1,419 @@ +--- +title: Provider vs Riverpod +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; +import CodeBlock from "@theme/CodeBlock"; +import family from "./family"; +import { + trimSnippet, + AutoSnippet, + When, +} from "../../../../../src/components/CodeSnippet"; + + +이 문서에서는 Provider와 Riverpod의 차이점과 유사점을 요약하여 설명합니다. + +:::info +한글에는 영어 대/소문자가 없어서 "Provider"와 "provider"를 "프로바이더", "공급자"등으로 번역시 구분할 수 없습니다. +이 문서에서는 pkg:Provider를 "Provider"로 표기하고, +pkg:Provider나 pkg:Riverpod에서 제공되는 provider들을 "provider"로 표기합니다. +::: + +## 공급자(Provider) 정의하기 + +두 패키지의 가장 큰 차이점은 "providers"를 정의하는 방식입니다. + +[Provider]를 사용하면 providers는 위젯이므로 위젯 트리 안에 배치됩니다, +일반적으로 `MultiProvider` 안에 배치됩니다: + +```dart +class Counter extends ChangeNotifier { + ... +} + +void main() { + runApp( + MultiProvider( + providers: [ + ChangeNotifierProvider(create: (context) => Counter()), + ], + child: MyApp(), + ) + ); +} +``` + +Riverpod에서 providers는 위젯이 **아닙니다**. 대신 일반 Dart 객체입니다. +마찬가지로 providers는 위젯 트리 외부에서 정의되며, 대신 전역 최종(global final) 변수로 선언됩니다. + +또한 Riverpod이 작동하려면 전체 애플리케이션 위에 `ProviderScope` 위젯을 추가해야 합니다. +따라서 Riverpod을 사용하는 것은 Provider 예시와 동일합니다: + +```dart +// Provider는 이제 최상위 변수 +final counterProvider = ChangeNotifierProvider((ref) => Counter()); + +void main() { + runApp( + // 이 위젯은 전체 프로젝트에서 Riverpod을 사용할 수 있게 합니다 + ProviderScope( + child: MyApp(), + ), + ); +} +``` + +provider 정의가 단순히 몇 줄 위로 올라간 것을 주목하세요. + +:::info +Riverpod providers는 일반 다트 객체이므로 Flutter 없이 Riverpod을 사용할 수 있습니다. +예를 들어, 명령줄 애플리케이션을 작성하는 데 Riverpod을 사용할 수 있습니다. +::: + +## providers 읽기: BuildContext + +Provider에서 providers를 읽는 한 가지 방법은 위젯의 'BuildContext'를 사용하는 것입니다. + +예를 들어, provider가 다음과 같이 정의된 경우: + +```dart +Provider(...); +``` + +그런 다음 Provider를 사용하여 읽는 것은 다음과 같습니다: + +```dart +class Example extends StatelessWidget { + @override + Widget build(BuildContext context) { + Model model = context.watch(); + + } +} +``` + +이것은 Riverpod에서 동일합니다. Riverpod의 스니펫은 다음과 같습니다: + +```dart +final modelProvider = Provider(...); + +class Example extends ConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + Model model = ref.watch(modelProvider); + + } +} +``` + +방법을 확인하세요: + +- Riverpod의 스니펫은 `StatelessWidget` 대신 `ConsumerWidget`을 확장(extend)합니다. + 이 다른 위젯 유형은 `build` 함수에 하나의 추가 매개변수인 `WidgetRef`를 추가합니다. + +- Riverpod에서는 `BuildContext.watch` 대신 `ConsumerWidget`에서 가져온 `WidgetRef`를 사용하여 `WidgetRef.watch`를 수행합니다. + +- Riverpod은 제네릭 타입에 의존하지 않습니다. 대신 공급자 정의(provider definition)를 통해 생성된 변수(variable)에 의존합니다. + +문구가 얼마나 유사한지도 주목하세요. Provider와 Riverpod은 모두 "watch" 키워드를 사용하여 "이 위젯은 값이 변경되면 다시 빌드되어야 합니다"라고 설명합니다. + +:::info +Riverpod은 provider 읽기에 대해 Provider와 동일한 용어를 사용합니다. + +- `BuildContext.watch` -> `WidgetRef.watch` +- `BuildContext.read` -> `WidgetRef.read` +- `BuildContext.select` -> `WidgetRef.watch(myProvider.select)` + +`context.watch`와 `context.read`에 대한 규칙은 Riverpod에도 적용됩니다: +`build` 메서드 내부에서는 "watch"를 사용합니다. +클릭 핸들러 및 기타 이벤트 내부에서는 "read"를 사용합니다. +값을 필터링하고 다시 빌드해야 하는 경우 "select"를 사용합니다. +::: + +## providers 읽기: Consumer + +Provider는 선택적으로 `Consumer`라는 위젯(및 `Consumer2`와 같은 변형)을 제공합니다. + +`Consumer`는 위젯 트리 보다 세분화하여 재빌드할 수 있으므로, 성능최적화에 도음이 됩니다. - 상태가 변경될 때 관련 위젯만 업데이트합니다: + +따라서 provider가 다음과 같이 정의된 경우: + +```dart +Provider(...); +``` + +Provider는 `Consumer`를 사용하여 provider를 읽을 수 있게합니다: + +```dart + +Provider allows reading that provider using `Consumer` with: + +```dart +Consumer( + builder: (BuildContext context, Model model, Widget? child) { + + } +) +``` + +Riverpod도 같은 원리를 가지고 있습니다. Riverpod에도 똑같은 용도의 'Consumer'라는 위젯이 있습니다. + +provider를 다음과 같이 정의했다면: + +```dart +final modelProvider = Provider(...); +``` + +`Consumer`를 사용하여 provider를 읽을 수 있습니다: + +```dart +Consumer( + builder: (BuildContext context, WidgetRef ref, Widget? child) { + Model model = ref.watch(modelProvider); + + } +) +``` + +`Consumer`가 어떻게 `WidgetRef` 객체를 제공하는지 주목해주세요. 이것은 이전 파트에서 `ConsumerWidget`과 관련된 것과 동일한 객체입니다. + +### Riverpod에는 `ConsumerN`에 해당하는 객체가 없음 + +Riverpod에서는 pkg:Provider의 `Consumer2`, `Consumer3` 등이 필요하지 않고, 누락된 것을 확인할 수 있습니다. (not needed nor missed) + +Riverpod을 사용하면, 여러 provider로 부터 값을 읽으려면 다음과 같이 여러 개의 `ref.watch` 문을 작성하면 됩니다.: + +```dart +Consumer( + builder: (context, ref, child) { + Model1 model = ref.watch(model1Provider); + Model2 model = ref.watch(model2Provider); + Model3 model = ref.watch(model3Provider); + // ... + } +) +``` + +위의 솔루션은 pkg:Provider의 `ConsumerN` API와 비교할 때 훨씬 덜 무겁게 느껴지고 이해하기 쉬울 것입니다. + +## providers 결합하기: ProxyProvider 와 stateless objects + +Provider를 사용할 때, providers를 결합하는 공식적인 방법은 `ProxyProvider` 위젯(또는 `ProxyProvider2`와 같은 변형)을 사용하는 것입니다. + +예를 들어 다음과 같이 정의할 수 있습니다: + +```dart +class UserIdNotifier extends ChangeNotifier { + String? userId; +} + +// ... + +ChangeNotifierProvider(create: (context) => UserIdNotifier()), +``` + +이제 두가지 옵션이 있습니다. +`UserIdNotifier`를 결합하여 새로운 "stateless" provider(일반적으로 ==를 재정의할 수 있는 불변값)를 만들 수 있습니다. +다음과 같은: + +```dart +ProxyProvider( + update: (context, userIdNotifier, _) { + return 'The user ID of the the user is ${userIdNotifier.userId}'; + } +) +``` + +이 프로바이더는 `UserIdNotifier.userId`가 변경될 때마다 자동으로 새 `String`을 반환합니다. + +Riverpod에서도 비슷한 작업을 수행할 수 있지만 구문이 다릅니다. +먼저, Riverpod에서 `UserIdNotifier`의 정의는 다음과 같습니다: + +```dart +class UserIdNotifier extends ChangeNotifier { + String? userId; +} + +// ... + +final userIdNotifierProvider = ChangeNotifierProvider( + (ref) => UserIdNotifier(), +); +``` + +거기에서, 'userId'를 기반으로 'String'을 생성하면 됩니다: + +```dart +final labelProvider = Provider((ref) { + UserIdNotifier userIdNotifier = ref.watch(userIdNotifierProvider); + return 'The user ID of the the user is ${userIdNotifier.userId}'; +}); +``` + +`ref.watch(userIdNotifierProvider)`를 수행하는 줄을 주목하세요. + +이 코드 줄은 Riverpod에게 `userIdNotifierProvider`의 내용을 가져오고, 그 값이 변경될 때마다 `labelProvider`도 다시 계산하도록 지시합니다. +따라서 `labelProvider`가 내보내는 `String`은 `userId`가 변경될 때마다 자동으로 업데이트됩니다. + +이 `ref.watch` 줄도 비슷하게 느껴질 것입니다. 이 패턴은 이전에 [위젯 내부에서 provider를 읽는 방법](#reading-providers-buildcontext)을 설명할 때 다뤘던 내용입니다. +실제로 providers는 이제 위젯과 같은 방식으로 다른 providers를 수신할 수 있습니다. + +## providers 결합하기: ProxyProvider 와 stateful objects + +providers를 결합할 때 또 다른 대안적인 사용 사례는 `ChangeNotifier` 인스턴스와 같은 상태 저장 객체를 노출하는 것입니다. + +이를 위해 `ChangeNotifierProxyProvider`(또는 `ChangeNotifierProxyProvider2`와 같은 변형)를 사용할 수 있습니다. +예를 들어 다음과 같이 정의할 수 있습니다: + +```dart +class UserIdNotifier extends ChangeNotifier { + String? userId; +} + +// ... + +ChangeNotifierProvider(create: (context) => UserIdNotifier()), +``` + +그런 다음 `UserIdNotifier.userId`를 기반으로 하는 새로운 `ChangeNotifier`를 정의할 수 있습니다. +예를 들어 다음과 같이 할 수 있습니다: + +```dart +class UserNotifier extends ChangeNotifier { + String? _userId; + + void setUserId(String? userId) { + if (userId != _userId) { + print('The user ID changed from $_userId to $userId'); + _userId = userId; + } + } +} + +// ... + +ChangeNotifierProxyProvider( + create: (context) => UserNotifier(), + update: (context, userIdNotifier, userNotifier) { + return userNotifier! + ..setUserId(userIdNotifier.userId); + }, +); +``` + +이 새 provider는 (재구성되지 않는) `UserNotifier`의 단일 인스턴스를 생성하고 사용자 ID가 변경될 때마다 문자열을 인쇄합니다. + +provider에서 동일한 작업을 수행하는 방식은 다릅니다. +먼저, Riverpod에서는 `UserIdNotifier`의 정의가 다음과 같습니다: + +```dart +class UserIdNotifier extends ChangeNotifier { + String? userId; +} + +// ... + +final userIdNotifierProvider = ChangeNotifierProvider( + (ref) => UserIdNotifier(), +), +``` + +이전의 `ChangeNotifierProxyProvider`에 해당하는 코드는 다음과 같습니다: + +```dart +class UserNotifier extends ChangeNotifier { + String? _userId; + + void setUserId(String? userId) { + if (userId != _userId) { + print('The user ID changed from $_userId to $userId'); + _userId = userId; + } + } +} + +// ... + +final userNotifierProvider = ChangeNotifierProvider((ref) { + final userNotifier = UserNotifier(); + ref.listen( + userIdNotifierProvider, + (previous, next) { + if (previous?.userId != next.userId) { + userNotifier.setUserId(next.userId); + } + }, + ); + + return userNotifier; +}); +``` + +이 스니펫의 핵심은 `ref.listen` 줄입니다. +이 `ref.listen` 함수는 provider를 수신 대기하고, provider가 변경될 때마다 함수를 실행하는 유틸리티입니다. + +해당 함수의 `previous` 및 `next` 매개 변수는 공급자가 변경되기 전의 마지막 값과 변경된 후의 새 값에 해당합니다. + +## 범위 지정 공급자(Scoping Providers) vs `.family` + `.autoDispose` +pkg:Provider에서 범위 지정은 두 가지 용도로 사용되었습니다: + - 페이지 이탈 시 상태 소멸(destroying state) + - 페이지당 커스텀 상태 보유 + +상태를 파괴(Destroy)하기 위해 스코핑(Scoping)을 사용하는 것은 이상적이지 않습니다. +문제는 범위 지정(Scoping)이 대규모 애플리케이션에서 제대로 작동하지 않는다는 것입니다. +예를 들어, 상태는 한 페이지에서 생성되지만 탐색 후 다른 페이지에서 나중에 소멸되는 경우가 많습니다. +이렇게 하면 여러 페이지에서 여러 개의 캐시를 활성화할 수 없습니다. + +마찬가지로 모달이나 다단계 양식과 같이 해당 상태를 위젯 트리의 다른 부분과 공유해야 하는 경우 '페이지별 사용자 지정 상태(custom state per page)' 접근 방식은 처리하기 어려워집니다. + +Riverpod은 다른 접근 방식을 취합니다: 첫째, 범위 지정(Scoping) providers는 권장하지 않으며, 둘째, `.family` 및 `.autoDispose`는 이를 완전히 대체하는 솔루션 입니다. + +Riverpod 내에서 '.autoDispose'로 표시된 공급자는 더 이상 사용되지 않을 때 자동으로 상태를 소멸(destroy)합니다. +공급자를 제거하는 마지막 위젯이 마운트 해제되면 Riverpod은 이를 감지하고 공급자를 파기(destroy)합니다. +이 동작을 테스트하려면 제공자에서 이 두 가지 수명 주기 메서드를 사용해 보세요: + + +```dart +ref.onCancel((){ + print("더 이상 어떤 것도 나를 Listen하지 않음!"); +}); +ref.onDispose((){ + print("`.autoDispose`로 정의된 경우, 방금 폐기되었음!"); +}); +``` + +이는 본질적으로 "상태 소멸(destroying state)" 문제를 해결합니다. + +또한 Provider를 `.family`로 표시할 수 있습니다. (동시에 `.autoDispose`로 표시할 수도 있습니다.) +이렇게 하면 providers에게 매개변수를 전달하여 내부적으로 여러 providers를 생성하고 추적할 수 있습니다. +즉, 매개변수를 전달할 때 *고유한 매개변수당 고유한 상태가 생성됩니다*. + + + +이렇게 하면 "페이지별 맞춤 상태(custom state per page)" 문제가 해결됩니다. +사실, 또 다른 이점이 있습니다. 이러한 상태는 더 이상 특정 페이지에 묶여 있지 않습니다. +대신 다른 페이지에서 동일한 상태에 액세스하려고 시도하는 경우 해당 페이지에서 매개변수를 재사용하기만 하면 액세스할 수 있습니다. + +여러 가지 면에서 providers에게 매개변수를 전달하는 것은 맵 키와 동일합니다. +키가 같으면 얻어지는 값도 동일합니다. 키가 다르면 다른 상태가 얻어집니다. + +[provider]: https://pub.dev/packages/provider +[ref.watch]: /docs/concepts/reading#using-refwatch-to-observe-a-provider +[ref.listen]: /docs/concepts/reading#using-reflisten-to-react-to-a-provider-change +[autodispose]: /docs/concepts/modifiers/auto_dispose +[workaround]: https://pub.dev/packages/provider#can-i-obtain-two-different-providers-using-the-same-type +[.family modifier]: /docs/concepts/modifiers/family +[keepAlive]: /docs/concepts/modifiers/auto_dispose#refkeepalive +[as these two features go hand-in-hand]: /docs/concepts/modifiers/family#prefer-using-autodispose-when-the-parameter-is-not-constant +[simplification of logic]: /docs/concepts/modifiers/family#usage +[we have to]: https://github.com/flutter/flutter/issues/128432 +[it turns out]: https://github.com/flutter/flutter/issues/106549 +[*can't* react when a consumer stops listening to them]: https://github.com/flutter/flutter/issues/106546 +[integrates well with Flutter]: /docs/concepts/reading#using-reflisten-to-react-to-a-provider-change +[ChangeNotifierProvider]: /docs/providers/change_notifier_provider +[Code generation]: /docs/about_code_generation +[AsyncNotifiers]: /docs/providers/notifier_provider +[combining Providers]: /docs/concepts/combining_providers +[global final variable]: /docs/concepts/providers#creating-a-provider diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/quickstart.mdx b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/quickstart.mdx new file mode 100644 index 000000000..67577692e --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/from_provider/quickstart.mdx @@ -0,0 +1,190 @@ +--- +title: 빠른 시작 +--- + +이 섹션은 [Provider] 패키지에 익숙한 분들 중 Riverpod에 대해 자세히 알고 싶은 분들을 위해 만들어졌습니다. + +무엇보다도 먼저 짧은 [시작하기] 글을 읽고 작은 [sandbox] 예제를 통해 Riverpod의 기능을 테스트해 보세요. +그 결과 마음에 든다면 마이그레이션을 확실히 고려해야 합니다. + +실제로 Provider에서 Riverpod로 마이그레이션하는 것은 매우 간단합니다. + +마이그레이션은 기본적으로 *증분* 방식으로 수행할 수 있는 몇 가지 단계로 구성됩니다. + +## `ChangeNotifierProvider`로 시작하기 + +Riverpod로 전환하는 동안 'ChangeNotifier'를 계속 사용하고, 최신의 멋진 기능을 최대한 빨리 사용하지 않는 것도 상관없습니다. + +실제로 다음과 같이 시작해도 좋습니다: + +```dart +// 아래와 같은 코드를 가지고 있다면 +class MyNotifier extends ChangeNotifier { + int state = 0; + + void increment() { + state++; + notifyListeners(); + } +} + +// ... 이것만 추가하세요! +final myNotifierProvider = ChangeNotifierProvider((ref) { + return MyNotifier(); +}); +``` + +보시다시피 Riverpod은 pkg:Provider에서 마이그레이션을 지원하기 위해 제공되는 [ChangeNotifierProvider] 클래스를 노출합니다. + +이 Provider는 새 코드를 작성할 때 권장되지 않으며, Riverpod을 사용하는 가장 좋은 방법은 아니지만 +마이그레이션을 시작하는 부드럽고 매우 쉬운 방법이라는 점에 유의하세요. + +:::tip +'ChangeNotifier'를 더 최신의 [Riverpod의 provider들]로 '즉시' 변경하려고 서두를 필요는 없습니다. +일부는 약간의 패러다임 전환이 필요하기 때문에 처음에는 어려울 수 있습니다. + +먼저 Riverpod에 익숙해지는 것이 중요하므로 천천히 하세요; +pkg:provider의 *거의* 모든 Provider가 pkg:riverpod에 엄격하게 대응한다는 것을 금방 알 수 있습니다. +::: + +## *잎사귀(leaves)*부터 시작 + +다른 것에 의존하지 않는 Provider, 즉 종속성 트리의 *잎사귀(leaves)*부터 시작하세요. +모든 잎사귀를 마이그레이션한 후에는 잎사귀에 의존하는 Provider로 이동할 수 있습니다. + +다시 말해, 처음부터 `ProxyProvider`를 마이그레이션하지 말고, 모든 종속성을 마이그레이션한 후에 처리하세요. + +이렇게 하면 마이그레이션 프로세스가 향상되고 간소화되는 동시에 오류를 최소화/추적할 수 있습니다. + + +## Riverpod과 Provider가 공존할 수 있습니다. +*Provider와 Riverpod을 동시에 사용할 수 있다는 것을 기억하세요.* + +실제로 import alias를 사용하면 두 API를 모두 사용할 수 있습니다. +이는 가독성 면에서도 좋으며 모호한 API 사용을 제거합니다. + +이렇게 하려면, 코드베이스에서 각 Provider 가져오기에 대해 가져오기 별칭(alias)을 사용하는 것을 고려해 보세요. + +:::info +가져오기 별칭을 효과적으로 구현하는 방법에 대한 전체 가이드가 곧 제공될 예정입니다. +::: + +## `Consumer`를 바로 사용할 *필요*는 없습니다 + +명심해야 할 것은 [Riverpod의 `Consumer` API]를 `즉시` 사용할 필요는 없다는 점입니다. +마이그레이션을 막 시작한 경우, [위에서 언급한 대로] `ChangeNotifierProvider`로 시작하는 것이 좋습니다. + +위에서 정의한 `myNotifierProvider`를 고려해 보세요. + +내부 코드가 pkg:Provider의 API에 의존하고 있을 가능성이 높으므로, 다음을 사용하여 pkg:Riverpod로 `ChangeNotifier`를 사용하기 시작하세요. +```dart +MultiProvider( + providers: [ + ChangeNotifierProvider.value(value: ref.watch(myNotifierProvider.notifier)), + ] +) +``` +이렇게 하면 처음에 루트 위젯만 `ConsumerWidget`으로 변환하면 됩니다. +이렇게 하면 pkg:Riverpod로 마이그레이션하는 것이 훨씬 쉬워집니다. + +## 한번에 하나씩 Provider를 마이그레이션하세요 + +기존 앱이 있는 경우, 모든 Provider를 한꺼번에 마이그레이션하지 마세요! + +장기적으로 모든 애플리케이션을 Riverpod로 이동하려고 노력해야 하지만, **자신을 지치게(burn) 하지 마세요**. +한번에 하나의 Provider씩 처리하세요. + +위의 예를 들어보겠습니다. `myNotifierProvider`를 Riverpod으로 **완전히** 마이그레이션한다는 것은 다음과 같이 작성하는 것을 의미합니다: + +```dart +class MyNotifier extends Notifier { + @override + int build() => 0; + + void increment() => state++; +} + +final myNotifierProvider = NotifierProvider(MyNotifier.new); +``` + +.. 그리고 _또한_ 해당 공급자가 소비되는 방식을 변경해야 합니다, +예를 들어 이 공급자에 대한 각 `context.watch`를 `ref.watch`로 변경해야 합니다. + +이 작업은 시간이 다소 걸리고 오류가 발생할 수 있으므로, 한 번에 끝내려고 서두르지 마세요. + +## `ProxyProvider` 마이그레이션 +pkg:Provider 내에서 `ProxyProvider`는 다른 프로바이더의 값을 결합하는 데 사용됩니다; +빌드는 다른 프로바이더의 값에 따라 반응적(reactively)으로 달라집니다. + +Riverpod에서는 대신 프로바이더는 [기본적으로 컴포저블이 가능]하므로, +`ProxyProvider`를 마이그레이션할 때 한 프로바이더에서 다른 프로바이더로 직접 종속성을 선언하려면 `ref.watch`를 작성하기만 하면 됩니다. + +오히려 Riverpod에서 값을 결합하는 것이 더 간단하고 직관적으로 느껴질 것이므로 마이그레이션을 통해 코드를 크게 간소화할 수 있습니다. + +또한 두 개 이상의 공급자를 결합할 때, 복잡한 과정을 거칠 필요 없이 `ref.watch`를 하나 더 추가하기만 하면 바로 사용할 수 있습니다. + +## 빠른(Eager) 초기화 + +Riverpod의 프로바이더는 최종 전역(global) 변수이기 때문에 [기본적으로 지연(Lazy)] 초기화 됩니다. + +시작 시 일부 워밍업 데이터나 유용한 서비스를 초기화해야 하는 경우, 가장 좋은 방법은 `MultiProvider`를 넣었던 위치에서 프로바이더를 먼저 읽는 것입니다. + +즉, Riverpod은 강제로 초기화할 수 없기 때문에, 시작 단계에서 읽고 캐시하여, 나머지 애플리케이션 내에서 필요할 때 바로 사용할 수 있도록(warm and ready) 할 수 있습니다. + +pkg:Riverpod의 Provider들의 초기화에 대한 전체 가이드는 [여기에서 확인할 수 있습니다]. + +## 코드 생성 +Riverpod을 *미래에 대비한* 방식으로 사용하려면 [코드 생성]을 권장합니다. +참고로, 메타프로그래밍(metaprogramming)이 보편화되면, 코드 생성 기능이 Riverpod의 기본값이 될 가능성이 높습니다. + +안타깝게도 `@riverpod`는 `ChangeNotifierProvider`에 대한 코드를 생성할 수 없습니다. +이 문제를 해결하기 위해 다음 유틸리티 확장 메소드(extesion method)를 사용할 수 있습니다: + +```dart +extension ChangeNotifierWithCodeGenExtension on Ref { + T listenAndDisposeChangeNotifier(T notifier) { + notifier.addListener(notifyListeners); + onDispose(() => notifier.removeListener(notifyListeners)); + onDispose(notifier.dispose); + return notifier; + } +} +``` + +그런 다음 다음 코드 생성 구문을 사용하여 `ChangeNotifier`를 노출할 수 있습니다: +```dart +// ignore_for_file: unsupported_provider_value +@riverpod +MyNotifier example(ExampleRef ref) { + return ref.listenAndDisposeChangeNotifier(MyNotifier()); +} +``` + +"기본" 마이그레이션이 완료되면 `ChangeNotifier`를 `Notifier`로 변경할 수 있으므로 임시 확장이 필요하지 않습니다. +앞의 사례를 예로 들면, "완전히 마이그레이션된" `Notifier`가 됩니다: + +```dart +@riverpod +class MyNotifier extends _$MyNotifier { + @override + int build() => 0; + + void increment() => state++; +} +``` + +이 작업이 완료되고 코드베이스에 더 이상 `ChangeNotifierProvider`가 없다는 것이 확실해지면 임시 확장자를 완전히 제거할 수 있습니다. + +코드 생성은 권장 사항이지만 *필수 사항*은 아니라는 점을 명심하세요. +마이그레이션에 대해 점진적으로 생각하는 것이 좋습니다: +한 번에 코드 생성 구문으로 *전환하면서* 마이그레이션을 구현하는 것이 과하다고 생각되면 점진적으로 마이그레이션을 고려하는 것이 *좋습니다*. + +이 가이드에 따라 나중에 한 단계 더 나아가 코드생성으로 마이그레이션할 수 있습니다. + +[코드 생성]: /docs/concepts/about_code_generation +[Riverpod의 provider들]: /docs/providers/notifier_provider +[기본적으로 컴포저블이 가능]: /docs/from_provider/motivation#combining-providers-is-hard-and-error-prone +[위에서 언급한 대로]: /docs/from_provider/quickstart#start-with-changenotifierprovider +[Riverpod의 `Consumer` API]: /docs/concepts/reading +[기본적으로 지연(Lazy)]: /docs/concepts/provider_lifecycles +[여기에서 확인할 수 있습니다]: /docs/essentials/eager_initialization diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/getting_started.mdx b/website/i18n/ko/docusaurus-plugin-content-docs/current/getting_started.mdx deleted file mode 100644 index 1542ee01a..000000000 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/getting_started.mdx +++ /dev/null @@ -1,154 +0,0 @@ ---- -title: 시작하기 -version: 1 ---- - -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; -import CodeBlock from "@theme/CodeBlock"; - -import pubspec from "../../../../i18n/ko/docusaurus-plugin-content-docs/current/getting_started/pubspec"; -import dartHelloWorld from "../../../../i18n/ko/docusaurus-plugin-content-docs/current/getting_started/dart_hello_world"; -import helloWorld from "../../../../i18n/ko/docusaurus-plugin-content-docs/current/getting_started/hello_world"; -import dartPubspec from "../../../../i18n/ko/docusaurus-plugin-content-docs/current/getting_started/dart_pubspec"; - -import { - trimSnippet, - AutoSnippet, - When } from "../../../../src/components/CodeSnippet"; - ---- - -## Try Riverpod online - -To get a feel of Riverpod, try it online on [Dartpad](https://dartpad.dev/?null_safety=true&id=ef06ab3ce0b822e6cc5db0575248e6e2). - -[Riverpod]의 내부 메커니즘에 들어가기 앞서, 우선 [Riverpod]을 설치하는 방법과 -"Hello world"를 표시하는 방법부터 함께 시작해봅시다. - -## 어떤 패키지를 설치해야하나요? - -[Riverpod]은 여러 종류의 패키지가 있습니다. 각각 다른 사용 목적을 가지고 있으며 개별마다 상이한 특징을 가지고 있습니다. -어떤 [Riverpod] 패키지를 설치할지는 만드는 앱의 형태에 따라 다릅니다. - -다음 아래의 표를 참조하여 사용할 패키지를 결정할 수 있습니다. - -| 앱의 형태 | 패키지명 | 설정 | -| ------------------------- | --------------------------------------------------------------------------------- | ------------------------------------------------------------ | -| Flutter + [flutter_hooks] | [hooks_riverpod] | [flutter_hooks] 과 [Riverpod]을 함께 병용한 패키지 | -| Flutter | [flutter_riverpod] | Flutter 앱에 [Riverpod] 을 사용할 경우의 기본 패키지 | -| Dart(Flutter 사용안함) | [riverpod](https://github.com/rrousselGit/riverpod/tree/master/packages/riverpod) | Flutter 에 관련된 모든 클래스가 완전제거된 [Riverpod] 패키지 | - -## 패키지 설치 방법 - -설치할 패키지의 종류가 결정되었다면, `pubspec.yaml`에 아래의 방법으로 패키지를 추가합니다. - - - - - - -패키지 추가 후 `flutter pub get` 를 실행해주세요. - - - 마지막으로, {" "}를 실행하여 코드를 생성합니다. - flutter pub run build_runner watch - - - - - - - -패키지 추가 후 `dart pub get`를 실행해주세요. - - - 마지막으로, {" "}를 실행하여 코드를 생성합니다. - flutter pub run build_runner watch - - - - - -이걸로 [Riverpod]이 앱에 추가되었습니다. - -## 사용예시: Hello world - -[Riverpod]설치를 완료하였다면 이제 사용을 시작해봅시다. -아래의 예제코드를 실행하면 Hello world가 화면에 표시됩니다. - -export const foo = 42; - - - - - - -`flutter run` 명령어를 실행합니다. -디바이스 화면에 Hello world가 표시됩니다. - - - - - - -`dart lib/main.dart` 명령어를 실행합니다. -콘솔창에 Hellow world가 출력됩니다. - - - - -## 더 나아가기: Code Snippets 설치하기 - -`Flutter`를 `VS Code` 에서 사용하는 경우, 확장에서 -[Flutter Riverpod Snippets](https://marketplace.visualstudio.com/items?itemName=robert-brunhage.flutter-riverpod-snippets) -패키지를 검색하여 [Riverpod] 전용 Code Snippets을 설치하여 사용할 수 있습니다. - -`Flutter`를 `Android Studio` 또는 `IntelliJ` 에서 사용하는 경우, -[Flutter Riverpod Snippets](https://plugins.jetbrains.com/plugin/14641-flutter-riverpod-snippets) -를 설치하여 사용할 수 있습니다. - -![img](/img/snippets/greetingProvider.gif) - - -## 다음 단계 선택하기 - -기본 컨셉에대해 확인하기: - -- [Learn more about providers](/docs/concepts/providers) - -cookbook 따라가기: - -- [How to test providers](/docs/cookbooks/testing) - - -[riverpod]: https://github.com/rrousselgit/riverpod -[hooks_riverpod]: https://pub.dev/packages/hooks_riverpod -[flutter_riverpod]: https://pub.dev/packages/flutter_riverpod -[flutter_hooks]: https://github.com/rrousselGit/flutter_hooks diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/getting_started/dart_hello_world/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/getting_started/dart_hello_world/raw.dart deleted file mode 100644 index 9c83c5a61..000000000 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/getting_started/dart_hello_world/raw.dart +++ /dev/null @@ -1,19 +0,0 @@ -// ignore_for_file: avoid_print - -/* SNIPPET START */ - -import 'package:riverpod/riverpod.dart'; - -// 우리는 값을 저장할 "provider"를 만들겁니다(여기서 값은 "Hello world"를 의미합니다). -// 프로바이더를 사용하는 것으로 값의 mock/override가 가능하게 됩니다. -final helloWorldProvider = Provider((_) => 'Hello world'); - -void main() { - // 이 객체는 프로바이더 상태를 저장하게 됩니다. - final container = ProviderContainer(); - - // "container" 덕분에, 여기서 우리의 프로바이더 값을 읽을 수 있습니다. - final value = container.read(helloWorldProvider); - - print(value); // Hello world -} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/getting_started/dart_pubspec/codegen.yaml b/website/i18n/ko/docusaurus-plugin-content-docs/current/getting_started/dart_pubspec/codegen.yaml deleted file mode 100644 index 138dd42d7..000000000 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/getting_started/dart_pubspec/codegen.yaml +++ /dev/null @@ -1,11 +0,0 @@ -name: my_app_name -environment: - sdk: ">=2.17.0 <3.0.0" - -dependencies: - riverpod: ^2.0.2 - riverpod_annotation: ^1.0.4 - -dev_dependencies: - build_runner: - riverpod_generator: ^1.0.4 \ No newline at end of file diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/getting_started/dart_pubspec/index.tsx b/website/i18n/ko/docusaurus-plugin-content-docs/current/getting_started/dart_pubspec/index.tsx deleted file mode 100644 index d0c456dcc..000000000 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/getting_started/dart_pubspec/index.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import raw from "!!raw-loader!./raw.yaml"; -import codegen from "!!raw-loader!./codegen.yaml"; - -export default { - raw, - hooks: raw, - codegen, - hooksCodegen: codegen, -}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/getting_started/dart_pubspec/raw.yaml b/website/i18n/ko/docusaurus-plugin-content-docs/current/getting_started/dart_pubspec/raw.yaml deleted file mode 100644 index f9797b9c7..000000000 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/getting_started/dart_pubspec/raw.yaml +++ /dev/null @@ -1,6 +0,0 @@ -name: my_app_name -environment: - sdk: ">=2.17.0 <3.0.0" - -dependencies: - riverpod: ^2.0.2 \ No newline at end of file diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/getting_started/pubspec/codegen.yaml b/website/i18n/ko/docusaurus-plugin-content-docs/current/getting_started/pubspec/codegen.yaml deleted file mode 100644 index 5b7a9b2d3..000000000 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/getting_started/pubspec/codegen.yaml +++ /dev/null @@ -1,14 +0,0 @@ -name: my_app_name -environment: - sdk: ">=2.17.0 <3.0.0" - flutter: ">=3.0.0" - -dependencies: - flutter: - sdk: flutter - flutter_riverpod: ^2.0.2 - riverpod_annotation: ^1.0.4 - -dev_dependencies: - build_runner: - riverpod_generator: ^1.0.4 \ No newline at end of file diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/getting_started/pubspec/hooks.yaml b/website/i18n/ko/docusaurus-plugin-content-docs/current/getting_started/pubspec/hooks.yaml deleted file mode 100644 index 5a1995205..000000000 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/getting_started/pubspec/hooks.yaml +++ /dev/null @@ -1,10 +0,0 @@ -name: my_app_name -environment: - sdk: ">=2.17.0 <3.0.0" - flutter: ">=3.0.0" - -dependencies: - flutter: - sdk: flutter - flutter_hooks: ^0.18.0 - hooks_riverpod: ^2.0.2 \ No newline at end of file diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/getting_started/pubspec/hooks_codegen.yaml b/website/i18n/ko/docusaurus-plugin-content-docs/current/getting_started/pubspec/hooks_codegen.yaml deleted file mode 100644 index 807ea6729..000000000 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/getting_started/pubspec/hooks_codegen.yaml +++ /dev/null @@ -1,15 +0,0 @@ -name: my_app_name -environment: - sdk: ">=2.17.0 <3.0.0" - flutter: ">=3.0.0" - -dependencies: - flutter: - sdk: flutter - flutter_hooks: ^0.18.0 - hooks_riverpod: ^2.0.2 - riverpod_annotation: ^1.0.4 - -dev_dependencies: - build_runner: - riverpod_generator: ^1.0.4 \ No newline at end of file diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/getting_started/pubspec/index.tsx b/website/i18n/ko/docusaurus-plugin-content-docs/current/getting_started/pubspec/index.tsx deleted file mode 100644 index d7e1ee500..000000000 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/getting_started/pubspec/index.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import raw from "!!raw-loader!./raw.yaml"; -import hooks from "!!raw-loader!./hooks.yaml"; -import codegen from "!!raw-loader!./codegen.yaml"; -import hooksCodegen from "!!raw-loader!./hooks_codegen.yaml"; - -export default { - raw, - hooks, - codegen, - hooksCodegen, -}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/getting_started/pubspec/raw.yaml b/website/i18n/ko/docusaurus-plugin-content-docs/current/getting_started/pubspec/raw.yaml deleted file mode 100644 index 605be5f5e..000000000 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/getting_started/pubspec/raw.yaml +++ /dev/null @@ -1,9 +0,0 @@ -name: my_app_name -environment: - sdk: ">=2.17.0 <3.0.0" - flutter: ">=3.0.0" - -dependencies: - flutter: - sdk: flutter - flutter_riverpod: ^2.0.2 \ No newline at end of file diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/introduction.mdx b/website/i18n/ko/docusaurus-plugin-content-docs/current/introduction.mdx deleted file mode 100644 index 4b19a3950..000000000 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/introduction.mdx +++ /dev/null @@ -1,76 +0,0 @@ ---- -title: 소개 ---- - ---- - -## What is Riverpod? - -Riverpod (anagram of [Provider](https://pub.dev/packages/provider)) is a reactive -caching framework for Flutter/Dart. It can automatically fetch, cache, combine and -recompute network requests, while also taking care of errors for you. - -## Motivation - -Modern applications rarely come with all the information necessary to render -their User Interface. Instead, the data is often fetched asynchronously -from a server. - -The problem is, working with asynchronous code is hard. Although Flutter comes -with some way to store state, it doesn't do much besides that. Thus, a number -of challenges remain unsolved: - -- Asynchronous requests need to be cached locally, as it would be unreasonable - to re-execute them whenever the UI refreshes. -- Since we have a cache, our cache could get out of date if we're not careful. -- We also need to handle errors and loading states - -Nailing those problems at scale can be difficult, and they are impacted by a large -amount of features, such as: - -- pull to refresh -- infinite lists / fetch as we scroll -- search as we type -- debouncing asynchronous requests -- cancelling asynchronous requests when no-longer used -- optimistic UIs -- offline mode -- ... - -These features can be tricky to implement, but are crucial for a good user experience. -Yet few packages try to tackle those problems directly, and a lot of the work -has to be done manually. - -That's where Riverpod comes in. -Riverpod tries to solve those problems, by offering a new unique -way of writing business logic, inspired by Flutter widgets. In -many ways Riverpod is comparable to widgets, but for state. - -Using this new approach, these complex features are mostly done by default. All -that's left is to focus on your UI. - -Skeptical? Here's an example. The following snippet is a simplification of the [Pub.dev](https://github.com/rrousselGit/riverpod/tree/master/examples/pub) -client application implemented using Riverpod. - -```dart -// Fetches the list of packages from pub.dev -@riverpod -Future> fetchPackages( - FetchPackagesRef ref, { - required int page, - String search = '', -}) async { - final dio = Dio(); - // Fetch an API. Here we're using package:dio, but we could use anything else. - final response = await dio.get( - 'https://pub.dartlang.org/api/search?page=$page&q=${Uri.encodeQueryComponent(search)}', - ); - - // Decode the JSON response into a Dart class. - final json = response.data as List; - return json.map(Package.fromJson).toList(); -} -``` - -This snippet is all the business logic you need for a "search as we type" -+ "pull to refresh" + "infinite list", while handling error/loading states. diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/getting_started.mdx b/website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/getting_started.mdx new file mode 100644 index 000000000..68ba4fe88 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/getting_started.mdx @@ -0,0 +1,191 @@ +--- +title: 시작하기 +pagination_next: essentials/first_request +version: 4 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; +import CodeBlock from "@theme/CodeBlock"; +import pubspec from "./getting_started/pubspec"; +import dartHelloWorld from "./getting_started/dart_hello_world"; +import pubadd from "./getting_started/pub_add"; +import helloWorld from "./getting_started/hello_world"; +import dartPubspec from "./getting_started/dart_pubspec"; +import dartPubadd from "./getting_started/dart_pub_add"; +import { + AutoSnippet, + When, +} from "../../../../../src/components/CodeSnippet"; +import { Link } from "../../../../../src/components/Link"; + +## Riverpod 온라인으로 경험해보기 + +Riverpod을 경험해보려면 [Dartpad](https://dartpad.dev/?null_safety=true&id=ef06ab3ce0b822e6cc5db0575248e6e2)나 [Zapp](https://zapp.run/new)에서 온라인으로 시도해보세요: + + + +## 패키지 설치하기 + +설치하려는 패키지가 결정되면, 아래와 같이 한 줄로 앱에 종속성을 추가합니다: + + + + + + + + + + + + + + + + +대안으로, `pubspec.yaml`에 종속성을 직접 추가할 수도 있습니다: + + + + + + +그리고, `flutter pub get`으로 패키지를 설치합니다. + + + 이제 코드 생성기를 실행할 수 있습니다.{" "} + flutter run build_runner watch. + + + + + + + +그리고, `dart pub get`으로 패키지를 설치합니다. + + + 이제 코드 생성기를 실행할 수 있습니다.{" "} + dart pub run build_runner watch. + + + + + +이게 다입니다. 당신의 앱에 [Riverpod]을 추가했습니다! + +## riverpod_lint/custom_lint 활성화하기 + +Riverpod은 선택사항으로 [riverpod_lint] 패키지를 제공합니다. +이 패키지는 더 좋은 코드를 작성하는데 도움이 되는 린트 규칙과 사용자 정의 리팩토링 옵션을 제공합니다. + +이전 단계를 따라왔다면 이 패키지는 이미 설치되어 있을 것입니다. +하지만 활성화하기 위해서는 별도의 단계가 필요합니다. + +[riverpod_lint]를 활성화하려면, 당신의 `pubspec.yaml`옆에 `analysis_options.yaml`을 추가하고 다음을 포함해야 합니다: + + + {`analyzer: + plugins: + - custom_lint`} + + +이제 코드베이스에서 Riverpod을 사용할 때 당신이 실수한 경우, IDE에서 경고가 보이게 됩니다. + +전체 경고와 리팩토링 목록을 보려면 [riverpod_lint] 페이지를 참고하세요. + +:::note +이 경고는 `dart analyze` 명령에서는 표시되지 않습니다. +CI/터미널에서 이 경고를 확인하려면 다음을 실행하세요: + +```sh +dart run custom_lint +``` + +::: + +## 사용 예시: Hello world + +이제 [Riverpod]를 설치했으니 사용해볼 수 있습니다. + +다음 스니펫은 새로운 종속성을 사용하여 "Hello world"를 만드는 방법을 보여줍니다: + + + + + + +이제 `flutter run`으로 앱을 시작할 수 있습니다. +이렇게 하면 기기에 "Hello world"가 표시됩니다. + + + + + + +이제 `dart lib/main.dart`으로 앱을 시작할 수 있습니다. +이렇게 하면 콘솔에 "Hello world"가 표시됩니다. + + + + +## 더 나아가기: 코드 스니펫 설치하기 + +`Flutter`와 `VS Code`를 사용하고 있다면 [Flutter Riverpod Snippets](https://marketplace.visualstudio.com/items?itemName=robert-brunhage.flutter-riverpod-snippets)를 사용해보세요. + +`Flutter`와 `Android Studio` 또는 `IntelliJ`를 사용하고 있다면 [Flutter Riverpod Snippets](https://plugins.jetbrains.com/plugin/14641-flutter-riverpod-snippets)를 사용해보세요. + +![img](/img/snippets/greetingProvider.gif) + +## 다음 단계 선택 + +몇 가지 기본 개념 알아보기: + +- + +쿡북(Cookbook) 따라하기: + +- + +[riverpod]: https://github.com/rrousselgit/riverpod +[hooks_riverpod]: https://pub.dev/packages/hooks_riverpod +[flutter_riverpod]: https://pub.dev/packages/flutter_riverpod +[flutter_hooks]: https://github.com/rrousselGit/flutter_hooks +[riverpod_lint]: https://pub.dev/packages/riverpod_lint diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/getting_started/hello_world/index.tsx b/website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/getting_started/dart_hello_world/index.tsx similarity index 100% rename from website/i18n/ko/docusaurus-plugin-content-docs/current/getting_started/hello_world/index.tsx rename to website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/getting_started/dart_hello_world/index.tsx diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/getting_started/dart_hello_world/main.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/getting_started/dart_hello_world/main.dart new file mode 100644 index 000000000..89bbd92dc --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/getting_started/dart_hello_world/main.dart @@ -0,0 +1,25 @@ +// ignore_for_file: avoid_print + +/* SNIPPET START */ + +import 'package:riverpod/riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'main.g.dart'; + +// 값(여기서는 "Hello world")을 저장할 "provider"를 생성합니다. +// provider를 이용하면, 노출된 값을 모의(Mock)하거나 오버라이드(override)할 수 있습니다. +@riverpod +String helloWorld(HelloWorldRef ref) { + return 'Hello world'; +} + +void main() { + // 이 객체는 providers의 상태가 저장되는 곳입니다. + final container = ProviderContainer(); + + // "container" 덕분에 provider를 읽을 수 있습니다. + final value = container.read(helloWorldProvider); + + print(value); // Hello world +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/getting_started/dart_hello_world/main.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/getting_started/dart_hello_world/main.g.dart new file mode 100644 index 000000000..180bedb23 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/getting_started/dart_hello_world/main.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'main.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$helloWorldHash() => r'8bbe6cff2b7b1f4e1f7be3d1820da793259f7bfc'; + +/// See also [helloWorld]. +@ProviderFor(helloWorld) +final helloWorldProvider = AutoDisposeProvider.internal( + helloWorld, + name: r'helloWorldProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$helloWorldHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef HelloWorldRef = AutoDisposeProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/getting_started/dart_hello_world/main.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/getting_started/dart_hello_world/raw.dart similarity index 76% rename from website/i18n/ko/docusaurus-plugin-content-docs/current/getting_started/dart_hello_world/main.dart rename to website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/getting_started/dart_hello_world/raw.dart index cb53853fb..445bc0a9c 100644 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/getting_started/dart_hello_world/main.dart +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/getting_started/dart_hello_world/raw.dart @@ -3,16 +3,10 @@ /* SNIPPET START */ import 'package:riverpod/riverpod.dart'; -import 'package:riverpod_annotation/riverpod_annotation.dart'; - -part 'main.g.dart'; // We create a "provider", which will store a value (here "Hello world"). // By using a provider, this allows us to mock/override the value exposed. -@riverpod -String helloWorld(HelloWorldRef ref) { - return 'Hello world'; -} +final helloWorldProvider = Provider((_) => 'Hello world'); void main() { // This object is where the state of our providers will be stored. diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/getting_started/dart_pub_add.tsx b/website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/getting_started/dart_pub_add.tsx new file mode 100644 index 000000000..07c03cf6b --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/getting_started/dart_pub_add.tsx @@ -0,0 +1,32 @@ +export function buildDeps({ + deps = [], + devDeps = [], +}: { + deps?: string[]; + devDeps?: string[]; +}) { + var result = ""; + for (const dep of deps) { + result += `dart pub add ${dep}\n`; + } + + for (const dep of [...devDeps, "custom_lint", "riverpod_lint"]) { + result += `dart pub add dev:${dep}\n`; + } + + return result; +} + +const raw = buildDeps({ deps: ["riverpod"] }); + +const codegen = buildDeps({ + deps: ["riverpod", "riverpod_annotation"], + devDeps: ["riverpod_generator", "build_runner"], +}); + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/getting_started/dart_pubspec.tsx b/website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/getting_started/dart_pubspec.tsx new file mode 100644 index 000000000..d44367c30 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/getting_started/dart_pubspec.tsx @@ -0,0 +1,40 @@ +import { + riverpodVersion, + riverpodAnnotationVersion, + riverpodGeneratorVersion, + riverpodLintVersion, +} from "../../../../../../src/versions"; + +const codegen = `name: my_app_name +environment: + sdk: ">=3.0.0 <4.0.0" + +dependencies: + riverpod: ^${riverpodVersion} + riverpod_annotation: ^${riverpodAnnotationVersion} + +dev_dependencies: + build_runner: + custom_lint: + riverpod_generator: ^${riverpodGeneratorVersion} + riverpod_lint: ^${riverpodLintVersion} +`; + +const raw = `name: my_app_name +environment: + sdk: ">=3.0.0 <4.0.0" + +dependencies: + riverpod: ^${riverpodVersion} + +dev_dependencies: + custom_lint: + riverpod_lint: ^${riverpodLintVersion} +`; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/getting_started/hello_world/main_hooks.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/hooks_codegen/main.dart similarity index 59% rename from website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/getting_started/hello_world/main_hooks.dart rename to website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/hooks_codegen/main.dart index c08110c6b..cd94bb4ad 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/getting_started/hello_world/main_hooks.dart +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/hooks_codegen/main.dart @@ -7,10 +7,10 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -part 'main_hooks.g.dart'; +part 'main.g.dart'; -// 我们创建一个 “provider”,它将用于保存一个值(这里是 “Hello world”)。 -// 通过使用一个 provider,我们能够模拟或覆盖被暴露的值。 +// 값을 저장할 “provider"를 생성합니다(여기서는 "Hello world"). +// 프로바이더를 사용하면, 노출된 값을 모의(Mock)하거나 재정의(Override)할 수 있습니다. @riverpod String helloWorld(HelloWorldRef ref) { return 'Hello world'; @@ -18,20 +18,20 @@ String helloWorld(HelloWorldRef ref) { void main() { runApp( - // 为了能让组件读取 provider,我们需要将整个 - // 应用都包裹在 “ProviderScope” 组件内。 - // 这里也就是存储我们所有 provider 状态的地方。 + // 위젯이 프로바이더를 읽을 수 있도록 하려면, + // 전체 애플리케이션을 "ProviderScope" 위젯으로 감싸야 합니다. + // 여기에 프로바이더의 상태가 저장됩니다. ProviderScope( child: MyApp(), ), ); } -// 扩展来自 Riverpod 的 HookConsumerWidget 而不是 HookWidget +// HookWidget 대신 Riverpod에서 제공되는 HookConsumerWidget을 확장합니다. class MyApp extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - // We can use hooks inside HookConsumerWidget + // HookConsumerWidget 내부에서 Hook을 사용할 수 있습니다. final counter = useState(0); final String value = ref.watch(helloWorldProvider); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/hooks_codegen/main.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/hooks_codegen/main.g.dart new file mode 100644 index 000000000..180bedb23 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/hooks_codegen/main.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'main.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$helloWorldHash() => r'8bbe6cff2b7b1f4e1f7be3d1820da793259f7bfc'; + +/// See also [helloWorld]. +@ProviderFor(helloWorld) +final helloWorldProvider = AutoDisposeProvider.internal( + helloWorld, + name: r'helloWorldProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$helloWorldHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef HelloWorldRef = AutoDisposeProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/index.tsx b/website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/index.tsx new file mode 100644 index 000000000..3f10c2a94 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/index.tsx @@ -0,0 +1,11 @@ +import raw from "!!raw-loader!./raw.dart"; +import raw_hooks from "!!raw-loader!./raw_hooks.dart"; +import codegen from "!!raw-loader!./main.dart"; +import hooksCodegen from "!!raw-loader!./hooks_codegen/main.dart"; + +export default { + raw, + hooks: raw_hooks, + codegen, + hooksCodegen: hooksCodegen, +}; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/getting_started/hello_world/main.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/main.dart similarity index 60% rename from website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/getting_started/hello_world/main.dart rename to website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/main.dart index 1ea9ff208..77de400be 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/getting_started/hello_world/main.dart +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/main.dart @@ -8,8 +8,8 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'main.g.dart'; -// 我们创建一个 “provider”,它将用于保存一个值(这里是 “Hello world”)。 -// 通过使用一个 provider,我们能够模拟或覆盖被暴露的值。 +// 값을 저장할 “provider"를 생성합니다(여기서는 "Hello world"). +// 프로바이더를 사용하면, 노출된 값을 모의(Mock)하거나 재정의(Override)할 수 있습니다. @riverpod String helloWorld(HelloWorldRef ref) { return 'Hello world'; @@ -17,16 +17,16 @@ String helloWorld(HelloWorldRef ref) { void main() { runApp( - // 为了能让组件读取 provider,我们需要将整个 - // 应用都包裹在 “ProviderScope” 组件内。 - // 这里也就是存储我们所有 provider 状态的地方。 + // 위젯이 프로바이더를 읽을 수 있도록 하려면, + // 전체 애플리케이션을 "ProviderScope" 위젯으로 감싸야 합니다. + // 여기에 프로바이더의 상태가 저장됩니다. ProviderScope( child: MyApp(), ), ); } -// 扩展来自 Riverpod 的 HookConsumerWidget 而不是 HookWidget +// StatelessWidget 대신 Riverpod에서 제공되는 ConsumerWidget을 확장합니다. class MyApp extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/main.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/main.g.dart new file mode 100644 index 000000000..180bedb23 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/main.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'main.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$helloWorldHash() => r'8bbe6cff2b7b1f4e1f7be3d1820da793259f7bfc'; + +/// See also [helloWorld]. +@ProviderFor(helloWorld) +final helloWorldProvider = AutoDisposeProvider.internal( + helloWorld, + name: r'helloWorldProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$helloWorldHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef HelloWorldRef = AutoDisposeProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/getting_started/hello_world/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/raw.dart similarity index 100% rename from website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/getting_started/hello_world/raw.dart rename to website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/raw.dart diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/getting_started/hello_world/raw_hooks.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/raw_hooks.dart similarity index 100% rename from website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/getting_started/hello_world/raw_hooks.dart rename to website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/raw_hooks.dart diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/getting_started/pub_add.tsx b/website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/getting_started/pub_add.tsx new file mode 100644 index 000000000..65c7be1bd --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/getting_started/pub_add.tsx @@ -0,0 +1,39 @@ +export function buildDeps({ + deps = [], + devDeps = [], +}: { + deps?: string[]; + devDeps?: string[]; +}) { + var result = ''; + for (const dep of deps) { + result += `flutter pub add ${dep}\n`; + } + + for (const dep of [...devDeps, "custom_lint", "riverpod_lint"]) { + result += `flutter pub add dev:${dep}\n`; + } + + return result; +} + +const raw = buildDeps({ deps: ["flutter_riverpod"] }); + +const codegen = buildDeps({ + deps: ["flutter_riverpod", "riverpod_annotation"], + devDeps: ["riverpod_generator", "build_runner"], +}); + +const hooks = buildDeps({ deps: ["hooks_riverpod", "flutter_hooks"] }); + +const hooksCodegen = buildDeps({ + deps: ["hooks_riverpod", "flutter_hooks", "riverpod_annotation"], + devDeps: ["riverpod_generator", "build_runner"], +}); + +export default { + raw: raw, + hooks: hooks, + codegen: codegen, + hooksCodegen: hooksCodegen, +}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/getting_started/pubspec.tsx b/website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/getting_started/pubspec.tsx new file mode 100644 index 000000000..da974c2a1 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/getting_started/pubspec.tsx @@ -0,0 +1,51 @@ +import { + flutterRiverpodVersion, + hooksRiverpodVersion, + riverpodAnnotationVersion, + riverpodGeneratorVersion, + riverpodLintVersion, +} from "../../../../../../src/versions"; + +function plain(riverpod: string) { + return `name: my_app_name +environment: + sdk: ">=3.0.0 <4.0.0" + flutter: ">=3.0.0" + +dependencies: + flutter: + sdk: flutter + ${riverpod} + +dev_dependencies: + custom_lint: + riverpod_lint: ^${riverpodLintVersion} +`; +} + +function codegen(riverpod: string) { + return `name: my_app_name +environment: + sdk: ">=3.0.0 <4.0.0" + flutter: ">=3.0.0" + +dependencies: + flutter: + sdk: flutter + ${riverpod} + riverpod_annotation: ^${riverpodAnnotationVersion} + +dev_dependencies: + build_runner: + custom_lint: + riverpod_generator: ^${riverpodGeneratorVersion} + riverpod_lint: ^${riverpodLintVersion} +`; +} + +export default { + raw: plain(`flutter_riverpod: ^${flutterRiverpodVersion}`), + hooks: plain(`hooks_riverpod: ^${hooksRiverpodVersion}\n flutter_hooks:`), + codegen: codegen(`flutter_riverpod: ^${flutterRiverpodVersion}`), + hooksCodegen: codegen(`hooks_riverpod: ^${hooksRiverpodVersion}\n flutter_hooks:`), +}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/why_riverpod.mdx b/website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/why_riverpod.mdx new file mode 100644 index 000000000..cfd6b051e --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/why_riverpod.mdx @@ -0,0 +1,56 @@ +--- +title: 왜 Riverpod인가? +version: 1 +--- + +import whyRiverpod from "./why_riverpod"; +import { AutoSnippet } from "../../../../../src/components/CodeSnippet"; + +## Riverpod 이란? + +Riverpod ([Provider](https://pub.dev/packages/provider)의 애너그램-철자를 바꾼 말)는 +Flutter/Dart를 위한 반응형 캐싱 프레임워크(Reactive caching framework)입니다. + +선언적 프로그래밍과 반응형 프로그래밍을 사용하여, Riverpod은 당신을 위해 애플리케이션의 상단 부분을 처리합니다. +내장된 오류 처리 및 캐싱을 통해 네트워크 요청을 수행할 수 있으며, +필요한 경우 데이터를 자동으로 데이터를 자동으로 다시 가져올 수 있습니다. + +## Motivation + +현대 애플리케이션은 UI를 렌더링하는 데 필요한 모든 정보를 제공하지 않습니다. +대신, 서버에서 비동기적으로 데이터를 가져오는 경우가 많습니다. + +문제는 비동기 코드를 사용하는 것이 어렵다는 것입니다. +Flutter는 상태 변수를 생성하고, 변경시 UI를 갱신하는 몇 가지 방법을 제공하지만, 아직은 상당히 제한적입니다. + +- 비동기 요청은 UI가 업데이트될 때마다 다시 실행하는 것은 불합리하므로 로컬에 캐시해야 할 필요가 있습니다. +- 캐시가 있기 때문에 우리가 조심하지 않으면 오래된 상태가 될 수 있습니다. +- 또한 오류 및 로딩 상태를 처리해야 합니다. + +이러한 문제를 대규모로 해결하는 것은 어려울 수 있으며, 다음과 같은 많은 기능에 영향을 받습니다: + +- 당겨서 새로 고침 +- 무한 목록 / 스크롤할 때 가져오기 +- 타이핑하는 동안 검색 +- 비동기 요청의 디바운싱(Debouncing) +- 더 이상 사용되지 않을 때 비동기 요청 취소 +- 낙관적(Optimistic) UI +- 오프라인 모드 +- ... + +이러한 기능은 구현하기 어려울 수 있지만, 좋은 사용자 경험을 위해 중요합니다. +아직까지 이러한 문제를 직접 해결하려는 패키지는 몇 개 없으며, 직접처리하려면 많은 작업이 필요합니다. + +그래서 Riverpod이 등장했습니다. +Riverpod은 Flutter 위젯에서 영감을 받아, 비즈니스 로직을 작성하는 새롭고 독특한 방식을 제공하여 이러한 문제를 해결하려고 합니다. +여러 가지 면에서 Riverpod은 위젯과 비슷하지만, 상태를 처리하기 위한 것입니다. + +이 새로운 접근 방식을 사용하면, 복잡한 기능을 대부분 기본적으로 처리할 수 있습니다. +남은 것은 UI에 집중하는 것입니다. + +회의적인가요? 여기 예제가 있습니다. +다음 스니펫은 Riverpod을 사용하여 구현된 [Pub.dev](https://github.com/rrousselGit/riverpod/tree/master/examples/pub) 클라이언트 어플리케이션의 단순화 버전입니다. + + + +이 스니펫은 당신이 에러/로딩 상태를 다루면서, "타이핑하는 동안 검색" + "당겨서 새로 고침" + "무한 목록"을 구현하는 데 필요한 모든 비즈니스 로직을 담고 있습니다. \ No newline at end of file diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/why_riverpod/codegen.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/why_riverpod/codegen.dart new file mode 100644 index 000000000..83e1ff5b6 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/why_riverpod/codegen.dart @@ -0,0 +1,31 @@ +// ignore_for_file: use_key_in_widget_constructors, omit_local_variable_types + +import 'package:dio/dio.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +class Package { + static Package fromJson(dynamic json) { + throw UnimplementedError(); + } +} + +/* SNIPPET START */ + +// Fetches the list of packages from pub.dev +@riverpod +Future> fetchPackages( + FetchPackagesRef ref, { + required int page, + String search = '', +}) async { + final dio = Dio(); + // Fetch an API. Here we're using package:dio, but we could use anything else. + final response = await dio.get>( + 'https://pub.dartlang.org/api/search?page=$page&q=${Uri.encodeQueryComponent(search)}', + ); + + // Decode the JSON response into a Dart class. + return response.data?.map(Package.fromJson).toList() ?? const []; +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/why_riverpod/codegen.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/why_riverpod/codegen.g.dart new file mode 100644 index 000000000..5c271e243 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/why_riverpod/codegen.g.dart @@ -0,0 +1,178 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$fetchPackagesHash() => r'eebf7d838a57f493fffebfd2c8d8ab76d3233165'; + +/// Copied from Dart SDK +class _SystemHash { + _SystemHash._(); + + static int combine(int hash, int value) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + value); + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); + return hash ^ (hash >> 6); + } + + static int finish(int hash) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + // ignore: parameter_assignments + hash = hash ^ (hash >> 11); + return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); + } +} + +/// See also [fetchPackages]. +@ProviderFor(fetchPackages) +const fetchPackagesProvider = FetchPackagesFamily(); + +/// See also [fetchPackages]. +class FetchPackagesFamily extends Family>> { + /// See also [fetchPackages]. + const FetchPackagesFamily(); + + /// See also [fetchPackages]. + FetchPackagesProvider call({ + required int page, + String search = '', + }) { + return FetchPackagesProvider( + page: page, + search: search, + ); + } + + @override + FetchPackagesProvider getProviderOverride( + covariant FetchPackagesProvider provider, + ) { + return call( + page: provider.page, + search: provider.search, + ); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'fetchPackagesProvider'; +} + +/// See also [fetchPackages]. +class FetchPackagesProvider extends AutoDisposeFutureProvider> { + /// See also [fetchPackages]. + FetchPackagesProvider({ + required int page, + String search = '', + }) : this._internal( + (ref) => fetchPackages( + ref as FetchPackagesRef, + page: page, + search: search, + ), + from: fetchPackagesProvider, + name: r'fetchPackagesProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$fetchPackagesHash, + dependencies: FetchPackagesFamily._dependencies, + allTransitiveDependencies: + FetchPackagesFamily._allTransitiveDependencies, + page: page, + search: search, + ); + + FetchPackagesProvider._internal( + super._createNotifier, { + required super.name, + required super.dependencies, + required super.allTransitiveDependencies, + required super.debugGetCreateSourceHash, + required super.from, + required this.page, + required this.search, + }) : super.internal(); + + final int page; + final String search; + + @override + Override overrideWith( + FutureOr> Function(FetchPackagesRef provider) create, + ) { + return ProviderOverride( + origin: this, + override: FetchPackagesProvider._internal( + (ref) => create(ref as FetchPackagesRef), + from: from, + name: null, + dependencies: null, + allTransitiveDependencies: null, + debugGetCreateSourceHash: null, + page: page, + search: search, + ), + ); + } + + @override + AutoDisposeFutureProviderElement> createElement() { + return _FetchPackagesProviderElement(this); + } + + @override + bool operator ==(Object other) { + return other is FetchPackagesProvider && + other.page == page && + other.search == search; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, page.hashCode); + hash = _SystemHash.combine(hash, search.hashCode); + + return _SystemHash.finish(hash); + } +} + +mixin FetchPackagesRef on AutoDisposeFutureProviderRef> { + /// The parameter `page` of this provider. + int get page; + + /// The parameter `search` of this provider. + String get search; +} + +class _FetchPackagesProviderElement + extends AutoDisposeFutureProviderElement> + with FetchPackagesRef { + _FetchPackagesProviderElement(super.provider); + + @override + int get page => (origin as FetchPackagesProvider).page; + @override + String get search => (origin as FetchPackagesProvider).search; +} +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/why_riverpod/index.tsx b/website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/why_riverpod/index.tsx new file mode 100644 index 000000000..339b89a56 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/why_riverpod/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./codegen.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/why_riverpod/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/why_riverpod/raw.dart new file mode 100644 index 000000000..131981f7b --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/introduction/why_riverpod/raw.dart @@ -0,0 +1,27 @@ +// ignore_for_file: use_key_in_widget_constructors, omit_local_variable_types + +import 'package:dio/dio.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +class Package { + static Package fromJson(dynamic json) { + throw UnimplementedError(); + } +} + +/* SNIPPET START */ + +// Fetches the list of packages from pub.dev +final fetchPackagesProvider = FutureProvider.autoDispose + .family, ({int page, String? search})>((ref, params) async { + final page = params.page; + final search = params.search ?? ''; + final dio = Dio(); + // Fetch an API. Here we're using package:dio, but we could use anything else. + final response = await dio.get>( + 'https://pub.dartlang.org/api/search?page=$page&q=${Uri.encodeQueryComponent(search)}', + ); + + // Decode the JSON response into a Dart class. + return response.data?.map(Package.fromJson).toList() ?? const []; +}); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/0.13.0_to_0.14.0.mdx b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/0.13.0_to_0.14.0.mdx index e577300ff..9bb15695c 100644 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/0.13.0_to_0.14.0.mdx +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/0.13.0_to_0.14.0.mdx @@ -1,11 +1,11 @@ --- -title: ^0.13.0 to ^0.14.0 +title: ^0.13.0에서 ^0.14.0 --- -With the release of version `0.14.0` of Riverpod, the syntax for using [StateNotifierProvider] changed -(see https://github.com/rrousselGit/riverpod/issues/341 for the explanation). +리버팟 `0.14.0` 버전이 출시되면서, [StateNotifierProvider] 사용 구문이 변경되었습니다. +(자세한 설명은 https://github.com/rrousselGit/riverpod/issues/341 참조) -For the entire article, consider the following [StateNotifier]: +전체 문서에 대해서는 다음 [StateNotifier]를 참조하세요: ```dart class MyModel {} @@ -15,12 +15,11 @@ class MyStateNotifier extends StateNotifier { } ``` -## The changes +## 변경사항 -- [StateNotifierProvider] takes an extra generic parameter, which should be - the type of the state of your [StateNotifier]. +- [StateNotifierProvider]는 추가 제네릭 매개변수(extra generic parameter)를 받는데, 이 매개변수는 [StateNotifier]의 상태 타입이어야 합니다. - Before: + 이전: ```dart final provider = StateNotifierProvider((ref) { @@ -28,7 +27,7 @@ class MyStateNotifier extends StateNotifier { }); ``` - After: + 변경후: ```dart final provider = StateNotifierProvider((ref) { @@ -36,9 +35,9 @@ class MyStateNotifier extends StateNotifier { }); ``` -- to obtain the [StateNotifier], you should now read `myProvider.notifier` instead of just `myProvider`: +- [StateNotifier]를 얻으려면, 이제 `myProvider`가 아닌 `myProvider.notifier`를 읽어야 합니다: - Before: + 이전: ```dart Widget build(BuildContext context, ScopedReader watch) { @@ -46,7 +45,7 @@ class MyStateNotifier extends StateNotifier { } ``` - After: + 변경후: ```dart Widget build(BuildContext context, ScopedReader watch) { @@ -54,9 +53,9 @@ class MyStateNotifier extends StateNotifier { } ``` -- to listen to the state of the [StateNotifier], you should now read `myProvider` instead of `myProvider.state`: +- [StateNotifier]의 상태를 수신(listen)하려면, 이제 `myProvider.state` 대신 `myProvider`를 읽어야 합니다: - Before: + 이전: ```dart Widget build(BuildContext context, ScopedReader watch) { @@ -64,7 +63,7 @@ class MyStateNotifier extends StateNotifier { } ``` - After: + 변경후: ```dart Widget build(BuildContext context, ScopedReader watch) { @@ -72,39 +71,38 @@ class MyStateNotifier extends StateNotifier { } ``` -## Using the migration tool to automatically upgrade your projects to the new syntax +## 마이그레이션 도구를 사용하여 프로젝트를 새 구문으로 자동 업그레이드하기 -With version 0.14.0 came the release of a command line tool for Riverpod, -which can help you migrate your projects. +버전 0.14.0에서는 프로젝트를 마이그레이션하는 데 도움이 되는 리버포드용 명령줄 도구가 출시되었습니다. -### Installing the command line +### 명령줄 설치하기 -To install the migration tool, run: +마이그레이션 도구를 설치하려면 다음을 실행합니다: ```sh dart pub global activate riverpod_cli ``` -You should now be able to run: +이제 실행할 수 있을 것입니다: ```sh riverpod --help ``` -### Usage +### 사용법 -Now that the command line is installed, we can start using it. +이제 명령줄이 설치되었으므로 사용을 시작할 수 있습니다. -- First, open the project you want to migrate in your terminal. -- **Do not** upgrade Riverpod. - The migration tool will upgrade the version of Riverpod for you. -- Make sure that your project does not contain errors. -- Execute: +- 먼저 터미널에서 마이그레이션하려는 프로젝트를 엽니다. +- **Do not** Riverpod을 업그레이드하지 마세요. + 마이그레이션 도구가 Riverpod의 버전을 업그레이드해 줍니다. +- 프로젝트에 오류가 없는지 확인합니다. +- 실행: ```sh riverpod migrate ``` -The tool will then analyze your project and suggest changes. For example you may see: +그러면 도구가 프로젝트를 분석하고 변경 사항을 제안합니다. 예를 들면 다음과 같습니다: ```diff Widget build(BuildContext context, ScopedReader watch) { @@ -115,8 +113,7 @@ Widget build(BuildContext context, ScopedReader watch) { Accept change (y = yes, n = no [default], A = yes to all, q = quit)? ``` -To accept the change, simply press y. Otherwise to reject it, press n. - +변경을 수락하려면 Y를 누르면 됩니다. 변경을 거부하려면 n을 누르세요. [statenotifierprovider]: ../providers/state_notifier_provider [statenotifier]: https://pub.dev/documentation/state_notifier/latest/state_notifier/StateNotifier-class.html diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/0.14.0_to_1.0.0.mdx b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/0.14.0_to_1.0.0.mdx index 480775da1..afe8533ef 100644 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/0.14.0_to_1.0.0.mdx +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/0.14.0_to_1.0.0.mdx @@ -1,53 +1,54 @@ --- -title: ^0.14.0 to ^1.0.0 +title: ^0.14.0에서 ^1.0.0 --- -After a long wait, the first stable version of Riverpod is finally released 👏 +import { Link } from "../../../../../src/components/Link"; -To see the full list of changes, consult the [Changelog](https://pub.dev/packages/flutter_riverpod/changelog#100). -In this page, we will focus on how to migrate an existing Riverpod application -from version 0.14.x to version 1.0.0. -## Using the migration tool to automatically upgrade your project to the new syntax +오랜 기다림 끝에 드디어 리버팟의 첫 번째 안정 버전이 출시되었습니다 👏. -Before explaining the various changes, it is worth noting that Riverpod comes with -a command-line tool to automatically migrate your project for you. +전체 변경 목록을 보려면 [변경 로그](https://pub.dev/packages/flutter_riverpod/changelog#100)를 참조하세요. +이 페이지에서는 기존 리버Riverpod드 애플리케이션을 버전 0.14.x에서 버전 1.0.0으로 마이그레이션하는 방법에 대해 중점적으로 설명합니다. -### Installing the command line tool +## 마이그레이션 도구를 사용하여 프로젝트를 새 구문으로 자동 업그레이드하기 -To install the migration tool, run: +다양한 변경 사항을 설명하기 전에, Riverpod에는 프로젝트를 자동으로 마이그레이션할 수 있는 명령줄 도구가 함께 제공된다는 점을 알아둘 필요가 있습니다. + +### 명령줄 도구 설치하기 + +마이그레이션 도구를 설치하려면 다음을 실행합니다: ```sh dart pub global activate riverpod_cli ``` -You should now be able to run: +이제 실행할 수 있을 것입니다: ```sh riverpod --help ``` -### Usage +### 사용법 -Now that the command line is installed, we can start using it. +이제 명령줄이 설치되었으므로 사용을 시작할 수 있습니다. -- First, open the project you want to migrate in your terminal. -- **Do not** upgrade Riverpod. - The migration tool will upgrade the version of Riverpod for you. +- 먼저 터미널에서 마이그레이션하려는 프로젝트를 엽니다. +- **Do not** Riverpod을 업그레이드하지 마세요. + 마이그레이션 도구가 Riverpod의 버전을 업그레이드해 줍니다. :::danger - Not upgrading Riverpod is important. - The tool will not execute properly if you have already installed version 1.0.0. - As such, make sure that you are properly using an older version before starting the tool. + Riverpod을 업그레이드하지 않는 것이 중요합니다. + 이미 버전 1.0.0을 설치한 경우 도구가 제대로 실행되지 않습니다. + 따라서 도구를 시작하기 전에 이전 버전을 제대로 사용하고 있는지 확인하세요. ::: -- Make sure that your project does not contain errors. -- Execute: +- 프로젝트에 오류가 없는지 확인합니다. +- 실행: ```sh riverpod migrate ``` -The tool will then analyze your project and suggest changes. For example you may see: +그러면 도구가 프로젝트를 분석하고 변경 사항을 제안합니다. 예를 들면 다음과 같습니다: ```diff -Widget build(BuildContext context, ScopedReader watch) { @@ -59,26 +60,23 @@ The tool will then analyze your project and suggest changes. For example you may Accept change (y = yes, n = no [default], A = yes to all, q = quit)? ``` -To accept the change, simply press y. Otherwise to reject it, press n. +변경을 수락하려면 Y를 누르면 됩니다. 변경을 거부하려면 n을 누르세요. -## The changes +## 변경사항 -Now that we've seen how to use the CLI to automatically upgrade your project, -let's see in detail the changes necessary. +이제 CLI를 사용하여 프로젝트를 자동으로 업그레이드하는 방법을 살펴봤으니 필요한 변경 사항을 자세히 살펴봅시다. -### Syntax unification +### 구문(Syntax) 통합 -Version 1.0.0 of Riverpod focused on the unification of the syntax for -interacting with providers. -Before, Riverpod had many similar yet different syntaxes for reading a provider, -such as `ref.watch(provider)` vs `useProvider(provider)` vs `watch(provider)`. -With version 1.0.0, only one syntax remains: `ref.watch(provider)`. The -others were removed. +리버포드 버전 1.0.0은 providers와 상호작용하기 위한 구문을 통합하는 데 중점을 두었습니다. +이전에는 `ref.watch(provider)` 대 `useProvider(provider)` 대 `watch(provider)`와 같이 provider를 읽는 구문이 비슷하지만 서로 다른 구문이 많았습니다. +버전 1.0.0에서는 구문이 하나만 남았습니다: ref.watch(provider)`. 나머지는 제거되었습니다. -As such: +따라서: -- `useProvider` is removed in favor of `HookConsumerWidget`. - Before: +- `useProvider`가 제거되고 `HookConsumerWidget`이 대신 사용됩니다. + + 이전: ```dart class Example extends HookWidget { @@ -91,7 +89,7 @@ As such: } ``` - After: + 변경후: ```dart class Example extends HookConsumerWidget { @@ -104,8 +102,9 @@ As such: } ``` -- The prototype of `ConsumerWidget`'s `build` and `Consumer`'s `builder` changed. - Before: +- `ConsumerWidget`의 `build` 와 `Consumer`의 `builder`의 프로토타입이 변경되었습니다. + + 이전: ```dart class Example extends ConsumerWidget { @@ -124,7 +123,7 @@ As such: ) ``` - After: + 변경후: ```dart class Example extends ConsumerWidget { @@ -143,8 +142,9 @@ As such: ) ``` -- `context.read` is removed in favor of `ref.read`. - Before: +- `context.read`가 `ref.read`로 대체되어 제거됩니다. + + 이전: ```dart class Example extends StatelessWidget { @@ -157,7 +157,7 @@ As such: } ``` - After: + 변경후: ```dart class Example extends ConsumerWidget { @@ -170,15 +170,15 @@ As such: } ``` -### StateProvider update +### StateProvider 업데이트 -[StateProvider] was updated to match [StateNotifierProvider]. +[StateProvider]가 [StateNotifierProvider]와 일치하도록 업데이트 되었습니다. -Before, doing `ref.watch(StateProvider)` returned a `StateController` instance. -Now it only returns the state of the `StateController`. +이전에는 `ref.watch(StateProvider)`를 실행하면 `StateController` 인스턴스가 반환되었습니다. +이제 `StateController`의 상태만 반환합니다. -To migrate you have a few solutions. -If your code only obtained the state without modifying it, you can change from: +마이그레이션을 위한 몇 가지 솔루션이 있습니다. +코드를 수정하지 않고 상태만 가져온 경우 다음에서 변경할 수 있습니다: ```dart final provider = StateProvider(...); @@ -192,7 +192,7 @@ Consumer( ) ``` -to: +에서 다음과 같이: ```dart final provider = StateProvider(...); @@ -206,7 +206,7 @@ Consumer( ) ``` -Alternatively you can use the new `StateProvider.state` to keep the old behavior. +또는 새로운 `StateProvider.state`를 사용하여 이전 동작을 유지할 수 있습니다. ```dart final provider = StateProvider(...); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_change_notifier.mdx b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_change_notifier.mdx new file mode 100644 index 000000000..cc6683e73 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_change_notifier.mdx @@ -0,0 +1,117 @@ +--- +title: "`ChangeNotifier`에서" +--- + +import old from "!!raw-loader!./from_change_notifier/old.dart"; +import declaration from "./from_change_notifier/declaration"; +import initialization from "./from_change_notifier/initialization"; +import migrated from "./from_change_notifier/migrated"; + +import { Link } from "../../../../../src/components/Link"; +import { AutoSnippet } from "../../../../../src/components/CodeSnippet"; + +Riverpod 내에서 `ChangeNotifierProvider`는 pkg:provider에서 원활한 전환을 제공하는 데 사용됩니다. + +이제 막 pkg:riverpod로 마이그레이션을 시작한 경우 전용 가이드를 읽어보세요( 참조). +이 글은 이미 Riverpod로 마이그레이션했지만 `ChangeNotifier`에서 완전히 벗어나고 싶은 분들을 위한 글입니다. + +대체로 `ChangeNotifier`에서 `AsyncNotifer`로 마이그레이션하려면 패러다임의 전환이 필요하지만, 마이그레이션된 코드를 통해 크게 간소화할 수 있습니다. +도 참조하세요. + +이 (잘못된) 예를 들어보겠습니다: + + +이 구현은 다음과 같은 몇 가지 잘못된 디자인 선택을 보여줍니다: +- 다양한 비동기 케이스를 처리하기 위해 `isLoading`과 `hasError`를 사용함. +- 지루한 `try`/`catch`/`finally` 표현식으로 요청을 신중하게 처리해야 합니다. +- 이 구현이 작동하도록 하기 위해 적시에 `notifyListeners`를 삽입해야 할 필요성 +- 일관성이 없거나 바람직하지 않은 상태(예: 빈 리스트로 초기화)가 존재할 수 있습니다. + +이 예제는 `ChangeNotifier`가 초보 개발자에게 어떻게 잘못된 디자인 선택으로 이어질 수 있는지 보여주기 위해 만들어졌습니다; +또한 변경 가능한 상태가 처음에 약속한 것보다 훨씬 더 어려울 수 있다는 점도 알아두세요. + +`Notifier`/`AsyncNotifer`를 불변 상태(immutable state)와 함께 사용하면 더 나은 디자인 선택과 오류 감소로 이어질 수 있습니다. + +위의 스니펫을 한 번에 한 단계씩 최신 API로 마이그레이션하는 방법을 살펴보겠습니다. + +## 마이그레이셔 시작 +먼저 새 provider / notifier를 선언해야 합니다. 여기에는 고유한 비즈니스 로직에 따라 몇 가지 사고 과정이 필요합니다. + +위의 요구 사항을 요약해 보겠습니다: +- 상태(state)는 매개 변수(parameters) 없이 네트워크 호출을 통해 얻은 `List`로 표현됩니다. +- 상태는 `loading`, `error` 및 `data` 상태에 대한 정보를 *또한* 노출해야 합니다. +- State는 노출된 일부 메서드를 통해 변경될 수 있으므로 함수만으로는 충분하지 않습니다. + +:::tip +위의 사고 과정은 다음 질문에 답하는 것으로 요약됩니다: +1. 부가작업(side effects)이 필요한가? + - `y`: Riverpod의 클래스 기반 API 사용 + - `n`: Riverpod의 함수 기반 API 사용 +2. 상태를 비동기적으로 로드해야 하나요? + - `y`: `build`가 `Future`를 반환하도록 합니다. + - `n`: `build`가 단순히 `T`를 반환하도록 합니다. +3. 몇 가지 매개변수가 필요한가요? + - `y`: `build`(또는 함수)가 매개 변수를 받아들이도록 합니다. + - `n`: `build`(또는 함수)가 추가 매개변수를 받지 않도록 합니다. +::: + +:::info +코드생성을 사용한다면 위의 생각 과정만으로도 충분합니다. +올바른 클래스 이름과 해당 클래스의 *특정* API에 대해 생각할 필요가 없습니다. +`@riverpod`는 반환 유형이 있는 클래스를 작성하기만 하면 됩니다. +::: + +기술적으로 가장 적합한 방법은 위의 모든 요구 사항을 충족하는 `AutoDisposeAsyncNotifier>`를 정의하는 것입니다. +먼저 의사 코드를 작성해 봅시다. + + + +:::tip +기억하세요: IDE에서 스니펫을 사용하여 지침을 얻거나 코드 작성 속도를 높이세요. +를 참조하세요. +::: + +`ChangeNotifier`의 구현과 관련해서는 더 이상 `todos`를 선언할 필요가 없습니다; +이러한 변수는 `state`이며, `build`와 함께 암시적으로 로드됩니다. + +실제로 Riverpod의 노티파이어는 한 번에 *하나의* 엔티티를 노출할 수 있습니다. + +:::tip +Riverpod의 API는 세분화되어 있지만, 마이그레이션할 때 여러 값을 보유하도록 사용자 정의 엔티티를 정의할 수 있습니다. +처음에는 마이그레이션을 원활하게 하기 위해 [Dart 3's records](https://dart.dev/language/records)를 사용하는 것이 좋습니다. +::: + + +### 초기화 +`build` 안에 초기화 로직을 작성하기만 하면 notifier를 쉽게 초기화할 수 있습니다. +이제 이전 `_init` 함수를 제거할 수 있습니다. + + + +기존 `_init`과 관련하여, 새로운 `build`에서는 더 이상 `isLoading` 또는 `hasError`와 같은 변수를 초기화할 필요가 없습니다. + +Riverpod `AsyncValue>` 노출을 통해 모든 비동기 provider를 자동으로 변환하며, +두 개의 단순한 boolean 플래그가 할 수 있는 것보다 비동기 상태의 복잡성을 훨씬 더 잘 처리합니다. + +실제로 `AsyncNotifier`를 사용하면 비동기 상태를 처리하기 위해 추가 `try`/`catch`/`finally`를 작성하는 것이 사실상 안티 패턴이 됩니다. + +### 변이 및 부가작업(Mutations and Side Effects) +초기화와 마찬가지로 부가작업을 수행할 때 `hasError`와 같은 boolean 플래그를 조작하거나 `try`/`catch`/`finally` 블록을 추가로 작성할 필요가 없습니다. + +아래에서는 모든 상용구를 줄이고 위의 예제를 성공적으로 완전히 마이그레이션했습니다: + + +:::tip +구문과 디자인 선택은 다를 수 있지만 결국에는 요청을 작성하고 나중에 상태를 업데이트하기만 하면 됩니다. +를 참조하세요. +::: + +## 마이그레이션 프로세스 요약 + +위에서 적용한 전체 마이그레이션 프로세스를 운영 관점에서 검토해 보겠습니다. + +1. 초기화를 생성자에서 호출되는 사용자 정의 메서드에서 `build`로 옮겼습니다. +2. `todos`, `isLoading`, `hasError` 프로퍼티를 제거했습니다: 내부 `state`로 충분합니다. +3. `try`-`catch`-`finally` 블록을 제거했습니다: futures를 반환하는 것으로 충분합니다. +4. 부가작업(`addTodo`)에도 동일한 단순화를 적용했습니다. +5. 단순히 `state` 재할당을 통해 변형을 적용했습니다. diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_change_notifier/declaration/declaration.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_change_notifier/declaration/declaration.dart new file mode 100644 index 000000000..b3ca06ef2 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_change_notifier/declaration/declaration.dart @@ -0,0 +1,33 @@ +// ignore_for_file: avoid_print, avoid_unused_constructor_parameters + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'declaration.g.dart'; + +class Todo { + const Todo(this.id); + Todo.fromJson(Object obj) : id = 0; + + final int id; +} + +class Http { + Future> get(String str) async => [str]; + Future> post(String str) async => [str]; +} + +final http = Http(); + +/* SNIPPET START */ +@riverpod +class MyNotifier extends _$MyNotifier { + @override + FutureOr> build() { + // TODO ... + return []; + } + + Future addTodo(Todo todo) async { + // TODO + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_change_notifier/declaration/declaration.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_change_notifier/declaration/declaration.g.dart new file mode 100644 index 000000000..6a0307fb2 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_change_notifier/declaration/declaration.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'declaration.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$myNotifierHash() => r'fc9a07f8ef9f792da2ac660d76ea0a809335ba18'; + +/// See also [MyNotifier]. +@ProviderFor(MyNotifier) +final myNotifierProvider = + AutoDisposeAsyncNotifierProvider>.internal( + MyNotifier.new, + name: r'myNotifierProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$myNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$MyNotifier = AutoDisposeAsyncNotifier>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_change_notifier/declaration/index.tsx b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_change_notifier/declaration/index.tsx new file mode 100644 index 000000000..1ad659c31 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_change_notifier/declaration/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./declaration.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_change_notifier/declaration/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_change_notifier/declaration/raw.dart new file mode 100644 index 000000000..c7485bbba --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_change_notifier/declaration/raw.dart @@ -0,0 +1,33 @@ +// ignore_for_file: avoid_print, avoid_unused_constructor_parameters + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +class Todo { + const Todo(this.id); + Todo.fromJson(Object obj) : id = 0; + + final int id; +} + +class Http { + Future> get(String str) async => [str]; + Future> post(String str) async => [str]; +} + +final http = Http(); + +/* SNIPPET START */ +@riverpod +class MyNotifier extends AutoDisposeAsyncNotifier> { + @override + FutureOr> build() { + // TODO ... + return []; + } + + Future addTodo(Todo todo) async { + // TODO + } +} + +final myNotifierProvider = AsyncNotifierProvider.autoDispose(MyNotifier.new); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_change_notifier/initialization/index.tsx b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_change_notifier/initialization/index.tsx new file mode 100644 index 000000000..3b3fbd2cb --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_change_notifier/initialization/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./initialization.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_change_notifier/initialization/initialization.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_change_notifier/initialization/initialization.dart new file mode 100644 index 000000000..4da805e13 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_change_notifier/initialization/initialization.dart @@ -0,0 +1,29 @@ +// ignore_for_file: avoid_print, avoid_unused_constructor_parameters + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'initialization.g.dart'; + +class Todo { + const Todo(this.id); + Todo.fromJson(Object obj) : id = 0; + + final int id; +} + +class Http { + Future> get(String str) async => [str]; + Future> post(String str) async => [str]; +} + +final http = Http(); + +/* SNIPPET START */ +@riverpod +class MyNotifier extends _$MyNotifier { + @override + FutureOr> build() async { + final json = await http.get('api/todos'); + return [...json.map(Todo.fromJson)]; + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_change_notifier/initialization/initialization.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_change_notifier/initialization/initialization.g.dart new file mode 100644 index 000000000..8fd8b75ce --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_change_notifier/initialization/initialization.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'initialization.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$myNotifierHash() => r'1c67c12443102cf8c43efbf6c630d3028d9847c3'; + +/// See also [MyNotifier]. +@ProviderFor(MyNotifier) +final myNotifierProvider = + AutoDisposeAsyncNotifierProvider>.internal( + MyNotifier.new, + name: r'myNotifierProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$myNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$MyNotifier = AutoDisposeAsyncNotifier>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_change_notifier/initialization/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_change_notifier/initialization/raw.dart new file mode 100644 index 000000000..24ab265f7 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_change_notifier/initialization/raw.dart @@ -0,0 +1,29 @@ +// ignore_for_file: avoid_print, avoid_unused_constructor_parameters + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +class Todo { + const Todo(this.id); + Todo.fromJson(Object obj) : id = 0; + + final int id; +} + +class Http { + Future> get(String str) async => [str]; + Future> post(String str) async => [str]; +} + +final http = Http(); + +/* SNIPPET START */ +@riverpod +class MyNotifier extends AutoDisposeAsyncNotifier> { + @override + FutureOr> build() async { + final json = await http.get('api/todos'); + return [...json.map(Todo.fromJson)]; + } +} + +final myNotifierProvider = AsyncNotifierProvider.autoDispose(MyNotifier.new); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_change_notifier/migrated/index.tsx b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_change_notifier/migrated/index.tsx new file mode 100644 index 000000000..075bfbdf5 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_change_notifier/migrated/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./migrated.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_change_notifier/migrated/migrated.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_change_notifier/migrated/migrated.dart new file mode 100644 index 000000000..b8f4ba829 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_change_notifier/migrated/migrated.dart @@ -0,0 +1,37 @@ +// ignore_for_file: avoid_print, avoid_unused_constructor_parameters + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'migrated.g.dart'; + +class Todo { + const Todo(this.id); + Todo.fromJson(Object obj) : id = 0; + + final int id; +} + +class Http { + Future> get(String str) async => [str]; + Future> post(String str) async => [str]; +} + +final http = Http(); + +/* SNIPPET START */ +@riverpod +class MyNotifier extends _$MyNotifier { + @override + FutureOr> build() async { + final json = await http.get('api/todos'); + + return [...json.map(Todo.fromJson)]; + } + + Future addTodo(Todo todo) async { + // optional: state = const AsyncLoading(); + final json = await http.post('api/todos'); + final newTodos = [...json.map(Todo.fromJson)]; + state = AsyncData(newTodos); + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_change_notifier/migrated/migrated.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_change_notifier/migrated/migrated.g.dart new file mode 100644 index 000000000..737a9ce7f --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_change_notifier/migrated/migrated.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'migrated.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$myNotifierHash() => r'bde95c56aa12eff7c8c01ede57ae4ad2b616c225'; + +/// See also [MyNotifier]. +@ProviderFor(MyNotifier) +final myNotifierProvider = + AutoDisposeAsyncNotifierProvider>.internal( + MyNotifier.new, + name: r'myNotifierProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$myNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$MyNotifier = AutoDisposeAsyncNotifier>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_change_notifier/migrated/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_change_notifier/migrated/raw.dart new file mode 100644 index 000000000..8572db71d --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_change_notifier/migrated/raw.dart @@ -0,0 +1,37 @@ +// ignore_for_file: avoid_print, avoid_unused_constructor_parameters + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +class Todo { + const Todo(this.id); + Todo.fromJson(Object obj) : id = 0; + + final int id; +} + +class Http { + Future> get(String str) async => [str]; + Future> post(String str) async => [str]; +} + +final http = Http(); + +/* SNIPPET START */ +@riverpod +class MyNotifier extends AutoDisposeAsyncNotifier> { + @override + FutureOr> build() async { + final json = await http.get('api/todos'); + + return [...json.map(Todo.fromJson)]; + } + + Future addTodo(Todo todo) async { + // optional: state = const AsyncLoading(); + final json = await http.post('api/todos'); + final newTodos = [...json.map(Todo.fromJson)]; + state = AsyncData(newTodos); + } +} + +final myNotifierProvider = AsyncNotifierProvider.autoDispose(MyNotifier.new); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_change_notifier/old.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_change_notifier/old.dart new file mode 100644 index 000000000..4e65e823d --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_change_notifier/old.dart @@ -0,0 +1,60 @@ +// ignore_for_file: avoid_print, avoid_unused_constructor_parameters + +import 'package:flutter/foundation.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +class Todo { + const Todo(this.id); + Todo.fromJson(Object obj) : id = 0; + + final int id; +} + +class Http { + Future> get(String str) async => [str]; + Future> post(String str) async => [str]; +} + +final http = Http(); + +/* SNIPPET START */ +class MyChangeNotifier extends ChangeNotifier { + MyChangeNotifier() { + _init(); + } + List todos = []; + bool isLoading = true; + bool hasError = false; + + Future _init() async { + try { + final json = await http.get('api/todos'); + todos = [...json.map(Todo.fromJson)]; + } on Exception { + hasError = true; + } finally { + isLoading = false; + notifyListeners(); + } + } + + Future addTodo(int id) async { + isLoading = true; + notifyListeners(); + + try { + final json = await http.post('api/todos'); + todos = [...json.map(Todo.fromJson)]; + hasError = false; + } on Exception { + hasError = true; + } finally { + isLoading = false; + notifyListeners(); + } + } +} + +final myChangeProvider = ChangeNotifierProvider((ref) { + return MyChangeNotifier(); +}); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier.mdx b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier.mdx new file mode 100644 index 000000000..881d10bb4 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier.mdx @@ -0,0 +1,243 @@ +--- +title: "`StateNotifier`에서" +--- + +import buildInit from "./from_state_notifier/build_init"; +import buildInitOld from "!!raw-loader!./from_state_notifier/build_init_old.dart"; +import consumersDontChange from "!!raw-loader!./from_state_notifier/consumers_dont_change.dart"; +import familyAndDispose from "./from_state_notifier/family_and_dispose"; +import familyAndDisposeOld from "!!raw-loader!./from_state_notifier/family_and_dispose_old.dart"; +import asyncNotifier from "./from_state_notifier/async_notifier"; +import asyncNotifierOld from "!!raw-loader!./from_state_notifier/async_notifier_old.dart"; +import addListener from "./from_state_notifier/add_listener"; +import addListenerOld from "!!raw-loader!./from_state_notifier/add_listener_old.dart"; +import fromStateProvider from "./from_state_notifier/from_state_provider"; +import fromStateProviderOld from "!!raw-loader!./from_state_notifier/from_state_provider_old.dart"; +import oldLifecycles from "./from_state_notifier/old_lifecycles"; +import oldLifecyclesOld from "!!raw-loader!./from_state_notifier/old_lifecycles_old.dart"; +import oldLifecyclesFinal from "./from_state_notifier/old_lifecycles_final"; +import obtainNotifierOnTests from "!!raw-loader!./from_state_notifier/obtain_notifier_on_tests.dart"; + +import { Link } from "../../../../../src/components/Link"; +import { AutoSnippet } from "../../../../../src/components/CodeSnippet"; + +[Riverpod 2.0](https://pub.dev/packages/flutter_riverpod/changelog#200)과 함께 새로운 클래스가 도입되었습니다: `Notifier` / `AsyncNotifer`. +이제 이러한 새로운 API를 위해 `StateNotifier`는 더 이상 사용되지 않습니다. + +이 페이지는 더 이상 사용되지 않는 `StateNotifier`에서 새로운 API로 마이그레이션하는 방법을 보여줍니다. + +`AsyncNotifier`가 도입한 주요 이점은 더 나은 `async` 지원입니다, +실제로 `AsyncNotifier`는 UI에서 수정할 수 있는 방법을 노출하는 `FutureProvider`로 생각할 수 있습니다. + +또한, 새로운 `(Async)Notifier`가 추가되었습니다: + +- 클래스 내부에 `Ref` 객체 노출하기 +- 코드 생성 방식(codegen)과 비코드 생성 방식(non-codegen) 간에 유사한 문법 제공 +- 동기화 버전과 비동기 버전 간에 유사한 문법 제공 +- 로직을 provider에서 벗어나 Notifiers 자체로 중앙 집중화하기 + +`Notifier`를 정의하는 방법, `StateNotifier`와 비교하는 방법, 비동기 상태를 위해 새로운 `AsyncNotifier`를 마이그레이션하는 방법을 살펴봅시다. + +## 새로운 문법 비교 + +이 비교를 시작하기 전에 `Notifier`을 정의하는 방법을 알아두세요. +를 참고하세요. + +이전 `StateNotifier` 문법을 사용하여 예제를 작성해 보겠습니다: + + +다음은 새로운 `Notifier` API로 작성된 동일한 예시이며, 대략 다음과 같이 변환됩니다: + + +`Notifier`와 `StateNotifier`를 비교하면 다음과 같은 주요 차이점을 확인할 수 있습니다: + +- `StateNotifier`의 반응형 종속성(reactive dependencies)은 provider에서 선언되는 반면, `Notifier`는 이 로직을 `build` 메서드에서 중앙 집중화합니다. +- `StateNotifier`의 전체 초기화 프로세스는 provider와 생성자 사이에 분할되어 있는 반면, `Notifier`는 이러한 로직을 배치할 수 있는 단일 위치를 예약합니다. +- `StateNotifier`와는 반대로, `Notifier`의 생성자에는 어떠한 로직도 작성되지 않는 것을 주목하세요. + +`Notifier`의 비동기 대응 클래스인 `AsyncNotifer`에서도 비슷한 점을 발견할 수 있습니다. + +## 비동기 `StateNotifier` 마이그레이션하기 + +새로운 API 구문의 가장 큰 장점은 비동기 데이터에 대한 향상된 DX입니다. +다음 예시를 살펴보겠습니다: + + + +다음은 새로운 `AsyncNotifier` API를 사용하여 재작성된 위의 예시입니다: + + + +`AsyncNotifier`는 `Notifier`와 마찬가지로 더 간단하고 통일된 API를 제공합니다. +여기서 `AsyncNotifier`는 메서드가 있는 `FutureProvider`로 쉽게 볼 수 있습니다. + +`AsyncNotifer`에는 `StateNotifier`에는 없는 유틸리티와 게터가 함께 제공됩니다, +예를 들어 [`future`](https://pub.dev/documentation/riverpod/latest/riverpod/AutoDisposeAsyncNotifier/future.html) +및 [`update`](https://pub.dev/documentation/riverpod/latest/riverpod/AutoDisposeAsyncNotifier/update.html). +이를 통해 비동기 변이(mutations)와 부수작업(side-effects)을 처리할 때 훨씬 더 간단한 로직을 작성할 수 있습니다. +를 참고하세요. + +:::tip +`StateNotifier>`에서 `AsyncNotifer`로 마이그레이션하는 방법은 다음과 같습니다: + +- 초기화 로직을 `build`에 넣기 +- 초기화 또는 부수작업 메서드에서 `catch`/`try` 블록을 제거합니다. +- `build`에서 `AsyncValue.guard`를 제거합니다. `Future`를 `AsyncValue`로 변환하기 때문입니다. +::: + +### 장점 + +이 몇 가지 예시를 살펴본 후, 이제 `Notifier` 와 `AsyncNotifer`의 주요 장점을 살펴보겠습니다: +- 새로운 구문은 특히 비동기 상태의 경우 훨씬 더 간단하고 가독성이 높아질 것입니다. +- 새로운 API에는 일반적으로 상용구 코드가 줄어들 가능성이 높습니다. +- 이제 작성하는 provider 타입에 관계없이 구문이 통합되어 코드 생성이 가능해졌습니다. +( 참조). + +더 자세히 살펴보고 더 많은 차이점과 유사점을 강조해 보겠습니다. + +## 명시적인 `.family` 및 `.autoDispose` 수정사항 + +또 다른 중요한 차이점은 새로운 API로 패밀리 및 자동폐기가 처리되는 방식입니다. + +`Notifier`에는 `FamilyNotifier` 및 `AutoDisposeNotifier`와 같은 자체 `.family` 및 `.autoDispose` 대응 항목이 있습니다. +항상 그렇듯이, 이러한 수정 사항을 결합할 수 있습니다 (일명 `AutoDisposeFamilyNotifier`). +`AsyncNotifer`에는 비동기 버전도 있습니다(예: `AutoDisposeFamilyAsyncNotifier`). + +수정 사항(Modifications)은 클래스 내부에 명시적으로 지정(stated)됩니다; +모든 매개변수는 `build` 메서드에 직접 주입되어 초기화 로직에서 사용할 수 있습니다. +이렇게 하면 가독성이 향상되고 간결해지며 전반적으로 실수가 줄어듭니다. + +다음 예제에서는 `StateNotifierProvider.family`를 정의하고 있습니다. + + +`BugsEncounteredNotifier`은 무겁거나 읽기 어려운 느낌입니다. +마이그레이션 된 `AsyncNotifier`를 살펴 보겠습니다: + + + +마이그레이션된 버전은 가볍게 읽을 수 있는 수준입니다. + +:::info +`(Async)Notifier`의 `.family` 매개변수는 `this.arg`(또는 코드생성을 사용하는 경우 `this.paramName`)를 통해 사용할 수 있습니다. +::: + +## 라이프사이클에 따라 동작 방식이 다릅니다. + +`Notifier`/`AsyncNotifier`와 `StateNotifier`의 수명 주기는 크게 다릅니다. + +이 예시는 이전 API의 로직이 얼마나 부족한지(sparse)를 다시 한 번 보여줍니다: + + + +여기서 `durationProvider`가 업데이트되면 `MyNotifier`를 _페기(dispose)_: 인스턴스가 다시 인스턴스화되고 내부 상태가 다시 초기화됩니다. +또한 다른 모든 provider와 달리 `dispose` 콜백은 클래스에서 별도로 정의해야 합니다. +마지막으로, _provider_에 `ref.onDispose`를 작성하는 것이 여전히 가능하기 때문에, 이 API의 로직이 얼마나 부족한지(sparse)를 다시 한 번 알 수 있습니다; +잠재적으로 개발자는 이 Notifier 동작을 이해하기 위해 여덟 곳(8개!)을 살펴봐야 할 수도 있습니다! + +이러한 모호함은 `Riverpod 2.0`을 통해 해결되었습니다. + +### 이전의 `dispose` vs `ref.onDispose` +`StateNotifier`의 `dispose` 메서드는 notifier 자체의 폐기(dispose) 이벤트를 참조하며, 일명 *자신을 처분하기 전에(before disposing of itself)* 호출되는 콜백입니다. + +`(Async)Notifier`은 이 속성을 갖지 않는데, *리빌드 시 폐기되지 않고* *내부 상태만 폐기*되기 때문입니다. +새로운 notifiers에서 폐기 수명주기는 다른 provider와 마찬가지로 `ref.onDispose`(및 기타)를 통해 _한_ 곳에서만 처리됩니다. +이렇게 하면 API와 DX가 단순화되어 라이프사이클 부작용을 이해하기 위해 살펴봐야 할 곳이 `build` 메서드 하나만 남게 됩니다. + +간단히 말해서, *내부 상태(internal state)*가 다시 빌드되기 전에 실행되는 콜백을 등록하려면 다른 모든 provider와 마찬가지로 `ref.onDispose`를 사용하면 됩니다. + +위의 스니펫을 다음과 같이 마이그레이션할 수 있습니다: + + + +이 마지막 스니펫에는 확실히 약간의 단순화가 있지만 여전히 열려 있는 문제가 있습니다: +이제 `update`를 수행하는 동안 notifiers가 아직 살아(alive)있는지 여부를 파악할 수 없습니다. +이로 인해 원치 않는 `StateError`가 발생할 수 있습니다. + +### 더 이상 `마운트되지(mounted)` 않음 +이는 `(Async)Notifier`에 `StateNotifier`에서 사용할 수 있는 `mounted` 프로퍼티가 없기 때문에 발생합니다. +수명 주기의 차이를 고려하면 이것은 완벽하게 이해가 됩니다; +가능하긴 하지만, 새로운 notifiers에서 `mounted` 프로퍼티는 오해의 소지가 있습니다: `mounted`는 거의 항상 `true`이 될 것입니다. + +[커스텀 해결방법](https://github.com/rrousselGit/riverpod/issues/1879#issuecomment-1303189191)을 만들 수는 있지만, +비동기 작업을 취소하여 이 문제를 해결하는 것이 좋습니다. + +작업 취소는 커스텀 [Completer](https://api.flutter.dev/flutter/dart-async/Completer-class.html) 또는 커스텀 파생어(derivative)를 사용하여 수행할 수 있습니다. + +예를 들어 `Dio`를 사용하여 네트워크 요청을 수행하는 경우 [cancel token](https://pub.dev/documentation/dio/latest/dio/CancelToken-class.html)을 사용하는 것이 좋습니다. +( 참고) + +따라서 위의 예는 다음과 같이 마이그레이션됩니다: + + +## 변이(Mutations) API는 이전과 동일합니다 + +지금까지 `StateNotifier`와 새로운 API의 차이점을 살펴보았습니다. +대신, `Notifier`, `AsyncNotifer`, `StateNotifier`가 공유하는 한 가지는 상태를 소비하고 변경할 수 있다는 점입니다. + +Consumers는 동일한 구문으로 이 세 공급자로부터 데이터를 얻을 수 있습니다, +이는 `StateNotifier`에서 마이그레이션하는 경우에 유용하며, 이는 notifiers 메서드에도 적용됩니다. + + + +## 기타 마이그레이션 + +`StateNotifier`와 `Notifier`(또는 `AsyncNotifier`)의 영향력이 크지 않은 차이점을 살펴봅시다. + +### `.addListener` 및 `.stream`에서 + +`StateNotifier`의 `.addListener`와 `.stream`은 상태 변경을 수신하는 데 사용할 수 있습니다. +이 두 API는 이제 오래된 것으로 간주됩니다. + +이는 `Notifier`, `AsyncNotifier` 및 기타 proviers와 완전한 API 통일성을 달성하기 위한 의도적인 것입니다. +실제로 `Notifier`나 `AsyncNotifier`를 사용하는 것은 다른 provier와 다르지 않아야 합니다. + +따라서 이 것이: + + +이렇게 됩니다: + + +간단히 말해, `Notifier`/`AsyncNotifer`를 수신하려면 `ref.listen`를 사용하면 됩니다. +를 참고하세요 + +### 테스트의 `.debugState`에서 + +`StateNotifier`는 `.debugState`를 노출합니다: +이 프로퍼티는 개발 모드에서 테스트 목적으로 클래스 외부에서 상태 액세스를 활성화하기 위해 pkg:state_notifier 사용자가 사용할 수 있습니다. + +테스트에서 상태에 액세스하기 위해 `.debugState`를 사용하는 경우 이 접근 방식을 중단해야 합니다. + +`Notifier` / `AsyncNotifer`에는 `.debugState`가 없으며, 대신 `.state`, 즉 `@visibleForTesting`을 직접 노출합니다. + +:::danger +AVOID 테스트에서 `.state`에 접근하지 마시고, 꼭 접근해야 한다면 `Notifier` / `AsyncNotifer`가 이미 제대로 인스턴스화된 경우에만 접근하세요; +그러면 테스트 내부에서 `.state`에 자유롭게 접근할 수 있습니다. + +실제로 `Notifier` / `AsyncNotifier`는 직접 인스턴스화해서는 안 됩니다; +대신 해당 provider를 사용해 상호작용해야 합니다: +그렇게 하지 않으면 notifier가 *중단(break)*됩니다, +ref와 family 인자가 초기화되지 않기 때문입니다. +::: + +`Notifier` 인스턴스가 없으신가요? +문제없습니다. 노출된 상태를 읽을 때와 마찬가지로 `ref.read`로 인스턴스를 가져올 수 있습니다: + + + +전용 가이드에서 테스트에 대해 자세히 알아보세요. 를 참고하세요. + +### `StateProvider`에서 + +`StateProvider`는 Riverpod에서 출시 이후 노출된 것으로, `StateNotifierProvider`의 간소화된 버전을 위해 몇 가지 LoC를 절약하기 위해 만들어졌습니다. +`StateNotifierProvider`는 더 이상 사용되지 않으므로 `StateProvider`도 피해야 합니다. +또한 현재는 새로운 API에 상응하는 `StateProvider`가 없습니다. + +그럼에도 불구하고 `StateProvider`에서 `Notifier`로 마이그레이션하는 것은 간단합니다. + +이 코드는: + + +이렇게 됩니다: + + +LoC가 몇 개 더 들더라도 `StateProvider`에서 마이그레이션하면 `StateNotifier`를 확실하게 보존(archive)할 수 있습니다. diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener/add_listener.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener/add_listener.dart new file mode 100644 index 000000000..ead1c72d8 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener/add_listener.dart @@ -0,0 +1,18 @@ +// ignore_for_file: avoid_print + +import 'package:flutter/material.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'add_listener.g.dart'; + +/* SNIPPET START */ +@riverpod +class MyNotifier extends _$MyNotifier { + @override + int build() { + ref.listenSelf((_, next) => debugPrint('$next')); + return 0; + } + + void add() => state++; +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener/add_listener.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener/add_listener.g.dart new file mode 100644 index 000000000..3d55dd0bb --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener/add_listener.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'add_listener.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$myNotifierHash() => r'9acd382ed579c545ace755687b155e28eba01d22'; + +/// See also [MyNotifier]. +@ProviderFor(MyNotifier) +final myNotifierProvider = + AutoDisposeNotifierProvider.internal( + MyNotifier.new, + name: r'myNotifierProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$myNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$MyNotifier = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener/index.tsx b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener/index.tsx new file mode 100644 index 000000000..6d7ac6d37 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./add_listener.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener/raw.dart new file mode 100644 index 000000000..e25b4a181 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener/raw.dart @@ -0,0 +1,17 @@ +// ignore_for_file: avoid_print + +import 'package:flutter/material.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +/* SNIPPET START */ +class MyNotifier extends Notifier { + @override + int build() { + ref.listenSelf((_, next) => debugPrint('$next')); + return 0; + } + + void add() => state++; +} + +final myNotifierProvider = NotifierProvider(MyNotifier.new); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener_old.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener_old.dart new file mode 100644 index 000000000..4950216c6 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener_old.dart @@ -0,0 +1,24 @@ +// ignore_for_file: avoid_print + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +/* SNIPPET START */ +class MyNotifier extends StateNotifier { + MyNotifier() : super(0); + + void add() => state++; +} + +final myNotifierProvider = StateNotifierProvider((ref) { + final notifier = MyNotifier(); + + final cleanup = notifier.addListener((state) => debugPrint('$state')); + ref.onDispose(cleanup); + + // 또는, 이와 동일하게: + // final listener = notifier.stream.listen((event) => debugPrint('$event')); + // ref.onDispose(listener.cancel); + + return notifier; +}); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier/async_notifier.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier/async_notifier.dart new file mode 100644 index 000000000..5ead76378 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier/async_notifier.dart @@ -0,0 +1,28 @@ +// ignore_for_file: avoid_print, avoid_unused_constructor_parameters + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'async_notifier.g.dart'; + +class Todo { + Todo.fromJson(Object obj); +} + +class Http { + Future> get(String str) async => [str]; +} + +final http = Http(); + +/* SNIPPET START */ +@riverpod +class AsyncTodosNotifier extends _$AsyncTodosNotifier { + @override + FutureOr> build() async { + final json = await http.get('api/todos'); + + return [...json.map(Todo.fromJson)]; + } + + // ... +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier/async_notifier.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier/async_notifier.g.dart new file mode 100644 index 000000000..5f72d5d1c --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier/async_notifier.g.dart @@ -0,0 +1,29 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'async_notifier.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$asyncTodosNotifierHash() => + r'10207327c7dee180e9da8beece5bfffedcf86e98'; + +/// See also [AsyncTodosNotifier]. +@ProviderFor(AsyncTodosNotifier) +final asyncTodosNotifierProvider = + AutoDisposeAsyncNotifierProvider>.internal( + AsyncTodosNotifier.new, + name: r'asyncTodosNotifierProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$asyncTodosNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$AsyncTodosNotifier = AutoDisposeAsyncNotifier>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier/index.tsx b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier/index.tsx new file mode 100644 index 000000000..a0ff513c3 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./async_notifier.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier/raw.dart new file mode 100644 index 000000000..52da61d76 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier/raw.dart @@ -0,0 +1,29 @@ +// ignore_for_file: avoid_print, avoid_unused_constructor_parameters + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +class Todo { + Todo.fromJson(Object obj); +} + +class Http { + Future> get(String str) async => [str]; +} + +final http = Http(); + +/* SNIPPET START */ +class AsyncTodosNotifier extends AsyncNotifier> { + @override + FutureOr> build() async { + final json = await http.get('api/todos'); + + return [...json.map(Todo.fromJson)]; + } + + // ... +} + +final asyncTodosNotifier = AsyncNotifierProvider>( + AsyncTodosNotifier.new, +); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier_old.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier_old.dart new file mode 100644 index 000000000..50d7f4aed --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier_old.dart @@ -0,0 +1,30 @@ +// ignore_for_file: avoid_print, avoid_unused_constructor_parameters + +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +class Todo { + Todo.fromJson(Object obj); +} + +class Http { + Future> get(String str) async => [str]; +} + +final http = Http(); + +/* SNIPPET START */ +class AsyncTodosNotifier extends StateNotifier>> { + AsyncTodosNotifier() : super(const AsyncLoading()) { + _postInit(); + } + + Future _postInit() async { + state = await AsyncValue.guard(() async { + final json = await http.get('api/todos'); + + return [...json.map(Todo.fromJson)]; + }); + } + + // ... +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init/build_init.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init/build_init.dart new file mode 100644 index 000000000..3e2303b7a --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init/build_init.dart @@ -0,0 +1,15 @@ +// ignore_for_file: avoid_print + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'build_init.g.dart'; + +/* SNIPPET START */ +@riverpod +class CounterNotifier extends _$CounterNotifier { + @override + int build() => 0; + + void increment() => state++; + void decrement() => state++; +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init/build_init.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init/build_init.g.dart new file mode 100644 index 000000000..9b4dc05a7 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init/build_init.g.dart @@ -0,0 +1,28 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'build_init.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$counterNotifierHash() => r'8d4e4011da15a0ef79af9622336839a0c9e406ab'; + +/// See also [CounterNotifier]. +@ProviderFor(CounterNotifier) +final counterNotifierProvider = + AutoDisposeNotifierProvider.internal( + CounterNotifier.new, + name: r'counterNotifierProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$counterNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$CounterNotifier = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init/index.tsx b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init/index.tsx new file mode 100644 index 000000000..276a143ac --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./build_init.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init/raw.dart new file mode 100644 index 000000000..0ba8eebed --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init/raw.dart @@ -0,0 +1,15 @@ +// ignore_for_file: avoid_print + +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +/* SNIPPET START */ +class CounterNotifier extends Notifier { + @override + int build() => 0; + + void increment() => state++; + void decrement() => state++; +} + +final counterNotifierProvider = NotifierProvider(CounterNotifier.new); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init_old.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init_old.dart new file mode 100644 index 000000000..82a8c54bd --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init_old.dart @@ -0,0 +1,13 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +/* SNIPPET START */ +class CounterNotifier extends StateNotifier { + CounterNotifier() : super(0); + + void increment() => state++; + void decrement() => state++; +} + +final counterNotifierProvider = StateNotifierProvider((ref) { + return CounterNotifier(); +}); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/consumers_dont_change.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/consumers_dont_change.dart new file mode 100644 index 000000000..6a964a5a6 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/consumers_dont_change.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +class CounterNotifier extends StateNotifier { + CounterNotifier() : super(0); + + void increment() => state++; + void decrement() => state++; +} + +final counterNotifierProvider = StateNotifierProvider((ref) { + return CounterNotifier(); +}); + +/* SNIPPET START */ +class SomeConsumer extends ConsumerWidget { + const SomeConsumer({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + /* highlight-start */ + final counter = ref.watch(counterNotifierProvider); + /* highlight-end */ + return Column( + children: [ + Text("You've counted up until $counter, good job!"), + TextButton( + /* highlight-start */ + onPressed: ref.read(counterNotifierProvider.notifier).increment, + /* highlight-end */ + child: const Text('Count even more!'), + ) + ], + ); + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose/family_and_dispose.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose/family_and_dispose.dart new file mode 100644 index 000000000..9a695614c --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose/family_and_dispose.dart @@ -0,0 +1,24 @@ +// ignore_for_file: unnecessary_this + +import 'dart:math'; + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../../utils.dart'; + +part 'family_and_dispose.g.dart'; + +/* SNIPPET START */ +@riverpod +class BugsEncounteredNotifier extends _$BugsEncounteredNotifier { + @override + FutureOr build(String featureId) { + return 99; + } + + Future fix(int amount) async { + final old = await future; + final result = await ref.read(taskTrackerProvider).fix(id: this.featureId, fixed: amount); + state = AsyncData(max(old - result, 0)); + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose/family_and_dispose.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose/family_and_dispose.g.dart new file mode 100644 index 000000000..36e1f1144 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose/family_and_dispose.g.dart @@ -0,0 +1,178 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'family_and_dispose.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$bugsEncounteredNotifierHash() => + r'c76e924f84db91c57d226896b062d9f4e8ab79e5'; + +/// Copied from Dart SDK +class _SystemHash { + _SystemHash._(); + + static int combine(int hash, int value) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + value); + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); + return hash ^ (hash >> 6); + } + + static int finish(int hash) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + // ignore: parameter_assignments + hash = hash ^ (hash >> 11); + return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); + } +} + +abstract class _$BugsEncounteredNotifier + extends BuildlessAutoDisposeAsyncNotifier { + late final String featureId; + + FutureOr build( + String featureId, + ); +} + +/// See also [BugsEncounteredNotifier]. +@ProviderFor(BugsEncounteredNotifier) +const bugsEncounteredNotifierProvider = BugsEncounteredNotifierFamily(); + +/// See also [BugsEncounteredNotifier]. +class BugsEncounteredNotifierFamily extends Family> { + /// See also [BugsEncounteredNotifier]. + const BugsEncounteredNotifierFamily(); + + /// See also [BugsEncounteredNotifier]. + BugsEncounteredNotifierProvider call( + String featureId, + ) { + return BugsEncounteredNotifierProvider( + featureId, + ); + } + + @override + BugsEncounteredNotifierProvider getProviderOverride( + covariant BugsEncounteredNotifierProvider provider, + ) { + return call( + provider.featureId, + ); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'bugsEncounteredNotifierProvider'; +} + +/// See also [BugsEncounteredNotifier]. +class BugsEncounteredNotifierProvider + extends AutoDisposeAsyncNotifierProviderImpl { + /// See also [BugsEncounteredNotifier]. + BugsEncounteredNotifierProvider( + String featureId, + ) : this._internal( + () => BugsEncounteredNotifier()..featureId = featureId, + from: bugsEncounteredNotifierProvider, + name: r'bugsEncounteredNotifierProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$bugsEncounteredNotifierHash, + dependencies: BugsEncounteredNotifierFamily._dependencies, + allTransitiveDependencies: + BugsEncounteredNotifierFamily._allTransitiveDependencies, + featureId: featureId, + ); + + BugsEncounteredNotifierProvider._internal( + super._createNotifier, { + required super.name, + required super.dependencies, + required super.allTransitiveDependencies, + required super.debugGetCreateSourceHash, + required super.from, + required this.featureId, + }) : super.internal(); + + final String featureId; + + @override + FutureOr runNotifierBuild( + covariant BugsEncounteredNotifier notifier, + ) { + return notifier.build( + featureId, + ); + } + + @override + Override overrideWith(BugsEncounteredNotifier Function() create) { + return ProviderOverride( + origin: this, + override: BugsEncounteredNotifierProvider._internal( + () => create()..featureId = featureId, + from: from, + name: null, + dependencies: null, + allTransitiveDependencies: null, + debugGetCreateSourceHash: null, + featureId: featureId, + ), + ); + } + + @override + AutoDisposeAsyncNotifierProviderElement + createElement() { + return _BugsEncounteredNotifierProviderElement(this); + } + + @override + bool operator ==(Object other) { + return other is BugsEncounteredNotifierProvider && + other.featureId == featureId; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, featureId.hashCode); + + return _SystemHash.finish(hash); + } +} + +mixin BugsEncounteredNotifierRef on AutoDisposeAsyncNotifierProviderRef { + /// The parameter `featureId` of this provider. + String get featureId; +} + +class _BugsEncounteredNotifierProviderElement + extends AutoDisposeAsyncNotifierProviderElement with BugsEncounteredNotifierRef { + _BugsEncounteredNotifierProviderElement(super.provider); + + @override + String get featureId => (origin as BugsEncounteredNotifierProvider).featureId; +} +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose/index.tsx b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose/index.tsx new file mode 100644 index 000000000..0780f2135 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./family_and_dispose.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose/raw.dart new file mode 100644 index 000000000..8dfce9447 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose/raw.dart @@ -0,0 +1,27 @@ +// ignore_for_file: unnecessary_this + +import 'dart:math'; + +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../../utils.dart'; + +/* SNIPPET START */ +class BugsEncounteredNotifier extends AutoDisposeFamilyAsyncNotifier { + @override + FutureOr build(String featureId) { + return 99; + } + + Future fix(int amount) async { + final old = await future; + final result = await ref.read(taskTrackerProvider).fix(id: this.arg, fixed: amount); + state = AsyncData(max(old - result, 0)); + } +} + +final bugsEncounteredNotifierProvider = + AsyncNotifierProvider.family.autoDispose( + BugsEncounteredNotifier.new, +); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose_old.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose_old.dart new file mode 100644 index 000000000..28f93a65c --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose_old.dart @@ -0,0 +1,30 @@ +// ignore_for_file: unnecessary_this + +import 'dart:math'; + +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +import '../utils.dart'; + +/* SNIPPET START */ +class BugsEncounteredNotifier extends StateNotifier> { + BugsEncounteredNotifier({ + required this.ref, + required this.featureId, + }) : super(const AsyncData(99)); + final String featureId; + final Ref ref; + + Future fix(int amount) async { + state = await AsyncValue.guard(() async { + final old = state.requireValue; + final result = await ref.read(taskTrackerProvider).fix(id: featureId, fixed: amount); + return max(old - result, 0); + }); + } +} + +final bugsEncounteredNotifierProvider = + StateNotifierProvider.family.autoDispose((ref, id) { + return BugsEncounteredNotifier(ref: ref, featureId: id); +}); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider/from_state_provider.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider/from_state_provider.dart new file mode 100644 index 000000000..2c71d6144 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider/from_state_provider.dart @@ -0,0 +1,14 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'from_state_provider.g.dart'; + +/* SNIPPET START */ +@riverpod +class CounterNotifier extends _$CounterNotifier { + @override + int build() => 0; + + @override + set state(int newState) => super.state = newState; + int update(int Function(int state) cb) => state = cb(state); +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider/from_state_provider.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider/from_state_provider.g.dart new file mode 100644 index 000000000..e9bbec271 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider/from_state_provider.g.dart @@ -0,0 +1,28 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'from_state_provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$counterNotifierHash() => r'b32033040f0fff627f1a6dfd9cfb4e93a842390b'; + +/// See also [CounterNotifier]. +@ProviderFor(CounterNotifier) +final counterNotifierProvider = + AutoDisposeNotifierProvider.internal( + CounterNotifier.new, + name: r'counterNotifierProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$counterNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$CounterNotifier = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider/index.tsx b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider/index.tsx new file mode 100644 index 000000000..f59794999 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./from_state_provider.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider/raw.dart new file mode 100644 index 000000000..97e4564f3 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider/raw.dart @@ -0,0 +1,13 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +/* SNIPPET START */ +class CounterNotifier extends Notifier { + @override + int build() => 0; + + @override + set state(int newState) => super.state = newState; + int update(int Function(int state) cb) => state = cb(state); +} + +final counterNotifierProvider = NotifierProvider(CounterNotifier.new); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider_old.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider_old.dart new file mode 100644 index 000000000..246f44a0c --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider_old.dart @@ -0,0 +1,6 @@ +import 'package:riverpod/riverpod.dart'; + +/* SNIPPET START */ +final counterProvider = StateProvider((ref) { + return 0; +}); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/obtain_notifier_on_tests.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/obtain_notifier_on_tests.dart new file mode 100644 index 000000000..1627be9e7 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/obtain_notifier_on_tests.dart @@ -0,0 +1,33 @@ +// ignore_for_file: unused_local_variable,omit_local_variable_types + +import 'package:flutter_test/flutter_test.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +class MyNotifier extends AutoDisposeNotifier { + @override + int build() { + return 0; + } +} + +final myNotifierProvider = NotifierProvider.autoDispose(MyNotifier.new); + +/* SNIPPET START */ +void main(List args) { + test('my test', () { + final container = ProviderContainer(); + addTearDown(container.dispose); + + // notifier 획득 + /* highlight-start */ + final AutoDisposeNotifier notifier = container.read(myNotifierProvider.notifier); + /* highlight-end */ + + // 거기서 노출되는 상태 획득 + /* highlight-start */ + final int state = container.read(myNotifierProvider); + /* highlight-end */ + + // TODO write your tests + }); +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles/index.tsx b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles/index.tsx new file mode 100644 index 000000000..9b77f551a --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./old_lifecycles.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles/old_lifecycles.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles/old_lifecycles.dart new file mode 100644 index 000000000..ffeed1c3e --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles/old_lifecycles.dart @@ -0,0 +1,36 @@ +import 'dart:async'; + +import 'package:dio/dio.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../../utils.dart'; + +part 'old_lifecycles.g.dart'; + +final repositoryProvider = Provider<_MyRepo>((ref) { + return _MyRepo(); +}); + +class _MyRepo { + Future update(int i, {CancelToken? token}) async {} +} + +/* SNIPPET START */ +@riverpod +class MyNotifier extends _$MyNotifier { + @override + int build() { + // 한 곳에서 코드를 읽고 쓰면 됩니다. + final period = ref.watch(durationProvider); + final timer = Timer.periodic(period, (t) => update()); + ref.onDispose(timer.cancel); + + return 0; + } + + Future update() async { + await ref.read(repositoryProvider).update(state + 1); + // `mounted`는 더 이상 없습니다! + state++; //throw 될 수 있습니다 + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles/old_lifecycles.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles/old_lifecycles.g.dart new file mode 100644 index 000000000..9d1158e62 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles/old_lifecycles.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'old_lifecycles.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$myNotifierHash() => r'0495c52ce893ee0304d4d5ac5648c634ed4a241e'; + +/// See also [MyNotifier]. +@ProviderFor(MyNotifier) +final myNotifierProvider = + AutoDisposeNotifierProvider.internal( + MyNotifier.new, + name: r'myNotifierProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$myNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$MyNotifier = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles/raw.dart new file mode 100644 index 000000000..691d71af9 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles/raw.dart @@ -0,0 +1,35 @@ +import 'dart:async'; + +import 'package:dio/dio.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../../utils.dart'; + +final repositoryProvider = Provider<_MyRepo>((ref) { + return _MyRepo(); +}); + +class _MyRepo { + Future update(int i, {CancelToken? token}) async {} +} + +/* SNIPPET START */ +class MyNotifier extends Notifier { + @override + int build() { + // Just read/write the code here, in one place + final period = ref.watch(durationProvider); + final timer = Timer.periodic(period, (t) => update()); + ref.onDispose(timer.cancel); + + return 0; + } + + Future update() async { + await ref.read(repositoryProvider).update(state + 1); + // `mounted` is no more! + state++; // This might throw. + } +} + +final myNotifierProvider = NotifierProvider(MyNotifier.new); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_final/index.tsx b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_final/index.tsx new file mode 100644 index 000000000..9823b1564 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_final/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./old_lifecycles_final.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_final/old_lifecycles_final.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_final/old_lifecycles_final.dart new file mode 100644 index 000000000..798fce7d8 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_final/old_lifecycles_final.dart @@ -0,0 +1,38 @@ +import 'dart:async'; + +import 'package:dio/dio.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../../utils.dart'; + +part 'old_lifecycles_final.g.dart'; + +final repositoryProvider = Provider<_MyRepo>((ref) { + return _MyRepo(); +}); + +class _MyRepo { + Future update(int i, {CancelToken? token}) async {} +} + +/* SNIPPET START */ +@riverpod +class MyNotifier extends _$MyNotifier { + @override + int build() { + // 한 곳에서 코드를 읽고 쓰면 됩니다. + final period = ref.watch(durationProvider); + final timer = Timer.periodic(period, (t) => update()); + ref.onDispose(timer.cancel); + + return 0; + } + + Future update() async { + final cancelToken = CancelToken(); + ref.onDispose(cancelToken.cancel); + await ref.read(repositoryProvider).update(state + 1, token: cancelToken); + // `cancelToken.cancel`이 호출되면 커스텀 예외가 발생합니다. + state++; + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_final/old_lifecycles_final.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_final/old_lifecycles_final.g.dart new file mode 100644 index 000000000..3600948dc --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_final/old_lifecycles_final.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'old_lifecycles_final.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$myNotifierHash() => r'8ea2586ea29d12306efd4b8b847142136dd20338'; + +/// See also [MyNotifier]. +@ProviderFor(MyNotifier) +final myNotifierProvider = + AutoDisposeNotifierProvider.internal( + MyNotifier.new, + name: r'myNotifierProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$myNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$MyNotifier = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_final/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_final/raw.dart new file mode 100644 index 000000000..94814a191 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_final/raw.dart @@ -0,0 +1,36 @@ +import 'dart:async'; + +import 'package:dio/dio.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../../utils.dart'; + +final repositoryProvider = Provider<_MyRepo>((ref) { + return _MyRepo(); +}); + +class _MyRepo { + Future update(int i, {CancelToken? token}) async {} +} + +/* SNIPPET START */ +class MyNotifier extends Notifier { + @override + int build() { + // Just read/write the code here, in one place + final period = ref.watch(durationProvider); + final timer = Timer.periodic(period, (t) => update()); + ref.onDispose(timer.cancel); + + return 0; + } + + Future update() async { + final cancelToken = CancelToken(); + ref.onDispose(cancelToken.cancel); + await ref.read(repositoryProvider).update(state + 1, token: cancelToken); + state++; + } +} + +final myNotifierProvider = NotifierProvider(MyNotifier.new); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_old.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_old.dart new file mode 100644 index 000000000..717637c81 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_old.dart @@ -0,0 +1,42 @@ +import 'dart:async'; + +import 'package:dio/dio.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../utils.dart'; + +final repositoryProvider = Provider<_MyRepo>((ref) { + return _MyRepo(); +}); + +class _MyRepo { + Future update(int i, {CancelToken? token}) async {} +} + +/* SNIPPET START */ +class MyNotifier extends StateNotifier { + MyNotifier(this.ref, this.period) : super(0) { + // 1 초기화로직 + _timer = Timer.periodic(period, (t) => update()); // 2 초기화시 부가작업 + } + final Duration period; + final Ref ref; + late final Timer _timer; + + Future update() async { + await ref.read(repositoryProvider).update(state + 1); // 3 변이(mutation) + if (mounted) state++; // 4 마운트된 속성 확인 + } + + @override + void dispose() { + _timer.cancel(); // 5 커스텀 폐기(dispose) 로직 + super.dispose(); + } +} + +final myNotifierProvider = StateNotifierProvider((ref) { + // 6 provider 정의 + final period = ref.watch(durationProvider); // 7 리액티브 종속성 로직 + return MyNotifier(ref, period); // 8 `ref`로 연결(pipe down) +}); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/utils.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/utils.dart new file mode 100644 index 000000000..e515b6f94 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/migration/utils.dart @@ -0,0 +1,23 @@ +import 'dart:math' as math; + +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +final randomProvider = Provider((ref) { + return math.Random().nextInt(6); +}); + +final taskTrackerProvider = Provider((ref) { + return TaskTrackerRepo(); +}); + +class TaskTrackerRepo { + Future fix({required String id, required int fixed}) async => 0; +} + +final durationProvider = Provider((ref) { + return Duration.zero; +}); + +final availableWaterProvider = Provider((ref) { + return 40; +}); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/change_notifier_provider.mdx b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/change_notifier_provider.mdx index bedca4e7f..5143f6f51 100644 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/change_notifier_provider.mdx +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/change_notifier_provider.mdx @@ -2,13 +2,17 @@ title: ChangeNotifierProvider --- -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; import CodeBlock from "@theme/CodeBlock"; import todos from "!!raw-loader!/docs/providers/change_notifier_provider/todos.dart"; import todosConsumer from "!!raw-loader!/docs/providers/change_notifier_provider/todos_consumer.dart"; import { trimSnippet } from "../../../../../src/components/CodeSnippet"; +:::caution +The content of this page may be outdated. +It will be updated in the future, but for now you may want to refer to the content +in the top of the sidebar instead (introduction/essentials/case-studies/...) +::: + `ChangeNotifierProvider` (flutter_riverpod/hooks_riverpod only) is a provider that is used to listen to and expose a [ChangeNotifier] from Flutter itself. @@ -18,7 +22,7 @@ Using `ChangeNotifierProvider` is discouraged by Riverpod and exists primarily f - supporting mutable state, even though immutable state is preferred :::info -Prefer using [StateNotifierProvider] instead. +Prefer using [NotifierProvider] instead. Consider using `ChangeNotifierProvider` only if you are absolutely certain that you want mutable state. ::: @@ -45,6 +49,7 @@ with the list of todos in our UI: [state_notifier]: https://pub.dev/packages/state_notifier [statenotifierprovider]: ./state_notifier_provider -[changenotifier]: https://pub.dev/documentation/state_notifier/latest/state_notifier/ChangeNotifier-class.html +[notifierprovider]: ./notifier_provider +[changenotifier]: https://api.flutter.dev/flutter/foundation/ChangeNotifier-class.html [provider]: ./provider [futureprovider]: ./future_provider diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/change_notifier_provider/todos.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/change_notifier_provider/todos.dart index c181f8fd7..d766920b1 100644 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/change_notifier_provider/todos.dart +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/change_notifier_provider/todos.dart @@ -32,16 +32,13 @@ class TodosNotifier extends ChangeNotifier { // Let's mark a todo as completed void toggle(String todoId) { - for (final todo in todos) { - if (todo.id == todoId) { - todo.completed = !todo.completed; - notifyListeners(); - } - } + final todo = todos.firstWhere((todo) => todo.id == todoId); + todo.completed = !todo.completed; + notifyListeners(); } } -// Finally, we are using StateNotifierProvider to allow the UI to interact with +// Finally, we are using ChangeNotifierProvider to allow the UI to interact with // our TodosNotifier class. final todosProvider = ChangeNotifierProvider((ref) { return TodosNotifier(); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/future_provider.mdx b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/future_provider.mdx index 934452c51..6f114320a 100644 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/future_provider.mdx +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/future_provider.mdx @@ -3,12 +3,15 @@ title: FutureProvider version: 1 --- -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; -import CodeBlock from "@theme/CodeBlock"; -import configProvider from "/docs/providers/future_provider/config_provider"; -import configConsumer from "/docs/providers/future_provider/config_consumer"; -import { trimSnippet,AutoSnippet } from "../../../../../src/components/CodeSnippet"; +import configProvider from "./future_provider/config_provider"; +import configConsumer from "./future_provider/config_consumer"; +import { AutoSnippet} from "../../../../../src/components/CodeSnippet"; + +:::caution +The content of this page may be outdated. +It will be updated in the future, but for now you may want to refer to the content +in the top of the sidebar instead (introduction/essentials/case-studies/...) +::: `FutureProvider` is the equivalent of [Provider] but for asynchronous code. @@ -25,7 +28,7 @@ ensuring that we always have the most up-to-date value. :::info `FutureProvider` does not offer a way of directly modifying the computation after a user interaction. It is designed to solve simple use-cases. -For more advanced scenarios, consider using [StateNotifierProvider]. +For more advanced scenarios, consider using [AsyncNotifierProvider]. ::: ## Usage example: reading a configuration file @@ -39,6 +42,7 @@ Using Flutter's asset system, this would be: + Then, the UI can listen to configurations like so: @@ -51,7 +55,7 @@ As you can see, listening to a `FutureProvider` inside a widget returns an [AsyncValue] – which allows handling the error/loading states. [ref.watch]: ../concepts/reading#using-refwatch-to-observe-a-provider -[statenotifierprovider]: ./state_notifier_provider +[asyncnotifierprovider]: ./notifier_provider [provider]: ./provider [asyncvalue]: https://pub.dev/documentation/riverpod/latest/riverpod/AsyncValue-class.html [future]: https://api.dart.dev/dart-async/Future-class.html diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/future_provider/config_consumer/codegen.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/future_provider/config_consumer/codegen.dart index 7f8e854f2..a96f35e72 100644 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/future_provider/config_consumer/codegen.dart +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/future_provider/config_consumer/codegen.dart @@ -3,17 +3,14 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import '../config_provider/codegen.dart'; - /* SNIPPET START */ Widget build(BuildContext context, WidgetRef ref) { final config = ref.watch(fetchConfigurationProvider); - return config.when( - loading: () => const CircularProgressIndicator(), - error: (err, stack) => Text('Error: $err'), - data: (config) { - return Text(config.host); - }, - ); + return switch (config) { + AsyncError(:final error) => Text('Error: $error'), + AsyncData(:final value) => Text(value.host), + _ => const CircularProgressIndicator(), + }; } diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/future_provider/config_consumer/hooks.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/future_provider/config_consumer/hooks.dart index 3f9e2231c..44d9b4df9 100644 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/future_provider/config_consumer/hooks.dart +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/future_provider/config_consumer/hooks.dart @@ -11,12 +11,11 @@ class MyConfiguration extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final config = ref.watch(configProvider); return Scaffold( - body: config.when( - loading: () => const Center(child: CircularProgressIndicator()), - error: (err, stack) => Center(child: Text('Error: $err')), - data: (config) { - return Center(child: Text(config.host)); + body: switch (config) { + AsyncError(:final error) => Center(child: Text('Error: $error')), + AsyncData(:final value) => Center(child: Text(value.host)), + _ => const Center(child: CircularProgressIndicator()), }, - )); + ); } } diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/future_provider/config_consumer/hooks_codegen.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/future_provider/config_consumer/hooks_codegen.dart index ece077f72..ade0628fd 100644 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/future_provider/config_consumer/hooks_codegen.dart +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/future_provider/config_consumer/hooks_codegen.dart @@ -3,23 +3,20 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import '../config_provider/codegen.dart'; - - /* SNIPPET START */ -class MyConfiguration extends HookConsumerWidget { +class MyConfiguration extends HookConsumerWidget { const MyConfiguration({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final config = ref.watch(fetchConfigurationProvider); return Scaffold( - body: config.when( - loading: () => const Center(child: CircularProgressIndicator()), - error: (err, stack) => Center(child: Text('Error: $err')), - data: (config) { - return Center(child: Text(config.host)); + body: switch (config) { + AsyncError(:final error) => Center(child: Text('Error: $error')), + AsyncData(:final value) => Center(child: Text(value.host)), + _ => const Center(child: CircularProgressIndicator()), }, - )); + ); } -} \ No newline at end of file +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/future_provider/config_consumer/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/future_provider/config_consumer/raw.dart index 4176a3866..9d39f3bcd 100644 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/future_provider/config_consumer/raw.dart +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/future_provider/config_consumer/raw.dart @@ -10,11 +10,9 @@ import '../config_provider/raw.dart'; Widget build(BuildContext context, WidgetRef ref) { AsyncValue config = ref.watch(configProvider); - return config.when( - loading: () => const CircularProgressIndicator(), - error: (err, stack) => Text('Error: $err'), - data: (config) { - return Text(config.host); - }, - ); + return switch (config) { + AsyncData(:final value) => Text(value.host), + AsyncError(:final error) => Text('Error: $error'), + _ => const CircularProgressIndicator(), + }; } diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/notifier_provider.mdx b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/notifier_provider.mdx new file mode 100644 index 000000000..4432f874f --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/notifier_provider.mdx @@ -0,0 +1,55 @@ +--- +title: (Async)NotifierProvider +--- + +import CodeBlock from "@theme/CodeBlock"; +import todos from "./notifier_provider/todos"; +import todosConsumer from "!!raw-loader!/docs/providers/notifier_provider/todos/todos_consumer.dart"; +import remoteTodos from "./notifier_provider/remote_todos"; +import remoteTodosConsumer from "!!raw-loader!/docs/providers/notifier_provider/remote_todos/todos_consumer.dart"; +import { trimSnippet, AutoSnippet } from "../../../../../src/components/CodeSnippet"; + +:::caution +The content of this page may be outdated. +It will be updated in the future, but for now you may want to refer to the content +in the top of the sidebar instead (introduction/essentials/case-studies/...) +::: + +[NotifierProvider] is a provider that is used to listen to and expose a [Notifier]. +[AsyncNotifierProvider] is a provider that is used to listen to and expose an [AsyncNotifier]. +[AsyncNotifier] is a [Notifier] that can be asynchronously initialized. +`(Async)NotifierProvider` along with `(Async)Notifier` is Riverpod's recommended solution +for managing state which may change in reaction to a user interaction. + +It is typically used for: + +- exposing a state which can change over time after reacting to custom events. +- centralizing the logic for modifying some state (aka "business logic") in a + single place, improving maintainability over time. + +As a usage example, we could use [NotifierProvider] to implement a todo-list. +Doing so would allow us to expose methods such as `addTodo` to let the UI +modify the list of todos on user interactions: + + + +Now that we have defined a [NotifierProvider], we can use it to interact +with the list of todos in our UI: + +{trimSnippet(todosConsumer)} + +As a usage example, we could use [AsyncNotifierProvider] to implement a remote todo-list. +Doing so would allow us to expose methods such as `addTodo` to let the UI +modify the list of todos on user interactions: + + + +Now that we have defined a [AsyncNotifierProvider], we can use it to interact +with the list of todos in our UI: + +{trimSnippet(remoteTodosConsumer)} + +[notifier]: https://pub.dev/documentation/riverpod/latest/riverpod/Notifier-class.html +[notifierprovider]: https://pub.dev/documentation/riverpod/latest/riverpod/NotifierProvider.html +[asyncnotifier]: https://pub.dev/documentation/riverpod/latest/riverpod/AsyncNotifier-class.html +[asyncnotifierprovider]: https://pub.dev/documentation/riverpod/latest/riverpod/AsyncNotifierProvider.html diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/codegen.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/codegen.dart new file mode 100644 index 000000000..e02195f87 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/codegen.dart @@ -0,0 +1,82 @@ +import 'dart:convert'; + +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.freezed.dart'; +part 'codegen.g.dart'; + +class Http { + Future get(String str) async => str; + Future delete(String str) async => str; + Future post(String str, Map body) async => str; + Future patch(String str, Map body) async => str; +} + +final http = Http(); + +/* SNIPPET START */ + +@freezed +class Todo with _$Todo { + factory Todo({ + required String id, + required String description, + required bool completed, + }) = _Todo; + + factory Todo.fromJson(Map json) => _$TodoFromJson(json); +} + +// This will generates a AsyncNotifier and AsyncNotifierProvider. +// The AsyncNotifier class that will be passed to our AsyncNotifierProvider. +// This class should not expose state outside of its "state" property, which means +// no public getters/properties! +// The public methods on this class will be what allow the UI to modify the state. +// Finally, we are using asyncTodosProvider(AsyncNotifierProvider) to allow the UI to +// interact with our Todos class. +@riverpod +class AsyncTodos extends _$AsyncTodos { + Future> _fetchTodo() async { + final json = await http.get('api/todos'); + final todos = jsonDecode(json) as List>; + return todos.map(Todo.fromJson).toList(); + } + + @override + FutureOr> build() async { + // Load initial todo list from the remote repository + return _fetchTodo(); + } + + Future addTodo(Todo todo) async { + // Set the state to loading + state = const AsyncValue.loading(); + // Add the new todo and reload the todo list from the remote repository + state = await AsyncValue.guard(() async { + await http.post('api/todos', todo.toJson()); + return _fetchTodo(); + }); + } + + // Let's allow removing todos + Future removeTodo(String todoId) async { + state = const AsyncValue.loading(); + state = await AsyncValue.guard(() async { + await http.delete('api/todos/$todoId'); + return _fetchTodo(); + }); + } + + // Let's mark a todo as completed + Future toggle(String todoId) async { + state = const AsyncValue.loading(); + state = await AsyncValue.guard(() async { + await http.patch( + 'api/todos/$todoId', + {'completed': true}, + ); + return _fetchTodo(); + }); + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/codegen.freezed.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/codegen.freezed.dart new file mode 100644 index 000000000..d1b8a01d5 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/codegen.freezed.dart @@ -0,0 +1,184 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'codegen.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +Todo _$TodoFromJson(Map json) { + return _Todo.fromJson(json); +} + +/// @nodoc +mixin _$Todo { + String get id => throw _privateConstructorUsedError; + String get description => throw _privateConstructorUsedError; + bool get completed => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $TodoCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $TodoCopyWith<$Res> { + factory $TodoCopyWith(Todo value, $Res Function(Todo) then) = + _$TodoCopyWithImpl<$Res, Todo>; + @useResult + $Res call({String id, String description, bool completed}); +} + +/// @nodoc +class _$TodoCopyWithImpl<$Res, $Val extends Todo> + implements $TodoCopyWith<$Res> { + _$TodoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? description = null, + Object? completed = null, + }) { + return _then(_value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + completed: null == completed + ? _value.completed + : completed // ignore: cast_nullable_to_non_nullable + as bool, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$TodoImplCopyWith<$Res> implements $TodoCopyWith<$Res> { + factory _$$TodoImplCopyWith( + _$TodoImpl value, $Res Function(_$TodoImpl) then) = + __$$TodoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String id, String description, bool completed}); +} + +/// @nodoc +class __$$TodoImplCopyWithImpl<$Res> + extends _$TodoCopyWithImpl<$Res, _$TodoImpl> + implements _$$TodoImplCopyWith<$Res> { + __$$TodoImplCopyWithImpl(_$TodoImpl _value, $Res Function(_$TodoImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? description = null, + Object? completed = null, + }) { + return _then(_$TodoImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + completed: null == completed + ? _value.completed + : completed // ignore: cast_nullable_to_non_nullable + as bool, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$TodoImpl implements _Todo { + _$TodoImpl( + {required this.id, required this.description, required this.completed}); + + factory _$TodoImpl.fromJson(Map json) => + _$$TodoImplFromJson(json); + + @override + final String id; + @override + final String description; + @override + final bool completed; + + @override + String toString() { + return 'Todo(id: $id, description: $description, completed: $completed)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$TodoImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.description, description) || + other.description == description) && + (identical(other.completed, completed) || + other.completed == completed)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, id, description, completed); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$TodoImplCopyWith<_$TodoImpl> get copyWith => + __$$TodoImplCopyWithImpl<_$TodoImpl>(this, _$identity); + + @override + Map toJson() { + return _$$TodoImplToJson( + this, + ); + } +} + +abstract class _Todo implements Todo { + factory _Todo( + {required final String id, + required final String description, + required final bool completed}) = _$TodoImpl; + + factory _Todo.fromJson(Map json) = _$TodoImpl.fromJson; + + @override + String get id; + @override + String get description; + @override + bool get completed; + @override + @JsonKey(ignore: true) + _$$TodoImplCopyWith<_$TodoImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/codegen.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/codegen.g.dart new file mode 100644 index 000000000..fb8e168d2 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/codegen.g.dart @@ -0,0 +1,44 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$TodoImpl _$$TodoImplFromJson(Map json) => _$TodoImpl( + id: json['id'] as String, + description: json['description'] as String, + completed: json['completed'] as bool, + ); + +Map _$$TodoImplToJson(_$TodoImpl instance) => + { + 'id': instance.id, + 'description': instance.description, + 'completed': instance.completed, + }; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$asyncTodosHash() => r'fd0d7502a1c17b7cedd2350519649dd680fc48cd'; + +/// See also [AsyncTodos]. +@ProviderFor(AsyncTodos) +final asyncTodosProvider = + AutoDisposeAsyncNotifierProvider>.internal( + AsyncTodos.new, + name: r'asyncTodosProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$asyncTodosHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$AsyncTodos = AutoDisposeAsyncNotifier>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/index.tsx b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/index.tsx new file mode 100644 index 000000000..339b89a56 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./codegen.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/raw.dart new file mode 100644 index 000000000..f4455a338 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/raw.dart @@ -0,0 +1,101 @@ +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +class Http { + Future get(String str) async => str; + Future delete(String str) async => str; + Future post(String str, Map body) async => str; + Future patch(String str, Map body) async => str; +} + +final http = Http(); + +/* SNIPPET START */ + +// An immutable state is preferred. +// We could also use packages like Freezed to help with the implementation. +@immutable +class Todo { + const Todo({ + required this.id, + required this.description, + required this.completed, + }); + + factory Todo.fromJson(Map map) { + return Todo( + id: map['id'] as String, + description: map['description'] as String, + completed: map['completed'] as bool, + ); + } + + // All properties should be `final` on our class. + final String id; + final String description; + final bool completed; + + Map toJson() => { + 'id': id, + 'description': description, + 'completed': completed, + }; +} + +// The Notifier class that will be passed to our NotifierProvider. +// This class should not expose state outside of its "state" property, which means +// no public getters/properties! +// The public methods on this class will be what allow the UI to modify the state. +class AsyncTodosNotifier extends AsyncNotifier> { + Future> _fetchTodo() async { + final json = await http.get('api/todos'); + final todos = jsonDecode(json) as List>; + return todos.map(Todo.fromJson).toList(); + } + + @override + Future> build() async { + // Load initial todo list from the remote repository + return _fetchTodo(); + } + + Future addTodo(Todo todo) async { + // Set the state to loading + state = const AsyncValue.loading(); + // Add the new todo and reload the todo list from the remote repository + state = await AsyncValue.guard(() async { + await http.post('api/todos', todo.toJson()); + return _fetchTodo(); + }); + } + + // Let's allow removing todos + Future removeTodo(String todoId) async { + state = const AsyncValue.loading(); + state = await AsyncValue.guard(() async { + await http.delete('api/todos/$todoId'); + return _fetchTodo(); + }); + } + + // Let's mark a todo as completed + Future toggle(String todoId) async { + state = const AsyncValue.loading(); + state = await AsyncValue.guard(() async { + await http.patch( + 'api/todos/$todoId', + {'completed': true}, + ); + return _fetchTodo(); + }); + } +} + +// Finally, we are using NotifierProvider to allow the UI to interact with +// our TodosNotifier class. +final asyncTodosProvider = + AsyncNotifierProvider>(() { + return AsyncTodosNotifier(); +}); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/todos_consumer.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/todos_consumer.dart new file mode 100644 index 000000000..5d00d05d9 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/todos_consumer.dart @@ -0,0 +1,37 @@ +// ignore_for_file: omit_local_variable_types, prefer_final_locals + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'codegen.dart'; + +/* SNIPPET START */ + +class TodoListView extends ConsumerWidget { + const TodoListView({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + // rebuild the widget when the todo list changes + final asyncTodos = ref.watch(asyncTodosProvider); + + // Let's render the todos in a scrollable list view + return switch (asyncTodos) { + AsyncData(:final value) => ListView( + children: [ + for (final todo in value) + CheckboxListTile( + value: todo.completed, + // When tapping on the todo, change its completed status + onChanged: (value) { + ref.read(asyncTodosProvider.notifier).toggle(todo.id); + }, + title: Text(todo.description), + ), + ], + ), + AsyncError(:final error) => Text('Error: $error'), + _ => const Center(child: CircularProgressIndicator()), + }; + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/codegen.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/codegen.dart new file mode 100644 index 000000000..866ed37bb --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/codegen.dart @@ -0,0 +1,68 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.freezed.dart'; +part 'codegen.g.dart'; + +/* SNIPPET START */ + +@freezed +class Todo with _$Todo { + factory Todo({ + required String id, + required String description, + required bool completed, + }) = _Todo; +} + +// This will generates a Notifier and NotifierProvider. +// The Notifier class that will be passed to our NotifierProvider. +// This class should not expose state outside of its "state" property, which means +// no public getters/properties! +// The public methods on this class will be what allow the UI to modify the state. +// Finally, we are using todosProvider(NotifierProvider) to allow the UI to +// interact with our Todos class. +@riverpod +class Todos extends _$Todos { + @override + List build() { + return []; + } + + // Let's allow the UI to add todos. + void addTodo(Todo todo) { + // Since our state is immutable, we are not allowed to do `state.add(todo)`. + // Instead, we should create a new list of todos which contains the previous + // items and the new one. + // Using Dart's spread operator here is helpful! + state = [...state, todo]; + // No need to call "notifyListeners" or anything similar. Calling "state =" + // will automatically rebuild the UI when necessary. + } + + // Let's allow removing todos + void removeTodo(String todoId) { + // Again, our state is immutable. So we're making a new list instead of + // changing the existing list. + state = [ + for (final todo in state) + if (todo.id != todoId) todo, + ]; + } + + // Let's mark a todo as completed + void toggle(String todoId) { + state = [ + for (final todo in state) + // we're marking only the matching todo as completed + if (todo.id == todoId) + // Once more, since our state is immutable, we need to make a copy + // of the todo. We're using our `copyWith` method implemented before + // to help with that. + todo.copyWith(completed: !todo.completed) + else + // other todos are not modified + todo, + ]; + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/codegen.freezed.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/codegen.freezed.dart new file mode 100644 index 000000000..0b73d3548 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/codegen.freezed.dart @@ -0,0 +1,166 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'codegen.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +/// @nodoc +mixin _$Todo { + String get id => throw _privateConstructorUsedError; + String get description => throw _privateConstructorUsedError; + bool get completed => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $TodoCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $TodoCopyWith<$Res> { + factory $TodoCopyWith(Todo value, $Res Function(Todo) then) = + _$TodoCopyWithImpl<$Res, Todo>; + @useResult + $Res call({String id, String description, bool completed}); +} + +/// @nodoc +class _$TodoCopyWithImpl<$Res, $Val extends Todo> + implements $TodoCopyWith<$Res> { + _$TodoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? description = null, + Object? completed = null, + }) { + return _then(_value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + completed: null == completed + ? _value.completed + : completed // ignore: cast_nullable_to_non_nullable + as bool, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$TodoImplCopyWith<$Res> implements $TodoCopyWith<$Res> { + factory _$$TodoImplCopyWith( + _$TodoImpl value, $Res Function(_$TodoImpl) then) = + __$$TodoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String id, String description, bool completed}); +} + +/// @nodoc +class __$$TodoImplCopyWithImpl<$Res> + extends _$TodoCopyWithImpl<$Res, _$TodoImpl> + implements _$$TodoImplCopyWith<$Res> { + __$$TodoImplCopyWithImpl(_$TodoImpl _value, $Res Function(_$TodoImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? description = null, + Object? completed = null, + }) { + return _then(_$TodoImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + completed: null == completed + ? _value.completed + : completed // ignore: cast_nullable_to_non_nullable + as bool, + )); + } +} + +/// @nodoc + +class _$TodoImpl implements _Todo { + _$TodoImpl( + {required this.id, required this.description, required this.completed}); + + @override + final String id; + @override + final String description; + @override + final bool completed; + + @override + String toString() { + return 'Todo(id: $id, description: $description, completed: $completed)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$TodoImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.description, description) || + other.description == description) && + (identical(other.completed, completed) || + other.completed == completed)); + } + + @override + int get hashCode => Object.hash(runtimeType, id, description, completed); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$TodoImplCopyWith<_$TodoImpl> get copyWith => + __$$TodoImplCopyWithImpl<_$TodoImpl>(this, _$identity); +} + +abstract class _Todo implements Todo { + factory _Todo( + {required final String id, + required final String description, + required final bool completed}) = _$TodoImpl; + + @override + String get id; + @override + String get description; + @override + bool get completed; + @override + @JsonKey(ignore: true) + _$$TodoImplCopyWith<_$TodoImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/codegen.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/codegen.g.dart new file mode 100644 index 000000000..d2f26ef1b --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/codegen.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$todosHash() => r'3485c14ec4db07efe5fe52243258a66e6f99b2b4'; + +/// See also [Todos]. +@ProviderFor(Todos) +final todosProvider = AutoDisposeNotifierProvider>.internal( + Todos.new, + name: r'todosProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$todosHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$Todos = AutoDisposeNotifier>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/index.tsx b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/index.tsx new file mode 100644 index 000000000..339b89a56 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./codegen.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/raw.dart new file mode 100644 index 000000000..ec3e50308 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/raw.dart @@ -0,0 +1,85 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +/* SNIPPET START */ + +// An immutable state is preferred. +// We could also use packages like Freezed to help with the implementation. +@immutable +class Todo { + const Todo({ + required this.id, + required this.description, + required this.completed, + }); + + // All properties should be `final` on our class. + final String id; + final String description; + final bool completed; + + // Since Todo is immutable, we implement a method that allows cloning the + // Todo with slightly different content. + Todo copyWith({String? id, String? description, bool? completed}) { + return Todo( + id: id ?? this.id, + description: description ?? this.description, + completed: completed ?? this.completed, + ); + } +} + +// The Notifier class that will be passed to our NotifierProvider. +// This class should not expose state outside of its "state" property, which means +// no public getters/properties! +// The public methods on this class will be what allow the UI to modify the state. +class TodosNotifier extends Notifier> { + // We initialize the list of todos to an empty list + @override + List build() { + return []; + } + + // Let's allow the UI to add todos. + void addTodo(Todo todo) { + // Since our state is immutable, we are not allowed to do `state.add(todo)`. + // Instead, we should create a new list of todos which contains the previous + // items and the new one. + // Using Dart's spread operator here is helpful! + state = [...state, todo]; + // No need to call "notifyListeners" or anything similar. Calling "state =" + // will automatically rebuild the UI when necessary. + } + + // Let's allow removing todos + void removeTodo(String todoId) { + // Again, our state is immutable. So we're making a new list instead of + // changing the existing list. + state = [ + for (final todo in state) + if (todo.id != todoId) todo, + ]; + } + + // Let's mark a todo as completed + void toggle(String todoId) { + state = [ + for (final todo in state) + // we're marking only the matching todo as completed + if (todo.id == todoId) + // Once more, since our state is immutable, we need to make a copy + // of the todo. We're using our `copyWith` method implemented before + // to help with that. + todo.copyWith(completed: !todo.completed) + else + // other todos are not modified + todo, + ]; + } +} + +// Finally, we are using NotifierProvider to allow the UI to interact with +// our TodosNotifier class. +final todosProvider = NotifierProvider>(() { + return TodosNotifier(); +}); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/todos_consumer.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/todos_consumer.dart new file mode 100644 index 000000000..192cb9f66 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/todos_consumer.dart @@ -0,0 +1,32 @@ +// ignore_for_file: omit_local_variable_types, prefer_final_locals + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'codegen.dart'; + +/* SNIPPET START */ + +class TodoListView extends ConsumerWidget { + const TodoListView({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + // rebuild the widget when the todo list changes + List todos = ref.watch(todosProvider); + + // Let's render the todos in a scrollable list view + return ListView( + children: [ + for (final todo in todos) + CheckboxListTile( + value: todo.completed, + // When tapping on the todo, change its completed status + onChanged: (value) => + ref.read(todosProvider.notifier).toggle(todo.id), + title: Text(todo.description), + ), + ], + ); + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider.mdx b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider.mdx index abda9ed16..136c8edbc 100644 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider.mdx +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider.mdx @@ -2,15 +2,19 @@ title: Provider --- -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; import CodeBlock from "@theme/CodeBlock"; -import todo from "!!raw-loader!/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/todo.dart"; -import completedTodos from "!!raw-loader!/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/completed_todos.dart"; -import todosConsumer from "!!raw-loader!/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/todos_consumer.dart"; -import unoptimizedPreviousButton from "!!raw-loader!/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/unoptimized_previous_button.dart"; -import optimizedPreviousButton from "!!raw-loader!/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/optimized_previous_button.dart"; -import { trimSnippet } from "../../../../../src/components/CodeSnippet"; +import todo from "./provider/todo"; +import completedTodos from "./provider/completed_todos"; +import todosConsumer from "!!raw-loader!/docs/providers/provider/todos_consumer.dart"; +import unoptimizedPreviousButton from "./provider/unoptimized_previous_button"; +import optimizedPreviousButton from "./provider/optimized_previous_button"; +import { trimSnippet, AutoSnippet } from "../../../../../src/components/CodeSnippet"; + +:::caution +The content of this page may be outdated. +It will be updated in the future, but for now you may want to refer to the content +in the top of the sidebar instead (introduction/essentials/case-studies/...) +::: `Provider` is the most basic of all providers. It creates a value... And that's about it. @@ -31,15 +35,15 @@ Since filtering a list could be slightly expensive, we ideally do not want to filter our list of todos whenever our application re-renders. In this situation, we could use `Provider` to do the filtering for us. -For that, assume that our application has an existing [StateNotifierProvider] +For that, assume that our application has an existing [NotifierProvider] which manipulates a list of todos: -{trimSnippet(todo)} + From there, we can use `Provider` to expose the filtered list of todos, showing only the completed todos: -{trimSnippet(completedTodos)} + With this code, our UI is now able to show the list of the completed todos by listening to `completedTodosProvider`: @@ -73,7 +77,7 @@ current page index, and if that index is equal to 0, we would disable the button This code could be: -{trimSnippet(unoptimizedPreviousButton)} + The issue with this code is that whenever we change the current page, the "previous" button will rebuild. @@ -85,7 +89,7 @@ allowed to go to the previous page directly within the "previous" button. A way to solve this is to extract this logic outside of the widget and into a `Provider`: -{trimSnippet(optimizedPreviousButton)} + By doing this small refactoring, our `PreviousButton` widget will no longer rebuild when the page index changes thanks to `Provider`. @@ -98,4 +102,4 @@ This change both improved the performance of our button and had the interesting benefit of extracting the logic outside of our widget. [ref.watch]: ../concepts/reading#using-refwatch-to-observe-a-provider -[statenotifierprovider]: ./state_notifier_provider +[notifierprovider]: ./notifier_provider diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/completed_todos/completed_todos.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/completed_todos/completed_todos.dart new file mode 100644 index 000000000..1a29d96f5 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/completed_todos/completed_todos.dart @@ -0,0 +1,15 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../todo/todo.dart'; + +part 'completed_todos.g.dart'; + +/* SNIPPET START */ + +@riverpod +List completedTodos(CompletedTodosRef ref) { + final todos = ref.watch(todosProvider); + + // we return only the completed todos + return todos.where((todo) => todo.isCompleted).toList(); +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/completed_todos/completed_todos.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/completed_todos/completed_todos.g.dart new file mode 100644 index 000000000..f7db84ee2 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/completed_todos/completed_todos.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'completed_todos.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$completedTodosHash() => r'855706c09268f428696b3b382ae1605818361b83'; + +/// See also [completedTodos]. +@ProviderFor(completedTodos) +final completedTodosProvider = AutoDisposeProvider>.internal( + completedTodos, + name: r'completedTodosProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$completedTodosHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef CompletedTodosRef = AutoDisposeProviderRef>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/completed_todos/index.tsx b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/completed_todos/index.tsx new file mode 100644 index 000000000..11451aa1c --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/completed_todos/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./completed_todos.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/completed_todos.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/completed_todos/raw.dart similarity index 60% rename from website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/completed_todos.dart rename to website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/completed_todos/raw.dart index 9d486731a..e24c48bd5 100644 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/completed_todos.dart +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/completed_todos/raw.dart @@ -1,13 +1,13 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'todo.dart'; +import '../todo/raw.dart'; /* SNIPPET START */ final completedTodosProvider = Provider>((ref) { - // todosProvider로부터 모든 할일(todos)목록을 가져옵니다. + // We obtain the list of all todos from the todosProvider final todos = ref.watch(todosProvider); - // 완료된(completed) 할일(todos)들만 반환합니다. + // we return only the completed todos return todos.where((todo) => todo.isCompleted).toList(); }); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/optimized_previous_button/index.tsx b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/optimized_previous_button/index.tsx new file mode 100644 index 000000000..fb83c92f1 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/optimized_previous_button/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./optimized_previous_button.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/optimized_previous_button/optimized_previous_button.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/optimized_previous_button/optimized_previous_button.dart new file mode 100644 index 000000000..7d3b8a332 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/optimized_previous_button/optimized_previous_button.dart @@ -0,0 +1,50 @@ +// A provider that controls the current page +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'optimized_previous_button.g.dart'; + +/* SNIPPET START */ + +@riverpod +class PageIndex extends _$PageIndex { + @override + int build() { + return 0; + } + + void goToPreviousPage() { + state = state - 1; + } +} + +// A provider which computes whether the user is allowed to go to the previous page +@riverpod +/* highlight-start */ +bool canGoToPreviousPage(CanGoToPreviousPageRef ref) { +/* highlight-end */ + return ref.watch(pageIndexProvider) != 0; +} + +class PreviousButton extends ConsumerWidget { + const PreviousButton({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + // We are now watching our new Provider + // Our widget is no longer calculating whether we can go to the previous page. +/* highlight-start */ + final canGoToPreviousPage = ref.watch(canGoToPreviousPageProvider); +/* highlight-end */ + + void goToPreviousPage() { + ref.read(pageIndexProvider.notifier).goToPreviousPage(); + } + + return ElevatedButton( + onPressed: canGoToPreviousPage ? goToPreviousPage : null, + child: const Text('previous'), + ); + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/optimized_previous_button/optimized_previous_button.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/optimized_previous_button/optimized_previous_button.g.dart new file mode 100644 index 000000000..d71214f1d --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/optimized_previous_button/optimized_previous_button.g.dart @@ -0,0 +1,42 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'optimized_previous_button.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$canGoToPreviousPageHash() => + r'801fe8182a37cd21ae83bdfccbe36c125b4d14fb'; + +/// See also [canGoToPreviousPage]. +@ProviderFor(canGoToPreviousPage) +final canGoToPreviousPageProvider = AutoDisposeProvider.internal( + canGoToPreviousPage, + name: r'canGoToPreviousPageProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$canGoToPreviousPageHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef CanGoToPreviousPageRef = AutoDisposeProviderRef; +String _$pageIndexHash() => r'59307ecf23b5b2432833da5ad6b312bf36435d0e'; + +/// See also [PageIndex]. +@ProviderFor(PageIndex) +final pageIndexProvider = AutoDisposeNotifierProvider.internal( + PageIndex.new, + name: r'pageIndexProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$pageIndexHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$PageIndex = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/optimized_previous_button.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/optimized_previous_button/raw.dart similarity index 67% rename from website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/optimized_previous_button.dart rename to website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/optimized_previous_button/raw.dart index a9a7d2469..30b1cc673 100644 --- a/website/i18n/it/docusaurus-plugin-content-docs/current/providers/provider/optimized_previous_button.dart +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/optimized_previous_button/raw.dart @@ -1,4 +1,4 @@ -// Un provider che controlla la pagina corrente +// A provider that controls the current page import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -6,12 +6,11 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; final pageIndexProvider = StateProvider((ref) => 0); -// Un provider che calcola se l'utente è abilitato ad andare alla pagina precedente - +// A provider which computes whether the user is allowed to go to the previous page /* highlight-start */ final canGoToPreviousPageProvider = Provider((ref) { /* highlight-end */ - return ref.watch(pageIndexProvider) == 0; + return ref.watch(pageIndexProvider) != 0; }); class PreviousButton extends ConsumerWidget { @@ -19,11 +18,11 @@ class PreviousButton extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - // Ora osserviamo il nostro nuovo Provider - // Il nostro widget non calcolerà più se possiamo andare alla pagina precedente. - /* highlight-start */ + // We are now watching our new Provider + // Our widget is no longer calculating whether we can go to the previous page. +/* highlight-start */ final canGoToPreviousPage = ref.watch(canGoToPreviousPageProvider); - /* highlight-end */ +/* highlight-end */ void goToPreviousPage() { ref.read(pageIndexProvider.notifier).update((state) => state - 1); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/todo/index.tsx b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/todo/index.tsx new file mode 100644 index 000000000..91d9cc4aa --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/todo/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./todo.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/todo.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/todo/raw.dart similarity index 59% rename from website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/todo.dart rename to website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/todo/raw.dart index 74a2e6609..11e62bc3a 100644 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/todo.dart +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/todo/raw.dart @@ -10,15 +10,18 @@ class Todo { final String description; } -class TodosNotifier extends StateNotifier> { - TodosNotifier() : super([]); +class TodosNotifier extends Notifier> { + @override + List build() { + return []; + } void addTodo(Todo todo) { state = [...state, todo]; } - // TODO "removeTodo"와 같은 다른 메소드들을 추가하기 + // TODO add other methods, such as "removeTodo", ... } -final todosProvider = StateNotifierProvider>((ref) { +final todosProvider = NotifierProvider>(() { return TodosNotifier(); }); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/todo/todo.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/todo/todo.dart new file mode 100644 index 000000000..7089bc962 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/todo/todo.dart @@ -0,0 +1,25 @@ +// ignore_for_file: avoid_positional_boolean_parameters +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'todo.g.dart'; + +/* SNIPPET START */ + +class Todo { + Todo(this.description, this.isCompleted); + final bool isCompleted; + final String description; +} + +@riverpod +class Todos extends _$Todos { + @override + List build() { + return []; + } + + void addTodo(Todo todo) { + state = [...state, todo]; + } + // TODO add other methods, such as "removeTodo", ... +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/todo/todo.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/todo/todo.g.dart new file mode 100644 index 000000000..a21239ae8 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/todo/todo.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'todo.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$todosHash() => r'4bd25c3c15bfff56ad6e733bd17ecb7284c4ceb2'; + +/// See also [Todos]. +@ProviderFor(Todos) +final todosProvider = AutoDisposeNotifierProvider>.internal( + Todos.new, + name: r'todosProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$todosHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$Todos = AutoDisposeNotifier>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/todos_consumer.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/todos_consumer.dart index d3c19dfde..9b93e9185 100644 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/todos_consumer.dart +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/todos_consumer.dart @@ -3,16 +3,16 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'completed_todos.dart'; +import 'completed_todos/completed_todos.dart'; Widget build() { return /* SNIPPET START */ - Consumer(builder: (context, ref, child) { - final completedTodos = ref.watch(completedTodosProvider); - // TODO a ListView/GridView/...등을 사용하여 todos를 표시하기/* SKIP */ - return Container(); - /* SKIP END */ - }); +Consumer(builder: (context, ref, child) { + final completedTodos = ref.watch(completedTodosProvider); + // TODO show the todos using a ListView/GridView/.../* SKIP */ + return Container(); + /* SKIP END */ +}); /* SNIPPET END */ } diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/unoptimized_previous_button/index.tsx b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/unoptimized_previous_button/index.tsx new file mode 100644 index 000000000..d345d4f5d --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/unoptimized_previous_button/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./unoptimized_previous_button.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/unoptimized_previous_button.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/unoptimized_previous_button/raw.dart similarity index 74% rename from website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/unoptimized_previous_button.dart rename to website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/unoptimized_previous_button/raw.dart index e5f56b568..828d382d5 100644 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/unoptimized_previous_button.dart +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/unoptimized_previous_button/raw.dart @@ -1,4 +1,4 @@ -// 현재 페이지를 제어하는 프로바이더 +// A provider that controls the current page import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -11,8 +11,8 @@ class PreviousButton extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - // 만약 첫페이지가 아니라면 이전 버튼이 활성화 됩니다. - final canGoToPreviousPage = ref.watch(pageIndexProvider) == 0; + // if not on first page, the previous button is active + final canGoToPreviousPage = ref.watch(pageIndexProvider) != 0; void goToPreviousPage() { ref.read(pageIndexProvider.notifier).update((state) => state - 1); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/unoptimized_previous_button/unoptimized_previous_button.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/unoptimized_previous_button/unoptimized_previous_button.dart new file mode 100644 index 000000000..f77de56c3 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/unoptimized_previous_button/unoptimized_previous_button.dart @@ -0,0 +1,39 @@ +// A provider that controls the current page +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'unoptimized_previous_button.g.dart'; + +/* SNIPPET START */ + +@riverpod +class PageIndex extends _$PageIndex { + @override + int build() { + return 0; + } + + void goToPreviousPage() { + state = state - 1; + } +} + +class PreviousButton extends ConsumerWidget { + const PreviousButton({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + // if not on first page, the previous button is active + final canGoToPreviousPage = ref.watch(pageIndexProvider) != 0; + + void goToPreviousPage() { + ref.read(pageIndexProvider.notifier).goToPreviousPage(); + } + + return ElevatedButton( + onPressed: canGoToPreviousPage ? goToPreviousPage : null, + child: const Text('previous'), + ); + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/unoptimized_previous_button/unoptimized_previous_button.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/unoptimized_previous_button/unoptimized_previous_button.g.dart new file mode 100644 index 000000000..2656fac9b --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/provider/unoptimized_previous_button/unoptimized_previous_button.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'unoptimized_previous_button.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$pageIndexHash() => r'59307ecf23b5b2432833da5ad6b312bf36435d0e'; + +/// See also [PageIndex]. +@ProviderFor(PageIndex) +final pageIndexProvider = AutoDisposeNotifierProvider.internal( + PageIndex.new, + name: r'pageIndexProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$pageIndexHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$PageIndex = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/state_notifier_provider.mdx b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/state_notifier_provider.mdx index a3cab49e6..ad8b55960 100644 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/state_notifier_provider.mdx +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/state_notifier_provider.mdx @@ -2,17 +2,19 @@ title: StateNotifierProvider --- -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; import CodeBlock from "@theme/CodeBlock"; import todos from "!!raw-loader!/docs/providers/state_notifier_provider/todos.dart"; import todosConsumer from "!!raw-loader!/docs/providers/state_notifier_provider/todos_consumer.dart"; import { trimSnippet } from "../../../../../src/components/CodeSnippet"; +:::caution +The content of this page may be outdated. +It will be updated in the future, but for now you may want to refer to the content +in the top of the sidebar instead (introduction/essentials/case-studies/...) +::: + `StateNotifierProvider` is a provider that is used to listen to and expose a -[StateNotifier] (from the package [state_notifier], which Riverpod re-exports). -`StateNotifierProvider` along with [StateNotifier] is Riverpod's recommended solution -for managing state which may change in reaction to a user interaction. +[StateNotifier] (from the package [state_notifier], which Riverpod re-exports). It is typically used for: @@ -21,6 +23,9 @@ It is typically used for: - centralizing the logic for modifying some state (aka "business logic") in a single place, improving maintainability over time. +:::info +Prefer using [NotifierProvider] instead. +::: As a usage example, we could use `StateNotifierProvider` to implement a todo-list. Doing so would allow us to expose methods such as `addTodo` to let the UI @@ -37,3 +42,4 @@ with the list of todos in our UI: [statenotifier]: https://pub.dev/documentation/state_notifier/latest/state_notifier/StateNotifier-class.html [provider]: ./provider [futureprovider]: ./future_provider +[notifierprovider]: ./notifier_provider diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/state_provider.mdx b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/state_provider.mdx index 88340c713..bb65359db 100644 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/state_provider.mdx +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/state_provider.mdx @@ -2,8 +2,6 @@ title: StateProvider --- -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; import CodeBlock from "@theme/CodeBlock"; import product from "!!raw-loader!/docs/providers/state_provider/product.dart"; import productListView from "!!raw-loader!/docs/providers/state_provider/product_list_view.dart"; @@ -15,9 +13,15 @@ import updateReadTwice from "!!raw-loader!/docs/providers/state_provider/update_ import updateReadOnce from "!!raw-loader!/docs/providers/state_provider/update_read_once.dart"; import { trimSnippet } from "../../../../../src/components/CodeSnippet"; +:::caution +The content of this page may be outdated. +It will be updated in the future, but for now you may want to refer to the content +in the top of the sidebar instead (introduction/essentials/case-studies/...) +::: + `StateProvider` is a provider that exposes a way to modify its state. -It is a simplification of [StateNotifierProvider], designed to avoid -having to write a [StateNotifier] class for very simple use-cases. +It is a simplification of [NotifierProvider], designed to avoid +having to write a [Notifier] class for very simple use-cases. `StateProvider` exists primarily to allow the modification of **simple** variables by the User Interface. @@ -34,10 +38,10 @@ You should not use `StateProvider` if: - your state is a complex object (such as a custom class, a list/map, ...) - the logic for modifying your state is more advanced than a simple `count++`. -For more advanced cases, consider using [StateNotifierProvider] instead and -create a [StateNotifier] class. +For more advanced cases, consider using [NotifierProvider] instead and +create a [Notifier] class. While the initial boilerplate will be a bit larger, having a custom -[StateNotifier] class is critical for the long-term maintainability of your +[Notifier] class is critical for the long-term maintainability of your project – as it centralizes the business logic of your state in a single place. ## Usage example: Changing the filter type using a dropdown @@ -119,7 +123,9 @@ This change achieves the same effect while making the syntax a bit better. [ref.watch]: ../concepts/reading#using-refwatch-to-observe-a-provider [ref.read]: ../concepts/reading#using-refread-to-obtain-the-state-of-a-provider-once [statenotifierprovider]: ./state_notifier_provider +[notifierprovider]: ./notifier_provider [futureprovider]: ./future_provider +[notifier]: https://pub.dev/documentation/riverpod/latest/riverpod/Notifier-class.html [statenotifier]: https://pub.dev/documentation/state_notifier/latest/state_notifier/StateNotifier-class.html [provider]: ./provider [asyncvalue]: https://pub.dev/documentation/riverpod/latest/riverpod/AsyncValue-class.html diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/stream_provider.mdx b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/stream_provider.mdx index 5497e32ba..bd40cfc5f 100644 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/stream_provider.mdx +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/stream_provider.mdx @@ -2,12 +2,16 @@ title: StreamProvider --- -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; import CodeBlock from "@theme/CodeBlock"; -import configProvider from "/docs/providers/future_provider/config_provider"; -import configConsumer from "/docs/providers/future_provider/config_consumer"; -import { trimSnippet } from "../../../../../src/components/CodeSnippet"; +import { trimSnippet,AutoSnippet } from "../../../../../src/components/CodeSnippet"; +import streamProvider from "./stream_provider/live_stream_chat_provider"; +import streamConsumer from "!!raw-loader!/docs/providers/stream_provider/live_stream_chat_consumer.dart"; + +:::caution +The content of this page may be outdated. +It will be updated in the future, but for now you may want to refer to the content +in the top of the sidebar instead (introduction/essentials/case-studies/...) +::: `StreamProvider` is similar to [FutureProvider] but for [Stream]s instead of [Future]s. @@ -32,6 +36,17 @@ Using `StreamProvider` over [StreamBuilder] has numerous benefits: immediate access to the most up-to-date event. - it allows easily mocking the stream during tests by overriding the `StreamProvider`. +## Usage example: live chat using sockets + +`StreamProvider` is used in when we handle stream of asynchronous data +such as Video Streaming, Weather broadcasting Apis or Live chat as follows: + + + +Then, the UI can listen to live streaming chats like so: + +{trimSnippet(streamConsumer)} + [ref.watch]: ../concepts/reading#using-refwatch-to-observe-a-provider [statenotifierprovider]: ./state_notifier_provider [provider]: ./provider @@ -41,4 +56,4 @@ Using `StreamProvider` over [StreamBuilder] has numerous benefits: [stream]: https://api.dart.dev/dart-async/Stream-class.html [stream.periodic]: https://api.dart.dev/stable/2.15.1/dart-async/Stream/Stream.periodic.html [family]: ../concepts/modifiers/family -[streambuilder]: https://api.flutter.dev/flutter/widgets/StreamBuilder-class.html +[streambuilder]: https://api.flutter.dev/flutter/widgets/StreamBuilder-class.html \ No newline at end of file diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_consumer.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_consumer.dart new file mode 100644 index 000000000..383def216 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_consumer.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'live_stream_chat_provider.dart'; + +/* SNIPPET START */ +Widget build(BuildContext context, WidgetRef ref) { + final liveChats = ref.watch(chatProvider); + + // Like FutureProvider, it is possible to handle loading/error states using AsyncValue.when + return switch (liveChats) { + // Display all the messages in a scrollable list view. + AsyncData(:final value) => ListView.builder( + // Show messages from bottom to top + reverse: true, + itemCount: value.length, + itemBuilder: (context, index) { + final message = value[index]; + return Text(message); + }, + ), + AsyncError(:final error) => Text(error.toString()), + _ => const CircularProgressIndicator(), + }; +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_provider.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_provider.dart new file mode 100644 index 000000000..d2bb3ad2a --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_provider.dart @@ -0,0 +1,18 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +/* SNIPPET START */ +final chatProvider = StreamProvider>((ref) async* { + // Connect to an API using sockets, and decode the output + final socket = await Socket.connect('my-api', 4242); + ref.onDispose(socket.close); + + var allMessages = const []; + await for (final message in socket.map(utf8.decode)) { + // A new message has been received. Let's add it to the list of all messages. + allMessages = [...allMessages, message]; + yield allMessages; + } +}); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_provider/codegen.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_provider/codegen.dart new file mode 100644 index 000000000..e2e34878c --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_provider/codegen.dart @@ -0,0 +1,23 @@ +// ignore_for_file: avoid_unused_constructor_parameters + +import 'dart:convert'; +import 'dart:io'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +/* SNIPPET START */ + +@riverpod +Stream> chat(ChatRef ref) async* { + // Connect to an API using sockets, and decode the output + final socket = await Socket.connect('my-api', 4242); + ref.onDispose(socket.close); + + var allMessages = const []; + await for (final message in socket.map(utf8.decode)) { + // A new message has been received. Let's add it to the list of all messages. + allMessages = [...allMessages, message]; + yield allMessages; + } +} diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_provider/codegen.g.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_provider/codegen.g.dart new file mode 100644 index 000000000..213c08a68 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_provider/codegen.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$chatHash() => r'db1302132f90e854fe2f5da9d97d89c9a3c8b858'; + +/// See also [chat]. +@ProviderFor(chat) +final chatProvider = AutoDisposeStreamProvider>.internal( + chat, + name: r'chatProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$chatHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ChatRef = AutoDisposeStreamProviderRef>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_provider/index.tsx b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_provider/index.tsx new file mode 100644 index 000000000..339b89a56 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_provider/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./codegen.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_provider/raw.dart b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_provider/raw.dart new file mode 100644 index 000000000..beb2bcd05 --- /dev/null +++ b/website/i18n/ko/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_provider/raw.dart @@ -0,0 +1,18 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +/* SNIPPET START */ +final chatProvider = StreamProvider>((ref) async* { + // Connect to an API using sockets, and decode the output + final socket = await Socket.connect('my-api', 4242); + ref.onDispose(socket.close); + + var allMessages = const []; + await for (final message in socket.map(utf8.decode)) { + // A new message has been received. Let's add it to the list of all messages. + allMessages = [...allMessages, message]; + yield allMessages; + } +}); diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/riverpod_for_provider_users.mdx b/website/i18n/ko/docusaurus-plugin-content-docs/current/riverpod_for_provider_users.mdx deleted file mode 100644 index b725a5b27..000000000 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/riverpod_for_provider_users.mdx +++ /dev/null @@ -1,401 +0,0 @@ ---- -title: Provider사용자를 위한 Riverpod 가이드 ---- - -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; -import CodeBlock from "@theme/CodeBlock"; -import pubspec from "./getting_started/pubspec"; -import dartHelloWorld from "./getting_started/dart_hello_world"; -import helloWorld from "./getting_started/hello_world"; -import dartPubspec from "./getting_started/dart_pubspec"; -import { - trimSnippet, - AutoSnippet, - When, -} from "../../../../src/components/CodeSnippet"; - -This article is designed for people familiar with the [Provider] package who -wants to learn about Riverpod. - -## The relationship between Riverpod and Provider - -Riverpod is designed to be the spiritual successor of Provider. Hence the -name "Riverpod", which is an anagram of "Provider". - -Riverpod was born while searching for solutions to the various technical -limitations that Provider face. Originally, Riverpod was supposed to be a -major version of Provider as a way to solve this problem. But it was -decided against as this would be a decently big breaking change, and -Provider is one of the most used Flutter packages. - -Still, conceptually, Riverpod and Provider are fairly similar. -Both packages fill a similar role. Both try to: - -- cache and dispose some stateful objects -- offer a way to mock those objects during tests -- offer a way for Widgets to listen to those objects in a simple way. - -At the same time, think of Riverpod as what Provider could've been if -it continued to mature for a few years. - -Riverpod fixes various fundamental problems with Provider, such as but not limited to: - -- Significantly simplifying the combination of "providers". - Instead of the tedious and error-prone `ProxyProvider`, Riverpod exposes - simple yet powerful utilites such as [ref.watch] and [ref.listen]. -- Allowing multiple "provider" to expose a value of the same type. - This removes the need for defining custom classes when exposing a - plain `int` or `String` would work just as well. -- Removing the need to re-define providers inside tests. - With Riverpod, providers are ready to use inside tests by default. -- Reducing the over-reliance on "scoping" to dispose objects by offering - alternate ways to dispose objects ([autoDispose]) - While powerful, scoping a provider is fairly advanced and hard to get right. - -... And a lot more. - -The only true downside of Riverpod is that it requires changing the widget type -to work: - -- Instead of extending `StatelessWidget`, with Riverpod you should extend - `ConsumerWidget`. -- Instead of extending `StatefulWidget`, with Riverpod you should extend - `ConsumerStatefulWidget`. - -But this inconvenience is fairly minor in the grand scheme of things. And this -requirement might one day disappear. - -So to answer the question you're probably asking yourself: -**Should I use Provider or Riverpod?** - -You probably should be using Riverpod. -Riverpod is overhaul better designed and could lead to drastic simplifications -of your logic. - -## The difference between Provider and Riverpod - -### Defining providers - -The primary difference between both packages is how "providers" are defined. - -With [Provider], providers are widgets and as such placed inside the widget tree, -typically inside a `MultiProvider`: - -```dart -class Counter extends ChangeNotifier { - ... -} - -void main() { - runApp( - MultiProvider( - providers: [ - ChangeNotifierProvider(create: (context) => Counter()), - ], - child: MyApp(), - ) - ); -} -``` - -With Riverpod, providers are **not** widgets. Instead they are plain Dart objects. -Similarly, providers are defined outside of the widget tree, and instead are declared -as global final variables. - -Also, for Riverpod to work, it is necessary to add a `ProviderScope` widget above the -entire application. As such, the equivalent of the Provider example using Riverpod -would be: - -```dart -// Providers are now top-level variables -final counterProvider = ChangeNotifierProvider((ref) => Counter()); - -void main() { - runApp( - // This widget enables Riverpod for the entire project - ProviderScope( - child: MyApp(), - ), - ); -} -``` - -Notice how the provider definition simply moved up a few lines. - -:::info -Since with Riverpod providers are plain Dart objects, it is possible to use -Riverpod without Flutter. -For example, Riverpod can be used to write command line applications. -::: - -### Reading providers: BuildContext - -With Provider, one way of reading providers is to use a Widget's `BuildContext`. - -For example, if a provider was defined as: - -```dart -Provider(...); -``` - -then reading it using [Provider] is done with: - -```dart -class Example extends StatelessWidget { - @override - Widget build(BuildContext context) { - Model model = context.watch(); - - } -} -``` - -The equivalent in Riverpod would be: - -```dart -final modelProvider = Provider(...); - -class Example extends ConsumerWidget { - @override - Widget build(BuildContext context, WidgetRef ref) { - Model model = ref.watch(modelProvider); - - } -} -``` - -Notice how: - -- Riverpod's snippet extends `ConsumerWidget` instead of `StatelessWidget`. - That different widget type adds one extra parameter to our `build` function: - `WidgetRef`. - -- Instead of `BuildContext.watch`, in Riverpod we do `WidgetRef.watch`, using - the `WidgetRef` which we obtained from `ConsumerWidget`. - -- Riverpod does not rely on generic types. Instead it relies on the variable - created using provider definition. - -Notice too how similar the wording is. Both Provider and Riverpod use the keyword -"watch" to describe "this widget should rebuild when the value changes". - -:::info -Riverpod uses the same terminology as Provider for reading providers. - -- `BuildContext.watch` -> `WidgetRef.watch` -- `BuildContext.read` -> `WidgetRef.read` - -The rules for `context.watch` vs `context.read` applies to Riverpod too: -Inside the `build` method, use "watch". Inside click handlers and other events, -use "read". -::: - -### Reading providers: Consumer - -Provider optionally comes with a widget named `Consumer` (and variants such as `Consumer2`) -for reading providers. - -`Consumer` is helpful as a performance optimization, by allowing more granular rebuilds -of the widget tree – updating only the revelant widgets when the state changes: - -As such, if a provider was defined as: - -```dart -Provider(...); -``` - -Provider allows reading that provider using `Consumer` with: - -```dart -Consumer( - builder: (BuildContext context, Model model, Widget? child) { - - } -) -``` - -Riverpod has the same principle. Riverpod, too, has a widget named `Consumer` -for the exact same purpose. - -If we defined a provider as: - -```dart -final modelProvider = Provider(...); -``` - -Then using `Consumer` we could do: - -```dart -Consumer( - builder: (BuildContext context, WidgetRef ref, Widget? child) { - Model model = ref.watch(modelProvider); - - } -) -``` - -Notice how `Consumer` gives us a `WidgetRef` object. This is the same object -as we saw in the previous part related to `ConsumerWidget`. - -### Combining providers: ProxyProvider with stateless objects - -When using Provider, the official way of combining providers is using the -`ProxyProvider` widget (or variants such as `ProxyProvider2`). - -For example we may define: - -```dart -class UserIdNotifier extends ChangeNotifier { - String? userId; -} - -// ... - -ChangeNotifierProvider(create: (context) => UserIdNotifier()), -``` - -From there we have two options. We may combine `UserIdNotifier` to create a new -"stateless" provider (typically an immutable value that possibly override ==). -Such as: - -```dart -ProxyProvider( - update: (context, userIdNotifier, _) { - return 'The user ID of the the user is ${userIdNotifier.userId}'; - } -) -``` - -This provider would automatically return a new `String` whenever -`UserIdNotifier.userId` changes. - -We can do something similar in Riverpod, but the syntax is different. -First, in Riverpod, the definition of our `UserIdNotifier` would be: - -```dart -class UserIdNotifier extends ChangeNotifier { - String? userId; -} - -// ... - -final userIdNotifierProvider = ChangeNotifierProvider( - (ref) => UserIdNotifier(), -), -``` - -From there, to generate our `String` based on the `userId`, we could do: - -```dart -final labelProvider = Provider((ref) { - UserIdNotifier userIdNotifier = ref.watch(userIdNotifierProvider); - return 'The user ID of the the user is ${userIdNotifier.userId}'; -}); -``` - -Notice the line doing `ref.watch(userIdNotifierProvider)`. - -This line of code tells Riverpod to obtain the content of the `userIdNotifierProvider` -and that whenever that value changes, `labelProvider` will be recomputed too. -As such, the `String` emitted by our `labelProvider` will automatically update -whenever the `userId` changes. - -This `ref.watch` line should feel similar. This pattern was covered previously -when explaining [how to read providers inside widgets](#reading-providers-buildcontext). -Indeed, providers are now able to listen to other providers in the same way -that widgets do. - -### Combining providers: ProxyProvider with stateful objects - -When combining providers, another alternative use-case is to expose -stateful objects, such as a `ChangeNotifier` instance. - -For that, we could use `ChangeNotifierProxyProvider` (or variants such as `ChangeNotifierProxyProvider2`). -For example we may define: - -```dart -class UserIdNotifier extends ChangeNotifier { - String? userId; -} - -// ... - -ChangeNotifierProvider(create: (context) => UserIdNotifier()), -``` - -Then, we can define a new `ChangeNotifier` that is based on `UserIdNotifier.userId`. -For example we could do: - -```dart -class UserNotifier extends ChangeNotifier { - String? _userId; - - void setUserId(String? userId) { - if (userId != _userId) { - print('The user ID changed from $_userId to $userId'); - _userId = userId; - } - } -} - -// ... - -ChangeNotifierProxyProvider( - create: (context) => UserNotifier(), - update: (context, userIdNotifier, userNotifier) { - return userNotifier! - ..setUserId(userIdNotifier.userId); - }, -); -``` - -This new provider creates a single instance of `UserNotifier` (which is never re-constructed) -and prints a string whenever the user ID changes. - -Doing the same thing in provider is achieved differently. -First, in Riverpod, the definition of our `UserIdNotifier` would be: - -```dart -class UserIdNotifier extends ChangeNotifier { - String? userId; -} - -// ... - -final userIdNotifierProvider = ChangeNotifierProvider( - (ref) => UserIdNotifier(), -), -``` - -From there, the equivalent to the previous `ChangeNotifierProxyProvider` would be: - -```dart -class UserNotifier extends ChangeNotifier {} - -final userNotfierProvider = ChangeNotifierProvider((ref) { - final userNotifier = UserNotifier(); - ref.listen( - userIdNotifierProvider, - (previous, next) { - if (previous?.userId != next.userId) { - print('The user ID changed from ${previous?.userId} to ${next.userId}'); - } - }, - ); - - return userNotifier; -}); -``` - -The core of this snippet is the `ref.listen` line. -This `ref.listen` function is a utility that allows listening to a provider, -and whenever the provider changes, executes a function. - -The `previous` and `next` parameters of that function correspond to the -last value before the provider changed and the new value after it changed. - -[provider]: https://pub.dev/packages/provider -[ref.watch]: /docs/concepts/reading#using-refwatch-to-observe-a-provider -[ref.listen]: /docs/concepts/reading#using-reflisten-to-react-to-a-provider-change -[autodispose]: /docs/concepts/modifiers/auto_dispose diff --git a/website/i18n/ko/docusaurus-theme-classic/footer.json b/website/i18n/ko/docusaurus-theme-classic/footer.json index 7c75e9ad7..989954b61 100644 --- a/website/i18n/ko/docusaurus-theme-classic/footer.json +++ b/website/i18n/ko/docusaurus-theme-classic/footer.json @@ -7,9 +7,25 @@ "message": "커뮤니티", "description": "The title of the footer links column with title=Community in the footer" }, + "link.title.Sponsors": { + "message": "스폰서", + "description": "The title of the footer links column with title=Sponsors in the footer" + }, + "link.item.label.Why Riverpod?": { + "message": "왜 Riverpod인가?", + "description": "The label of footer link with label=Why Riverpod? linking to docs/introduction/why_riverpod" + }, "link.item.label.Getting started": { "message": "시작하기", - "description": "The label of footer link with label=Getting started linking to docs/getting_started" + "description": "The label of footer link with label=Getting started linking to docs/introduction/getting_started" + }, + "link.item.label.Discord": { + "message": "Discord", + "description": "The label of footer link with label=Discord linking to https://discord.gg/Bbumvej" + }, + "link.item.label.GitHub": { + "message": "GitHub", + "description": "The label of footer link with label=GitHub linking to https://github.com/rrousselgit/riverpod" }, "link.item.label.Stack Overflow": { "message": "Stack Overflow", @@ -19,12 +35,20 @@ "message": "Twitter", "description": "The label of footer link with label=Twitter linking to https://twitter.com/remi_rousselet" }, - "link.item.label.GitHub": { - "message": "GitHub", - "description": "The label of footer link with label=GitHub linking to https://github.com/rrousselgit/riverpod" + "link.item.label.Code of conduct": { + "message": "행동 강령", + "description": "The label of footer link with label=Code of conduct linking to https://github.com/rrousselGit/riverpod/blob/master/CODE_OF_CONDUCT.md" + }, + "link.item.label.Contributing guide": { + "message": "기여 가이드", + "description": "The label of footer link with label=Contributing guide linking to https://github.com/rrousselGit/riverpod/blob/rework-flow/CONTRIBUTING.md" }, "copyright": { - "message": "Copyright © 2022 Remi Rousselet.
Built with Docusaurus.", + "message": "Copyright © 2024 Remi Rousselet.
Built with Docusaurus.", "description": "The footer copyright" + }, + "logo.alt": { + "message": "Riverpod", + "description": "The alt text of footer logo" } } diff --git a/website/i18n/ko/docusaurus-theme-classic/navbar.json b/website/i18n/ko/docusaurus-theme-classic/navbar.json index ec37a15b5..a016241eb 100644 --- a/website/i18n/ko/docusaurus-theme-classic/navbar.json +++ b/website/i18n/ko/docusaurus-theme-classic/navbar.json @@ -3,6 +3,10 @@ "message": "Riverpod", "description": "The title in the navbar" }, + "logo.alt": { + "message": "Riverpod", + "description": "The alt text of navbar logo" + }, "item.label.Docs": { "message": "문서", "description": "Navbar item with label Docs" @@ -11,4 +15,4 @@ "message": "GitHub", "description": "Navbar item with label GitHub" } -} \ No newline at end of file +} diff --git a/website/i18n/zh-Hans/code.json b/website/i18n/zh-Hans/code.json index 15ced48b9..48b627308 100644 --- a/website/i18n/zh-Hans/code.json +++ b/website/i18n/zh-Hans/code.json @@ -1,46 +1,67 @@ { "home.shared_state_title": { - "message": "任意位置声明共享状态" + "message": "随处声明共享状态" }, "home.shared_state_body": { - "message": "不再需要在你的 {main} 和UI文件之间来回跳转。 {br} 将共享状态的代码放在它所属的位置上,无论是放在独立的package中还是放在需要它的Widget旁边,都不会丢失它的可测试性。", + "message": "无需在 {main} 和 UI 文件之间来回切换。 {br}{br} 只要将共享状态的代码放在它所属位置上,无论是放在独立的 package 中,还是需要它的 widget 旁,都不会丢失可测试性。", "description": "The homepage input placeholder" }, "home.recompute_title": { - "message": "最小粒度更新UI和重新计算状态" + "message": "按需更新状态和重绘 UI" }, "home.recompute_body": { - "message": "我们无需在 {build} 中对列表进行排序或过滤,也不必依赖高级的缓存机制。 {br}{br} 使用 {Provider} 和 {families} 修饰符,那么它只会在你真正需要的时候才发起HTTP请求或整理你的列表。" + "message": "不必在 {build} 方法中执行列表排序或过滤,也不必依赖高级的缓存机制。{br} {br} 使用组合提供者 {Provider} 和 {families} 方法,在你真正需要的时候才去发起 HTTP 请求或者排序列表。" + }, + "home.refactors_title": { + "message": "通过重构简化日常工作" + }, + "home.refactors_body": { + "message": "Riverpod 提供了多种重构提示, 例如 \"Wrap widget in a Consumer\" 等等。 请参阅 {warnings}." + }, + "home.refactors_list_link": { + "message": "重构提示列表" + }, + "home.lint_title": { + "message": "使用 lint 规则保持代码可维护" + }, + "home.lint_body": { + "message": "实现了 Riverpod 定制的 lint 新规则,并且还会不断添加更多规则。这可确保您的代码保持最佳状态。请参阅 {warnings}。" + }, + "home.lint_rules_list_link": { + "message": "lint 规则列表" }, "home.safe_read_title": { - "message": "安全使用Provider" + "message": "安全的读取 providers" }, "home.safe_read_body": { - "message": "使用provider不再导致错误的状态。恰当地使用provider,你可以始终获得一个可靠的结果。 {br}{br} 这在异步操作中也是如此,Riverpod可以让你简洁地处理loading/error状态。" + "message": "读取 provider 永远不会导致异常的状态。如果你编写了读取一个 provider 所需的代码,你将得到一个符合预期的值。 {br} {br}这甚至适用于异步加载的值。与 provider 相反,Riverpod 允许简洁地处理 loading/error 状态。" }, "home.devtool_title": { - "message": "在DevTools中检查状态" + "message": "在 DevTools 中检查你的状态" }, "home.devtool_body": { - "message": "你可以在Flutter的开发工具中一目了然地看到Riverpod的各种状态。 {br} 此外,一款功能更全面的状态检查工具正在开发中。" + "message": "使用了 Riverpod,你的状态将可以在 Flutter 自带的 DevTools 中一目了然。 {br} {br} 此外,一款全面的状态检查工具正在开发中……" + }, + "homepage.declarative_title": { + "message": "声明式编程" }, - "homepage.compile_safe_title": { - "message": "编译安全" + "homepage.declarative_body": { + "message": "以类似于无状态小部件的方式编写业务逻辑。{br} 让您的网络请求在必要时自动重新计算,并使您的逻辑易于重用、组合、维护。" }, - "homepage.compile_safe_body": { - "message": "不再需要关心{ProviderNotFound}或忘记处理加载中的状态。使用Riverpod,你的代码如果能够通过编译,那么就可以使用。" + "homepage.common_ui_patterns_title": { + "message": "轻松实现常见的 UI 模式" }, - "homepage.unlimited_provider_title": { - "message": "没有限制的Provider" + "homepage.common_ui_patterns_body": { + "message": "使用 Riverpod,只需几行代码即可实现常见但复杂的 UI 模式,例如“下拉刷新”、“实时搜索”等。" }, - "homepage.unlimited_provider_body": { - "message": "Riverpod的灵感来自于Provider,且解决一些关键的问题:比如支持多个同类型的provider、异步的provider、在任意地方添加各种provider……" + "homepage.tooling_ready_title": { + "message": "工具准备就绪" }, - "homepage.no_flutter_dependency_title": { - "message": "不依赖于Flutter" + "homepage.tooling_ready_body": { + "message": "Riverpod 通过将常见错误作为编译错误来增强编译器。 它还提供自定义 lint 规则和重构选项。 它甚至有一个用于生成文档的命令行。" }, - "homepage.no_flutter_dependency_body": { - "message": "Riverpod可以在不依赖Flutter的情况下创建、共享和测试Provider。你不需要{BuildContext}就能监听Provider。" + "homepage.features_title": { + "message": "特性" }, "home.tagline": { "message": "响应式缓存和数据绑定框架" @@ -91,10 +112,6 @@ "message": "页面已崩溃。", "description": "The title of the fallback page when the page crashed" }, - "theme.ErrorPageContent.tryAgain": { - "message": "重试", - "description": "The label of the button to try again when the page crashed" - }, "theme.NotFound.title": { "message": "找不到页面", "description": "The title of the 404 page" @@ -187,10 +204,6 @@ "message": "浅色模式", "description": "The name for the light color mode" }, - "theme.docs.breadcrumbs.home": { - "message": "主页面", - "description": "The ARIA label for the home page in the breadcrumbs" - }, "theme.docs.breadcrumbs.navAriaLabel": { "message": "页面路径", "description": "The ARIA label for the breadcrumbs" @@ -216,7 +229,7 @@ "description": "Pluralized label for \"{count} docs tagged\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)" }, "theme.docs.tagDocListPageTitle": { - "message": "{nDocsTagged}「{tagName}」", + "message": "{nDocsTagged} 带 “{tagName}”", "description": "The title of the page for a docs tag" }, "theme.docs.versionBadge.label": { @@ -227,19 +240,19 @@ "description": "The link label to edit the current page" }, "theme.common.headingLinkTitle": { - "message": "标题的直接链接", + "message": "跳转到 {heading}", "description": "Title for link to heading" }, "theme.lastUpdated.atDate": { - "message": "于 {date} ", + "message": "于 {date}", "description": "The words used to describe on which date a page has been last updated" }, "theme.lastUpdated.byUser": { - "message": "由 {user} ", + "message": "由 {user}", "description": "The words used to describe by who the page has been last updated" }, "theme.lastUpdated.lastUpdatedAtBy": { - "message": "最后{byUser}{atDate}更新", + "message": "最后由 {byUser} 在 {atDate} 更新", "description": "The sentence used to display when a page has been last updated, and by who" }, "theme.navbar.mobileVersionsDropdown.label": { @@ -255,7 +268,7 @@ "description": "The ARIA label for close button of announcement bar" }, "theme.blog.sidebar.navAriaLabel": { - "message": "最近博文导航", + "message": "博客最近帖子的导航", "description": "The ARIA label for recent posts in the blog sidebar" }, "theme.CodeBlock.copied": { @@ -275,21 +288,21 @@ "description": "The title attribute for toggle word wrapping button of code block lines" }, "theme.DocSidebarItem.toggleCollapsedCategoryAriaLabel": { - "message": "打开/收起侧边栏菜单「{label}」", + "message": "打开/关闭侧边栏菜单“{label}”", "description": "The ARIA label to toggle the collapsible sidebar category" }, + "theme.NavBar.navAriaLabel": { + "message": "主导航", + "description": "The ARIA label for the main navigation" + }, "theme.navbar.mobileLanguageDropdown.label": { "message": "选择语言", "description": "The label for the mobile language switcher dropdown" }, "theme.TOCCollapsible.toggleButtonLabel": { - "message": "本页总览", + "message": "目录", "description": "The label used by the button on the collapsible TOC component" }, - "theme.blog.post.readingTime.plurals": { - "message": "阅读需 {readingTime} 分钟", - "description": "Pluralized label for \"{readingTime} min read\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)" - }, "theme.blog.post.readMore": { "message": "阅读更多", "description": "The label used in blog post item excerpts to link to full blog posts" @@ -298,6 +311,14 @@ "message": "阅读 {title} 的全文", "description": "The ARIA label for the link to full blog posts from excerpts" }, + "theme.blog.post.readingTime.plurals": { + "message": "阅读至少需要 {readingTime} 分钟", + "description": "Pluralized label for \"{readingTime} min read\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)" + }, + "theme.docs.breadcrumbs.home": { + "message": "主页", + "description": "The ARIA label for the home page in the breadcrumbs" + }, "theme.docs.sidebar.collapseButtonTitle": { "message": "收起侧边栏", "description": "The title attribute for collapse button of doc sidebar" @@ -306,6 +327,10 @@ "message": "收起侧边栏", "description": "The title attribute for collapse button of doc sidebar" }, + "theme.docs.sidebar.navAriaLabel": { + "message": "文档侧边栏", + "description": "The ARIA label for the sidebar navigation" + }, "theme.docs.sidebar.closeSidebarButtonAriaLabel": { "message": "关闭导航栏", "description": "The ARIA label for close button of mobile sidebar" @@ -326,6 +351,9 @@ "message": "展开侧边栏", "description": "The ARIA label and title attribute for expand button of doc sidebar" }, + "theme.SearchBar.seeAll": { + "message": "查看所有 {count} 条结果" + }, "theme.SearchBar.label": { "message": "搜索", "description": "The ARIA label and placeholder for search button" @@ -379,7 +407,7 @@ "description": "The ARIA label for the Enter key button that makes the selection" }, "theme.SearchModal.footer.navigateText": { - "message": "导航", + "message": "选择", "description": "The explanatory text of the action for the Arrow up and Arrow down key" }, "theme.SearchModal.footer.navigateUpKeyAriaLabel": { @@ -427,7 +455,7 @@ "description": "Pluralized label for \"{count} documents found\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)" }, "theme.SearchPage.existingResultsTitle": { - "message": "「{query}」的搜索结果", + "message": "“{query}”的搜索结果", "description": "The search page title for non-empty query" }, "theme.SearchPage.emptyResultsTitle": { @@ -454,8 +482,9 @@ "message": "正在获取新的搜索结果...", "description": "The paragraph for fetching new search results" }, - "theme.SearchBar.seeAll": { - "message": "查看全部 {count} 个结果" + "theme.ErrorPageContent.tryAgain": { + "message": "再试一次", + "description": "The label of the button to try again rendering when the React error boundary captures an error" }, "theme.common.skipToMainContent": { "message": "跳到主要内容", @@ -465,4 +494,4 @@ "message": "标签", "description": "The title of the tag list page" } -} \ No newline at end of file +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current.json b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current.json index 80cbbdfe0..5943108db 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current.json +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current.json @@ -3,25 +3,33 @@ "message": "Next", "description": "The label for version current" }, - "sidebar.Sidebar.category.All Providers": { - "message": "Provider类型", - "description": "The label for category All Providers in sidebar Sidebar" + "sidebar.Sidebar.category.Introduction": { + "message": "介绍", + "description": "The label for category Introduction in sidebar Sidebar" + }, + "sidebar.Sidebar.category.Riverpod for Provider Users": { + "message": "给 Provider 开发者的 Riverpod 指南", + "description": "The label for category Riverpod for Provider Users in sidebar Sidebar" + }, + "sidebar.Sidebar.category.Essentials": { + "message": "要点", + "description": "The label for category Essentials in sidebar Sidebar" + }, + "sidebar.Sidebar.category.Case studies": { + "message": "应用案例", + "description": "The label for category Case studies in sidebar Sidebar" + }, + "sidebar.Sidebar.category.Advanced topics": { + "message": "进阶内容", + "description": "The label for category Advanced topics in sidebar Sidebar" }, "sidebar.Sidebar.category.Concepts": { "message": "概念", "description": "The label for category Concepts in sidebar Sidebar" }, - "sidebar.Sidebar.category.Modifiers": { - "message": "修饰符", - "description": "The label for category Modifiers in sidebar Sidebar" - }, - "sidebar.Sidebar.category.Guides": { - "message": "指南", - "description": "The label for category Guides in sidebar Sidebar" - }, - "sidebar.Sidebar.category.Migration": { + "sidebar.Sidebar.category.Migration guides": { "message": "迁移指南", - "description": "The label for category Migration in sidebar Sidebar" + "description": "The label for category Migration guides in sidebar Sidebar" }, "sidebar.Sidebar.category.Official examples": { "message": "官方示例", @@ -31,6 +39,22 @@ "message": "第三方示例", "description": "The label for category Third party examples in sidebar Sidebar" }, + "sidebar.Sidebar.category.Concepts (old)": { + "message": "概念(旧)", + "description": "The label for category Concepts (old) in sidebar Sidebar" + }, + "sidebar.Sidebar.category.Modifiers": { + "message": "修饰符", + "description": "The label for category Modifiers in sidebar Sidebar" + }, + "sidebar.Sidebar.category.All Providers (old)": { + "message": "提供者程序类型(旧)", + "description": "The label for category All Providers (old) in sidebar Sidebar" + }, + "sidebar.Sidebar.category.Guides (old)": { + "message": "指南(旧)", + "description": "The label for category Guides (old) in sidebar Sidebar" + }, "sidebar.Sidebar.link.Counter": { "message": "计数器", "description": "The label for link Counter in sidebar Sidebar, linking to https://github.com/rrousselGit/riverpod/tree/master/examples/counter" @@ -60,27 +84,27 @@ "description": "The label for link Dictionary App in sidebar Sidebar, linking to https://github.com/lohanidamodar/fl_dictio" }, "sidebar.Sidebar.link.Time Tracking App (with Firebase)": { - "message": "时间追踪应用 (使用Firebase)", + "message": "时间追踪应用(使用 Firebase)", "description": "The label for link Time Tracking App (with Firebase) in sidebar Sidebar, linking to https://github.com/bizz84/starter_architecture_flutter_firebase" }, "sidebar.Sidebar.link.Firebase Phone Authentication with Riverpod": { - "message": "Firebase Phone Authentication with Riverpod", + "message": "使用 Riverpod 的 Firebase 手机号验证", "description": "The label for link Firebase Phone Authentication with Riverpod in sidebar Sidebar, linking to https://github.com/julienlebren/flutter_firebase_phone_auth_riverpod" }, "sidebar.Sidebar.link.ListView paging with search": { "message": "带搜索栏的列表分页", "description": "The label for link ListView paging with search in sidebar Sidebar, linking to https://github.com/tbm98/flutter_loadmore_search" }, - "sidebar.Sidebar.link.Resocoder's Weather Bloc to Weather Riverpod": { - "message": "Resocoder's Weather Bloc to Weather Riverpod", - "description": "The label for link Resocoder's Weather Bloc to Weather Riverpod in sidebar Sidebar, linking to https://github.com/campanagerald/flutter-bloc-library-v1-tutorial" + "sidebar.Sidebar.link.Resocoder's Weather Bloc to Weather Riverpod V2": { + "message": "Resocoder 的天气应用迁移从 Bloc 到 Riverpod V2", + "description": "The label for link Resocoder's Weather Bloc to Weather Riverpod V2 in sidebar Sidebar, linking to https://github.com/coyksdev/flutter-bloc-library-v1-tutorial" }, "sidebar.Sidebar.link.Blood Pressure Tracker App": { "message": "血压记录应用", "description": "The label for link Blood Pressure Tracker App in sidebar Sidebar, linking to https://github.com/UrosTodosijevic/blood_pressure_tracker" }, "sidebar.Sidebar.link.Firebase Authentication with Riverpod Following Flutter DDD Architecture Pattern": { - "message": "Firebase Authentication with Riverpod Following Flutter DDD Architecture Pattern", + "message": "使用 Riverpod 并遵循领域驱动开发(DDD)架构模式的 Firebase 验证", "description": "The label for link Firebase Authentication with Riverpod Following Flutter DDD Architecture Pattern in sidebar Sidebar, linking to https://github.com/pythonhubpy/firebase_authentication_flutter_DDD" }, "sidebar.Sidebar.link.Todo App with Backup and Restore feature": { @@ -88,15 +112,15 @@ "description": "The label for link Todo App with Backup and Restore feature in sidebar Sidebar, linking to https://github.com/TheAlphaApp/flutter_riverpod_todo_app" }, "sidebar.Sidebar.link.Integrating Hive database with Riverpod (simple example)": { - "message": "将Hive数据库与Riverpod集成 (简单示例)", + "message": "将 Hive 数据库与 Riverpod 集成(简单示例)", "description": "The label for link Integrating Hive database with Riverpod (simple example) in sidebar Sidebar, linking to https://github.com/GitGud31/theme_riverpod_hive" }, "sidebar.Sidebar.link.Browser App with Riverpod": { - "message": "使用Riverpod的浏览器应用", + "message": "使用 Riverpod 的浏览器应用", "description": "The label for link Browser App with Riverpod in sidebar Sidebar, linking to https://github.com/MarioCroSite/simple_browser_app" }, "sidebar.Sidebar.link.GoRouter with Riverpod": { - "message": "将GoRouter与Riverpod集成", + "message": "将 GoRouter 与 Riverpod 集成", "description": "The label for link GoRouter with Riverpod in sidebar Sidebar, linking to https://github.com/lucavenir/go_router_riverpod" }, "sidebar.Sidebar.link.Piano Chords Test": { @@ -104,13 +128,21 @@ "description": "The label for link Piano Chords Test in sidebar Sidebar, linking to https://github.com/akvus/piano_fun" }, "sidebar.Sidebar.link.Movies API App with Caching & Pagination": { - "message": "带缓存和分页功能的电影应用", + "message": "带缓存和分页功能的电影 API 应用", "description": "The label for link Movies API App with Caching & Pagination in sidebar Sidebar, linking to https://github.com/Roaa94/movies_app" }, "sidebar.Sidebar.link.AWS Amplify Storage Gallery App with Riverpod & Freezed": { - "message": "AWS Amplify Storage Gallery App with Riverpod & Freezed", + "message": "使用 Riverpod 和 Freezed 的 AWS Amplify 存储图片应用", "description": "The label for link AWS Amplify Storage Gallery App with Riverpod & Freezed in sidebar Sidebar, linking to https://github.com/offlineprogrammer/amplify_storage_app" }, + "sidebar.Sidebar.link.Clean Architecture demonstration with Riverpod": { + "message": "Riverpod 实现 Clean 架构的示范", + "description": "The label for link Clean Architecture demonstration with Riverpod in sidebar Sidebar, linking to https://github.com/Uuttssaavv/flutter-clean-architecture-riverpod" + }, + "sidebar.Sidebar.link.Delivery App with Google Maps and Live Tracking": { + "message": "具备谷歌地图和在线追踪功能的送货应用", + "description": "The label for link Delivery App with Google Maps and Live Tracking in sidebar Sidebar, linking to https://github.com/AhmedLSayed9/deliverzler" + }, "sidebar.Sidebar.link.API reference": { "message": "API 参考", "description": "The label for link API reference in sidebar Sidebar, linking to https://pub.dev/documentation/hooks_riverpod/latest/hooks_riverpod/hooks_riverpod-library.html" diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/about_code_generation.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/about_code_generation.mdx deleted file mode 100644 index 737a92e49..000000000 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/about_code_generation.mdx +++ /dev/null @@ -1,58 +0,0 @@ ---- -title: 关于代码生成 ---- - -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; -import CodeBlock from "@theme/CodeBlock"; -import fetchUser from "!!raw-loader!./about_codegen/main.dart"; -import rawFetchUser from "!!raw-loader!./about_codegen/raw.dart"; -import { - trimSnippet, - CodeSnippet, -} from "../../../../src/components/CodeSnippet"; - -代码生成是指使用工具为我们生成代码。 -在Dart中,它的缺点是需要额外的步骤来“编译”应用。 -尽管这个问题可能会在不久的将来得到解决, -但Dart团队正在研究并解决这个问题的潜在方案。 - -在Riverpod的上下文中,代码生成就是稍微改变定义“provider”的语法。打个比方,原本我们这样写: - -{trimSnippet(rawFetchUser)} - -使用代码生成,我们会写: - -{trimSnippet(fetchUser)} - -使用Riverpod时,代码生成是完全可选的。 当然你也完全可以不使用。 -与此同时,Riverpod支持代码生成,且推荐你使用它。 - -有关如何安装和使用Riverpod的代码生成器的信息, -请参阅[开始上手](./getting_started)页面。确保在文档的侧边栏中启用代码生成。 - -## 为什么在Riverpod中使用代码生成? - -你可能在想:“如果在Riverpod中代码生成是可选的,为什么要使用?” - -让你的代码生活更简单。 -这包括但不限于: - -- 更好的语法, 更可读且更灵活,而且还能减少学习曲线。 - - 不需要担心`FutureProvider`、`Provider` 还是其他 provider。仅需写下你的逻辑, - Riverpod将为你选择最合适的provider。 - - 向provider传递参数现在不受限制。不再局限于使用 [family](./concepts/modifiers/family) 和传递单个参数, - 现在可以传递任何形式的参数。这包括命名参数、可选参数甚至默认值。 -- 在Riverpod中编写的代码支持 **有状态热重载**。 -- 更好地调试,通过生成额外的元数据然后用调试器调试。 -- Riverpod的一些功能将只支持代码生成。 - -与此同时,许多应用程序中已经使用了代码生成比如 [Freezed](https://pub.dev/packages/freezed) 或 [json_serializable](https://pub.dev/packages/json_serializable)。 -在这种情况下,你的项目可能已经为代码生成配置好了,使用Riverpod应该很简单。 - -[hookwidget]: https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/HookWidget-class.html -[statefulwidget]: https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html -[riverpod]: https://github.com/rrousselgit/riverpod -[hooks_riverpod]: https://pub.dev/packages/hooks_riverpod -[flutter_riverpod]: https://pub.dev/packages/flutter_riverpod -[flutter_hooks]: https://github.com/rrousselGit/flutter_hooks diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/about_hooks.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/about_hooks.mdx deleted file mode 100644 index 718985c60..000000000 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/about_hooks.mdx +++ /dev/null @@ -1,272 +0,0 @@ ---- -title: 关于钩子 ---- - -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; -import CodeBlock from "@theme/CodeBlock"; -import pubspec from "./getting_started/pubspec"; -import dartHelloWorld from "./getting_started/dart_hello_world"; -import helloWorld from "./getting_started/hello_world"; -import dartPubspec from "./getting_started/dart_pubspec"; -import { - trimSnippet, - AutoSnippet, - When, -} from "../../../../src/components/CodeSnippet"; - -本页解释了什么是钩子,以及它们如何与Riverpod相关。 - -“Hooks”是独立于Riverpod: [flutter_hooks] 的package中的常用工具。 -尽管 [flutter_hooks] 是一个完全独立的package,与Riverpod没有任何关系(至少没有直接关系), -但将Riverpod和 [flutter_hooks] 放在一起很常见。 -毕竟,Riverpod和 [flutter_hooks] 是由同一个团队维护的。 - -钩子是完全可选的。特别是如果你刚开始使用Flutter,那么你不必使用钩子。 -虽然它们是很强大的工具,但没有Flutter的那个“味”。 -所以,入门选择使用Flutter和Riverpod这样的搭配。当你有更多相关的经验时,那么你可以回来尝试一下钩子。 - -## 什么是钩子? - -钩子是在widget中使用的函数。它们被设计为 [StatefulWidget] 的替代品,以使逻辑更具可重用性和可组合性。 - -Hooks是一个来自 [React](https://reactjs.org/) 的概念, -而 [flutter_hooks] 仅仅是Flutter的一个React实现。 -所以是的,在Flutter中钩子可能感觉有点不合适。 -理想情况下,将来我们会有专门为Flutter设计的用钩子解决问题的解决方案。 - -如果说Riverpod的provider是针对“全局”应用状态的,钩子则是针对本地widget的状态。 -钩子通常用于处理有状态的UI对象,如 [TextEditingController](https://api.flutter.dev/flutter/widgets/TextEditingController-class.html), -[AnimationController](https://api.flutter.dev/flutter/animation/AnimationController-class.html)。 -它们也可以作为“构建器(builder)”模式的替代品, -用不涉及“嵌套”的替代品来替换 [FutureBuilder](https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html)/[TweenAnimatedBuilder](https://api.flutter.dev/flutter/widgets/TweenAnimationBuilder-class.html) 之类的widget——极大地提高了可读性。 - -一般来说,钩子有助于: - -- 表单 -- 动画 -- 响应用户事件 -- …… - -例如,我们可以使用钩子手动实现淡入动画,其中widget开始时不可见,然后慢慢出现。 - -如果我们使用 [StatefulWidget],,代码看起来会像这样: - -```dart -class FadeIn extends StatefulWidget { - const FadeIn({Key? key, required this.child}) : super(key: key); - - final Widget child; - - @override - State createState() => _FadeInState(); -} - -class _FadeInState extends State with SingleTickerProviderStateMixin { - late final AnimationController animationController = AnimationController( - vsync: this, - duration: const Duration(seconds: 2), - ); - - @override - void initState() { - super.initState(); - animationController.forward(); - } - - @override - void dispose() { - animationController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return AnimatedBuilder( - animation: animationController, - builder: (context, child) { - return Opacity( - opacity: animationController.value, - child: widget.child, - ); - }, - ); - } -} -``` - -使用钩子会是: - -```dart -class FadeIn extends HookWidget { - const FadeIn({Key? key, required this.child}) : super(key: key); - - final Widget child; - - @override - Widget build(BuildContext context) { - // Create an AnimationController. The controller will automatically be - // disposed when the widget is unmounted. - final animationController = useAnimationController( - duration: const Duration(seconds: 2), - ); - - // useEffect is the equivalent of initState + didUpdateWidget + dispose. - // The callback passed to useEffect is executed the first time the hook is - // invoked, and then whenever the list passed as second parameter changes. - // Since we pass an empty const list here, that's strictly equivalent to `initState`. - useEffect(() { - // start the animation when the widget is first rendered. - animationController.forward(); - // We could optionally return some "dispose" logic here - return null; - }, const []); - - // Tell Flutter to rebuild this widget when the animation updates. - // This is equivalent to AnimatedBuilder - useAnimation(animationController); - - return Opacity( - opacity: animationController.value, - child: child, - ); - } -} -``` - -在这段代码中有一些有趣的事情需要注意: - -- 不存在内存泄漏。这段代码不会在widget重新构建时重新创建一个新的 `AnimationController`, - 并且它在widget销毁时正确地释放控制器。 - -- 在同一个小部件中,可以任意多次地使用钩子。 - 因此,如果我们想要我们可以创建多个 `AnimationController`: - - ```dart - @override - Widget build(BuildContext context) { - final animationController = useAnimationController( - duration: const Duration(seconds: 2), - ); - final anotherController = useAnimationController( - duration: const Duration(seconds: 2), - ); - - ... - } - ``` - - 这创建了两个控制器,但没有任何负面效果。 - -- 如果我们愿意,我们可以将这个逻辑重构为一个单独的可重用函数: - - ```dart - double useFadeIn() { - final animationController = useAnimationController( - duration: const Duration(seconds: 2), - ); - useEffect(() { - animationController.forward(); - return null; - }, const []); - useAnimation(animationController); - return animationController.value; - } - ``` - - 然后我们可以在widget中使用这个函数,只要那个widget是[HookWidget]: - - ```dart - class FadeIn extends HookWidget { - const FadeIn({Key? key, required this.child}) : super(key: key); - - final Widget child; - - @override - Widget build(BuildContext context) { - final fade = useFadeIn(); - - return Opacity(opacity: fade, child: child); - } - } - ``` - - 注意我们的 `useFadeIn` 函数是如何完全独立于我们的 `FadeIn` widget的。 - 如果我们愿意,我们可以在一个完全不同的widget中使用 `useFadeIn` 函数,它仍然也可以工作! - -## 如何使用钩子 - -钩子有独特的约束条件: - -- 它们只能在扩展了 [HookWidget] 的widget的 `build` 方法中使用: - - **正确**: - - ```dart - class Example extends HookWidget { - @override - Widget build(BuildContext context) { - final controller = useAnimationController(); - ... - } - } - ``` - - **错误**: - - ```dart - // 不是一个HookWidget - class Example extends StatelessWidget { - @override - Widget build(BuildContext context) { - final controller = useAnimationController(); - ... - } - } - ``` - - **错误**: - - ```dart - class Example extends HookWidget { - @override - Widget build(BuildContext context) { - return ElevatedButton( - onPressed: () { - // Not _actually_ inside the "build" method, but instead inside - // a user interaction lifecycle (here "on pressed"). - final controller = useAnimationController(); - }, - child: Text('click me'), - ); - } - } - ``` - -- 它们不能在条件语句或循环语句内使用。 - - **错误**: - - ```dart - class Example extends HookWidget { - const Example({required this.condition, super.key}); - final bool condition; - @override - Widget build(BuildContext context) { - if (condition) { - // Hooks should not be used inside "if"s/"for"s, ... - final controller = useAnimationController(); - } - ... - } - } - ``` - -有关钩子的更多信息,请参见 [flutter_hooks]。 - -[hookwidget]: https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/HookWidget-class.html -[statefulwidget]: https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html -[riverpod]: https://github.com/rrousselgit/riverpod -[hooks_riverpod]: https://pub.dev/packages/hooks_riverpod -[flutter_riverpod]: https://pub.dev/packages/flutter_riverpod -[flutter_hooks]: https://github.com/rrousselGit/flutter_hooks diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/advanced/select.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/advanced/select.mdx new file mode 100644 index 000000000..d020b217b --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/advanced/select.mdx @@ -0,0 +1,129 @@ +--- +title: 性能优化 +--- + +import { AutoSnippet } from "@site/src/components/CodeSnippet"; +import select from "./select/select"; + +import selectAsync from "./select/select_async"; + + +通过到目前为止我们所看到的一切,我们已经可以构建一个功能齐全的应用程序。 +但是,您可能对性能有疑问。 + + +在本页中,我们将介绍一些可能优化代码的提示和技巧。 + +:::caution + +在进行任何优化之前,请确保对您的应用程序进行基准测试。 +优化所增加的复杂性可能不值得微小的收益。 +::: + + +## 使用 "select" 过滤小部件或提供者程序的重建。 + + +您已经注意到,默认情况下,只要对象的任何属性发生更改, +使用 `ref.watch` 都会导致消费者程序或提供者程序进行重建。 +例如,观察 `User` 并仅使用其 "name", +如果 "age" 发生变化,仍然会导致消费者程序重建。 + + +但如果您的使用者仅使用属性的子集, +您希望避免在其他属性更改时重建小部件。 + + +这可以通过使用提供者程序的 `select` 功能来实现。 +这样做时,`ref.watch` 将不再返回完整对象, +而是返回选定的属性。 +现在,仅当这些选定的属性发生变化时, +您的消费者程序或提供者程序才会重建。 + + + +:::info + +您可以根据需要多次调用 `select`。 +您可以为您想要的每个属性自由调用一次。 +::: + +:::caution + +所选属性希望是不可变的对象。 +返回 `List` 然后改变该列表不会触发重建。 +::: + +:::caution + +使用 `select` 会稍微减慢单个读取操作的速度, +并稍微增加代码的复杂性。 +如果那些“其他属性”很少改变,那么可能不值得使用它。 +::: + + +### 选择异步属性​ + + +考虑一种情况,如果您尝试优化一个监听其他提供者程序的提供者程序, +其他提供者程序很可能是异步的。 + + +通常,您可以使用 `ref.watch(anotherProvider.future)` 来获取该值。 +问题是,`select` 将应用于 `AsyncValue` - 这不是您可以等待的事情。 + + +为此,您可以使用 `selectAsync`。它是异步代码所独有的, +并且允许对提供者程序发出的数据执行 `select` 操作。 +它的用法与 `select` 类似,但返回一个 `Future` 类型: + + diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/advanced/select/select/codegen.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/advanced/select/select/codegen.dart new file mode 100644 index 000000000..f0d79bff2 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/advanced/select/select/codegen.dart @@ -0,0 +1,30 @@ +// ignore_for_file: unused_local_variable, avoid_multiple_declarations_per_line, omit_local_variable_types, prefer_final_locals, use_key_in_widget_constructors + +import 'package:flutter/widgets.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +/* SNIPPET START */ +class User { + late String firstName, lastName; +} + +@riverpod +User example(ExampleRef ref) => User() + ..firstName = 'John' + ..lastName = 'Doe'; + +class ConsumerExample extends ConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + // 替代这种写法: + // String name = ref.watch(provider).firstName!; + // 我们可以写: + String name = ref.watch(exampleProvider.select((it) => it.firstName)); + // 这将导致 widget 只监听 "firstName" 上的更改。 + + return Text('Hello $name'); + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/advanced/select/select/codegen.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/advanced/select/select/codegen.g.dart new file mode 100644 index 000000000..018542dba --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/advanced/select/select/codegen.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$exampleHash() => r'72881c6147d44adb957180debefe7696d93107f0'; + +/// See also [example]. +@ProviderFor(example) +final exampleProvider = AutoDisposeProvider.internal( + example, + name: r'exampleProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$exampleHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ExampleRef = AutoDisposeProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/advanced/select/select/index.ts b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/advanced/select/select/index.ts new file mode 100644 index 000000000..9d50c6614 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/advanced/select/select/index.ts @@ -0,0 +1,4 @@ +import raw from '!!raw-loader!./raw.dart'; +import codegen from '!!raw-loader!./codegen.dart'; + +export default { raw, codegen }; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/advanced/select/select/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/advanced/select/select/raw.dart new file mode 100644 index 000000000..c39bc426d --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/advanced/select/select/raw.dart @@ -0,0 +1,29 @@ +// ignore_for_file: unused_local_variable, avoid_multiple_declarations_per_line, omit_local_variable_types, prefer_final_locals, use_key_in_widget_constructors + +import 'package:flutter/widgets.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +/* SNIPPET START */ +class User { + late String firstName, lastName; +} + +final provider = Provider( + (ref) => User() + ..firstName = 'John' + ..lastName = 'Doe', +); + +class ConsumerExample extends ConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + // 替代这种写法: + // String name = ref.watch(provider).firstName!; + // 我们可以写: + String name = ref.watch(provider.select((it) => it.firstName)); + // 这将导致 widget 只监听 "firstName" 上的更改。 + + return Text('Hello $name'); + } +} +/* SNIPPET END */ diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/advanced/select/select_async/codegen.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/advanced/select/select_async/codegen.dart new file mode 100644 index 000000000..fe57e77d3 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/advanced/select/select_async/codegen.dart @@ -0,0 +1,26 @@ +// ignore_for_file: unused_local_variable, avoid_multiple_declarations_per_line, omit_local_variable_types, prefer_final_locals, use_key_in_widget_constructors, body_might_complete_normally_nullable + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +class User { + late String firstName, lastName; +} + +final userProvider = FutureProvider( + (ref) => User() + ..firstName = 'John' + ..lastName = 'Doe', +); +/* SNIPPET START */ +@riverpod +Object? example(ExampleRef ref) async { + // 等待 user 可用,并只监听 "firstName" 属性 + final firstName = await ref.watch( + userProvider.selectAsync((it) => it.firstName), + ); + + // TODO 使用 "firstName" 获取其他信息 +} +/* SNIPPET END */ diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/advanced/select/select_async/codegen.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/advanced/select/select_async/codegen.g.dart new file mode 100644 index 000000000..ab6da16f7 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/advanced/select/select_async/codegen.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$exampleHash() => r'1fccbdbec0e3585bc9d3a5709ac88a8919dd78fa'; + +/// See also [example]. +@ProviderFor(example) +final exampleProvider = AutoDisposeProvider.internal( + example, + name: r'exampleProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$exampleHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ExampleRef = AutoDisposeProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/advanced/select/select_async/index.ts b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/advanced/select/select_async/index.ts new file mode 100644 index 000000000..9d50c6614 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/advanced/select/select_async/index.ts @@ -0,0 +1,4 @@ +import raw from '!!raw-loader!./raw.dart'; +import codegen from '!!raw-loader!./codegen.dart'; + +export default { raw, codegen }; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/advanced/select/select_async/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/advanced/select/select_async/raw.dart new file mode 100644 index 000000000..719f2cf2d --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/advanced/select/select_async/raw.dart @@ -0,0 +1,23 @@ +// ignore_for_file: unused_local_variable, avoid_multiple_declarations_per_line, omit_local_variable_types, prefer_final_locals, use_key_in_widget_constructors + +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +class User { + late String firstName, lastName; +} + +final userProvider = FutureProvider( + (ref) => User() + ..firstName = 'John' + ..lastName = 'Doe', +); +/* SNIPPET START */ +final provider = FutureProvider((ref) async { + // 等待 user 可用,并只监听 "firstName" 属性 + final firstName = await ref.watch( + userProvider.selectAsync((it) => it.firstName), + ); + + // TODO 使用 "firstName" 获取其他信息 +}); +/* SNIPPET END */ diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel.mdx new file mode 100644 index 000000000..b5a31fb6c --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel.mdx @@ -0,0 +1,278 @@ +--- +title: 网络请求的去抖动或取消 +--- + +import { Link } from "@site/src/components/Link"; +import { AutoSnippet, When } from "@site/src/components/CodeSnippet"; +import homeScreen from "!raw-loader!./cancel/home_screen.dart"; +import extension from "!raw-loader!./cancel/extension.dart"; +import detailScreen from "./cancel/detail_screen"; +import detailScreenCancel from "./cancel/detail_screen_cancel"; +import detailScreenDebounce from "./cancel/detail_screen_debounce"; +import providerWithExtension from "./cancel/provider_with_extension"; + + +随着应用程序变得越来越复杂,同时处理多个网络请求是很常见的。 +例如,用户可能在搜索框中键入内容并为每次击键触发新的请求。 +如果用户打字速度很快,应用程序可能会同时处理许多请求。 + + +或者,用户可能会触发请求,然后在请求完成之前导航到不同的页面。 +在这种情况下,应用程序可能有一个不再需要的正在运行的请求。 + + +要在这些情况下优化性能,您可以使用以下几种技术: + + +- “去抖动”请求。这意味着您要等到用户停止输入一段时间后再发送请求。 + 这可确保即使用户键入速度很快,您也只会针对给定输入发送一个请求。 +- “取消”请求。这意味着如果用户在请求完成之前离开页面,您将取消请求。 + 这可确保您不会浪费时间处理用户永远不会看到的响应。 + + +在 Riverpod 中,这两种技术都可以以类似的方式实现。 +关键是使用 `ref.onDispose` 方法与“自动处置”或 `ref.watch` +结合来实现所需的行为。 + + +为了展示这一点,我们将制作一个包含两个页面的简单应用程序: + + +- 主屏幕,带有打开新页面的按钮 +- 详细信息页面,显示来自 [Bored API](https://www.boredapi.com/) + 的随机活动,并且能够刷新活动。 + 有关如何实现下拉刷新的信息, + 请参阅。 + + +然后我们将实现以下行为: + + +- 如果用户打开详细信息页面然后立即导航回来, + 我们将取消该活动的请求。 +- 如果用户连续多次刷新活动,我们将对请求进行去抖动, + 以便在用户停止刷新后仅发送一个请求。 + + +## 应用​ + + +展示应用程序、打开详细页面和刷新活动的 Gif。 + + +首先,让我们创建应用程序,不进行任何去抖动或取消操作。 +我们不会在这里使用任何花哨的东西,而是坚持使用普通的 `FloatingActionButton` +和 `Navigator.push` 来打开详细信息页面。 + + +首先,让我们从定义主屏幕开始。像往常一样, +我们不要忘记在应用程序的根组件上指定 `ProviderScope` 。 + + + + +然后,让我们定义我们的详细信息页面。 +要获取活动并实施下拉刷新, +请参阅应用案例。 + + + + +## 取消请求 + + +现在我们有了一个可以运行的应用程序,让我们实现取消逻辑。 + + +为此,我们将在用户离开页面时使用 `ref.onDispose` 取消请求。 +为了使其运作,启用提供者程序的自动处置非常重要。 + + +取消请求所需的确切代码取决于 HTTP 客户端。 +在此示例中,我们将使用 `package:http` , +但相同的原则也适用于其他客户端。 + + +这里的关键点是当用户离开时将调用 `ref.onDispose`。 +这是因为我们的提供者程序不再使用,因此通过自动处置进行了处置。 +因此,我们可以使用此回调来取消请求。 +当使用 `package:http` 时,可以通过关闭 HTTP 客户端来完成。 + + + + +## 请求​去抖 + + +现在我们已经实现了取消,让我们实现去抖动。 +目前,如果用户连续多次刷新活动, +我们将为每次刷新发送一个请求。 + + +从技术上来说,既然我们已经实行了取消,这就不成问题了。 +如果用户连续多次刷新活动, +则当发出新请求时,先前的请求将被取消。 + + +然而,这并不理想。我们仍然发送多个请求, +浪费带宽和服务器资源。 +相反,我们可以做的是延迟我们的请求, +直到用户在固定的时间内停止刷新活动。 + + +这里的逻辑和取消逻辑非常相似。 +我们将再次使用 `ref.onDispose`。然而,这里的想法是, +我们将依靠 `onDispose` 在请求开始之前中止请求, +而不是关闭 HTTP 客户端。 +然后我们会任意等待 500ms,然后再发送请求。 +然后,如果用户在 500 毫秒过去之前再次刷新活动, +将调用 `onDispose` 并中止请求。 + +:::info + +要中止请求,常见的做法是主动抛出。 +在提供者程序被处置后,将提供者程序内部抛出异常是安全的。 +该异常自然会被 Riverpod 捕获并被忽略。 +::: + + + + +## 更进一步:同时做这两件事​ + + +我们现在知道如何取消和取消请求。 +但目前,如果我们想做另一个请求, +我们需要将相同的逻辑复制粘贴到多个位置。这并不理想。 + + +然而,我们可以更进一步,实现一个可重用的实用程序来同时完成这两个任务。 + + +这里的想法是在 `Ref` 上实现一个扩展方法, +该方法将在单个方法中处理取消和去抖。 + + + + +然后我们可以在我们的提供者程序中使用此扩展方法,如下所示: + + diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen/codegen.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen/codegen.dart new file mode 100644 index 000000000..e3bf193a2 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen/codegen.dart @@ -0,0 +1,61 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:http/http.dart' as http; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.freezed.dart'; +part 'codegen.g.dart'; + +/* SNIPPET START */ +@freezed +class Activity with _$Activity { + factory Activity({ + required String activity, + required String type, + required int participants, + required double price, + }) = _Activity; + + factory Activity.fromJson(Map json) => + _$ActivityFromJson(json); +} + +@riverpod +Future activity(ActivityRef ref) async { + final response = await http.get( + Uri.https('www.boredapi.com', '/api/activity'), + ); + + final json = jsonDecode(response.body) as Map; + return Activity.fromJson(Map.from(json)); +} + +class DetailPageView extends ConsumerWidget { + const DetailPageView({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final activity = ref.watch(activityProvider); + + return Scaffold( + appBar: AppBar( + title: const Text('Detail page'), + ), + body: RefreshIndicator( + onRefresh: () => ref.refresh(activityProvider.future), + child: ListView( + children: [ + switch (activity) { + AsyncValue(:final valueOrNull?) => Text(valueOrNull.activity), + AsyncValue(:final error?) => Text('Error: $error'), + _ => const Center(child: CircularProgressIndicator()), + }, + ], + ), + ), + ); + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen/codegen.freezed.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen/codegen.freezed.dart new file mode 100644 index 000000000..1020def96 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen/codegen.freezed.dart @@ -0,0 +1,209 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'codegen.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +Activity _$ActivityFromJson(Map json) { + return _Activity.fromJson(json); +} + +/// @nodoc +mixin _$Activity { + String get activity => throw _privateConstructorUsedError; + String get type => throw _privateConstructorUsedError; + int get participants => throw _privateConstructorUsedError; + double get price => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $ActivityCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ActivityCopyWith<$Res> { + factory $ActivityCopyWith(Activity value, $Res Function(Activity) then) = + _$ActivityCopyWithImpl<$Res, Activity>; + @useResult + $Res call({String activity, String type, int participants, double price}); +} + +/// @nodoc +class _$ActivityCopyWithImpl<$Res, $Val extends Activity> + implements $ActivityCopyWith<$Res> { + _$ActivityCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? activity = null, + Object? type = null, + Object? participants = null, + Object? price = null, + }) { + return _then(_value.copyWith( + activity: null == activity + ? _value.activity + : activity // ignore: cast_nullable_to_non_nullable + as String, + type: null == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as String, + participants: null == participants + ? _value.participants + : participants // ignore: cast_nullable_to_non_nullable + as int, + price: null == price + ? _value.price + : price // ignore: cast_nullable_to_non_nullable + as double, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$ActivityImplCopyWith<$Res> + implements $ActivityCopyWith<$Res> { + factory _$$ActivityImplCopyWith( + _$ActivityImpl value, $Res Function(_$ActivityImpl) then) = + __$$ActivityImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String activity, String type, int participants, double price}); +} + +/// @nodoc +class __$$ActivityImplCopyWithImpl<$Res> + extends _$ActivityCopyWithImpl<$Res, _$ActivityImpl> + implements _$$ActivityImplCopyWith<$Res> { + __$$ActivityImplCopyWithImpl( + _$ActivityImpl _value, $Res Function(_$ActivityImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? activity = null, + Object? type = null, + Object? participants = null, + Object? price = null, + }) { + return _then(_$ActivityImpl( + activity: null == activity + ? _value.activity + : activity // ignore: cast_nullable_to_non_nullable + as String, + type: null == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as String, + participants: null == participants + ? _value.participants + : participants // ignore: cast_nullable_to_non_nullable + as int, + price: null == price + ? _value.price + : price // ignore: cast_nullable_to_non_nullable + as double, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$ActivityImpl implements _Activity { + _$ActivityImpl( + {required this.activity, + required this.type, + required this.participants, + required this.price}); + + factory _$ActivityImpl.fromJson(Map json) => + _$$ActivityImplFromJson(json); + + @override + final String activity; + @override + final String type; + @override + final int participants; + @override + final double price; + + @override + String toString() { + return 'Activity(activity: $activity, type: $type, participants: $participants, price: $price)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ActivityImpl && + (identical(other.activity, activity) || + other.activity == activity) && + (identical(other.type, type) || other.type == type) && + (identical(other.participants, participants) || + other.participants == participants) && + (identical(other.price, price) || other.price == price)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => + Object.hash(runtimeType, activity, type, participants, price); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$ActivityImplCopyWith<_$ActivityImpl> get copyWith => + __$$ActivityImplCopyWithImpl<_$ActivityImpl>(this, _$identity); + + @override + Map toJson() { + return _$$ActivityImplToJson( + this, + ); + } +} + +abstract class _Activity implements Activity { + factory _Activity( + {required final String activity, + required final String type, + required final int participants, + required final double price}) = _$ActivityImpl; + + factory _Activity.fromJson(Map json) = + _$ActivityImpl.fromJson; + + @override + String get activity; + @override + String get type; + @override + int get participants; + @override + double get price; + @override + @JsonKey(ignore: true) + _$$ActivityImplCopyWith<_$ActivityImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen/codegen.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen/codegen.g.dart new file mode 100644 index 000000000..69ee1d982 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen/codegen.g.dart @@ -0,0 +1,46 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$ActivityImpl _$$ActivityImplFromJson(Map json) => + _$ActivityImpl( + activity: json['activity'] as String, + type: json['type'] as String, + participants: json['participants'] as int, + price: (json['price'] as num).toDouble(), + ); + +Map _$$ActivityImplToJson(_$ActivityImpl instance) => + { + 'activity': instance.activity, + 'type': instance.type, + 'participants': instance.participants, + 'price': instance.price, + }; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$activityHash() => r'c73d0af18bcf7072f6a5a913b0b272649fb99a81'; + +/// See also [activity]. +@ProviderFor(activity) +final activityProvider = AutoDisposeFutureProvider.internal( + activity, + name: r'activityProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$activityHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ActivityRef = AutoDisposeFutureProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen/index.ts b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen/index.ts new file mode 100644 index 000000000..9d50c6614 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen/index.ts @@ -0,0 +1,4 @@ +import raw from '!!raw-loader!./raw.dart'; +import codegen from '!!raw-loader!./codegen.dart'; + +export default { raw, codegen }; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen/raw.dart new file mode 100644 index 000000000..b3ad1faaf --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen/raw.dart @@ -0,0 +1,65 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:http/http.dart' as http; + +/* SNIPPET START */ +class Activity { + Activity({ + required this.activity, + required this.type, + required this.participants, + required this.price, + }); + + factory Activity.fromJson(Map json) { + return Activity( + activity: json['activity']! as String, + type: json['type']! as String, + participants: json['participants']! as int, + price: json['price']! as double, + ); + } + + final String activity; + final String type; + final int participants; + final double price; +} + +final activityProvider = FutureProvider.autoDispose((ref) async { + final response = await http.get( + Uri.https('www.boredapi.com', '/api/activity'), + ); + + final json = jsonDecode(response.body) as Map; + return Activity.fromJson(json); +}); + +class DetailPageView extends ConsumerWidget { + const DetailPageView({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final activity = ref.watch(activityProvider); + + return Scaffold( + appBar: AppBar( + title: const Text('Detail page'), + ), + body: RefreshIndicator( + onRefresh: () => ref.refresh(activityProvider.future), + child: ListView( + children: [ + switch (activity) { + AsyncValue(:final valueOrNull?) => Text(valueOrNull.activity), + AsyncValue(:final error?) => Text('Error: $error'), + _ => const Center(child: CircularProgressIndicator()), + }, + ], + ), + ), + ); + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_cancel/codegen.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_cancel/codegen.dart new file mode 100644 index 000000000..004f47430 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_cancel/codegen.dart @@ -0,0 +1,28 @@ +import 'dart:convert'; + +import 'package:http/http.dart' as http; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../detail_screen/codegen.dart'; + +part 'codegen.g.dart'; + +/* SNIPPET START */ +@riverpod +Future activity(ActivityRef ref) async { + // 我们使用 package:http 创建一个 HTTP 客户端 + final client = http.Client(); + // 处置时,我们会关闭客户端。 + // 这将取消客户端可能有的任何待处理请求。 + ref.onDispose(client.close); + + // 现在,我们使用客户端提出请求,而不是使用 "get "函数。 + final response = await client.get( + Uri.https('www.boredapi.com', '/api/activity'), + ); + + // 其余代码与之前的相同 + final json = jsonDecode(response.body) as Map; + return Activity.fromJson(Map.from(json)); +} +/* SNIPPET END */ diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_cancel/codegen.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_cancel/codegen.g.dart new file mode 100644 index 000000000..d8ec45ada --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_cancel/codegen.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$activityHash() => r'304864a6b8051925061a2bba397574ec45b94d08'; + +/// See also [activity]. +@ProviderFor(activity) +final activityProvider = AutoDisposeFutureProvider.internal( + activity, + name: r'activityProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$activityHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ActivityRef = AutoDisposeFutureProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_cancel/index.ts b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_cancel/index.ts new file mode 100644 index 000000000..9d50c6614 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_cancel/index.ts @@ -0,0 +1,4 @@ +import raw from '!!raw-loader!./raw.dart'; +import codegen from '!!raw-loader!./codegen.dart'; + +export default { raw, codegen }; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_cancel/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_cancel/raw.dart new file mode 100644 index 000000000..86d8f9390 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_cancel/raw.dart @@ -0,0 +1,25 @@ +import 'dart:convert'; + +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:http/http.dart' as http; + +import '../detail_screen/codegen.dart'; + +/* SNIPPET START */ +final activityProvider = FutureProvider.autoDispose((ref) async { + // 我们使用 package:http 创建一个 HTTP 客户端 + final client = http.Client(); + // 处置时,我们会关闭客户端。 + // 这将取消客户端可能有的任何待处理请求。 + ref.onDispose(client.close); + + // 现在,我们使用客户端提出请求,而不是使用 "get "函数。 + final response = await client.get( + Uri.https('www.boredapi.com', '/api/activity'), + ); + + // 其余代码与之前的相同 + final json = jsonDecode(response.body) as Map; + return Activity.fromJson(Map.from(json)); +}); +/* SNIPPET END */ diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_debounce/codegen.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_debounce/codegen.dart new file mode 100644 index 000000000..69844a261 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_debounce/codegen.dart @@ -0,0 +1,38 @@ +import 'dart:convert'; + +import 'package:http/http.dart' as http; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../detail_screen/codegen.dart'; + +part 'codegen.g.dart'; + +/* SNIPPET START */ +@riverpod +Future activity(ActivityRef ref) async { + // 我们会捕捉提供者程序目前是否已被处置。 + var didDispose = false; + ref.onDispose(() => didDispose = true); + + // 我们将请求延迟 500 毫秒,以等待用户停止刷新。 + await Future.delayed(const Duration(milliseconds: 500)); + + // 如果在延迟期间处理了提供者程序,则意味着用户再次刷新了请求。 + // 我们会抛出一个异常来取消请求。 + // 在这里使用异常是安全的,因为它会被 Riverpod 捕捉到。 + if (didDispose) { + throw Exception('Cancelled'); + } + + // 以下代码与之前的代码片段保持不变 + final client = http.Client(); + ref.onDispose(client.close); + + final response = await client.get( + Uri.https('www.boredapi.com', '/api/activity'), + ); + + final json = jsonDecode(response.body) as Map; + return Activity.fromJson(Map.from(json)); +} +/* SNIPPET END */ diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_debounce/codegen.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_debounce/codegen.g.dart new file mode 100644 index 000000000..6d0d3104b --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_debounce/codegen.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$activityHash() => r'ef908e3b46693862f082769663b14d5369d6e155'; + +/// See also [activity]. +@ProviderFor(activity) +final activityProvider = AutoDisposeFutureProvider.internal( + activity, + name: r'activityProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$activityHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ActivityRef = AutoDisposeFutureProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_debounce/index.ts b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_debounce/index.ts new file mode 100644 index 000000000..9d50c6614 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_debounce/index.ts @@ -0,0 +1,4 @@ +import raw from '!!raw-loader!./raw.dart'; +import codegen from '!!raw-loader!./codegen.dart'; + +export default { raw, codegen }; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_debounce/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_debounce/raw.dart new file mode 100644 index 000000000..a8f7b76ae --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/detail_screen_debounce/raw.dart @@ -0,0 +1,35 @@ +import 'dart:convert'; + +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:http/http.dart' as http; + +import '../detail_screen/codegen.dart'; + +/* SNIPPET START */ +final activityProvider = FutureProvider.autoDispose((ref) async { + // 我们会捕捉提供者程序目前是否已被处置。 + var didDispose = false; + ref.onDispose(() => didDispose = true); + + // 我们将请求延迟 500 毫秒,以等待用户停止刷新。 + await Future.delayed(const Duration(milliseconds: 500)); + + // 如果在延迟期间处理了提供者程序,则意味着用户再次刷新了请求。 + // 我们会抛出一个异常来取消请求。 + // 在这里使用异常是安全的,因为它会被 Riverpod 捕捉到。 + if (didDispose) { + throw Exception('Cancelled'); + } + + // 以下代码与之前的代码片段保持不变 + final client = http.Client(); + ref.onDispose(client.close); + + final response = await client.get( + Uri.https('www.boredapi.com', '/api/activity'), + ); + + final json = jsonDecode(response.body) as Map; + return Activity.fromJson(Map.from(json)); +}); +/* SNIPPET END */ diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/extension.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/extension.dart new file mode 100644 index 000000000..8a06acb22 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/extension.dart @@ -0,0 +1,32 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:http/http.dart' as http; + +/* SNIPPET START */ +extension DebounceAndCancelExtension on Ref { + /// 等待 [duration](默认为 500ms), + /// 然后返回一个 [http.Client],用于发出请求。 + /// + /// 当提供者程序被处置时,该客户端将自动关闭。 + Future getDebouncedHttpClient([Duration? duration]) async { + // 首先,我们要处理去抖问题。 + var didDispose = false; + onDispose(() => didDispose = true); + + // 我们将请求延迟 500 毫秒,以等待用户停止刷新。 + await Future.delayed(duration ?? const Duration(milliseconds: 500)); + + // 如果在延迟期间处理了提供者程序,则意味着用户再次刷新了请求。 + // 我们会抛出一个异常来取消请求。 + // 在这里使用异常是安全的,因为它会被 Riverpod 捕捉到。 + if (didDispose) { + throw Exception('Cancelled'); + } + + // 现在我们创建客户端,并在处理提供者程序时关闭客户端。 + final client = http.Client(); + onDispose(client.close); + + // 最后,我们返回客户端,让我们的提供者程序提出请求。 + return client; + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/home_screen.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/home_screen.dart new file mode 100644 index 000000000..28f0c50cf --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/home_screen.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'detail_screen/codegen.dart'; + +/* SNIPPET START */ +void main() => runApp(const ProviderScope(child: MyApp())); + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + routes: { + '/detail-page': (_) => const DetailPageView(), + }, + home: const ActivityView(), + ); + } +} + +class ActivityView extends ConsumerWidget { + const ActivityView({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Scaffold( + appBar: AppBar(title: const Text('Home screen')), + body: const Center( + child: Text('Click the button to open the detail page'), + ), + floatingActionButton: FloatingActionButton( + onPressed: () => Navigator.of(context).pushNamed('/detail-page'), + child: const Icon(Icons.add), + ), + ); + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/provider_with_extension/codegen.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/provider_with_extension/codegen.dart new file mode 100644 index 000000000..4adab910f --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/provider_with_extension/codegen.dart @@ -0,0 +1,25 @@ +import 'dart:convert'; + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../detail_screen/codegen.dart'; +import '../extension.dart'; + +part 'codegen.g.dart'; + +/* SNIPPET START */ +@riverpod +Future activity(ActivityRef ref) async { + // 我们使用之前创建的扩展来获取 HTTP 客户端。 + final client = await ref.getDebouncedHttpClient(); + + // 现在,我们使用客户端而不是 "get "函数来发出请求。 + // 如果用户离开页面,我们的请求自然会被取消。 + final response = await client.get( + Uri.https('www.boredapi.com', '/api/activity'), + ); + + final json = jsonDecode(response.body) as Map; + return Activity.fromJson(Map.from(json)); +} +/* SNIPPET END */ diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/provider_with_extension/codegen.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/provider_with_extension/codegen.g.dart new file mode 100644 index 000000000..b3407fc98 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/provider_with_extension/codegen.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$activityHash() => r'f045dd6e89fde6bbe12a89f243290d289a3e692d'; + +/// See also [activity]. +@ProviderFor(activity) +final activityProvider = AutoDisposeFutureProvider.internal( + activity, + name: r'activityProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$activityHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ActivityRef = AutoDisposeFutureProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/provider_with_extension/index.ts b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/provider_with_extension/index.ts new file mode 100644 index 000000000..9d50c6614 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/provider_with_extension/index.ts @@ -0,0 +1,4 @@ +import raw from '!!raw-loader!./raw.dart'; +import codegen from '!!raw-loader!./codegen.dart'; + +export default { raw, codegen }; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/provider_with_extension/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/provider_with_extension/raw.dart new file mode 100644 index 000000000..7e445b37b --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/cancel/provider_with_extension/raw.dart @@ -0,0 +1,22 @@ +import 'dart:convert'; + +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../detail_screen/codegen.dart'; +import '../extension.dart'; + +/* SNIPPET START */ +final activityProvider = FutureProvider.autoDispose((ref) async { + // 我们使用之前创建的扩展来获取 HTTP 客户端。 + final client = await ref.getDebouncedHttpClient(); + + // 现在,我们使用客户端而不是 "get "函数来发出请求。 + // 如果用户离开页面,我们的请求自然会被取消。 + final response = await client.get( + Uri.https('www.boredapi.com', '/api/activity'), + ); + + final json = jsonDecode(response.body) as Map; + return Activity.fromJson(Map.from(json)); +}); +/* SNIPPET END */ diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh.mdx new file mode 100644 index 000000000..c308e42de --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh.mdx @@ -0,0 +1,253 @@ +--- +title: 下拉刷新 +--- + +import { Link } from "@site/src/components/Link"; +import { AutoSnippet, When } from "@site/src/components/CodeSnippet"; +import activity from "./pull_to_refresh/activity"; +import fetchActivity from "./pull_to_refresh/fetch_activity"; +import displayActivity from "!!raw-loader!./pull_to_refresh/display_activity.dart"; +import displayActivity2 from "!!raw-loader!./pull_to_refresh/display_activity2.dart"; +import displayActivity3 from "!!raw-loader!./pull_to_refresh/display_activity3.dart"; +import displayActivity4 from "!!raw-loader!./pull_to_refresh/display_activity4.dart"; +import fullApp from "./pull_to_refresh/full_app"; + + +由于其声明性,Riverpod 本身就支持拉动刷新。 + + +一般来说,拉动刷新可能很复杂,因为有多个问题需要解决: + + +- 第一次进入页面时,我们想要显示一个微调器(spinner)。 + 但在重刷新期间,我们希望显示刷新指示器。 + 我们不应该同时显示刷新指示器_和_微调器。 +- 当刷新挂起时,我们希望显示以前的数据/错误。 +- 只要重刷新发生,我们就需要显示刷新指示器。 + + +让我们看看如何使用 Riverpod 解决这个问题。 +为此,我们将制作一个简单的示例,向用户推荐随机活动。 +并且进行下拉刷新将触发新的建议: + + +上面描述的应用软件工作时的 gif + + +## 制作一个简单的应用程序。​ + + +在实现下拉刷新之前,我们首先需要刷新一些东西。 +我们可以制作一个简单的应用程序,使用 [Bored API](https://www.boredapi.com/) +向用户建议随机活动。 + + +首先,我们定义一个 `Activity` 类: + + + + +该类将负责以类型安全的方式表示建议的活动,并处理 JSON 编码/解码。 +使用 Freezed/json_serialized 不是必需的,但建议使用。 + + +现在,我们要定义一个提供者程序发出 HTTP GET 请求来获取单个活动: + + + + +我们现在可以使用此提供者程序来显示随机活动。 +目前,我们不会处理加载/错误状态,而只是在可用时显示活动: + + + + +## 添加 `RefreshIndicator` + + +现在我们有了一个简单的应用程序,我们可以向它添加一个 `RefreshIndicator`。 +该小部件是一个官方的 Material 小部件,负责在用户下拉屏幕时显示刷新指示器。 + + +使用 `RefreshIndicator` 需要一个可滚动的表面。但到目前为止,我们还没有。 +我们可以通过使用 `ListView`/`GridView`/`SingleChildScrollView` 等等来解决这个问题: + + + + +用户现在可以下拉屏幕。但我们的数据还没有刷新。 + + +## 添加刷新逻辑​ + + +当用户下拉屏幕时,`RefreshIndicator` 将调用 +`onRefresh` 回调。我们可以使用该回调来刷新我们的数据。 +在那里,我们可以使用 `ref.refresh` 刷新我们选择的提供者程序。 + + +**注意**:`onRefresh` 期望返回一个 `Future`。 +刷新完成后,future 的完成非常重要。 + + +为了获得这样的 future,我们可以读取提供者程序的 `.future` 属性。 +这将返回一个 future,该 future 在我们的提供者程序解决后完成。 + + +因此,我们可以将 `RefreshIndicator` 更新为如下所示: + + + + +## 仅在初始加载和处理错误期间显示微调器。 + + +目前,我们的 UI 不处理错误/加载状态。 +相反,当加载/刷新完成时,数据会神奇地弹出。 + + +让我们通过优雅地处理这些状态来改变这一点。有两种情况: + + +- 在初始加载期间,我们希望显示全屏微调器。 +- 在刷新期间,我们希望显示刷新指示器和之前的数据/错误。 + + +幸运的是,当在 Riverpod 中监听异步提供者程序时, +Riverpod 为我们提供了一个 `AsyncValue` ,它提供了我们需要的一切。 + + +然后可以将 `AsyncValue` 与 Dart 3.0 的模式匹配结合起来,如下所示: + + + +:::caution + +我们在这里使用 `valueOrNull`,就像目前一样, +如果处于错误/加载状态,则使用 `value` 会抛出异常。 + + +Riverpod 3.0 将对此进行更改,使 `value` 的行为类似于 `valueOrNull`。 +但现在,让我们坚持使用 `valueOrNull`。 +::: + +:::tip + +请注意我们的模式匹配中 `:final valueOrNull?` 语法的使用。 +只能使用此语法,因为 `activityProvider` 返回不可为 null 的 `Activity`。 + + +如果您的数据可以是 `null`,则可以使用 `AsyncValue(hasData: true, :final valueOrNull)`。 +这将正确处理数据为 `null` 的情况,但需要一些额外的字符。 +::: + + +## 总结:完整的应用 + + +以下是组合了我们迄今为止所涵盖的所有内容的源码: + + diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/activity/codegen.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/activity/codegen.dart new file mode 100644 index 000000000..ac3e1b17d --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/activity/codegen.dart @@ -0,0 +1,19 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'codegen.g.dart'; +part 'codegen.freezed.dart'; + +/* SNIPPET START */ +@freezed +class Activity with _$Activity { + factory Activity({ + required String activity, + required String type, + required int participants, + required double price, + }) = _Activity; + + factory Activity.fromJson(Map json) => + _$ActivityFromJson(json); +} +/* SNIPPET END */ diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/activity/codegen.freezed.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/activity/codegen.freezed.dart new file mode 100644 index 000000000..1020def96 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/activity/codegen.freezed.dart @@ -0,0 +1,209 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'codegen.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +Activity _$ActivityFromJson(Map json) { + return _Activity.fromJson(json); +} + +/// @nodoc +mixin _$Activity { + String get activity => throw _privateConstructorUsedError; + String get type => throw _privateConstructorUsedError; + int get participants => throw _privateConstructorUsedError; + double get price => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $ActivityCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ActivityCopyWith<$Res> { + factory $ActivityCopyWith(Activity value, $Res Function(Activity) then) = + _$ActivityCopyWithImpl<$Res, Activity>; + @useResult + $Res call({String activity, String type, int participants, double price}); +} + +/// @nodoc +class _$ActivityCopyWithImpl<$Res, $Val extends Activity> + implements $ActivityCopyWith<$Res> { + _$ActivityCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? activity = null, + Object? type = null, + Object? participants = null, + Object? price = null, + }) { + return _then(_value.copyWith( + activity: null == activity + ? _value.activity + : activity // ignore: cast_nullable_to_non_nullable + as String, + type: null == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as String, + participants: null == participants + ? _value.participants + : participants // ignore: cast_nullable_to_non_nullable + as int, + price: null == price + ? _value.price + : price // ignore: cast_nullable_to_non_nullable + as double, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$ActivityImplCopyWith<$Res> + implements $ActivityCopyWith<$Res> { + factory _$$ActivityImplCopyWith( + _$ActivityImpl value, $Res Function(_$ActivityImpl) then) = + __$$ActivityImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String activity, String type, int participants, double price}); +} + +/// @nodoc +class __$$ActivityImplCopyWithImpl<$Res> + extends _$ActivityCopyWithImpl<$Res, _$ActivityImpl> + implements _$$ActivityImplCopyWith<$Res> { + __$$ActivityImplCopyWithImpl( + _$ActivityImpl _value, $Res Function(_$ActivityImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? activity = null, + Object? type = null, + Object? participants = null, + Object? price = null, + }) { + return _then(_$ActivityImpl( + activity: null == activity + ? _value.activity + : activity // ignore: cast_nullable_to_non_nullable + as String, + type: null == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as String, + participants: null == participants + ? _value.participants + : participants // ignore: cast_nullable_to_non_nullable + as int, + price: null == price + ? _value.price + : price // ignore: cast_nullable_to_non_nullable + as double, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$ActivityImpl implements _Activity { + _$ActivityImpl( + {required this.activity, + required this.type, + required this.participants, + required this.price}); + + factory _$ActivityImpl.fromJson(Map json) => + _$$ActivityImplFromJson(json); + + @override + final String activity; + @override + final String type; + @override + final int participants; + @override + final double price; + + @override + String toString() { + return 'Activity(activity: $activity, type: $type, participants: $participants, price: $price)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ActivityImpl && + (identical(other.activity, activity) || + other.activity == activity) && + (identical(other.type, type) || other.type == type) && + (identical(other.participants, participants) || + other.participants == participants) && + (identical(other.price, price) || other.price == price)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => + Object.hash(runtimeType, activity, type, participants, price); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$ActivityImplCopyWith<_$ActivityImpl> get copyWith => + __$$ActivityImplCopyWithImpl<_$ActivityImpl>(this, _$identity); + + @override + Map toJson() { + return _$$ActivityImplToJson( + this, + ); + } +} + +abstract class _Activity implements Activity { + factory _Activity( + {required final String activity, + required final String type, + required final int participants, + required final double price}) = _$ActivityImpl; + + factory _Activity.fromJson(Map json) = + _$ActivityImpl.fromJson; + + @override + String get activity; + @override + String get type; + @override + int get participants; + @override + double get price; + @override + @JsonKey(ignore: true) + _$$ActivityImplCopyWith<_$ActivityImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/activity/codegen.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/activity/codegen.g.dart new file mode 100644 index 000000000..79ae3cd00 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/activity/codegen.g.dart @@ -0,0 +1,25 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$ActivityImpl _$$ActivityImplFromJson(Map json) => + _$ActivityImpl( + activity: json['activity'] as String, + type: json['type'] as String, + participants: json['participants'] as int, + price: (json['price'] as num).toDouble(), + ); + +Map _$$ActivityImplToJson(_$ActivityImpl instance) => + { + 'activity': instance.activity, + 'type': instance.type, + 'participants': instance.participants, + 'price': instance.price, + }; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/activity/index.ts b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/activity/index.ts new file mode 100644 index 000000000..9d50c6614 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/activity/index.ts @@ -0,0 +1,4 @@ +import raw from '!!raw-loader!./raw.dart'; +import codegen from '!!raw-loader!./codegen.dart'; + +export default { raw, codegen }; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/activity/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/activity/raw.dart new file mode 100644 index 000000000..42365b7bb --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/activity/raw.dart @@ -0,0 +1,24 @@ +/* SNIPPET START */ +class Activity { + Activity({ + required this.activity, + required this.type, + required this.participants, + required this.price, + }); + + factory Activity.fromJson(Map json) { + return Activity( + activity: json['activity']! as String, + type: json['type']! as String, + participants: json['participants']! as int, + price: json['price']! as double, + ); + } + + final String activity; + final String type; + final int participants; + final double price; +} +/* SNIPPET END */ diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/display_activity.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/display_activity.dart new file mode 100644 index 000000000..4e4d03305 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/display_activity.dart @@ -0,0 +1,22 @@ +// ignore_for_file: use_key_in_widget_constructors + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'fetch_activity/codegen.dart'; + +/* SNIPPET START */ +class ActivityView extends ConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + final activity = ref.watch(activityProvider); + + return Scaffold( + appBar: AppBar(title: const Text('Pull to refresh')), + body: Center( + // 如果有活动,则显示,否则等待 + child: Text(activity.valueOrNull?.activity ?? ''), + ), + ); + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/display_activity2.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/display_activity2.dart new file mode 100644 index 000000000..7394ea664 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/display_activity2.dart @@ -0,0 +1,28 @@ +// ignore_for_file: avoid_print, use_key_in_widget_constructors + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'fetch_activity/codegen.dart'; + +/* SNIPPET START */ +class ActivityView extends ConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + final activity = ref.watch(activityProvider); + + return Scaffold( + appBar: AppBar(title: const Text('Pull to refresh')), + /* highlight-start */ + body: RefreshIndicator( + onRefresh: () async => print('refresh'), + child: ListView( + children: [ + /* highlight-end */ + Text(activity.valueOrNull?.activity ?? ''), + ], + ), + ), + ); + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/display_activity3.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/display_activity3.dart new file mode 100644 index 000000000..bc41065fe --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/display_activity3.dart @@ -0,0 +1,30 @@ +// ignore_for_file: avoid_print, use_key_in_widget_constructors + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'fetch_activity/codegen.dart'; + +/* SNIPPET START */ +class ActivityView extends ConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + final activity = ref.watch(activityProvider); + + return Scaffold( + appBar: AppBar(title: const Text('Pull to refresh')), + body: RefreshIndicator( + // 通过重刷新调用 "activityProvider.future" 并返回结果, + // 刷新指示器将一直显示,直到获取到新的活动。 + /* highlight-start */ + onRefresh: () => ref.refresh(activityProvider.future), + /* highlight-end */ + child: ListView( + children: [ + Text(activity.valueOrNull?.activity ?? ''), + ], + ), + ), + ); + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/display_activity4.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/display_activity4.dart new file mode 100644 index 000000000..42bb2fef4 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/display_activity4.dart @@ -0,0 +1,36 @@ +// ignore_for_file: avoid_print, use_key_in_widget_constructors + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'activity/codegen.dart'; +import 'fetch_activity/codegen.dart'; + +/* SNIPPET START */ +class ActivityView extends ConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + final activity = ref.watch(activityProvider); + + return Scaffold( + appBar: AppBar(title: const Text('Pull to refresh')), + body: RefreshIndicator( + onRefresh: () => ref.refresh(activityProvider.future), + child: ListView( + children: [ + switch (activity) { + // 如果有数据可用,我们就显示它。 + // 请注意,数据在重刷新时仍然可用。 + AsyncValue(:final valueOrNull?) => + Text(valueOrNull.activity), + // 有一个错误,因此我们将其呈现出来。 + AsyncValue(:final error?) => Text('Error: $error'), + // 无数据/错误,因此我们处于加载状态。 + _ => const CircularProgressIndicator(), + }, + ], + ), + ), + ); + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/fetch_activity/codegen.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/fetch_activity/codegen.dart new file mode 100644 index 000000000..0cec67cba --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/fetch_activity/codegen.dart @@ -0,0 +1,20 @@ +import 'dart:convert'; + +import 'package:http/http.dart' as http; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../activity/codegen.dart'; + +part 'codegen.g.dart'; + +/* SNIPPET START */ +@riverpod +Future activity(ActivityRef ref) async { + final response = await http.get( + Uri.https('www.boredapi.com', '/api/activity'), + ); + + final json = jsonDecode(response.body) as Map; + return Activity.fromJson(Map.from(json)); +} +/* SNIPPET END */ diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/fetch_activity/codegen.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/fetch_activity/codegen.g.dart new file mode 100644 index 000000000..16aa62d6b --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/fetch_activity/codegen.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$activityHash() => r'c73d0af18bcf7072f6a5a913b0b272649fb99a81'; + +/// See also [activity]. +@ProviderFor(activity) +final activityProvider = AutoDisposeFutureProvider.internal( + activity, + name: r'activityProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$activityHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ActivityRef = AutoDisposeFutureProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/fetch_activity/index.ts b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/fetch_activity/index.ts new file mode 100644 index 000000000..9d50c6614 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/fetch_activity/index.ts @@ -0,0 +1,4 @@ +import raw from '!!raw-loader!./raw.dart'; +import codegen from '!!raw-loader!./codegen.dart'; + +export default { raw, codegen }; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/fetch_activity/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/fetch_activity/raw.dart new file mode 100644 index 000000000..1cec3b128 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/fetch_activity/raw.dart @@ -0,0 +1,17 @@ +import 'dart:convert'; + +import 'package:http/http.dart' as http; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../activity/raw.dart'; + +/* SNIPPET START */ +final activityProvider = FutureProvider.autoDispose((ref) async { + final response = await http.get( + Uri.https('www.boredapi.com', '/api/activity'), + ); + + final json = jsonDecode(response.body) as Map; + return Activity.fromJson(json); +}); +/* SNIPPET END */ diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/full_app/codegen.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/full_app/codegen.dart new file mode 100644 index 000000000..b7d53c744 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/full_app/codegen.dart @@ -0,0 +1,69 @@ +// ignore_for_file: use_key_in_widget_constructors, unreachable_from_main + +/* SNIPPET START */ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:http/http.dart' as http; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; +part 'codegen.freezed.dart'; + +void main() => runApp(ProviderScope(child: MyApp())); + +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MaterialApp(home: ActivityView()); + } +} + +class ActivityView extends ConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + final activity = ref.watch(activityProvider); + + return Scaffold( + appBar: AppBar(title: const Text('Pull to refresh')), + body: RefreshIndicator( + onRefresh: () => ref.refresh(activityProvider.future), + child: ListView( + children: [ + switch (activity) { + AsyncValue(:final valueOrNull?) => + Text(valueOrNull.activity), + AsyncValue(:final error?) => Text('Error: $error'), + _ => const CircularProgressIndicator(), + }, + ], + ), + ), + ); + } +} + +@riverpod +Future activity(ActivityRef ref) async { + final response = await http.get( + Uri.https('www.boredapi.com', '/api/activity'), + ); + + final json = jsonDecode(response.body) as Map; + return Activity.fromJson(Map.from(json)); +} + +@freezed +class Activity with _$Activity { + factory Activity({ + required String activity, + required String type, + required int participants, + required double price, + }) = _Activity; + + factory Activity.fromJson(Map json) => + _$ActivityFromJson(json); +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/full_app/codegen.freezed.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/full_app/codegen.freezed.dart new file mode 100644 index 000000000..1020def96 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/full_app/codegen.freezed.dart @@ -0,0 +1,209 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'codegen.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +Activity _$ActivityFromJson(Map json) { + return _Activity.fromJson(json); +} + +/// @nodoc +mixin _$Activity { + String get activity => throw _privateConstructorUsedError; + String get type => throw _privateConstructorUsedError; + int get participants => throw _privateConstructorUsedError; + double get price => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $ActivityCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ActivityCopyWith<$Res> { + factory $ActivityCopyWith(Activity value, $Res Function(Activity) then) = + _$ActivityCopyWithImpl<$Res, Activity>; + @useResult + $Res call({String activity, String type, int participants, double price}); +} + +/// @nodoc +class _$ActivityCopyWithImpl<$Res, $Val extends Activity> + implements $ActivityCopyWith<$Res> { + _$ActivityCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? activity = null, + Object? type = null, + Object? participants = null, + Object? price = null, + }) { + return _then(_value.copyWith( + activity: null == activity + ? _value.activity + : activity // ignore: cast_nullable_to_non_nullable + as String, + type: null == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as String, + participants: null == participants + ? _value.participants + : participants // ignore: cast_nullable_to_non_nullable + as int, + price: null == price + ? _value.price + : price // ignore: cast_nullable_to_non_nullable + as double, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$ActivityImplCopyWith<$Res> + implements $ActivityCopyWith<$Res> { + factory _$$ActivityImplCopyWith( + _$ActivityImpl value, $Res Function(_$ActivityImpl) then) = + __$$ActivityImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String activity, String type, int participants, double price}); +} + +/// @nodoc +class __$$ActivityImplCopyWithImpl<$Res> + extends _$ActivityCopyWithImpl<$Res, _$ActivityImpl> + implements _$$ActivityImplCopyWith<$Res> { + __$$ActivityImplCopyWithImpl( + _$ActivityImpl _value, $Res Function(_$ActivityImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? activity = null, + Object? type = null, + Object? participants = null, + Object? price = null, + }) { + return _then(_$ActivityImpl( + activity: null == activity + ? _value.activity + : activity // ignore: cast_nullable_to_non_nullable + as String, + type: null == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as String, + participants: null == participants + ? _value.participants + : participants // ignore: cast_nullable_to_non_nullable + as int, + price: null == price + ? _value.price + : price // ignore: cast_nullable_to_non_nullable + as double, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$ActivityImpl implements _Activity { + _$ActivityImpl( + {required this.activity, + required this.type, + required this.participants, + required this.price}); + + factory _$ActivityImpl.fromJson(Map json) => + _$$ActivityImplFromJson(json); + + @override + final String activity; + @override + final String type; + @override + final int participants; + @override + final double price; + + @override + String toString() { + return 'Activity(activity: $activity, type: $type, participants: $participants, price: $price)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ActivityImpl && + (identical(other.activity, activity) || + other.activity == activity) && + (identical(other.type, type) || other.type == type) && + (identical(other.participants, participants) || + other.participants == participants) && + (identical(other.price, price) || other.price == price)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => + Object.hash(runtimeType, activity, type, participants, price); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$ActivityImplCopyWith<_$ActivityImpl> get copyWith => + __$$ActivityImplCopyWithImpl<_$ActivityImpl>(this, _$identity); + + @override + Map toJson() { + return _$$ActivityImplToJson( + this, + ); + } +} + +abstract class _Activity implements Activity { + factory _Activity( + {required final String activity, + required final String type, + required final int participants, + required final double price}) = _$ActivityImpl; + + factory _Activity.fromJson(Map json) = + _$ActivityImpl.fromJson; + + @override + String get activity; + @override + String get type; + @override + int get participants; + @override + double get price; + @override + @JsonKey(ignore: true) + _$$ActivityImplCopyWith<_$ActivityImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/full_app/codegen.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/full_app/codegen.g.dart new file mode 100644 index 000000000..69ee1d982 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/full_app/codegen.g.dart @@ -0,0 +1,46 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$ActivityImpl _$$ActivityImplFromJson(Map json) => + _$ActivityImpl( + activity: json['activity'] as String, + type: json['type'] as String, + participants: json['participants'] as int, + price: (json['price'] as num).toDouble(), + ); + +Map _$$ActivityImplToJson(_$ActivityImpl instance) => + { + 'activity': instance.activity, + 'type': instance.type, + 'participants': instance.participants, + 'price': instance.price, + }; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$activityHash() => r'c73d0af18bcf7072f6a5a913b0b272649fb99a81'; + +/// See also [activity]. +@ProviderFor(activity) +final activityProvider = AutoDisposeFutureProvider.internal( + activity, + name: r'activityProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$activityHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ActivityRef = AutoDisposeFutureProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/full_app/index.ts b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/full_app/index.ts new file mode 100644 index 000000000..9d50c6614 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/full_app/index.ts @@ -0,0 +1,4 @@ +import raw from '!!raw-loader!./raw.dart'; +import codegen from '!!raw-loader!./codegen.dart'; + +export default { raw, codegen }; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/full_app/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/full_app/raw.dart new file mode 100644 index 000000000..c4186fb31 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/case_studies/pull_to_refresh/full_app/raw.dart @@ -0,0 +1,73 @@ +// ignore_for_file: use_key_in_widget_constructors, unreachable_from_main + +/* SNIPPET START */ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:http/http.dart' as http; + +void main() => runApp(ProviderScope(child: MyApp())); + +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MaterialApp(home: ActivityView()); + } +} + +class ActivityView extends ConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + final activity = ref.watch(activityProvider); + + return Scaffold( + appBar: AppBar(title: const Text('Pull to refresh')), + body: RefreshIndicator( + onRefresh: () => ref.refresh(activityProvider.future), + child: ListView( + children: [ + switch (activity) { + AsyncValue(:final valueOrNull?) => + Text(valueOrNull.activity), + AsyncValue(:final error?) => Text('Error: $error'), + _ => const CircularProgressIndicator(), + }, + ], + ), + ), + ); + } +} + +final activityProvider = FutureProvider.autoDispose((ref) async { + final response = await http.get( + Uri.https('www.boredapi.com', '/api/activity'), + ); + + final json = jsonDecode(response.body) as Map; + return Activity.fromJson(json); +}); + +class Activity { + Activity({ + required this.activity, + required this.type, + required this.participants, + required this.price, + }); + + factory Activity.fromJson(Map json) { + return Activity( + activity: json['activity']! as String, + type: json['type']! as String, + participants: json['participants']! as int, + price: json['price']! as double, + ); + } + + final String activity; + final String type; + final int participants; + final double price; +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_code_generation.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_code_generation.mdx new file mode 100644 index 000000000..17215217c --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_code_generation.mdx @@ -0,0 +1,488 @@ +--- +title: 关于代码生成 +version: 1 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; +import CodeBlock from "@theme/CodeBlock"; +import fetchUser from "!!raw-loader!./about_codegen/main.dart"; +import rawFetchUser from "!!raw-loader!./about_codegen/raw.dart"; +import { Link } from "@site/src/components/Link"; +import { trimSnippet, CodeSnippet } from "@site/src/components/CodeSnippet"; +import syncFn from "!!raw-loader!./about_codegen/provider_type/sync_fn.dart"; +import syncClass from "!!raw-loader!./about_codegen/provider_type/sync_class.dart"; +import asyncFnFuture from "!!raw-loader!./about_codegen/provider_type/async_fn_future.dart"; +import asyncClassFuture from "!!raw-loader!./about_codegen/provider_type/async_class_future.dart"; +import asyncFnStream from "!!raw-loader!./about_codegen/provider_type/async_fn_stream.dart"; +import asyncClassStream from "!!raw-loader!./about_codegen/provider_type/async_class_stream.dart"; +import familyFn from "!!raw-loader!./about_codegen/provider_type/family_fn.dart"; +import familyClass from "!!raw-loader!./about_codegen/provider_type/family_class.dart"; +import provider from "!!raw-loader!./about_codegen/provider_type/non_code_gen/provider.dart"; +import notifierProvider from "!!raw-loader!./about_codegen/provider_type/non_code_gen/notifier_provider.dart"; +import futureProvider from "!!raw-loader!./about_codegen/provider_type/non_code_gen/future_provider.dart"; +import asyncNotifierProvider from "!!raw-loader!./about_codegen/provider_type/non_code_gen/async_notifier_provider.dart"; +import streamProvider from "!!raw-loader!./about_codegen/provider_type/non_code_gen/stream_provider.dart"; +import streamNotifierProvider from "!!raw-loader!./about_codegen/provider_type/non_code_gen/stream_notifier_provider.dart"; +import autoDisposeCodeGen from "!!raw-loader!./about_codegen/provider_type/auto_dispose.dart"; +import autoDisposeNonCodeGen from "!!raw-loader!./about_codegen/provider_type/non_code_gen/auto_dispose.dart"; +import familyCodeGen from "!!raw-loader!./about_codegen/provider_type/family.dart"; +import familyNonCodeGen from "!!raw-loader!./about_codegen/provider_type/non_code_gen/family.dart"; +const TRANSPARENT_STYLE = { backgroundColor: "transparent" }; +const RED_STYLE = { color: "indianred", fontWeight: "700" }; +const BLUE_STYLE = { color: "rgb(103, 134, 196)", fontWeight: "700" }; +const FONT_16_STYLE = { + fontSize: "16px", + fontWeight: "700", +}; +const BLUE_20_STYLE = { + color: "rgb(103, 134, 196)", + fontSize: "20px", + fontWeight: "700", +}; +const PROVIDER_STYLE = { + textAlign: "center", + fontWeight: "600", + maxWidth: "210px", +}; +const BEFORE_STYLE = { + minWidth: "60px", + textAlign: "center", + fontWeight: "600", + color: "crimson", +}; +const AFTER_STYLE = { + minWidth: "60px", + textAlign: "center", + fontWeight: "600", + color: "rgb(40,180,40)", +}; + + +代码生成是使用工具为我们生成代码的想法。 +在 Dart 中,它的缺点是需要额外的步骤来“编译”应用程序。 +尽管这个问题可能在不久的将来得到解决, +但 Dart 团队正在研究这个问题的潜在解决方案。 + + +在 Riverpod 的上下文中,代码生成是稍微改变定义 "provider" 的语法。例如,代替: + +{trimSnippet(rawFetchUser)} + + +使用代码生成,我们可以编写: + +{trimSnippet(fetchUser)} + + +使用 Riverpod 时,代码生成是完全可选的。 +没有的话完全可以使用 Riverpod。 +同时,Riverpod 支持代码生成并推荐使用它。 + + +有关如何安装和使用 Riverpod 代码生成器的信息, +请参阅页面。 +确保在文档的侧栏中启用代码生成。 + + +## 我应该使用代码生成吗?​ + + +Riverpod 中的代码生成是可选的。 +考虑到这一点,您可能会想是否应该使用它。 + + +答案是:**很可能是的**。 +使用代码生成是使用 Riverpod 的推荐方式。 +这是一种更面向未来的方法,可以让您充分发挥 Riverpod 的潜力。 +与此同时,许多应用程序已经使用 +[Freezed](https://pub.dev/packages/freezed) 或 [json_serializable](https://pub.dev/packages/json_serializable) +等包来生成代码。在这种情况下,您的项目可能已经设置为代码生成, +并且使用 Riverpod 应该很简单。 + + +目前,代码生成是可选的,因为许多人不喜欢 `build_runner`。 +但是,一旦 Dart 中提供了[静态元编程](https://github.com/dart-lang/language/issues/1482), +`build_runner` 将不再是问题。 +届时,代码生成语法将是 Riverpod 中唯一可用的语法。 + + +如果使用 `build_runner` 对您来说是一个破坏性的事情, +那么只有那时您才应该考虑不使用代码生成。 +但请记住,您将错过一些功能,并且将来您将不得不迁移到代码生成。 +尽管如此,当这种情况发生时, +Riverpod 将提供一个迁移工具,以使过渡尽可能顺利。 + + +## 使用代码生成有什么好处?​ + + +您可能想知道:“如果 Riverpod 中代码生成是可选的,为什么要使用它?” + + +这和其他包的目的一样:让您的生活更轻松。这包括但不限于: + + +- 更好的语法,更具可读性/灵活性,并且学习曲线更短。 + - 无需担心提供者程序的类型。写下您的逻辑,Riverpod 将为您选择最合适的提供者程序。 + - 语法看起来不再像我们定义了“肮脏的全局变量”。相反,我们定义了一个自定义函数/类。 + - 向提供者程序传递参数现在不受限制。 + 您现在可以传递任何参数,而不是仅限于使用 `.family` 并传递单个位置参数。 + 这包括命名参数、可选参数,甚至默认值。 +- 用 Riverpod 编写的代码的**有状态热重载**。 +- 通过生成调试器随后拾取的额外元数据来更好地进行调试。 +- 某些 Riverpod 功能仅在代码生成时可用。 + + +## 语法​ + + +### 定义提供者程序:​ + + +使用代码生成定义提供者程序时,记住以下几点会很有帮助: + + +- 提供者程序可以定义为带注释的函数或 + 带注释的。它们几乎相同, + 但基于类的提供者程序的优点是包含公共方法,使 + 外部对象能够修改提供者程序的状态(副作用)。 + 函数提供者程序是用于编写基于类的提供者程序的语法糖,只有 `build` 方法, + 因此不能由 UI 修改。 +- 支持所有 Dart 异步原语(Future、FutureOr 和 Stream)。 +- 当函数被标记为async时, + 提供者程序会自动处理错误/加载状态并公开 AsyncValue。 + + + + + + + + + + + + + + + + + + + + + + + +
+ 函数式的 +
+ (不能使用公共方法执行副作用) +
+ 基于类的 +
+ (可以使用公共方法执行副作用) +
+ + 同步的 + + + {trimSnippet(syncFn)} + + {trimSnippet(syncClass)} +
+ + 异步的 - Future + + + {trimSnippet(asyncFnFuture)} + + {trimSnippet(asyncClassFuture)} +
+ + 异步的 - Stream + + + {trimSnippet(asyncFnStream)} + + {trimSnippet(asyncClassStream)} +
+ + +### 启用/禁用自动处置 autoDispose:​ + + +使用代码生成时,提供者程序默认为 autoDispose。 +这意味着当没有监听器附加到它们(ref.watch/ref.listen)时,它们会自动处理掉自己。 +此默认设置更符合 Riverpod 的理念。 +最初没有使用代码生成变体时,默认情况下 autoDispose 处于关闭状态, +以适应从 `package:provider` 迁移的用户。 + + +如果您想禁用 autoDispose,可以通过将 `keepAlive: true` 传递给注释来实现。 + +{trimSnippet(autoDisposeCodeGen)} + + +### 将参数传递给提供者程序(family):​ + + +使用代码生成时,我们不再需要依赖 `family` 修饰符将参数传递给提供者程序。 +相反,我们的提供者程序的主函数可以接受任意数量的参数,包括命名、可选或默认值。 +但请注意,这些参数应该仍然具有 == 的一致性。 +这意味着要么应该缓存值,要么应该覆盖 == 参数。 + + + + + + + + + + + + + + +
+ 函数式的 + + 基于类的 +
+ {trimSnippet(familyFn)} + + {trimSnippet(familyClass)} +
+ + +## 从非代码生成变体迁移:​ + + +使用非代码生成变体时,需要手动确定提供者程序的类型。 +以下是转换为代码生成变体的相应选项: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Provider +
之前 + {trimSnippet(provider)} +
之后 + {trimSnippet(syncFn)} +
+ NotifierProvider +
之前 + {trimSnippet(notifierProvider)} +
之后 + {trimSnippet(syncClass)} +
+ FutureProvider +
之前 + {trimSnippet(futureProvider)} +
之后 + {trimSnippet(asyncFnFuture)} +
+ StreamProvider +
之前 + {trimSnippet(streamProvider)} +
之后 + {trimSnippet(asyncFnStream)} +
+ AsyncNotifierProvider +
之前 + + {trimSnippet(asyncNotifierProvider)} + +
之后 + {trimSnippet(asyncClassFuture)} +
+ StreamNotifierProvider +
之前 + + {trimSnippet(streamNotifierProvider)} + +
之后 + {trimSnippet(asyncClassStream)} +
+ +[hookwidget]: https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/HookWidget-class.html +[statefulwidget]: https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html +[riverpod]: https://github.com/rrousselgit/riverpod +[hooks_riverpod]: https://pub.dev/packages/hooks_riverpod +[flutter_riverpod]: https://pub.dev/packages/flutter_riverpod +[flutter_hooks]: https://github.com/rrousselGit/flutter_hooks +[build]: https://pub.dev/documentation/riverpod/latest/riverpod/Notifier/build.html diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/main.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/main.dart new file mode 100644 index 000000000..1ccb42807 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/main.dart @@ -0,0 +1,22 @@ +// ignore_for_file: use_key_in_widget_constructors, omit_local_variable_types, avoid_unused_constructor_parameters + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'main.g.dart'; + +class User { + User.fromJson(Object obj); +} + +class Http { + Future get(String str) async => str; +} + +final http = Http(); + +/* SNIPPET START */ +@riverpod +Future fetchUser(FetchUserRef ref, {required int userId}) async { + final json = await http.get('api/user/$userId'); + return User.fromJson(json); +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/about_codegen/main.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/main.g.dart similarity index 100% rename from website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/about_codegen/main.g.dart rename to website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/main.g.dart diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_class_future.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_class_future.dart new file mode 100644 index 000000000..2c7affffc --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_class_future.dart @@ -0,0 +1,14 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'async_class_future.g.dart'; + +/* SNIPPET START */ +@riverpod +class Example extends _$Example { + @override + Future build() async { + return Future.value('foo'); + } + + // 添加改变状态的方法 +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_class_future.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_class_future.g.dart new file mode 100644 index 000000000..749d6e6dc --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_class_future.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'async_class_future.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$exampleHash() => r'8a906741b8ea4b9b0d3f0b924779704b3e1773a1'; + +/// See also [Example]. +@ProviderFor(Example) +final exampleProvider = + AutoDisposeAsyncNotifierProvider.internal( + Example.new, + name: r'exampleProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$exampleHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$Example = AutoDisposeAsyncNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_class_stream.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_class_stream.dart new file mode 100644 index 000000000..d76d9fd67 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_class_stream.dart @@ -0,0 +1,14 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'async_class_stream.g.dart'; + +/* SNIPPET START */ +@riverpod +class Example extends _$Example { + @override + Stream build() async* { + yield 'foo'; + } + + // 添加改变状态的方法 +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_class_stream.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_class_stream.g.dart new file mode 100644 index 000000000..77984b80a --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_class_stream.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'async_class_stream.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$exampleHash() => r'4bca936132b77a9a804549f086f33571724b4804'; + +/// See also [Example]. +@ProviderFor(Example) +final exampleProvider = + AutoDisposeStreamNotifierProvider.internal( + Example.new, + name: r'exampleProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$exampleHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$Example = AutoDisposeStreamNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_fn_future.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_fn_future.dart new file mode 100644 index 000000000..95fdd909c --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_fn_future.dart @@ -0,0 +1,9 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'async_fn_future.g.dart'; + +/* SNIPPET START */ +@riverpod +Future example(ExampleRef ref) async { + return Future.value('foo'); +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_fn_future.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_fn_future.g.dart new file mode 100644 index 000000000..3274e8a9b --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_fn_future.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'async_fn_future.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$exampleHash() => r'e620af6b870a76eea4228989433de0666957d813'; + +/// See also [example]. +@ProviderFor(example) +final exampleProvider = AutoDisposeFutureProvider.internal( + example, + name: r'exampleProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$exampleHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ExampleRef = AutoDisposeFutureProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_fn_stream.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_fn_stream.dart new file mode 100644 index 000000000..74da790ad --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_fn_stream.dart @@ -0,0 +1,9 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'async_fn_stream.g.dart'; + +/* SNIPPET START */ +@riverpod +Stream example(ExampleRef ref) async* { + yield 'foo'; +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_fn_stream.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_fn_stream.g.dart new file mode 100644 index 000000000..42904d939 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/async_fn_stream.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'async_fn_stream.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$exampleHash() => r'8a2b19776fb9bbb1631f898bd6446b57b102dd9d'; + +/// See also [example]. +@ProviderFor(example) +final exampleProvider = AutoDisposeStreamProvider.internal( + example, + name: r'exampleProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$exampleHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ExampleRef = AutoDisposeStreamProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/auto_dispose.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/auto_dispose.dart new file mode 100644 index 000000000..7716e905c --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/auto_dispose.dart @@ -0,0 +1,12 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'auto_dispose.g.dart'; + +/* SNIPPET START */ +// AutoDispose provider (keepAlive 默认为 false) +@riverpod +String example1(Example1Ref ref) => 'foo'; + +// Non autoDispose provider +@Riverpod(keepAlive: true) +String example2(Example2Ref ref) => 'foo'; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/auto_dispose.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/auto_dispose.g.dart new file mode 100644 index 000000000..e6e00432c --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/auto_dispose.g.dart @@ -0,0 +1,40 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'auto_dispose.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$example1Hash() => r'8a5f0865f758792cc8e4f2ca67db334196df6e88'; + +/// See also [example1]. +@ProviderFor(example1) +final example1Provider = AutoDisposeProvider.internal( + example1, + name: r'example1Provider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$example1Hash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef Example1Ref = AutoDisposeProviderRef; +String _$example2Hash() => r'bc25731d759be185125d12d995d0b89b07d1e271'; + +/// See also [example2]. +@ProviderFor(example2) +final example2Provider = Provider.internal( + example2, + name: r'example2Provider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$example2Hash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef Example2Ref = ProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/family.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/family.dart new file mode 100644 index 000000000..e1ee685fb --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/family.dart @@ -0,0 +1,7 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'family.g.dart'; + +/* SNIPPET START */ +@riverpod +String example(ExampleRef ref, int param) => 'Hello $param'; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/family.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/family.g.dart new file mode 100644 index 000000000..a037ab96c --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/family.g.dart @@ -0,0 +1,159 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'family.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$exampleHash() => r'c4f5a651a55bcf34b0c92d98d77436844cbdc097'; + +/// Copied from Dart SDK +class _SystemHash { + _SystemHash._(); + + static int combine(int hash, int value) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + value); + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); + return hash ^ (hash >> 6); + } + + static int finish(int hash) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + // ignore: parameter_assignments + hash = hash ^ (hash >> 11); + return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); + } +} + +/// See also [example]. +@ProviderFor(example) +const exampleProvider = ExampleFamily(); + +/// See also [example]. +class ExampleFamily extends Family { + /// See also [example]. + const ExampleFamily(); + + /// See also [example]. + ExampleProvider call( + int param, + ) { + return ExampleProvider( + param, + ); + } + + @override + ExampleProvider getProviderOverride( + covariant ExampleProvider provider, + ) { + return call( + provider.param, + ); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'exampleProvider'; +} + +/// See also [example]. +class ExampleProvider extends AutoDisposeProvider { + /// See also [example]. + ExampleProvider( + int param, + ) : this._internal( + (ref) => example( + ref as ExampleRef, + param, + ), + from: exampleProvider, + name: r'exampleProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$exampleHash, + dependencies: ExampleFamily._dependencies, + allTransitiveDependencies: ExampleFamily._allTransitiveDependencies, + param: param, + ); + + ExampleProvider._internal( + super._createNotifier, { + required super.name, + required super.dependencies, + required super.allTransitiveDependencies, + required super.debugGetCreateSourceHash, + required super.from, + required this.param, + }) : super.internal(); + + final int param; + + @override + Override overrideWith( + String Function(ExampleRef provider) create, + ) { + return ProviderOverride( + origin: this, + override: ExampleProvider._internal( + (ref) => create(ref as ExampleRef), + from: from, + name: null, + dependencies: null, + allTransitiveDependencies: null, + debugGetCreateSourceHash: null, + param: param, + ), + ); + } + + @override + AutoDisposeProviderElement createElement() { + return _ExampleProviderElement(this); + } + + @override + bool operator ==(Object other) { + return other is ExampleProvider && other.param == param; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, param.hashCode); + + return _SystemHash.finish(hash); + } +} + +mixin ExampleRef on AutoDisposeProviderRef { + /// The parameter `param` of this provider. + int get param; +} + +class _ExampleProviderElement extends AutoDisposeProviderElement + with ExampleRef { + _ExampleProviderElement(super.provider); + + @override + int get param => (origin as ExampleProvider).param; +} +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/family_class.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/family_class.dart new file mode 100644 index 000000000..bc012e266 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/family_class.dart @@ -0,0 +1,17 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'family_class.g.dart'; + +/* SNIPPET START */ +@riverpod +class Example extends _$Example { + @override + String build( + int param1, { + String param2 = 'foo', + }) { + return 'Hello $param1 & param2'; + } + + // 添加改变状态的方法 +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/family_class.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/family_class.g.dart new file mode 100644 index 000000000..2e9a830bc --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/family_class.g.dart @@ -0,0 +1,195 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'family_class.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$exampleHash() => r'c81e9d94e763b25403ab6b7fa03f092003570142'; + +/// Copied from Dart SDK +class _SystemHash { + _SystemHash._(); + + static int combine(int hash, int value) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + value); + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); + return hash ^ (hash >> 6); + } + + static int finish(int hash) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + // ignore: parameter_assignments + hash = hash ^ (hash >> 11); + return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); + } +} + +abstract class _$Example extends BuildlessAutoDisposeNotifier { + late final int param1; + late final String param2; + + String build( + int param1, { + String param2 = 'foo', + }); +} + +/// See also [Example]. +@ProviderFor(Example) +const exampleProvider = ExampleFamily(); + +/// See also [Example]. +class ExampleFamily extends Family { + /// See also [Example]. + const ExampleFamily(); + + /// See also [Example]. + ExampleProvider call( + int param1, { + String param2 = 'foo', + }) { + return ExampleProvider( + param1, + param2: param2, + ); + } + + @override + ExampleProvider getProviderOverride( + covariant ExampleProvider provider, + ) { + return call( + provider.param1, + param2: provider.param2, + ); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'exampleProvider'; +} + +/// See also [Example]. +class ExampleProvider extends AutoDisposeNotifierProviderImpl { + /// See also [Example]. + ExampleProvider( + int param1, { + String param2 = 'foo', + }) : this._internal( + () => Example() + ..param1 = param1 + ..param2 = param2, + from: exampleProvider, + name: r'exampleProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$exampleHash, + dependencies: ExampleFamily._dependencies, + allTransitiveDependencies: ExampleFamily._allTransitiveDependencies, + param1: param1, + param2: param2, + ); + + ExampleProvider._internal( + super._createNotifier, { + required super.name, + required super.dependencies, + required super.allTransitiveDependencies, + required super.debugGetCreateSourceHash, + required super.from, + required this.param1, + required this.param2, + }) : super.internal(); + + final int param1; + final String param2; + + @override + String runNotifierBuild( + covariant Example notifier, + ) { + return notifier.build( + param1, + param2: param2, + ); + } + + @override + Override overrideWith(Example Function() create) { + return ProviderOverride( + origin: this, + override: ExampleProvider._internal( + () => create() + ..param1 = param1 + ..param2 = param2, + from: from, + name: null, + dependencies: null, + allTransitiveDependencies: null, + debugGetCreateSourceHash: null, + param1: param1, + param2: param2, + ), + ); + } + + @override + AutoDisposeNotifierProviderElement createElement() { + return _ExampleProviderElement(this); + } + + @override + bool operator ==(Object other) { + return other is ExampleProvider && + other.param1 == param1 && + other.param2 == param2; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, param1.hashCode); + hash = _SystemHash.combine(hash, param2.hashCode); + + return _SystemHash.finish(hash); + } +} + +mixin ExampleRef on AutoDisposeNotifierProviderRef { + /// The parameter `param1` of this provider. + int get param1; + + /// The parameter `param2` of this provider. + String get param2; +} + +class _ExampleProviderElement + extends AutoDisposeNotifierProviderElement + with ExampleRef { + _ExampleProviderElement(super.provider); + + @override + int get param1 => (origin as ExampleProvider).param1; + @override + String get param2 => (origin as ExampleProvider).param2; +} +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/family_fn.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/family_fn.dart new file mode 100644 index 000000000..863df6f40 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/family_fn.dart @@ -0,0 +1,13 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'family_fn.g.dart'; + +/* SNIPPET START */ +@riverpod +String example( + ExampleRef ref, + int param1, { + String param2 = 'foo', +}) { + return 'Hello $param1 & param2'; +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/family_fn.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/family_fn.g.dart new file mode 100644 index 000000000..aa8282264 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/family_fn.g.dart @@ -0,0 +1,176 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'family_fn.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$exampleHash() => r'99b3ed3d53932bd1354259200ebf08493af9ada2'; + +/// Copied from Dart SDK +class _SystemHash { + _SystemHash._(); + + static int combine(int hash, int value) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + value); + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); + return hash ^ (hash >> 6); + } + + static int finish(int hash) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + // ignore: parameter_assignments + hash = hash ^ (hash >> 11); + return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); + } +} + +/// See also [example]. +@ProviderFor(example) +const exampleProvider = ExampleFamily(); + +/// See also [example]. +class ExampleFamily extends Family { + /// See also [example]. + const ExampleFamily(); + + /// See also [example]. + ExampleProvider call( + int param1, { + String param2 = 'foo', + }) { + return ExampleProvider( + param1, + param2: param2, + ); + } + + @override + ExampleProvider getProviderOverride( + covariant ExampleProvider provider, + ) { + return call( + provider.param1, + param2: provider.param2, + ); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'exampleProvider'; +} + +/// See also [example]. +class ExampleProvider extends AutoDisposeProvider { + /// See also [example]. + ExampleProvider( + int param1, { + String param2 = 'foo', + }) : this._internal( + (ref) => example( + ref as ExampleRef, + param1, + param2: param2, + ), + from: exampleProvider, + name: r'exampleProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$exampleHash, + dependencies: ExampleFamily._dependencies, + allTransitiveDependencies: ExampleFamily._allTransitiveDependencies, + param1: param1, + param2: param2, + ); + + ExampleProvider._internal( + super._createNotifier, { + required super.name, + required super.dependencies, + required super.allTransitiveDependencies, + required super.debugGetCreateSourceHash, + required super.from, + required this.param1, + required this.param2, + }) : super.internal(); + + final int param1; + final String param2; + + @override + Override overrideWith( + String Function(ExampleRef provider) create, + ) { + return ProviderOverride( + origin: this, + override: ExampleProvider._internal( + (ref) => create(ref as ExampleRef), + from: from, + name: null, + dependencies: null, + allTransitiveDependencies: null, + debugGetCreateSourceHash: null, + param1: param1, + param2: param2, + ), + ); + } + + @override + AutoDisposeProviderElement createElement() { + return _ExampleProviderElement(this); + } + + @override + bool operator ==(Object other) { + return other is ExampleProvider && + other.param1 == param1 && + other.param2 == param2; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, param1.hashCode); + hash = _SystemHash.combine(hash, param2.hashCode); + + return _SystemHash.finish(hash); + } +} + +mixin ExampleRef on AutoDisposeProviderRef { + /// The parameter `param1` of this provider. + int get param1; + + /// The parameter `param2` of this provider. + String get param2; +} + +class _ExampleProviderElement extends AutoDisposeProviderElement + with ExampleRef { + _ExampleProviderElement(super.provider); + + @override + int get param1 => (origin as ExampleProvider).param1; + @override + String get param2 => (origin as ExampleProvider).param2; +} +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/async_notifier_provider.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/async_notifier_provider.dart new file mode 100644 index 000000000..7316f1b0b --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/async_notifier_provider.dart @@ -0,0 +1,16 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +/* SNIPPET START */ +final exampleProvider = + AsyncNotifierProvider.autoDispose( + ExampleNotifier.new, +); + +class ExampleNotifier extends AutoDisposeAsyncNotifier { + @override + Future build() async { + return Future.value('foo'); + } + + // 添加改变状态的方法 +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/auto_dispose.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/auto_dispose.dart new file mode 100644 index 000000000..3a1b26fb5 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/auto_dispose.dart @@ -0,0 +1,12 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +/* SNIPPET START */ +// autoDispose provider +final example1Provider = Provider.autoDispose((ref) { + return 'foo'; +}); + +// non autoDispose provider +final example2Provider = Provider((ref) { + return 'foo'; +}); diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/family.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/family.dart new file mode 100644 index 000000000..a9e59d48d --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/family.dart @@ -0,0 +1,6 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +/* SNIPPET START */ +final exampleProvider = Provider.family((ref, param) { + return 'Hello $param'; +}); diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/future_provider.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/future_provider.dart new file mode 100644 index 000000000..6306a76fb --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/future_provider.dart @@ -0,0 +1,7 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +/* SNIPPET START */ +final exampleProvider = + FutureProvider.autoDispose((ref) async { + return Future.value('foo'); +}); diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/notifier_provider.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/notifier_provider.dart new file mode 100644 index 000000000..1c220578e --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/notifier_provider.dart @@ -0,0 +1,15 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +/* SNIPPET START */ +final exampleProvider = NotifierProvider.autoDispose( + ExampleNotifier.new, +); + +class ExampleNotifier extends AutoDisposeNotifier { + @override + String build() { + return 'foo'; + } + + // 添加改变状态的方法 +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/provider.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/provider.dart new file mode 100644 index 000000000..1f541352a --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/provider.dart @@ -0,0 +1,8 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +/* SNIPPET START */ +final exampleProvider = Provider.autoDispose( + (ref) { + return 'foo'; + }, +); diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/stream_notifier_provider.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/stream_notifier_provider.dart new file mode 100644 index 000000000..2951f2f00 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/stream_notifier_provider.dart @@ -0,0 +1,16 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +/* SNIPPET START */ +final exampleProvider = + StreamNotifierProvider.autoDispose(() { + return ExampleNotifier(); +}); + +class ExampleNotifier extends AutoDisposeStreamNotifier { + @override + Stream build() async* { + yield 'foo'; + } + + // 添加改变状态的方法 +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/stream_provider.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/stream_provider.dart new file mode 100644 index 000000000..5e7f8463a --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/non_code_gen/stream_provider.dart @@ -0,0 +1,7 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +/* SNIPPET START */ +final exampleProvider = + StreamProvider.autoDispose((ref) async* { + yield 'foo'; +}); diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/sync_class.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/sync_class.dart new file mode 100644 index 000000000..7974e175a --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/sync_class.dart @@ -0,0 +1,14 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'sync_class.g.dart'; + +/* SNIPPET START */ +@riverpod +class Example extends _$Example { + @override + String build() { + return 'foo'; + } + + // 添加改变状态的方法 +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/sync_class.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/sync_class.g.dart new file mode 100644 index 000000000..e03372d41 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/sync_class.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'sync_class.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$exampleHash() => r'c237193ab6d57674973aaa02eb73db6f6822eb26'; + +/// See also [Example]. +@ProviderFor(Example) +final exampleProvider = AutoDisposeNotifierProvider.internal( + Example.new, + name: r'exampleProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$exampleHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$Example = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/sync_fn.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/sync_fn.dart new file mode 100644 index 000000000..0d4922734 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/sync_fn.dart @@ -0,0 +1,9 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'sync_fn.g.dart'; + +/* SNIPPET START */ +@riverpod +String example(ExampleRef ref) { + return 'foo'; +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/sync_fn.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/sync_fn.g.dart new file mode 100644 index 000000000..dbc326dcd --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/provider_type/sync_fn.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'sync_fn.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$exampleHash() => r'dd4e9043c704a42a3fc025e7fef9515f659fc78a'; + +/// See also [example]. +@ProviderFor(example) +final exampleProvider = AutoDisposeProvider.internal( + example, + name: r'exampleProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$exampleHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ExampleRef = AutoDisposeProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/raw.dart new file mode 100644 index 000000000..4630c27ff --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_codegen/raw.dart @@ -0,0 +1,19 @@ +// ignore_for_file: use_key_in_widget_constructors, omit_local_variable_types, avoid_unused_constructor_parameters + +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +class User { + User.fromJson(Object obj); +} + +class Http { + Future get(String str) async => str; +} + +final http = Http(); + +/* SNIPPET START */ +final fetchUserProvider = FutureProvider.autoDispose.family((ref, userId) async { + final json = await http.get('api/user/$userId'); + return User.fromJson(json); +}); diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_hooks.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_hooks.mdx new file mode 100644 index 000000000..6eadaa2cf --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_hooks.mdx @@ -0,0 +1,570 @@ +--- +title: 关于 Hooks(钩子) +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; +import hookAndConsumer from "!!raw-loader!./about_hooks/hook_and_consumer.dart"; +import hookConsumer from "!!raw-loader!./about_hooks/hook_consumer.dart"; +import hookConsumerWidget from "!!raw-loader!./about_hooks/hook_consumer_widget.dart"; +import { CodeSnippet } from "@site/src/components/CodeSnippet"; +import { Link } from "@site/src/components/Link"; + + +本页介绍了什么是 Hooks 以及它们与 Riverpod 的关系。 + + +"Hooks" 是独立于 Riverpod 的单独包中常见的实用程序:[flutter_hooks]。 +虽然 [flutter_hooks] 是一个完全独立的包, +并且与 Riverpod 没有任何关系(至少没有直接关系), +但通常将 Riverpod 和 [flutter_hooks] 配对在一起。 + + +## 你应该使用 hooks 吗?​ + + +Hooks 是一个强大的工具,但并不适合所有人。 +如果您是 Riverpod 的新手,您可能应该避免使用 hooks。 + + +虽然 hooks 很有用,但对于 Riverpod 来说并不是必需的。 +您不应该为了 Riverpod 开始使用 hooks。 +相反,您开始使用 hooks,是因为您想使用 hooks。 + + +使用 hooks 是一种权衡。它们非常适合生成健壮且可重用的代码, +但它们也是一个需要学习的新概念,一开始可能会令人困惑。 +Hooks 不是 Flutter 的核心概念。因此,它们在 Flutter/Dart 中会感觉格格不入。 + + +## 什么是 Hooks?​ + + +Hooks 是小部件内部使用的函数。它们被设计为 [StatefulWidget] 的替代品, +以使逻辑更加可重用和可组合。 + + +Hooks 是来自 [React](https://reactjs.org/) 的一个概念,[flutter_hooks] +只是 React 实现到 Flutter 的一个端口。 +因此,是的,hooks 在 Flutter 中可能感觉有点不合适。理想情况下, +未来我们会有一个专门为 Flutter 设计的 Hooks 解决问题的解决方案。 + + +如果 Riverpod 的提供者程序用于“全局”应用程序状态,则 Hooks 用于本地小部件状态。 +Hooks 通常用于处理有状态的 UI 对象,例如 [TextEditingController](https://api.flutter.dev/flutter/widgets/TextEditingController-class.html)、 +[AnimationController](https://api.flutter.dev/flutter/animation/AnimationController-class.html)。 +它们还可以作为“构建器”模式的替代品,用不涉及“嵌套”的替代方案替换诸如 +[FutureBuilder](https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html)/[TweenAnimatedBuilder](https://api.flutter.dev/flutter/widgets/TweenAnimationBuilder-class.html) +之类的小部件,从而大大提高可读性。 + + +一般来说,钩子有助于: + + +- 表单 +- 动画 +- 对用户事件做出反应 +- …… + + +例如,我们可以使用钩子手动实现淡入动画,其中小部件开始不可见并慢慢出现。 + + +如果我们使用 [StatefulWidget],代码将如下所示: + +```dart +class FadeIn extends StatefulWidget { + const FadeIn({Key? key, required this.child}) : super(key: key); + + final Widget child; + + @override + State createState() => _FadeInState(); +} + +class _FadeInState extends State with SingleTickerProviderStateMixin { + late final AnimationController animationController = AnimationController( + vsync: this, + duration: const Duration(seconds: 2), + ); + + @override + void initState() { + super.initState(); + animationController.forward(); + } + + @override + void dispose() { + animationController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: animationController, + builder: (context, child) { + return Opacity( + opacity: animationController.value, + child: widget.child, + ); + }, + ); + } +} +``` + + +使用 hooks,相当于: + +```dart +class FadeIn extends HookWidget { + const FadeIn({Key? key, required this.child}) : super(key: key); + + final Widget child; + + @override + Widget build(BuildContext context) { + // 创建一个 AnimationController。 + // 卸载 widget 时,控制器将自动处置。 + final animationController = useAnimationController( + duration: const Duration(seconds: 2), + ); + + // useEffect 相当于 initState + didUpdateWidget + dispose。 + // 传给 useEffect 的回调会在第一次调用钩子时执行, + // 然后每当作为第二个参数传递的列表发生变化时也会执行。 + // 由于我们在这里传递的是一个空的常量列表, + // 因此严格意义上等同于 `initState`。 + useEffect(() { + // 在首次呈现 widget 时启动动画。 + animationController.forward(); + // 我们可以选择在这里返回一些“处置”逻辑 + return null; + }, const []); + + // 告诉 Flutter 在动画更新时重建此部件。 + // 这相当于 AnimatedBuilder + useAnimation(animationController); + + return Opacity( + opacity: animationController.value, + child: child, + ); + } +} +``` + + +这段代码中有一些有趣的事情需要注意: + + +- 不存在内存泄漏。每当小部件重建时,此代码都不会重新创建新的 `AnimationController`, + 并且在卸载小部件时正确处置控制器。 + + +- 在同一个小部件中可以根据需要多次使用钩子。 + 因此,如果我们愿意,我们可以创建多个 `AnimationController`: + + ```dart + @override + Widget build(BuildContext context) { + final animationController = useAnimationController( + duration: const Duration(seconds: 2), + ); + final anotherController = useAnimationController( + duration: const Duration(seconds: 2), + ); + + ... + } + ``` + + 这会创建两个控制器,不会产生任何负面后果。 + + +- 如果我们愿意,我们可以将此逻辑重构为一个单独的可重用函数: + + ```dart + double useFadeIn() { + final animationController = useAnimationController( + duration: const Duration(seconds: 2), + ); + useEffect(() { + animationController.forward(); + return null; + }, const []); + useAnimation(animationController); + return animationController.value; + } + ``` + + 然后我们可以在我们的小部件中使用这个函数,只要该小部件是 [HookWidget]: + + ```dart + class FadeIn extends HookWidget { + const FadeIn({Key? key, required this.child}) : super(key: key); + + final Widget child; + + @override + Widget build(BuildContext context) { + final fade = useFadeIn(); + + return Opacity(opacity: fade, child: child); + } + } + ``` + + 请注意我们的 `useFadeIn` 函数是如何完全独立于我们的 `FadeIn` 小部件的。 + 如果我们愿意,我们可以在完全不同的小部件中使用该 `useFadeIn` 函数,并且它仍然可以工作! + + +## hooks 的规则​ + + +Hooks 具有独特的约束: + + +- 它们只能在扩展 [HookWidget] 的小部件的 `build` 方法中使用: + + **好**: + + ```dart + class Example extends HookWidget { + @override + Widget build(BuildContext context) { + final controller = useAnimationController(); + ... + } + } + ``` + + **坏**: + + ```dart + // 不是 HookWidget + class Example extends StatelessWidget { + @override + Widget build(BuildContext context) { + final controller = useAnimationController(); + ... + } + } + ``` + + **坏**: + + ```dart + class Example extends HookWidget { + @override + Widget build(BuildContext context) { + return ElevatedButton( + onPressed: () { + // _实际上_不是在 "build" 方法中, + // 而是在用户交互生命周期中(这里是 "按下时")。 + final controller = useAnimationController(); + }, + child: Text('click me'), + ); + } + } + ``` + + +- 它们不能在条件语句或在循环语句中使用。 + + **坏**: + + ```dart + class Example extends HookWidget { + const Example({required this.condition, super.key}); + final bool condition; + @override + Widget build(BuildContext context) { + if (condition) { + // 不应该在 "if"/"for"/... 中使用 Hooks + final controller = useAnimationController(); + } + ... + } + } + ``` + + +有关钩子的更多信息,请参阅 [flutter_hooks]。 + + +## Hooks 和 Riverpod + + +### 安装 + + +由于 Hooks 与 Riverpod 是独立的,因此需要单独安装 Hooks。 +如果你想使用它们,安装 [hooks_riverpod] 是不够的。 +您仍然需要将 [flutter_hooks] 添加到您的依赖项中。 +请参阅 了解更多信息。 + + +### 用途​ + + +在某些情况下,您可能想要编写一个同时使用 hooks 和 Riverpod 的 Widget。 +但您可能已经注意到,Hooks 和 Riverpod 都提供了自己的 +自定义小部件基本类型:[HookWidget] 和 [ConsumerWidget]。 +但类一次只能扩展一个父类。 + + +为了解决这个问题,你可以使用 [hooks_riverpod] 包。 +该包提供了一个 [HookConsumerWidget] 类, +它将 [HookWidget] 和 [ConsumerWidget] 组合成一个类型。 +因此,您可以继承 [HookConsumerWidget] 而不是 [HookWidget]: + + + + +或者,您可以使用两个包提供的“构建器 builder”。 +例如,我们可以坚持使用 `StatelessWidget`, +并同时使用 `HookBuilder` 和 `Consumer`。 + + + +:::note + +这种方法无需使用 `hooks_riverpod` 即可工作。只需要 `flutter_riverpod`。 +::: + + +如果您喜欢这种方法,[hooks_riverpod] 通过提供 [HookConsumer] 来简化它, +它是两个构建器的组合: + + + +[hookwidget]: https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/HookWidget-class.html +[hookconsumer]: https://pub.dev/documentation/hooks_riverpod/latest/hooks_riverpod/HookConsumer-class.html +[hookconsumerwidget]: https://pub.dev/documentation/hooks_riverpod/latest/hooks_riverpod/HookConsumerWidget-class.html +[consumerwidget]: https://pub.dev/documentation/flutter_riverpod/latest/flutter_riverpod/ConsumerWidget-class.html +[statefulwidget]: https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html +[riverpod]: https://github.com/rrousselgit/riverpod +[hooks_riverpod]: https://pub.dev/packages/hooks_riverpod +[flutter_riverpod]: https://pub.dev/packages/flutter_riverpod +[flutter_hooks]: https://github.com/rrousselGit/flutter_hooks diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_hooks/hook_and_consumer.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_hooks/hook_and_consumer.dart new file mode 100644 index 000000000..fc5e96628 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_hooks/hook_and_consumer.dart @@ -0,0 +1,28 @@ +// ignore_for_file: use_key_in_widget_constructors, unused_local_variable + +import 'package:flutter/widgets.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +import '../providers/creating_a_provider/codegen.dart'; + +class MyValue {} + +/* SNIPPET START */ + +class Example extends StatelessWidget { + @override + Widget build(BuildContext context) { + // 我们可以使用这两个软件包提供的构建器 + return Consumer( + builder: (context, ref, child) { + return HookBuilder(builder: (context) { + final counter = useState(0); + final value = ref.watch(myProvider); + + return Text('Hello $counter $value'); + }); + }, + ); + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_hooks/hook_consumer.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_hooks/hook_consumer.dart new file mode 100644 index 000000000..1ada8fe2a --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_hooks/hook_consumer.dart @@ -0,0 +1,26 @@ +// ignore_for_file: use_key_in_widget_constructors, unused_local_variable + +import 'package:flutter/widgets.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +import '../providers/creating_a_provider/codegen.dart'; + +class MyValue {} + +/* SNIPPET START */ + +class Example extends StatelessWidget { + @override + Widget build(BuildContext context) { + // 相当于同时使用 Consumer 和 HookBuilder。 + return HookConsumer( + builder: (context, ref, child) { + final counter = useState(0); + final value = ref.watch(myProvider); + + return Text('Hello $counter $value'); + }, + ); + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_hooks/hook_consumer_widget.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_hooks/hook_consumer_widget.dart new file mode 100644 index 000000000..ecb222750 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/about_hooks/hook_consumer_widget.dart @@ -0,0 +1,23 @@ +// ignore_for_file: use_key_in_widget_constructors, unused_local_variable + +import 'package:flutter/widgets.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +import '../providers/creating_a_provider/codegen.dart'; + +class MyValue {} + +/* SNIPPET START */ + +// 我们扩展了 HookConsumerWidget,而不是 HookWidget +class Example extends HookConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + // 我们可以在这里同时使用钩子和提供者程序 + final counter = useState(0); + final value = ref.watch(myProvider); + + return Text('Hello $counter $value'); + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/async_initialization.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/async_initialization.dart new file mode 100644 index 000000000..18817b776 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/async_initialization.dart @@ -0,0 +1,58 @@ +// ignore_for_file: omit_local_variable_types, prefer_final_locals + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class LoadingScreen extends StatelessWidget { + const LoadingScreen({super.key}); + + @override + Widget build(BuildContext context) { + return const CircularProgressIndicator(); + } +} + +/* SNIPPET START */ +// We'd like to obtain an instance of shared preferences synchronously in a provider +final countProvider = StateProvider((ref) { + final preferences = ref.watch(sharedPreferencesProvider); + final currentValue = preferences.getInt('count') ?? 0; + ref.listenSelf((prev, curr) { + preferences.setInt('count', curr); + }); + return currentValue; +}); + +// We don't have an actual instance of SharedPreferences, and we can't get one except asynchronously +final sharedPreferencesProvider = + Provider((ref) => throw UnimplementedError()); + +Future main() async { + // Show a loading indicator before running the full app (optional) + // The platform's loading screen will be used while awaiting if you omit this. + runApp(const LoadingScreen()); + + // Get the instance of shared preferences + final prefs = await SharedPreferences.getInstance(); + return runApp( + ProviderScope( + overrides: [ + // Override the unimplemented provider with the value gotten from the plugin + sharedPreferencesProvider.overrideWithValue(prefs), + ], + child: const MyApp(), + ), + ); +} + +class MyApp extends ConsumerWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + // Use the provider without dealing with async issues + final count = ref.watch(countProvider); + return Text('$count'); + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/characters_provider/codegen.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/characters_provider/codegen.dart new file mode 100644 index 000000000..b544b3778 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/characters_provider/codegen.dart @@ -0,0 +1,29 @@ +import 'package:dio/dio.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import 'models.dart'; + +part 'codegen.g.dart'; + +final dio = Dio(); + +/* SNIPPET START */ + +// The current search filter +final searchProvider = StateProvider((ref) => ''); + +@riverpod +Stream configs(ConfigsRef ref) { + return Stream.value(Configuration()); +} + +@riverpod +Future> characters(CharactersRef ref) async { + final search = ref.watch(searchProvider); + final configs = await ref.watch(configsProvider.future); + final response = await dio.get>>( + '${configs.host}/characters?search=$search'); + + return response.data!.map(Character.fromJson).toList(); +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/characters_provider/codegen.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/characters_provider/codegen.g.dart new file mode 100644 index 000000000..acc45c280 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/characters_provider/codegen.g.dart @@ -0,0 +1,40 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$configsHash() => r'166cbe95e6b49ed7bc78c96041fb14abddbf6911'; + +/// See also [configs]. +@ProviderFor(configs) +final configsProvider = AutoDisposeStreamProvider.internal( + configs, + name: r'configsProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$configsHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ConfigsRef = AutoDisposeStreamProviderRef; +String _$charactersHash() => r'b1e8e15bbeab60d92fe959d9e1dd4ceba6a31446'; + +/// See also [characters]. +@ProviderFor(characters) +final charactersProvider = AutoDisposeFutureProvider>.internal( + characters, + name: r'charactersProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$charactersHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef CharactersRef = AutoDisposeFutureProviderRef>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/characters_provider/index.tsx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/characters_provider/index.tsx new file mode 100644 index 000000000..339b89a56 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/characters_provider/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./codegen.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/characters_provider/models.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/characters_provider/models.dart new file mode 100644 index 000000000..e21e1d7c6 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/characters_provider/models.dart @@ -0,0 +1,17 @@ + +class Character { + Character(); + + // ignore: avoid_unused_constructor_parameters + factory Character.fromJson(Map json) { + return Character(); + } +} + +class Configuration { + Configuration({ + this.host = '', + }); + + final String host; +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/characters_provider/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/characters_provider/raw.dart new file mode 100644 index 000000000..948148bd0 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/characters_provider/raw.dart @@ -0,0 +1,25 @@ +import 'package:dio/dio.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'models.dart'; + +final dio = Dio(); + +/* SNIPPET START */ + +// The current search filter +final searchProvider = StateProvider((ref) => ''); + +/// Configurations which can change over time +final configsProvider = StreamProvider( + (ref) => Stream.value(Configuration()), +); + +final charactersProvider = FutureProvider>((ref) async { + final search = ref.watch(searchProvider); + final configs = await ref.watch(configsProvider.future); + final response = await dio.get>>( + '${configs.host}/characters?search=$search'); + + return response.data!.map(Character.fromJson).toList(); +}); diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/city_provider/codegen.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/city_provider/codegen.dart new file mode 100644 index 000000000..8b8f234b9 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/city_provider/codegen.dart @@ -0,0 +1,7 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +/* SNIPPET START */ +@riverpod +String city(CityRef ref) => 'London'; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/city_provider/codegen.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/city_provider/codegen.g.dart new file mode 100644 index 000000000..674b539be --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/city_provider/codegen.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$cityHash() => r'2ccdee096b5d5c1cafa736b3e52b788431b9af38'; + +/// See also [city]. +@ProviderFor(city) +final cityProvider = AutoDisposeProvider.internal( + city, + name: r'cityProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$cityHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef CityRef = AutoDisposeProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/city_provider/index.tsx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/city_provider/index.tsx new file mode 100644 index 000000000..339b89a56 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/city_provider/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./codegen.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/city_provider/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/city_provider/raw.dart new file mode 100644 index 000000000..d8093431c --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/city_provider/raw.dart @@ -0,0 +1,4 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +/* SNIPPET START */ +final cityProvider = Provider((ref) => 'London'); diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/filtered_todo_list_provider/codegen.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/filtered_todo_list_provider/codegen.dart new file mode 100644 index 000000000..0d2e49b28 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/filtered_todo_list_provider/codegen.dart @@ -0,0 +1,31 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../todo_list_provider/raw.dart'; + +part 'codegen.g.dart'; + +enum Filter { + none, + completed, + uncompleted, +} + +final filterProvider = StateProvider((ref) => Filter.none); + +/* SNIPPET START */ + +@riverpod +List filteredTodoList(FilteredTodoListRef ref) { + final filter = ref.watch(filterProvider); + final todos = ref.watch(todoListProvider); + + switch (filter) { + case Filter.none: + return todos; + case Filter.completed: + return todos.where((todo) => todo.completed).toList(); + case Filter.uncompleted: + return todos.where((todo) => !todo.completed).toList(); + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/filtered_todo_list_provider/codegen.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/filtered_todo_list_provider/codegen.g.dart new file mode 100644 index 000000000..f8563b534 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/filtered_todo_list_provider/codegen.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$filteredTodoListHash() => r'1c35eb0fce8fc7c7cda86413b02f606f8c8ae2b4'; + +/// See also [filteredTodoList]. +@ProviderFor(filteredTodoList) +final filteredTodoListProvider = AutoDisposeProvider>.internal( + filteredTodoList, + name: r'filteredTodoListProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$filteredTodoListHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef FilteredTodoListRef = AutoDisposeProviderRef>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/filtered_todo_list_provider/index.tsx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/filtered_todo_list_provider/index.tsx new file mode 100644 index 000000000..339b89a56 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/filtered_todo_list_provider/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./codegen.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/filtered_todo_list_provider/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/filtered_todo_list_provider/raw.dart new file mode 100644 index 000000000..85885e800 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/filtered_todo_list_provider/raw.dart @@ -0,0 +1,26 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../todo_list_provider/raw.dart'; + +enum Filter { + none, + completed, + uncompleted, +} + +final filterProvider = StateProvider((ref) => Filter.none); + +/* SNIPPET START */ + +final filteredTodoListProvider = Provider>((ref) { + final filter = ref.watch(filterProvider); + final todos = ref.watch(todoListProvider); + + switch (filter) { + case Filter.none: + return todos; + case Filter.completed: + return todos.where((todo) => todo.completed).toList(); + case Filter.uncompleted: + return todos.where((todo) => !todo.completed).toList(); + } +}); \ No newline at end of file diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/read_in_provider/codegen.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/read_in_provider/codegen.dart new file mode 100644 index 000000000..b1cb3dbe3 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/read_in_provider/codegen.dart @@ -0,0 +1,17 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +@riverpod +MyValue another(AnotherRef ref) => MyValue(); + +class MyValue {} + +/* SNIPPET START */ + +@riverpod +MyValue my(MyRef ref) { + // Bad practice to call `read` here + final value = ref.read(anotherProvider); + return value; +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/read_in_provider/codegen.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/read_in_provider/codegen.g.dart new file mode 100644 index 000000000..acabbbbfe --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/read_in_provider/codegen.g.dart @@ -0,0 +1,40 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$anotherHash() => r'bb412edc55657c14eace37792cd18e5254604a36'; + +/// See also [another]. +@ProviderFor(another) +final anotherProvider = AutoDisposeProvider.internal( + another, + name: r'anotherProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$anotherHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef AnotherRef = AutoDisposeProviderRef; +String _$myHash() => r'2712c772be4dbaabd4c99fd803f927a7e9938b21'; + +/// See also [my]. +@ProviderFor(my) +final myProvider = AutoDisposeProvider.internal( + my, + name: r'myProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$myHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef MyRef = AutoDisposeProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/read_in_provider/index.tsx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/read_in_provider/index.tsx new file mode 100644 index 000000000..339b89a56 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/read_in_provider/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./codegen.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/read_in_provider/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/read_in_provider/raw.dart new file mode 100644 index 000000000..76bae4653 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/read_in_provider/raw.dart @@ -0,0 +1,13 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +final anotherProvider = Provider((ref) => MyValue()); + +class MyValue {} + +/* SNIPPET START */ + +final myProvider = Provider((ref) { + // Bad practice to call `read` here + final value = ref.read(anotherProvider); + return value; +}); diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/select_async_provider/codegen.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/select_async_provider/codegen.dart new file mode 100644 index 000000000..e44e202bc --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/select_async_provider/codegen.dart @@ -0,0 +1,25 @@ + +import 'package:dio/dio.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import 'models.dart'; + +part 'codegen.g.dart'; + +final dio = Dio(); + +/* SNIPPET START */ + +@riverpod +Stream config(ConfigRef ref) => Stream.value(Configuration()); + +@riverpod +Future> products(ProductsRef ref) async { + // Listens only to the host. If something else in the configurations + // changes, this will not pointlessly re-evaluate our provider. + final host = await ref.watch(configProvider.selectAsync((config) => config.host)); + + final result = await dio.get>>('$host/products'); + + return result.data!.map(Product.fromJson).toList(); +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/select_async_provider/codegen.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/select_async_provider/codegen.g.dart new file mode 100644 index 000000000..ba5c2aec1 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/select_async_provider/codegen.g.dart @@ -0,0 +1,40 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$configHash() => r'3021d1a8aac384e99d5d22714ffe6e868954888b'; + +/// See also [config]. +@ProviderFor(config) +final configProvider = AutoDisposeStreamProvider.internal( + config, + name: r'configProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$configHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ConfigRef = AutoDisposeStreamProviderRef; +String _$productsHash() => r'd1f4523880408cf8ee0e68969c40cf87d5c78557'; + +/// See also [products]. +@ProviderFor(products) +final productsProvider = AutoDisposeFutureProvider>.internal( + products, + name: r'productsProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$productsHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ProductsRef = AutoDisposeFutureProviderRef>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/select_async_provider/index.tsx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/select_async_provider/index.tsx new file mode 100644 index 000000000..339b89a56 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/select_async_provider/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./codegen.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/select_async_provider/models.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/select_async_provider/models.dart new file mode 100644 index 000000000..3a711b4b6 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/select_async_provider/models.dart @@ -0,0 +1,21 @@ +// ignore_for_file: sort_constructors_first + +class Product { + const Product({this.title = ''}); + + final String title; + + factory Product.fromJson(Map map) { + return Product( + title: map['title'] as String, + ); + } +} + +class Configuration { + Configuration({ + this.host = '', + }); + + final String host; +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/select_async_provider/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/select_async_provider/raw.dart new file mode 100644 index 000000000..ae35300b0 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/select_async_provider/raw.dart @@ -0,0 +1,20 @@ +import 'package:dio/dio.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'models.dart'; + +final dio = Dio(); + +/* SNIPPET START */ + +final configProvider = + StreamProvider((ref) => Stream.value(Configuration())); + +final productsProvider = FutureProvider>((ref) async { + // Listens only to the host. If something else in the configurations + // changes, this will not pointlessly re-evaluate our provider. + final host = await ref.watch(configProvider.selectAsync((config) => config.host)); + final result = await dio.get>>('$host/products'); + + return result.data!.map(Product.fromJson).toList(); +}); \ No newline at end of file diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/todo_list_provider/codegen.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/todo_list_provider/codegen.dart new file mode 100644 index 000000000..a1942dbd4 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/todo_list_provider/codegen.dart @@ -0,0 +1,24 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.freezed.dart'; +part 'codegen.g.dart'; + +@freezed +class Todo with _$Todo { + factory Todo({ + required String id, + required String description, + required bool completed, + }) = _Todo; +} + +/* SNIPPET START */ + +@riverpod +class TodoList extends _$TodoList { + @override + List build() { + return []; + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/todo_list_provider/codegen.freezed.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/todo_list_provider/codegen.freezed.dart new file mode 100644 index 000000000..0b73d3548 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/todo_list_provider/codegen.freezed.dart @@ -0,0 +1,166 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'codegen.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +/// @nodoc +mixin _$Todo { + String get id => throw _privateConstructorUsedError; + String get description => throw _privateConstructorUsedError; + bool get completed => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $TodoCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $TodoCopyWith<$Res> { + factory $TodoCopyWith(Todo value, $Res Function(Todo) then) = + _$TodoCopyWithImpl<$Res, Todo>; + @useResult + $Res call({String id, String description, bool completed}); +} + +/// @nodoc +class _$TodoCopyWithImpl<$Res, $Val extends Todo> + implements $TodoCopyWith<$Res> { + _$TodoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? description = null, + Object? completed = null, + }) { + return _then(_value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + completed: null == completed + ? _value.completed + : completed // ignore: cast_nullable_to_non_nullable + as bool, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$TodoImplCopyWith<$Res> implements $TodoCopyWith<$Res> { + factory _$$TodoImplCopyWith( + _$TodoImpl value, $Res Function(_$TodoImpl) then) = + __$$TodoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String id, String description, bool completed}); +} + +/// @nodoc +class __$$TodoImplCopyWithImpl<$Res> + extends _$TodoCopyWithImpl<$Res, _$TodoImpl> + implements _$$TodoImplCopyWith<$Res> { + __$$TodoImplCopyWithImpl(_$TodoImpl _value, $Res Function(_$TodoImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? description = null, + Object? completed = null, + }) { + return _then(_$TodoImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + completed: null == completed + ? _value.completed + : completed // ignore: cast_nullable_to_non_nullable + as bool, + )); + } +} + +/// @nodoc + +class _$TodoImpl implements _Todo { + _$TodoImpl( + {required this.id, required this.description, required this.completed}); + + @override + final String id; + @override + final String description; + @override + final bool completed; + + @override + String toString() { + return 'Todo(id: $id, description: $description, completed: $completed)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$TodoImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.description, description) || + other.description == description) && + (identical(other.completed, completed) || + other.completed == completed)); + } + + @override + int get hashCode => Object.hash(runtimeType, id, description, completed); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$TodoImplCopyWith<_$TodoImpl> get copyWith => + __$$TodoImplCopyWithImpl<_$TodoImpl>(this, _$identity); +} + +abstract class _Todo implements Todo { + factory _Todo( + {required final String id, + required final String description, + required final bool completed}) = _$TodoImpl; + + @override + String get id; + @override + String get description; + @override + bool get completed; + @override + @JsonKey(ignore: true) + _$$TodoImplCopyWith<_$TodoImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/todo_list_provider/codegen.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/todo_list_provider/codegen.g.dart new file mode 100644 index 000000000..5cb2dae07 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/todo_list_provider/codegen.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$todoListHash() => r'6c965beb867ffeee119133f0ae2e6ebeb68e6f7e'; + +/// See also [TodoList]. +@ProviderFor(TodoList) +final todoListProvider = + AutoDisposeNotifierProvider>.internal( + TodoList.new, + name: r'todoListProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$todoListHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$TodoList = AutoDisposeNotifier>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/todo_list_provider/index.tsx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/todo_list_provider/index.tsx new file mode 100644 index 000000000..339b89a56 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/todo_list_provider/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./codegen.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/todo_list_provider/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/todo_list_provider/raw.dart new file mode 100644 index 000000000..a341b7f5b --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/todo_list_provider/raw.dart @@ -0,0 +1,36 @@ +// ignore_for_file: public_member_api_docs, sort_constructors_first +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +class Todo { + const Todo({ + required this.id, + required this.description, + required this.completed, + }); + + // All properties should be `final` on our class. + final String id; + final String description; + final bool completed; + + // Since Todo is immutable, we implement a method that allows cloning the + // Todo with slightly different content. + Todo copyWith({String? id, String? description, bool? completed}) { + return Todo( + id: id ?? this.id, + description: description ?? this.description, + completed: completed ?? this.completed, + ); + } +} + +/* SNIPPET START */ + +class TodoList extends Notifier> { + @override + List build() { + return []; + } +} + +final todoListProvider = NotifierProvider>(TodoList.new); diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/weather_provider/codegen.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/weather_provider/codegen.dart new file mode 100644 index 000000000..8f7af2644 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/weather_provider/codegen.dart @@ -0,0 +1,20 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +@riverpod +String city(CityRef ref) => 'London'; + +class Weather {} + +Future fetchWeather({required String city}) async => Weather(); +/* SNIPPET START */ +@riverpod +Future weather(WeatherRef ref) { + // We use `ref.watch` to listen to another provider, and we pass it the provider + // that we want to consume. Here: cityProvider + final city = ref.watch(cityProvider); + + // We can then use the result to do something based on the value of `cityProvider`. + return fetchWeather(city: city); +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/weather_provider/codegen.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/weather_provider/codegen.g.dart new file mode 100644 index 000000000..5398e9c62 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/weather_provider/codegen.g.dart @@ -0,0 +1,40 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$cityHash() => r'2ccdee096b5d5c1cafa736b3e52b788431b9af38'; + +/// See also [city]. +@ProviderFor(city) +final cityProvider = AutoDisposeProvider.internal( + city, + name: r'cityProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$cityHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef CityRef = AutoDisposeProviderRef; +String _$weatherHash() => r'9a79d0269032630918eef9d3f562ff35b5860061'; + +/// See also [weather]. +@ProviderFor(weather) +final weatherProvider = AutoDisposeFutureProvider.internal( + weather, + name: r'weatherProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$weatherHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef WeatherRef = AutoDisposeFutureProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/weather_provider/index.tsx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/weather_provider/index.tsx new file mode 100644 index 000000000..339b89a56 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/weather_provider/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./codegen.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/weather_provider/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/weather_provider/raw.dart new file mode 100644 index 000000000..d25ec4ffc --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/weather_provider/raw.dart @@ -0,0 +1,18 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +final cityProvider = Provider((ref) => 'London'); + +class Weather {} + +Future fetchWeather({required String city}) async => Weather(); + +/* SNIPPET START */ + +final weatherProvider = FutureProvider((ref) async { + // We use `ref.watch` to listen to another provider, and we pass it the provider + // that we want to consume. Here: cityProvider + final city = ref.watch(cityProvider); + + // We can then use the result to do something based on the value of `cityProvider`. + return fetchWeather(city: city); +}); diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/whole_object_provider/codegen.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/whole_object_provider/codegen.dart new file mode 100644 index 000000000..1a90008ed --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/whole_object_provider/codegen.dart @@ -0,0 +1,24 @@ +import 'package:dio/dio.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import 'models.dart'; + +part 'codegen.g.dart'; + +final dio = Dio(); + +/* SNIPPET START */ + +@riverpod +Stream config(ConfigRef ref) => Stream.value(Configuration()); + +@riverpod +Future> products(ProductsRef ref) async { + // Will cause productsProvider to re-fetch the products if anything in the + // configurations changes + final configs = await ref.watch(configProvider.future); + + final result = + await dio.get>>('${configs.host}/products'); + return result.data!.map(Product.fromJson).toList(); +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/whole_object_provider/codegen.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/whole_object_provider/codegen.g.dart new file mode 100644 index 000000000..895d80ff7 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/whole_object_provider/codegen.g.dart @@ -0,0 +1,40 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$configHash() => r'3021d1a8aac384e99d5d22714ffe6e868954888b'; + +/// See also [config]. +@ProviderFor(config) +final configProvider = AutoDisposeStreamProvider.internal( + config, + name: r'configProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$configHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ConfigRef = AutoDisposeStreamProviderRef; +String _$productsHash() => r'637254615fa398af0d36e212f09e5d3d8ff866aa'; + +/// See also [products]. +@ProviderFor(products) +final productsProvider = AutoDisposeFutureProvider>.internal( + products, + name: r'productsProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$productsHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ProductsRef = AutoDisposeFutureProviderRef>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/whole_object_provider/index.tsx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/whole_object_provider/index.tsx new file mode 100644 index 000000000..339b89a56 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/whole_object_provider/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./codegen.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/whole_object_provider/models.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/whole_object_provider/models.dart new file mode 100644 index 000000000..8009c2fd8 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/whole_object_provider/models.dart @@ -0,0 +1,16 @@ +// ignore_for_file: avoid_unused_constructor_parameters + +class Product { + Product(); + factory Product.fromJson(Map json) { + return Product(); + } +} + +class Configuration { + Configuration({ + this.host = '', + }); + + final String host; +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/whole_object_provider/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/whole_object_provider/raw.dart new file mode 100644 index 000000000..be2be903c --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_provider_states/whole_object_provider/raw.dart @@ -0,0 +1,20 @@ +import 'package:dio/dio.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'models.dart'; + +final dio = Dio(); + +/* SNIPPET START */ + +final configProvider = + StreamProvider((ref) => Stream.value(Configuration())); + +final productsProvider = FutureProvider>((ref) async { + // Will cause productsProvider to re-fetch the products if anything in the + // configurations changes + final configs = await ref.watch(configProvider.future); + + final result = await dio.get>>('${configs.host}/products'); + return result.data!.map(Product.fromJson).toList(); +}); diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_providers.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_providers.mdx index d6282f10d..5834dd0c2 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_providers.mdx +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/combining_providers.mdx @@ -1,65 +1,77 @@ --- -title: 组合 Provider 状态 +title: Combining Provider States --- -请确保先阅读有关 [Providers](/docs/concepts/providers) 的内容。 -在本指南中,我们将学习如何组合provider的状态。 +import charactersProvider from "./combining_provider_states/characters_provider"; +import cityProvider from "./combining_provider_states/city_provider"; +import filteredTodoListProvider from "./combining_provider_states/filtered_todo_list_provider"; +import readInProvider from "./combining_provider_states/read_in_provider"; +import selectAsyncProvider from "./combining_provider_states/select_async_provider"; +import todoListProvider from "./combining_provider_states/todo_list_provider"; +import weatherProvider from "./combining_provider_states/weather_provider"; +import wholeObjectProvider from "./combining_provider_states/whole_object_provider"; +import { Link } from "@site/src/components/Link"; +import { + trimSnippet, + AutoSnippet, + When, +} from "@site/src/components/CodeSnippet"; + +:::caution + +本页内容可能已经过时。 +今后会进行更新,但目前您可能需要参考侧边栏顶部的内容(介绍/要点/应用案例/......)。 +::: -## 组合 Provider 状态 +Make sure to read first. +In this guide, we will learn about combining provider states. -我们之前已经了解了如何创建简单的provider。 -但实际在许多情况下,provider想要读取另一个provider的状态。 +## Combining provider states -为此,我们可以使用传递给provider回调函数的e [ref] 对象,并使用它的 [watch] 方法。 +We've previously seen how to create a simple provider. But the reality is, +in many situations a provider will want to read the state of another provider. -例如,思考一下下面的provider: +To do that, we can use the [ref] object passed to the callback of our provider, +and use its [watch] method. -```dart -final cityProvider = Provider((ref) => 'London'); -``` +As an example, consider the following provider: + + We can now create another provider that will consume our `cityProvider`: -现在我们可以创建另一个provider来使用我们的 `cityProvider`: -```dart -final weatherProvider = FutureProvider((ref) async { - // 我们使用 `ref. watch` 来监听另一个provider, - // 我们将使用的cityProvider传递给它。 - final city = ref.watch(cityProvider); + - // 然后,我们可以根据 `cityProvider` 的值来做一些事情。 - return fetchWeather(city: city); -}); -``` +That's it. We've created a provider that depends on another provider. -这样。我们创建了一个依赖于另一个provider的provider。 +## FAQ -## 常见问题解答 +### What if the value being listened to changes over time? -### 如果被监听的值随着时间的推移而改变怎么办? +Depending on the provider that you are listening to, the value obtained may +change over time. +For example, you may be listening to a [NotifierProvider], or the provider +being listened to may have been forced to refresh through the use of +[ProviderContainer.refresh]/[ref.refresh]. -例如,你可能正在监听的 [StateNotifierProvider] 或 -已经强制刷新([ProviderContainer.refresh] / [ref.refresh])过的provider被监听时。 +When using [watch], Riverpod is able to detect that the value being listened to changed +and will _automatically_ re-execute the provider's creation callback when needed. -当使用 [watch] 时,Riverpod能够检测到被监听的值发生了变化 -并且在需要时将_自动_重新执行provider的回调函数。 +This can be useful for computed states. +For example, consider a that exposes a todo-list: -这在计算状态时很有用。 -例如一个 [StateNotifierProvider] 暴露了一个待办清单: + -```dart -class TodoList extends StateNotifier> { - TodoList(): super(const []); -} +A common use-case would be to have the UI filter the list of todos to show +only the completed/uncompleted todos. -final todoListProvider = StateNotifierProvider((ref) => TodoList()); -``` - -一个常见的用例是让UI过滤待办清单列表,只显示完成/未完成的待办事项。 +An easy way to implement such a scenario would be to: -实现这种场景的一个简单方法是: - -- 创建一个 [StateProvider]并暴露当前选择的筛选方法: +- create a [StateProvider], which exposes the currently selected filter method: ```dart enum Filter { @@ -71,70 +83,45 @@ final todoListProvider = StateNotifierProvider((ref) => TodoList()); final filterProvider = StateProvider((ref) => Filter.none); ``` -- 创建一个单独的,结合了筛选器方法和待办清单列表的provider,以暴露经过筛选的待办清单列表: +- make a separate provider which combines the filter method and the todo-list + to expose the filtered todo-list: - ```dart - final filteredTodoListProvider = Provider>((ref) { - final filter = ref.watch(filterProvider); - final todos = ref.watch(todoListProvider); - - switch (filter) { - case Filter.none: - return todos; - case Filter.completed: - return todos.where((todo) => todo.completed).toList(); - case Filter.uncompleted: - return todos.where((todo) => !todo.completed).toList(); - } - }); - ``` + -然后,我们的UI可以监听 `filteredTodoListProvider` 来监听过滤后的待办清单。 -使用这种方法,当过滤器或待办清单列表发生变化时,UI将自动更新。 +Then, our UI can listen to `filteredTodoListProvider` to listen to the filtered todo-list. +Using such an approach, the UI will automatically update when either the filter +or the todo-list changes. -要查看这种方法的详细内容,你可以查看 -[待办清单示例](https://github.com/rrousselGit/riverpod/tree/master/examples/todos)的源代码。 +To see this approach in action, you can look at the source code of the [Todo List +example](https://github.com/rrousselGit/riverpod/tree/master/examples/todos). :::info -这种行为不只针对 [Provider],它适用于所有的provider。 +This behavior is not specific to [Provider], and works with all providers. For example, you could combine [watch] with [FutureProvider] to implement a search feature that supports live-configuration changes: -例如,你可以将 [watch] 与 [FutureProvider] 结合来实现一个支持实时配置更改的搜索功能: - -```dart -// 当前的搜索条件 -final searchProvider = StateProvider((ref) => ''); - -/// 可随时间变化的配置 -final configsProvider = StreamProvider(...); -final charactersProvider = FutureProvider>((ref) async { - final search = ref.watch(searchProvider); - final configs = await ref.watch(configsProvider.future); - final response = await dio.get('${configs.host}/characters?search=$search'); + - return response.data.map((json) => Character.fromJson(json)).toList(); -}); -``` - -这段代码将从服务中获取一个字符列表,并在配置更改或搜索查询更改时自动重新获取该列表。 +This code will fetch a list of characters from the service, and automatically +re-fetch the list whenever the configurations change or when the search query changes. ::: -### 我可以在不监听的情况下读取provider吗? +### Can I read a provider without listening to it? -有时,我们希望读取provider的内容,但不需要在那个值发生变化时重新创建暴露的值。 +Sometimes, we want to read the content of a provider, but without re-creating +the value exposed when the value obtained changes. -一个例子就是`Repository`,它从另一个provider读取用户令牌进行身份验证。 -我们可以在用户令牌更改时使用 [watch] 并创建一个新的 `Repository` ,但这样做几乎没有任何用处。 +An example would be a `Repository`, which reads from another provider the user token +for authentication. +We could use [watch] and create a new `Repository` whenever the user token changes, +but there is little to no use in doing that. -在这种情况下,我们可以使用 [read],它类似于 [watch], -但是当获取的值发生变化时,它不会导致provider重新创建它所暴露的值。 +In this situation, we can use [read], which is similar to [watch], but will not +cause the provider to recreate the value it exposes when the value obtained changes. In that case, a common practice is to pass the provider's `Ref` to the object created. The object created will then be able to read providers whenever it wants. -所以在这种情况,常见的做法是将provider的 `Ref` 传递给创建的对象。 -创建的对象将能够在任何需要的时候读取提供程序。 ```dart final userTokenProvider = StateProvider((ref) => null); @@ -158,25 +145,21 @@ class Repository { } ``` -:::danger **不要**在provider的里面调用 [read] 方法 +:::danger **DON'T** call [read] inside the body of a provider -```dart -final myProvider = Provider((ref) { - // 在此处调用“read”的错误做法 - final value = ref.read(anotherProvider); -}); -``` + -如果你使用 [read] 来避免不必要的对象重建, -请参考[我的provider更新过于频繁我该怎么办](#我的provider更新过于频繁我该怎么办) +If you used [read] as an attempt to avoid unwanted rebuilds of your object, +refer to [My provider updates too often, what can I do?](#my-provider-updates-too-often-what-can-i-do) ::: -### 如何测试一个接收 [ref] 作为其构造函数参数的对象? +### How to test an object that receives [ref] as a parameter of its constructor? -如果你正在使用[我可以在不监听的情况下读取provider吗?](#我可以在不监听的情况下读取provider吗)中描述的模式, -你可能想知道如何为对象编写测试。 +If you are using the pattern described in [Can I read a provider without listening to it?](#can-i-read-a-provider-without-listening-to-it), +you may be wondering how to write tests for your object. -在这个场景中,考虑直接测试provider而不是原始对象。你可以通过使用 [ProviderContainer] 类来实现: +In this scenario, consider testing the provider directly instead of the raw object. +You can do so by using the [ProviderContainer] class: ```dart final repositoryProvider = Provider((ref) => Repository(ref)); @@ -194,49 +177,35 @@ test('fetches catalog', () async { }); ``` -### 我的provider更新过于频繁,我该怎么办? +### My provider updates too often, what can I do? -如果你的对象被频繁地重新创建,你的provider可能会监听它并不关心的对象。 +If your object is re-created too often your provider is likely listening +to objects that it doesn't care about. -例如,你可能正在监听一个 `Configuration` 对象,但只使用 `host` 属性。 -通过监听整个 `Configuration` 对象,如果 `host` 以外的某个属性发生了变化, -仍然会导致重新评估你的provider——这可能是我们不希望所发生的。 +For example, you may be listening to a `Configuration` object, but only use the `host` +property. +By listening to the entire `Configuration` object, if a property other than `host` +changes, this still causes your provider to be re-evaluated – which may be +undesired. -这个问题的解决方案是创建一个单独的provider,_只_在 `Configuration` 中暴露你需要的内容(所以是 `host`): +The solution to this problem is to create a separate provider that exposes _only_ +what you need in `Configuration` (so `host`): -**避免**听整个对象: +**AVOID** listening to the entire object: -```dart -final configProvider = StreamProvider(...); + -final productsProvider = FutureProvider>((ref) async { - // 如果配置(configurations)发生变化,将导致productsProvider重新获取产品 - final configs = await ref.watch(configProvider.future); +**PREFER** using select when you only need a single property of an object: - return dio.get('${configs.host}/products'); -}); -``` - - -当你只需要一个对象的单个属性时,**最好**使用select: - -```dart -final configProvider = StreamProvider(...); - -final productsProvider = FutureProvider>((ref) async { - // 只监听host。如果配置(configurations)中的其他内容发生了更改,这将重新评估我们的provider。 - final host = await ref.watch(configProvider.selectAsync((config) => config.host)); - - return dio.get('$host/products'); -}); -``` + -这将只在 `host` 更改时重新构建productsProvider。 +This will only rebuild the `productsProvider` when the `host` changes. [provider]: ../providers/provider [stateprovider]: ../providers/state_provider [futureprovider]: ../providers/future_provider [statenotifierprovider]: ../providers/state_notifier_provider +[notifierProvider]: ../providers/notifier_provider [ref]: https://pub.dev/documentation/riverpod/latest/riverpod/Ref-class.html [watch]: https://pub.dev/documentation/riverpod/latest/riverpod/Ref/watch.html [read]: https://pub.dev/documentation/riverpod/latest/riverpod/Ref/read.html diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/dialog_scope.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/dialog_scope.dart new file mode 100644 index 000000000..4ee7792f9 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/dialog_scope.dart @@ -0,0 +1,58 @@ +// ignore_for_file: omit_local_variable_types, prefer_final_locals + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +/* SNIPPET START */ + +// Have a counter that is being incremented by the FloatingActionButton +final counterProvider = StateProvider((ref) => 0); + +class Home extends ConsumerWidget { + const Home({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + // We want to show a dialog with the count on a button press + return Scaffold( + body: Column( + children: [ + ElevatedButton( + onPressed: () { + showDialog( + context: context, + builder: (c) { + // We wrap the dialog with a ProviderScope widget, providing the + // parent container to ensure the dialog can access the same providers + // that are accessible by the Home widget. + return ProviderScope( + parent: ProviderScope.containerOf(context), + child: const AlertDialog( + content: CounterDisplay(), + ), + ); + }, + ); + }, + child: const Text('Show Dialog'), + ), + ], + ), + floatingActionButton: FloatingActionButton( + child: const Icon(Icons.add), + onPressed: () { + ref.read(counterProvider.notifier).state++; + }, + )); + } +} + +class CounterDisplay extends ConsumerWidget { + const CounterDisplay({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final count = ref.watch(counterProvider); + return Text('$count'); + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/modifiers/auto_dispose.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/modifiers/auto_dispose.mdx index eaf3e5bf7..eaece8112 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/modifiers/auto_dispose.mdx +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/modifiers/auto_dispose.mdx @@ -2,22 +2,27 @@ title: .autoDispose --- -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; +:::caution +The content of this page may be outdated. +It will be updated in the future, but for now you may want to refer to the content +in the top of the sidebar instead (introduction/essentials/case-studies/...) +::: -一个常见的用例是当不再使用provider时销毁它的状态。 +A common use case is to destroy the state of a provider +when it is no-longer used. -这么做有很多原因,比如: +There are multiple reasons for doing so, such as: -- 使用Firebase时,关闭连接,避免不必要的开销。 -- 当用户离开页面并重新进入时重置状态。 +- When using Firebase, to close the connection and avoid unnecessary cost. +- To reset the state when the user leaves a screen and re-enters it. -Provider内置了 `.autoDispose` 修饰符来支持这些功能。 +Providers come with built-in support for this use case, through the `.autoDispose` +modifier. -## 用法 +## Usage -要告诉Riverpod在provider不再使用时销毁它的状态, -只需将 `.autoDispose` 附加到你的provider上: +To tell Riverpod to destroy the state of a provider when it is no longer used, +simply append `.autoDispose` to your provider: ```dart final userProvider = StreamProvider.autoDispose((ref) { @@ -25,12 +30,14 @@ final userProvider = StreamProvider.autoDispose((ref) { }); ``` -就是这样。当不再使用 `userProvider` 时,它的状态将自动被销毁。 +That's it. Now, the state of `userProvider` will automatically be destroyed +when it is no longer used. -注意泛型参数是在`autoDispose`之后而不是之前传递的 —— `autoDispose` 不是一个命名构造器。 +Note how the generic parameters are passed after `autoDispose` instead of before – +`autoDispose` is not a named constructor. :::note -如果你想,你也可以结合 `.autoDispose` 和其他修饰符: +You can combine `.autoDispose` with other modifiers if you need to: ```dart final userProvider = StreamProvider.autoDispose.family((ref, id) { @@ -42,11 +49,13 @@ final userProvider = StreamProvider.autoDispose.family((ref, id) { ### ref.keepAlive -使用 `autoDispose` 的 provider还会添加一个额外的方法到 `ref` 上: `keepAlive`。 +Marking a provider with `autoDispose` also adds an extra method on `ref`: `keepAlive`. -`keepAlive` 函数用于告诉Riverpod即使不再监听provider的状态也应该保留。 +The `keepAlive` function is used to tell Riverpod that the state of the provider +should be preserved even if no longer listened to. -一个用例是在HTTP请求完成后将此标志设置为 `true`: +A use-case would be to set this flag to `true` after an HTTP request has +completed: ```dart final myProvider = FutureProvider.autoDispose((ref) async { @@ -56,51 +65,56 @@ final myProvider = FutureProvider.autoDispose((ref) async { }); ``` -这样,如果请求失败,用户离开页面,然后重新进入,那么请求将再次执行。 -但是如果请求成功完成,状态将被保留,重新进入就不会触发新的请求。 +This way, if the request fails and the user leaves the screen then re-enters +it, then the request will be performed again. +But if the request completed successfully, the state will be preserved +and re-entering the screen will not trigger a new request. :::info -在1.0.x的版本中, `maintainState` 属性等价于 `keepAlive`。 +In version 1.0.x, the equivalent of `keepAlive` is the property called `maintainState`. ::: -## 示例:取消不再使用的HTTP请求 +## Example: Canceling HTTP requests when no longer used -`autoDispose` 修饰符可以与 [FutureProvider] 和 `ref.onDispose` 结合使用, -以便在不再需要HTTP请求时轻松取消它们。 +The `autoDispose` modifier could be combined with [FutureProvider] and `ref.onDispose` +to easily cancel HTTP requests when they are no longer needed. -目标: +The goal is: -- 当用户进入一个页面时启动一个HTTP请求 -- 如果用户在请求完成前离开页面,则取消HTTP请求 -- 如果请求成功,离开并重新进入页面则不会开始新的请求 +- Start an HTTP request when the user enters a screen +- if the user leaves the screen before the request completed, cancel the HTTP request +- if the request succeeded, leaving and re-entering the screen does not start a new request -下面是代码示例: +In code, this would be: ```dart final myProvider = FutureProvider.autoDispose((ref) async { - // 来自package:dio的一个对象,允许取消http请求 + // An object from package:dio that allows cancelling http requests final cancelToken = CancelToken(); - // 当provider被销毁时,取消http请求 + // When the provider is destroyed, cancel the http request ref.onDispose(() => cancelToken.cancel()); - // 获取我们的数据并传递“cancelToken”来取消工作 + // Fetch our data and pass our `cancelToken` for cancellation to work final response = await dio.get('path', cancelToken: cancelToken); - // 如果请求成功完成,则保持该状态 + // If the request completed successfully, keep the state ref.keepAlive(); return response; }); ``` -## 参数类型 'AutoDisposeProvider' 不能被赋值给参数类型 'AlwaysAliveProviderBase' +## The argument type 'AutoDisposeProvider' can't be assigned to the parameter type 'AlwaysAliveProviderBase' -当使用 `.autoDispose` 时,你可能会发现你的应用在编译时出现类似错误: +When using `.autoDispose`, you may find yourself in a situation where your +application does not compile with an error similar to: > The argument type 'AutoDisposeProvider' can't be assigned to the parameter > type 'AlwaysAliveProviderBase' -不要担心!这个错误是正常的。发生这种情况是因为你很可能编写了一个bug: +Don't worry! This error is voluntary. It happens because you most likely +have a bug: -你试图在未标记 `.autoDispose` 的provider中监听标记了 `.autoDispose` 的provider,例如: +You tried to listen to a provider marked with `.autoDispose` in a provider that +is **not** marked with `.autoDispose`, such as: ```dart final firstProvider = Provider.autoDispose((ref) => 0); @@ -112,9 +126,9 @@ final secondProvider = Provider((ref) { }); ``` -我们不希望这样,因为它将导致 `firstProvider` 永远不会被销毁。 +This is undesired, as it would cause `firstProvider` to never be disposed. -要解决这个问题,可以考虑用 `.autoDispose` 标记 `secondProvider`: +To fix this, consider marking `secondProvider` with `.autoDispose` too: ```dart final firstProvider = Provider.autoDispose((ref) => 0); diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/modifiers/family.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/modifiers/family.mdx index ae2018282..9c64ba95c 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/modifiers/family.mdx +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/modifiers/family.mdx @@ -4,23 +4,31 @@ title: .family import Tabs from "@theme/Tabs"; import TabItem from "@theme/TabItem"; +import { Link } from "@site/src/components/Link"; -在阅读本文之前,请考虑阅读有关 [providers](/docs/concepts/providers) 的内容以及 [如何读取provider](/docs/concepts/reading)。 -在本部分中,我们将详细讨论`.family`修饰符。 +:::caution +The content of this page may be outdated. +It will be updated in the future, but for now you may want to refer to the content +in the top of the sidebar instead (introduction/essentials/case-studies/...) +::: + +Before reading this, consider reading about and . +In this part, we will talk in detail about the `.family` provider modifier. -`.family` 修饰符有一个目的:根据外部参数获取唯一的provider。 +The `.family` modifier has one purpose: Getting a unique provider based on external parameters. -比如`family`的一些常用的使用场景: +Some common use-cases for `family` would be: -- 结合 [FutureProvider] 和 `.family` 通过它的ID来获取一条 `消息`。 -- 将当前 `区域` 传递给provider,以便让我们可以处理翻译相关的内容。 +- Combining [FutureProvider] with `.family` to fetch a `Message` from its ID +- Passing the current `Locale` to a provider, so that we can handle translations -## 用法 +## Usage -family的工作方式是向provider添加一个额外的参数。 -然后可以在我们的provider中自由地使用这个参数来创建一些状态。 +The way families works is by adding an extra parameter to the provider. +This parameter can then be freely used in our provider to create some state. -比如说,我们可以组合 `family` 和 [FutureProvider] 通过它的ID来获取一条 `消息` : +For example, we can combine `family` with [FutureProvider] to fetch +a `Message` from its ID: ```dart final messagesFamily = FutureProvider.family((ref, id) async { @@ -28,8 +36,8 @@ final messagesFamily = FutureProvider.family((ref, id) async { }); ``` -在使用 `messagesFamily` provider时,语法略有不同。 -通常的语法将不能使用: +When using our `messagesFamily` provider, the syntax is slightly different. +The usual syntax will not work anymore: ```dart Widget build(BuildContext context, WidgetRef ref) { @@ -38,7 +46,7 @@ Widget build(BuildContext context, WidgetRef ref) { } ``` -相反,我们需要将一个参数传递给 `messagesFamily`: +Instead, we need to pass a parameter to `messagesFamily`: ```dart Widget build(BuildContext context, WidgetRef ref) { @@ -47,8 +55,9 @@ Widget build(BuildContext context, WidgetRef ref) { ``` :::info -可以同时使用具有不同参数的 family。 -比如,我们可以使用`titleFamily`同时读取法语和英语的翻译: +It is possible to use a family with different parameters simultaneously. +For example, we could use a `titleFamily` to read both the French and English +translations at the same time: ```dart @override @@ -62,20 +71,22 @@ Widget build(BuildContext context, WidgetRef ref) { ::: -## 参数限制 +## Parameter restrictions -为了让family正常工作,传递给provider的参数必须具有一致的 `hashCode` 和 `==`。 +For families to work correctly, it is critical for the parameter passed to +a provider to have a consistent `hashCode` and `==`. +Ideally, the parameter should either be a primitive (bool/int/double/String), +a constant (providers), or an immutable object that overrides `==` and `hashCode`. -理想情况下,参数应该是基础类型(bool/int/double/String)、常量(providers)或重载 `==` 和 `hashCode` 的不可变对象。 +### _PREFER_ using `autoDispose` when the parameter is not constant: -### 当参数不是常量时_最好_使用`autoDispose`: +You may want to use families to pass the input of a search field to your provider. +But that value can change often and never be reused. +This could cause memory leaks as, by default, a provider is never destroyed even +if no longer used. -你可能希望使用family将搜索内容传递给你的provider。 -但是这个值经常会改变,并且永远不会被重用。 -这可能会导致内存泄漏,因为默认情况下,即使不再使用provider也不会销毁。 - -同时使用 `.family` 和 `.autoDispose` 可以解决内存泄漏的问题: +Using both `.family` and `.autoDispose` fixes that memory leak: ```dart final characters = FutureProvider.autoDispose.family, String>((ref, filter) async { @@ -83,21 +94,21 @@ final characters = FutureProvider.autoDispose.family, String>((r }); ``` -## 将多个参数传递给family +## Passing multiple parameters to a family -family不支持将多个值传递给provider。 +Families have no built-in support for passing multiple values to a provider. On the other hand, that value could be _anything_ (as long as it matches with the restrictions mentioned previously). -另一方面,该值可以是任何东西(只要它在前面提到的限制当中)。 -这包括: +This includes: + +- A tuple from [tuple](http://pub.dev/packages/tuple) +- Objects generated with [Freezed] or [built_value](https://pub.dev/packages/built_value) +- Objects using [equatable](https://pub.dev/packages/equatable) -- 一个元组 [tuple](http://pub.dev/packages/tuple) -- 使用[Freezed] 或 [built_value](https://pub.dev/packages/built_value) 生成的对象 -- 使用 [equatable](https://pub.dev/packages/equatable) 的对象 +Here's an example of using [Freezed] or [equatable] for multiple parameters: -下面是使用 [Freezed] 或 [equatable] 来传递多个参数的示例: +本页内容可能已经过时。 +今后会进行更新,但目前您可能需要参考侧边栏顶部的内容(介绍/要点/应用案例/......)。 +::: -## 什么时候创建并销毁我的Provider? +## When does my Provider get created and disposed? -所有不同类型的provider经历的状态是相同的: +The states that all different types of providers can go through are the same: -- 未初始化 -- 活动中 -- 暂停 -- 已销毁 +- Uninitialized +- Alive +- Paused +- Disposed -### 已销毁 / 未初始化 +### Disposed / Uninitialized -**未初始化**或**已销毁**的provider不占用任何内存,因为它的状态没有初始化。 -从本质上来说,它只是定义了在你需要时如何创建provider的状态。 -它将一直保持这种状态,直到**活动中**provider被创建或由于UI中的[WidgetRef] 读取、观察或监听它。 +An **Uninitialized** or **Disposed** provider does not take up any memory since its state is not initialized. +Essentially it is just a definition of how to create the provider's state when you need it. +It will stay that way until an **Alive** provider or a [WidgetRef] from the UI reads, watches, or listens to it. -### 创建中 -> 活动中 +### Creating -> Alive -当读取、监听或观察**未初始化**的provider时,将创建其状态。 +When an **Uninitialized** provider is read, listened to or watched it's state will be created. -在创建期间,你的provider的构建函数将会运行。 -使用回调函数所暴露的 `ref` 读取(read)或观察(watch)的任何provider将根据需要创建,并且将检索它们的状态。 +During creation your provider's build function will be run. +Any providers that you read or watch using the `ref` exposed by the callback will be created as needed and their state will be retrieved. -如果在创建过程中存在任何循环依赖关系,Riverpod将抛出一个错误。 -修复此错误的最佳方式是重新设计依赖项,使其具有单向数据流。 +If there are any circular dependencies during this creation process Riverpod will throw an error. +The best way to fix this error is to redesign your dependencies to have a uni-directional dataflow. -provider的状态存储在 [ProviderContainer] 中。在Flutter应用中,此容器位于[ProviderScope] widget中。 +The provider's state is stored in a [ProviderContainer]. In a Flutter app this container is in a [ProviderScope] widget. -因此,尽管如何创建状态(provider)的定义是全局的,但状态实际上是本地的, -并且使用嵌套的[ProviderScope] widget和覆盖在UI的不同部分中有所不同。 +As such, even though the definition of how to create the state (the provider) is global, the state is actually local, +and can be different in different portions of your UI using nested [ProviderScope] widgets and overrides. -这与flutter widget的工作方式非常相似。你只需为其定义一次, -但是可以根据需要在树的不同部分重用状态。 +This is very similar to how flutter widgets work. You only pay for the definition once, but can reuse the state in different parts of the tree as needed. -### 活动中 +### Alive -当你的provider为**活动中**时,对其状态的更改将导致依赖的provider和/或依赖的UI重新构建。 +When your provider is **Alive**, changes to its state will cause dependent providers and/or the dependent UI to rebuild. -从另一个角度来看,作为响应式框架,你可以观察其他provider,以便在它的某个依赖项发生变化时重建自己。 +From the other perspective, as a reactive framework, you can watch other providers to have the provider recreate itself whenever one of it's dependencies changes. -如果你需要一些依赖于其他状态的长期状态,你可以使用Ref的 [listen] 方法来订阅另一个provider上的更改,而不会导致重新构建该provider。 +If you need to have some long-lived state that depends on other state you can use Ref's [listen] method to subscribe for changes on another provider without causing a rebuild of the provider. -如果你需要使用来自另一个可能有副作用的provider的状态,你可以使用Ref的[read]方法从另一个provider获取当前状态。 +If you need to use the state from another provider in a side-effect, you can use Ref's [read] method to obtain the current state from another provider. -通常,在构造 [StateNotifier] 或 [ChangeNotifier] 类时,应该传入引用, -以允许Notifier根据需要获取依赖项的当前值。 -通过使用来自Riverpod 2.0的新[Notifier] 和 [AsyncNotifier]类,ref已经作为类的实例成员可用。 +Typically when constructing a [StateNotifier] or [ChangeNotifier] class you should pass in the `ref` to allow the Notifier to obtain the current value of dependencies as needed. By using the new [Notifier] and [AsyncNotifier] classes from Riverpod 2.0, the ref is already available as an instance member of the class. -### 活动中 -> 暂停 -当其他provider或UI不再监听**活动的** provider时,它将进入**暂停**状态。这意味着它将不再对正在监听的provider的更改做出反应。 -这是一种优化,如果你没有监听的provider,就没有必要让它保持活动状态。每个未被使用的provider都将返回到**暂停**状态,从而减少你的应用的计算负担。 +### Alive -> Paused +When an **Alive** provider is no longer listened to by other providers or the UI, it goes into a **Paused** state. +This means that it no longer will react to changes on providers it is listening to. +This is an optimization, as if you are not listening to the provider, there is no need to keep it alive. +Every provider not being used will be returned to a **Paused** state, reducing the computational burden of your app. -如果你需要让provider保持活动状态以防止副作用发生,请确保在UI中应该保持活动状态的适当位置监听它。 +If you need to keep a provider alive for side-effects, make sure to listen to it in an appropriate place in the UI where it should be kept alive. -如果你需要在provider暂停时执行一些操作,请使用ref的 [onCancel] 方法来注册回调函数。 +If you need to perform some action when a provider is paused use the ref's [onCancel] method to register callbacks. -当provider从暂停状态恢复到活动状态时,如果你需要执行一些操作,请使用ref的[onResume]方法来注册回调。 +If you need to perform some action when a provider resumes to an Alive state from a paused state, use the ref's [onResume] method to register callbacks. -如果你想销毁状态,那么除了不占用计算资源外,还可以销毁状态的内存,请在provider上使用`.autoDispose`修饰符。 -这将导致它在不再被使用时转换到**已销毁**状态,而不是**暂停**状态。 +If you want the state to be disposed, so that in addition to taking no computational resources, it also disposes of the memory of the state, use the `.autoDispose` modifier on your provider definition. +This will cause it to transition to a **Disposed** state instead of **Paused** when it is no longer being used. -### 活动中 -> 销毁中 +### Alive -> Disposing -销毁provider有几个原因。 +There are a few reasons for a provider to be disposed. -- 当使用`.autoDispose`修饰符定义并不再被UI或其他providerg观察时 -- 当手动刷新或provider失效时 -- 当provider由于被监视的依赖项之一发生变化而被重新创建时 +- When defined using the `.autoDispose` modifier and no longer being watched by the UI or another provider +- When the provider is being manually refreshed or invalidated +- When the provider is being recreated due to one of it's watched dependencies changing -刷新会导致provider立即再次执行创建过程,而无效则会导致provider的下一次读取/观察导致重新构建provider。 +Refreshing causes the provider to immediately go through the creation process again, whereas invalidating causes the next read / watch of the provider to cause the provider to be rebuilt. -## 在状态被销毁前执行操作 -如果你需要在销毁provider时执行一些操作,使用ref的 [onDispose] 方法来注册回调函数。 +## Performing actions before the state destruction +If you need to perform some action when a provider is disposed, use the ref's [onDispose] method to register callbacks. -下面的例子使用onDispose关闭StreamController: +The following example uses onDispose to close a StreamController: :::note -根据所使用的provider,它可能已经处理了清理过程。 -例如,[StateNotifierProvider] 将调用返回的 [StateNotifier] 的`dispose`方法。 +Depending on the provider used, it may already take care of the clean-up process. +For example, [StateNotifierProvider] will call the `dispose` method of the returned [StateNotifier]. ::: [onDispose]: https://pub.dev/documentation/riverpod/latest/riverpod/Ref/onDispose.html diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/provider_observer.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/provider_observer.mdx index e7584160c..88bec051e 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/provider_observer.mdx +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/provider_observer.mdx @@ -4,25 +4,35 @@ title: ProviderObserver import CodeBlock from "@theme/CodeBlock"; import logger from "!!raw-loader!/docs/concepts/provider_observer_logger.dart"; -import { trimSnippet } from "../../../../../src/components/CodeSnippet"; +import { trimSnippet } from "@site/src/components/CodeSnippet"; + +:::caution + +本页内容可能已经过时。 +今后会进行更新,但目前您可能需要参考侧边栏顶部的内容(介绍/要点/应用案例/......)。 +::: -[ProviderObserver] 可以监听ProviderContainer的变更。 +[ProviderObserver] listens to the changes of a ProviderContainer. -要使用它,请扩展ProviderObserver类并重写要使用的方法。 +To use it, extend the class ProviderObserver and override the method you want to use. -[ProviderObserver] 有三种方法: +[ProviderObserver] has three methods : -- 每次初始化provider时都会调用 `didAddProvider`,并且将值暴露为 `value`。 -- 每次销毁provider时都会调用 `didDisposeProvider`。 -- 每当provider发出通知时,都会调用 `didUpdateProvider`。 +- `didAddProvider` is called every time a provider was initialized, and the value exposed is `value`. +- `didDisposeProvider` is called every time a provider was disposed. +- `didUpdateProvider` is called every time by providers when they emit a notification. -### 用法 +### Usage : -[ProviderObserver] 的一个简单用例是通过重写 `didUpdateProvider` 方法来记录provider中的更改。 +A simple use case for [ProviderObserver] is to log the changes in providers by overriding the `didUpdateProvider` method. {trimSnippet(logger)} -现在,每当我们的provider的值被更新时,记录器都会记录它: +Now, every time the value of our provider is updated, the logger will log it: ``` I/flutter (16783): { @@ -31,12 +41,11 @@ I/flutter (16783): "newValue": "1" I/flutter (16783): } ``` -:::note -对于可变的状态,例如 [StateController] ([StateProvider.state] 的状态)和[ChangeNotifier], -新旧值将会是相同的。 +:::note: +For states that are mutable such as [StateController] (the state of [StateProvider.state]) and +[ChangeNotifier] the previousValue and newValue will be the same ::: since they reference the same `StateController` / `ChangeNotifier`. -由于他们引用了相同的`StateController` / `ChangeNotifier`。 [providerobserver]: https://pub.dev/documentation/riverpod/latest/riverpod/ProviderObserver-class.html [statecontroller]: https://pub.dev/documentation/riverpod/latest/riverpod/StateController-class.html diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/provider_observer_logger.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/provider_observer_logger.dart new file mode 100644 index 000000000..40f8120e6 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/provider_observer_logger.dart @@ -0,0 +1,61 @@ +// ignore_for_file: use_key_in_widget_constructors, avoid_print + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +/* SNIPPET START */ + +// A Counter example implemented with riverpod with Logger + +class Logger extends ProviderObserver { + @override + void didUpdateProvider( + ProviderBase provider, + Object? previousValue, + Object? newValue, + ProviderContainer container, + ) { + print(''' +{ + "provider": "${provider.name ?? provider.runtimeType}", + "newValue": "$newValue" +}'''); + } +} + +void main() { + runApp( + // Adding ProviderScope enables Riverpod for the entire project + // Adding our Logger to the list of observers + ProviderScope(observers: [Logger()], child: const MyApp()), + ); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp(home: Home()); + } +} + +final counterProvider = StateProvider((ref) => 0, name: 'counter'); + +class Home extends ConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + final count = ref.watch(counterProvider); + + return Scaffold( + appBar: AppBar(title: const Text('Counter example')), + body: Center( + child: Text('$count'), + ), + floatingActionButton: FloatingActionButton( + onPressed: () => ref.read(counterProvider.notifier).state++, + child: const Icon(Icons.add), + ), + ); + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/providers.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/providers.mdx index ff5a7ac2f..e3831587e 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/providers.mdx +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/providers.mdx @@ -2,80 +2,102 @@ title: Providers --- -现在我们安装了 [Riverpod], 接下来我们讲一下 "providers" 相关的概念。 +import creatingProvider from "./providers/creating_a_provider"; +import declaringManyProviders from "./providers/declaring_many_providers"; +import { + AutoSnippet, +} from "@site/src/components/CodeSnippet"; +import { Link } from "@site/src/components/Link"; -Providers 是 [Riverpod] 应用重要的组成部分。 -provider是一个封装了一部分状态的对象,并且能够监听其中的状态。 +:::caution + +本页内容可能已经过时。 +今后会进行更新,但目前您可能需要参考侧边栏顶部的内容(介绍/要点/应用案例/......)。 +::: -## 为什么要使用providers? +Now that we have installed [Riverpod], let's talk about "providers". -将一部分状态包裹在provider中: +Providers are the most important part of a [Riverpod] application. +A provider is an object that encapsulates a piece of state and allows listening +to that state. -- 允许在各种地方简单地访问所需的状态。 Provider可以完全取代各种编程模式,比如: - 单例模式、服务定位器模式、依赖注入或 InheritedWidgets。 +## Why use providers? -- 与其他状态组合很容易。 - 想要将多个对象合并成一个对象而感到苦恼? Provider自带了这个功能。 +Wrapping a piece of state in a provider: -- 性能优化。 - 无论是筛选功能的widget还是缓存一个计算量比较大的状态, - provider能够确保只有受状态变化影响的部分才会被重新计算。 +- Allows easily accessing that state in multiple locations. + Providers are a complete replacement for patterns like Singletons, + Service Locators, Dependency Injection or InheritedWidgets. -- 提高你的应用程序的可测试性。 - 使用 provider, 你不需要复杂的 `setUp`/`tearDown` (配置/销毁) 的过程。 - 除此之外,在测试中,任何provider都可以覆盖不同的行为,这能够轻松地测试非常特殊的行为。 +- Simplifies combining this state with others. + Ever struggled to merge multiple objects into one? This scenario is built + directly inside providers. -- 能够方便地集成一些高级的功能, 比如登录或下拉刷新. +- Enables performance optimizations. + Whether for filtering widget rebuilds or for caching expensive state computations; + providers ensure that only what is impacted by a state change is recomputed. -## 创建一个provider +- Increases the testability of your application. + With providers, you do not need complex `setUp`/`tearDown` steps. Furthermore, + any provider can be overridden to behave differently during a test, which + allows easily testing a very specific behavior. -Provider有许多变体,但它们的工作方式都是一样的。 +- Allows easy integration with advanced features, such as logging or + pull-to-refresh. -如下所示,最常见的使用方法是将它们声明为全局常量: +## Creating a provider -```dart -final myProvider = Provider((ref) { - return MyValue(); -}); -``` +Providers come in many variants, but they all work the same way. + +The most common usage is to declare them as global constants like so: + + :::note -不要因为provider的功能全面而感到害怕。Provider是完全不可变的。 -定义一个provider和定义一个函数一样简单,而且provider是可测试和可维护的。 +Do not be frightened by the global aspect of providers. +Providers are fully immutable. Declaring a provider is no different from declaring +a function, and providers are testable and maintainable. ::: -这段代码由三部分组成: +This snippet consists of three components: -- `final myProvider`, 声明myProvider这个变量。 - 这个变量我们将会用来读取其中的状态。 Provider应当一直是 `final`的。 +- `final myProvider`, the declaration of a variable. + This variable is what we will use in the future to read the state of our provider. + Providers should always be `final`. -- `Provider`, 我们决定使用的provider类型。 - [Provider] 大多数provider类型的基础。 它暴露了一个永远不会改变的常量。 - 我们可以吧 [Provider] 换成 其他provider 比如:[StreamProvider] 或 [StateNotifierProvider] - 来改变其中状态的类型。 +- `Provider`, the provider that we decided to use. + [Provider] is the most basic of all providers. It exposes an object that never + changes. + We could replace [Provider] with other providers like [StreamProvider] or + [NotifierProvider], to change how the value is interacted with. -- 一个创建共享状态的函数。 - 那个函数会接收一个叫`ref`的对象作为参数。这个`ref`对象能够让我们在函数中读取其他的provider, - 当provider中的状态需要销毁时执行一些操作等等。 +- A function that creates the shared state. + That function will always receive an object called `ref` as a parameter. This object + allows us to read other providers, perform some operations when the state + of our provider will be destroyed, and much more. -provider中的函数返回的对象类型取决于使用provider类型。 -比如一个 [Provider] 的函数中可以返回任意的对象。 -再比如说[StreamProvider]中的函数的返回值类型只能是[Stream]。 +The type of the object returned by the function passed to a provider depends on +the provider used. +For example, the function of a [Provider] can create any object. +On the other hand, [StreamProvider]'s callback will be expected to return a [Stream]. :::info -你可以没有限制地声明各种provider。 -与使用 `package:provider` 相反, [Riverpod] 允许创建多个相同类型且暴露不同状态的provider: +You can declare as many providers as you want without limitations. +As opposed to when using `package:provider`, [Riverpod] allows creating multiple +providers exposing a state of the same "type": -```dart -final cityProvider = Provider((ref) => 'London'); -final countryProvider = Provider((ref) => 'England'); -``` + -创建都是`String`类型的provider不会有任何问题。 +The fact that both providers create a `String` does not cause any problem. ::: :::caution -在Flutter平台上,为了让provider正常工作,你必须用 [ProviderScope] 包裹你的Flutter应用: +For providers to work, you must add [ProviderScope] at the root of your +Flutter applications: ```dart void main() { @@ -85,48 +107,52 @@ void main() { ::: -## 不同类型的Provider +## Different Types of Providers -有很多类型的provider可供在不同的情况下使用。 +There are multiple types of providers for multiple different use cases. -由于这些不同类型provider都可使用,有的时候很难理解什么时候应该使用这种provider而不是另外一种provider。 -选择下面表格中的provider添加到你的应用中。 +With all of these providers available, it is sometimes difficult to understand when to use one provider type over another. +Use the table below to choose a provider that fits what you want to provide to the widget tree. -| Provider 类型 | 创建Provider的函数 | 使用场景 | -| ------------------------ | -------------------------- | -------------------------------------------------- | -| [Provider] | 返回任意类型 | 服务类 / 计算属性 (过滤的列表) | -| [StateProvider] | 返回任意类型 | 过滤条件/简单状态对象 | -| [FutureProvider] | 返回任意类型的Future | API调用的结果 | -| [StreamProvider] | 返回任意类型的Stream | API返回的Stream | -| [StateNotifierProvider] | 返回StateNotifier的子类 | 一种复杂的状态对象,除了通过接口之外,它是不可变的 | -| [ChangeNotifierProvider] | 返回ChangeNotifier的子类 | 需要可变的复杂状态对象 | +| Provider Type | Provider Create Function | Example Use Case | +| ------------------------ | ------------------------------------- | ----------------------------------------------------------------------------------------------------- | +| [Provider] | Returns any type | A service class / computed property (filtered list) | +| [StateProvider] | Returns any type | A filter condition / simple state object | +| [FutureProvider] | Returns a Future of any type | A result from an API call | +| [StreamProvider] | Returns a Stream of any type | A stream of results from an API | +| [NotifierProvider] | Returns a subclass of (Async)Notifier | A complex state object that is immutable except through an interface | +| [StateNotifierProvider] | Returns a subclass of StateNotifier | A complex state object that is immutable except through an interface. Prefer using a notifierProvider | +| [ChangeNotifierProvider] | Returns a subclass of ChangeNotifier | A complex state object that requires mutability | :::caution -尽管所有的provider都有它的使用目的,由于 [不可变状态相关的问题](/docs/concepts/why_immutability) 的原因, -我们不推荐在较大型的应用程序中使用 [ChangeNotifierProvider] 。 -`flutter_riverpod` 中的 [ChangeNotifierProvider] 提供了一个简单的方式来让你从 `package:provider` 迁移到 [riverpod] , -这允许一些 flutter 上一些特定的用法比如与Navigator 2 package 集成。 -::: +While all providers have their purpose, [ChangeNotifierProvider]s are not recommended for scalable applications. See . It exists in the +`flutter_riverpod` package to provide an easy migration path from +`package:provider`, and allows for some flutter specific use-cases such as +integration with some Navigator 2 packages. ::: -## Provider 修饰符 +## Provider Modifiers -所有的Provider都有一个内置的方式为不同的provider添加额外的功能。 +All Providers have a built-in way to add extra functionalities to your different providers. -它们可能会向`ref`对象添加额外的功能或改变provider的使用方式。 -修饰符可以在所有provider上通过简单的命名构造器使用: +They may add new features to the `ref` object or change slightly how the provider +is consumed. +Modifiers can be used on all providers, with a syntax similar to named constructor: ```dart final myAutoDisposeProvider = StateProvider.autoDispose((ref) => 0); final myFamilyProvider = Provider.family((ref, id) => '$id'); ``` -目前有两个修饰符可用: +At the moment, there are two modifiers available: -- [`.autoDispose`](/docs/concepts/modifiers/auto_dispose)可以在状态不在被监听的情况下让provider自动销毁。 -- [`.family`](/docs/concepts/modifiers/family)可以创建有外部参数的provider。 +- , which will make the + provider automatically destroy its state when it is no longer being listened + to. +- , which allows creating a + provider from external parameters. :::note -provider能一次使用多个修饰符: +A provider can use multiple modifiers at once: ```dart final userProvider = FutureProvider.autoDispose.family((ref, userId) async { @@ -136,10 +162,10 @@ final userProvider = FutureProvider.autoDispose.family((ref, userId) ::: -着就是本指南的内容! +That's it for this guide! -继续阅读 [如何读取provider](/docs/concepts/reading) -或者学习 [如何组合provider](/docs/concepts/combining_providers)。 +You can continue with . +Alternatively, you can see . [get_it]: http://pub.dev/packages/get_it [inheritedwidget]: https://api.flutter.dev/flutter/widgets/InheritedWidget-class.html @@ -154,6 +180,6 @@ final userProvider = FutureProvider.autoDispose.family((ref, userId) [futureprovider]: /docs/providers/future_provider [stateprovider]: /docs/providers/state_provider [statenotifierprovider]: /docs/providers/state_notifier_provider +[notifierProvider]: /docs/providers/notifier_provider [changenotifierprovider]: https://pub.dev/documentation/flutter_riverpod/latest/flutter_riverpod/ChangeNotifierProvider-class.html - [providerscope]: https://pub.dev/documentation/flutter_riverpod/latest/flutter_riverpod/ProviderScope-class.html diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/providers/creating_a_provider/codegen.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/providers/creating_a_provider/codegen.dart new file mode 100644 index 000000000..8dc6c5e12 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/providers/creating_a_provider/codegen.dart @@ -0,0 +1,12 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +class MyValue {} + +/* SNIPPET START */ + +@riverpod +MyValue my(MyRef ref) { + return MyValue(); +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/providers/creating_a_provider/codegen.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/providers/creating_a_provider/codegen.g.dart new file mode 100644 index 000000000..601ed73b6 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/providers/creating_a_provider/codegen.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$myHash() => r'0810ee24cae78c131d00773ac20d254c83eefab7'; + +/// See also [my]. +@ProviderFor(my) +final myProvider = AutoDisposeProvider.internal( + my, + name: r'myProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$myHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef MyRef = AutoDisposeProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/providers/creating_a_provider/index.tsx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/providers/creating_a_provider/index.tsx new file mode 100644 index 000000000..339b89a56 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/providers/creating_a_provider/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./codegen.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/providers/creating_a_provider/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/providers/creating_a_provider/raw.dart new file mode 100644 index 000000000..d63faf94b --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/providers/creating_a_provider/raw.dart @@ -0,0 +1,9 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +class MyValue {} + +/* SNIPPET START */ + +final myProvider = Provider((ref) { + return MyValue(); +}); diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/providers/declaring_many_providers/codegen.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/providers/declaring_many_providers/codegen.dart new file mode 100644 index 000000000..a9472e02a --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/providers/declaring_many_providers/codegen.dart @@ -0,0 +1,11 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +/* SNIPPET START */ + +@riverpod +String city(CityRef ref) => 'London'; +@riverpod +String country(CountryRef ref) => 'England'; + diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/providers/declaring_many_providers/codegen.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/providers/declaring_many_providers/codegen.g.dart new file mode 100644 index 000000000..b3a642c74 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/providers/declaring_many_providers/codegen.g.dart @@ -0,0 +1,40 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$cityHash() => r'2ccdee096b5d5c1cafa736b3e52b788431b9af38'; + +/// See also [city]. +@ProviderFor(city) +final cityProvider = AutoDisposeProvider.internal( + city, + name: r'cityProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$cityHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef CityRef = AutoDisposeProviderRef; +String _$countryHash() => r'd1513349c3bc0c99763cb4fb29eb012f2351bc4c'; + +/// See also [country]. +@ProviderFor(country) +final countryProvider = AutoDisposeProvider.internal( + country, + name: r'countryProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$countryHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef CountryRef = AutoDisposeProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/providers/declaring_many_providers/index.tsx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/providers/declaring_many_providers/index.tsx new file mode 100644 index 000000000..339b89a56 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/providers/declaring_many_providers/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./codegen.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/providers/declaring_many_providers/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/providers/declaring_many_providers/raw.dart new file mode 100644 index 000000000..60a3d4786 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/providers/declaring_many_providers/raw.dart @@ -0,0 +1,6 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +/* SNIPPET START */ + +final cityProvider = Provider((ref) => 'London'); +final countryProvider = Provider((ref) => 'England'); \ No newline at end of file diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading.mdx index 3f6c19154..fffa27013 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading.mdx +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading.mdx @@ -1,9 +1,7 @@ --- -title: 读取 Provider +title: Reading a Provider --- -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; import CodeBlock from "@theme/CodeBlock"; import counter from "./reading/counter"; import consumerWidget from "./reading/consumer_widget"; @@ -22,58 +20,74 @@ import { trimSnippet, AutoSnippet, When, -} from "../../../../../src/components/CodeSnippet"; +} from "@site/src/components/CodeSnippet"; +import { Link } from "@site/src/components/Link"; -在阅读本指南之前,请确保先阅读有关[Providers](/docs/concepts/providers)的内容。 +:::caution + +本页内容可能已经过时。 +今后会进行更新,但目前您可能需要参考侧边栏顶部的内容(介绍/要点/应用案例/......)。 +::: + +Before reading this guide, make sure to first. -在本指南中,我们将了解如何使用provider。 +In this guide, we will see how to consume a provider. -## 获取一个“ref”对象 +## Obtaining a "ref" object -首先,也是最重要的,在读取provider之前,我们需要获取一个“ref”对象。 +First and foremost, before reading a provider, we need to obtain a "ref" object. -这个对象能够让我们与provider交互,不管是来自widget还是其他provider。 +This object is what allows us to interact with providers, be it from a widget +or another provider. -### 从provider获取“ref” +### Obtaining a "ref" from a provider -所有provider都接收一个“ref”作为参数: +All providers receive a "ref" as a parameter: -将此参数传递给provider暴露的值是安全的。 +This parameter is safe to pass to the value exposed by the provider. -一个常见的用例是将provider的“ref”传递给`StateNotifier` +A common use-case is to pass the provider's "ref" to a `StateNotifier` -这样做允许`Counter`类读取provider。 +Doing so allows our `Counter` class to read providers. -### 从widget获取“ref” +### Obtaining a "ref" from a widget -Widget自然没有ref参数。但是 [Riverpod] 提供了多种从widget中获取ref的解决方案。 +Widgets naturally do not have a ref parameter. But [Riverpod] offers multiple +solutions to obtain one from widgets. -#### 扩展ConsumerWidget而不是StatelessWidget +#### Extending ConsumerWidget instead of StatelessWidget -在widget树中获取ref的最常用方法是将 [StatelessWidget] 替换为 [ConsumerWidget] 。 +The most common way to obtain a ref in the widget tree is +to replace [StatelessWidget] with [ConsumerWidget]. -[ConsumerWidget] 在用法上与 [StatelessWidget] 相同,唯一的区别是它在构建方法上有一个额外的参数:“ref”对象。 +[ConsumerWidget] is identical in use to [StatelessWidget], with the only +difference being that it has an extra parameter on its build method: the "ref" object. -一个典型的 [ConsumerWidget] 如下所示: +A typical [ConsumerWidget] looks like: -#### 扩展ConsumerStatefulWidget+ConsumerState而不是StatefulWidget+State +#### Extending ConsumerStatefulWidget+ConsumerState instead of StatefulWidget+State -与 [ConsumerWidget] 类似, [ConsumerStatefulWidget] 和 [ConsumerState] -等价于带有状态的StatefulWidget,区别在于状态中有一个“ref”对象。 +Similar to [ConsumerWidget], [ConsumerStatefulWidget] and [ConsumerState] are the equivalent of a +[StatefulWidget] with its [State], with the difference that the state has a "ref" object. -这一次,“ref”没有作为构建方法的参数传递,而是作为 [ConsumerState] 对象的属性传递: +This time, the "ref" isn't passed as parameter of the build method, but is +a property of the [ConsumerState] object: @@ -81,226 +95,251 @@ Widget自然没有ref参数。但是 [Riverpod] 提供了多种从widget中获 -#### 扩展HookConsumerWidget类而不是HookWidget类 +#### Extending HookConsumerWidget instead of HookWidget -此选项适用于使用 [flutter_hooks] 的用户。由于 [flutter_hooks] 需要扩展 [HookWidget] 才能工作, -使用钩子的widget无法扩展 [ConsumerWidget]。 +This option is for [flutter_hooks] users. Since [flutter_hooks] requires +extending [HookWidget] to work, widgets that use hooks are unable to extend +[ConsumerWidget]. -[hooks_riverpod] package 暴露了一个名为 [HookConsumerWidget] 的widget。 -[HookConsumerWidget] 同时充当 [ConsumerWidget] 和 [HookWidget]。 -这允许widget既监听provider又使用钩子。 +The package [hooks_riverpod] exposes a new widget called [HookConsumerWidget]. +[HookConsumerWidget] acts as both a [ConsumerWidget] and a [HookWidget]. This +allows a widget to both listen to providers and use hooks. -比如说: +An example would be: -#### 扩展 StatefulHookConsumerWidget 而不是 HookWidget +#### Extending StatefulHookConsumerWidget instead of HookWidget -此选项适用于 [flutter_hooks] 用户,他们需要使用 [StatefulWidget] 生命周期方法还有钩子。 +This option is for [flutter_hooks] users, who need to use [StatefulWidget] lifecycle methods in addition to hooks. + +An example would be: -举个例子: -#### Consumer 和 HookConsumer widgets +#### Consumer and HookConsumer widgets -获得widget内部的“ref”的最后一种方法是依赖 [Consumer]/[HookConsumer]。 +A final way to obtain a "ref" inside widgets is to rely on [Consumer]/ +[HookConsumer]. -这些类是可用于在build回调中获取“ref”的widget,具有与 [ConsumerWidget]/[HookConsumerWidget] 相同的属性。 +These classes are widgets that can be used to obtain a "ref" in a builder callback, with the same +properties as [ConsumerWidget]/[HookConsumerWidget]. -因此,这些widget是一种不需要定义类就能获得“ref”的方法。比如: +As such, these widgets are a way to obtain a "ref" without having to define a class. +An example would be: {trimSnippet(consumerHook)} -## 使用ref与provider交互 +## Using ref to interact with providers -现在我们有了一个“ref”,我们可以开始使用它了。 +Now that we have a "ref", we can start using it. -“ref”有三种主要用法: +There are three primary usages for "ref": -- 获取provider的值并监听更改,这样当该值发生更改时, - 将重新构建订阅该值的widget或provider。 - 这是使用 `ref.watch` 完成的 -- 在provider上添加监听器,以执行诸如导航到新页面或每当provider更改时显示模态框等操作。 - 这是使用 `ref.listen` 完成的。 -- 在忽略更改的情况下获取provider的值。 - 当我们在诸如“on click”之类的事件中需要provider的值时很有用。 - 这是使用 `ref.read` 完成的。 +- obtaining the value of a provider and listening to changes, such that when + this value changes, this will rebuild the widget or provider that subscribed + to the value. + This is done using `ref.watch` +- adding a listener on a provider, to execute an action such as navigating to a new + page or showing a modal whenever that provider changes. + This is done using `ref.listen`. +- obtaining the value of a provider while ignoring changes. + This is useful when we need the value of a provider in an event + such as "on click". + This is done using `ref.read`. :::note -尽可能使用 `ref.watch` 而不是 `ref.read` 或 `ref.listen` 来实现你的功能。 -通过依赖 `ref.watch` ,你的应用变得既具有响应性又具有声明性,这使得项目会更易于维护。 +Whenever possible, prefer using `ref.watch` over `ref.read` or `ref.listen` to +implement a feature. +By relying on `ref.watch`, your application becomes both reactive +and declarative, which makes it more maintainable. ::: ### Using ref.watch to observe a provider -### 使用 ref.watch 观察provider -Ref.watch在widget的`build`方法中或在provider的主体中使用,以使widget/provider监听provider: +`ref.watch` is used inside the `build` method of a widget or +inside the body of a provider to have the widget/provider listen to a provider: -例如,一个provider可以使用 `ref.watch` 将多个provider组合成一个新值。 +For example, a provider could use `ref.watch` to combine multiple providers +into a new value. -筛选待办清单就是一个例子。我们可以有两个provider: +An example would be filtering a todo-list. +We could have two providers: -- `filterTypeProvider`,一个能够暴露当前过滤器类型(不显示,只显示完成的内容等等)的provider。 - -- `todosProvider`,暴露整个待办清单列表的provider。 +- `filterTypeProvider`, a provider that exposes the current type of filter + (none, show only completed tasks, ...) +- `todosProvider`, a provider that exposes the entire list of tasks -通过 `ref.watch`,我们可以创建第三个provider, -它结合了这两个provider来创建一个过滤过的待办清单列表: +And by using `ref.watch`, we could make a third provider that combines both providers to +create a filtered list of tasks: -有了这段代码,`filteredTodoListProvider` 现在暴露了过滤后的清单列表。 +With this code, `filteredTodoListProvider` now exposes the filtered list of tasks. -如果筛选器或待办清单列表发生变化,筛选后的列表也会自动更新。 -同时,如果过滤器和待办清单列表都没有改变,则不会重新计算那个列表。 +The filtered list will also automatically update if either the filter or the list of tasks +changed. At the same time, the filtered list will not be recomputed if +neither the filter nor the list of tasks changed. -类似地,widget可以使用ref.watch显示来自provider的内容, -并在内容发生变化时更新用户界面: +Similarly, a widget can use `ref.watch` to show +the content from a provider and update the user interface whenever that content changes: -这个代码段显示了一个widget,它监听了存储`count`的provider。 -如果该`count`发生变化,widget将重新构建,UI将更新以显示新的值。 +This snippet shows a widget that listens to a provider which stores a `count`. +And if that `count` changes, the widget will rebuild and the UI will update +to show the new value. :::caution -像在 [ElevatedButton] 的 `onPressed` 中那样,`watch` 方法不应该被异步调用。 -它也不应该在 `initState` 和其他 [State] 的生命周期中使用。 +The `watch` method should not be called asynchronously, +like inside an `onPressed` of an [ElevatedButton]. Nor should it be used +inside `initState` and other [State] life-cycles. -在这种情况下,请考虑使用 `ref.read`。 +In those cases, consider using `ref.read` instead. ::: -### 使用ref.listen来响应provider的变化 +### Using ref.listen to react to a provider change -与 `ref.watch` 类似,也可以使用ref.listen来观察一个provider。 +Similarly to `ref.watch`, it is possible to use `ref.listen` to observe a provider. The main difference between them is that, rather than rebuilding the widget/provider if the listened to provider changes, using `ref.listen` will instead call a custom function. -它们之间的主要区别是,如果监听的provider发生更改, -使用 `ref.listen` 将调用自定义的函数,而不是重新构建widget/provider。 -这对于在发生特定变化时执行操作很有用,例如在发生错误时显示snackbar。 +That can be useful for performing actions when a certain change happens, such +as showing a snackbar when an error happens. -`ref.listen` 方法需要两个位置参数,第一个是Provider,第二个是当状态改变时我们想要执行的回调函数。 -当调用回调函数时将传递前一个状态的值和新状态的值。 +The `ref.listen` method needs 2 positional arguments, the first one is the Provider and the second one is the callback function that we want to execute when the state changes. +The callback function when called will be passed 2 values, the value of the previous State and the value of the new State. -`ref.listen` 方法可以在provider内部使用: +The `ref.listen` method can be used inside the body of a provider: -或者在widget的 `build` 方法中: +or inside the `build` method of a widget: :::caution -像在 [ElevatedButton] 的 `onPressed` 中那样,`listen` 方法不应该被异步调用。 -它也不应该在 `initState` 和其他 [State] 的生命周期中使用。 +The `listen` method should not be called asynchronously, +like inside an `onPressed` of an [ElevatedButton]. Nor should it be used +inside `initState` and other [State] life-cycles. ::: -### 使用ref.read获取一个provider的状态 +### Using ref.read to obtain the state of a provider -`ref.read` 是一种不监听provider状态的方法。 +The `ref.read` method is a way to obtain the state of a provider without listening to it. -它通常在用户交互触发的函数中使用。 -例如,我们可以使用 `ref.read` 在用户单击按钮时将计数器数值加1: +It is commonly used inside functions triggered by user interactions. +For example, we can use `ref.read` to increment a counter when a user clicks a button: :::note -你应该尽量避免使用 `ref.read` ,因为它不是响应式的。 +Using `ref.read` should be avoided as much as possible because it is not reactive. -它的存在是由于使用 `watch` 或 `listen` 会导致问题。如果可以,使用 `watch`/`listen` 更好,尤其是 `watch`。 +It exists for cases where using `watch` or `listen` would cause issues. +If you can, it is almost always better to use `watch`/`listen`, especially `watch`. ::: -#### **不要**在build方法中使用 `ref.read` +#### **DON'T** use `ref.read` inside the build method -你可能会想使用 `ref.read` 来优化widget的性能: +You might be tempted to use `ref.read` to optimize the performance of a widget +by doing: -但这样做是非常糟糕的,它可能会导致预料之外的bug。 +But this is a very bad practice and can cause bugs that are difficult to track. -以这种方式使用 `ref.read` 通常会让人觉得“provider暴露的值永远不会改变, -所以使用 `ref.read` 是安全的”。但问题是, -虽然现在的provider可能确实永远不会更新它的值,但你无法保证以后的值还是一样的。 +Using `ref.read` this way is commonly associated with the thought "The value +exposed by a provider never changes so using 'ref.read' is safe". The problem +with this assumption is that, while today that provider may indeed never update +its value, there is no guarantee that tomorrow will be the same. -应用往往会发生很多变更,假设在未来,以前从未改变的一个值将需要改变。 -如果你使用 `ref.read`,当该值需要更改时,你必须遍历整个代码库将 `ref.read` 更改为 `ref.watch`, -这很容易出错,而且你很可能会忘记某些情况。 +Software tends to change a lot, and it is likely that in the future, a value +that previously never changed will need to change. +If you use `ref.read`, when that value needs to change, you have +to go through your entire codebase to change `ref.read` into `ref.watch` – +which is error prone and you are likely to forget some cases. -但如果一开始就使用 `ref.watch`,当你重构时遇到的问题就会更少。 +If you use `ref.watch` to begin with, you will have fewer problems when refactoring. -**_但是我想要用 `ref.read` 来减少小部件重新构建的次数_** +**_But I wanted to use `ref.read` to reduce the number of times my widget rebuilds_** -这个想法很好,但需要注意的是,使用 `ref.watch` 也可以达到完全相同的效果(减少重新构建的次数)。 +While the goal is commendable, it is important to note that you can achieve the +exact same effect (reducing the number of builds) using `ref.watch` instead. -provider提供了许多获取值的方法,同时也减少了重新构建的次数,你可以使用这些方法。 +Providers offer various ways to obtain a value while reducing the number of +rebuilds, which you could use instead. -比如,不应该这样: +For example instead of -我们应该: +we could do: -这两段代码实现了相同的效果:当计数器增加时,我们的按钮也不会重新构建。 +Both snippets achieve the same effect: our button will not rebuild when the +counter increments. -另一方面,第二种方法支持重置计数器。例如,应用的另一部分可以调用: +On the other hand, the second approach supports cases where the counter is reset. +For example, another part of the application could call: ```dart ref.refresh(counterProvider); ``` + +which would recreate the `StateController` object. - -来重新创建 `Counter` 对象。 - -如果我们在这里使用 `ref.read`,我们的按钮仍将使用之前的 `StateController` 实例, -但实际上该实例已被丢弃,不应该再使用。 -当我们正确使用 `ref.watch` 将重新构建按钮以使用新的 `Counter`实例 +If we used `ref.read` here, our button would still use the previous +`StateController` instance, which was disposed and should no-longer be used. +Whereas using `ref.watch` correctly rebuilds the button to use the new +`StateController`. -来重新创建 `Counter` 对象。 +which would recreate the `Counter` object. -如果我们在这里使用 `ref.read`,我们的按钮仍将使用之前的 `Counter` 实例, -但实际上该实例已被丢弃,不应该再使用。 -而正确使用 `ref.watch` 将重新构建按钮以使用新的 `Counter`实例。 +If we used `ref.read` here, our button would still use the previous `Counter` +instance, which was disposed and should no-longer be used. Whereas using +`ref.watch` correctly rebuilds the button to use the new `Counter`. -## 选择读取的方式 +## Deciding what to read -根据你想要监听的provider,你可能有多个可以监听的值。 +Depending on the provider you want to listen to, you may have multiple possible +values that you can listen to. -比如,考虑以下 [StreamProvider]: +As an example, consider the following [StreamProvider]: ```dart final userProvider = StreamProvider(...); ``` -当读取这个 `userProvider` 时,你可以: +When reading this `userProvider`, you can: -- 通过监听 `userProvider` 本身 同步读取当前状态: +- synchronously read the current state by listening to `userProvider` itself: ```dart Widget build(BuildContext context, WidgetRef ref) { AsyncValue user = ref.watch(userProvider); - return user.when( - loading: () => const CircularProgressIndicator(), - error: (error, stack) => const Text('Oops'), - data: (user) => Text(user.name), - ); + return switch (user) { + AsyncData(:final value) => Text(value.name), + AsyncError(:final error) => const Text('Oops $error'), + _ => const CircularProgressIndicator(), + }; } ``` -- 通过监听 `userProvider.stream` 获取关联的 [Stream]: +- obtain the associated [Stream], by listening to `userProvider.stream`: ```dart Widget build(BuildContext context, WidgetRef ref) { @@ -308,7 +347,7 @@ final userProvider = StreamProvider(...); } ``` -- 通过监听 `userProvider.future`,获得一个解析最新值的 [Future] : +- obtain a [Future] that resolves with the latest value emitted, by listening to `userProvider.future`: ```dart Widget build(BuildContext context, WidgetRef ref) { @@ -316,18 +355,21 @@ final userProvider = StreamProvider(...); } ``` -不同的provider可能提供用法。 -要了解更多信息,请阅读[API 参考](https://pub.dev/documentation/riverpod/latest/riverpod/riverpod-library.html)来获取每个provider的文档。 +Other providers may offer different alternative values. +For more information, refer to the documentation of each provider by +consulting the [API reference](https://pub.dev/documentation/riverpod/latest/riverpod/riverpod-library.html). -## 使用“select”来过滤重建内容 +## Using "select" to filter rebuilds -与读取provider相关的最后一个特性是能够减少widget/provider从 `ref.watch` 重新构建的次数, -或者减少 `ref.listen` 执行函数的频率。 +One final feature to mention related to reading providers is the ability to +reduce the number of times a widget/provider rebuilds from `ref.watch`, or how often `ref.listen` +executes a function. -记住,这一点很重要,因为默认情况下,监听provider将监听整个对象状态。 -但有时widget/provider可能只关心某些属性的更改,而不是整个对象。 +This is important to keep in mind as, by default, listening to a provider +listens to the entire object state. But sometimes, a widget/provider may only +care about changes to some properties instead of the whole object. -比如说,一个provider可能暴露一个 `User` 对象: +For example, a provider may expose a `User`: ```dart abstract class User { @@ -336,7 +378,7 @@ abstract class User { } ``` -但是widget可能只使用用户名: +But a widget may only use the user name: ```dart Widget build(BuildContext context, WidgetRef ref) { @@ -345,11 +387,13 @@ Widget build(BuildContext context, WidgetRef ref) { } ``` -如果我们简单地使用 `ref.watch`,这将在用户年龄(`age`)发生变化时重新构建widget。 +If we naively used `ref.watch`, this would rebuild the widget when the user's +`age` changes. -解决方案是使用 `select` 显式地告诉Riverpod我们只想监听 `User` 的 `name` 属性。 +The solution is to use `select` to explicitly tell Riverpod that we only +want to listen to the name property of the `User`. -更新后的代码如下: +The updated code would be: ```dart Widget build(BuildContext context, WidgetRef ref) { @@ -358,15 +402,17 @@ Widget build(BuildContext context, WidgetRef ref) { } ``` -通过使用 `select`,我们可以指定一个返回我们所关心的属性的函数。 - -每当 `User` 发生变化时,Riverpod将调用该函数并比较以前和新的结果。 -如果它们是不同的(例如当名称更改时),Riverpod将重新构建widget。 +By using `select`, we are able to specify a function +that returns the property that we care about. -当然了,如果它们相等(例如当年龄改变时),Riverpod将不会重建widget。 +Whenever the `User` changes, Riverpod will call this function and +compare the previous and new result. If they are different (such as when the name +changed), Riverpod will rebuild the widget. +However, if they are equal (such as when the age changed), Riverpod will not +rebuild the widget. :::info -也可以 `select` 和 `ref.listen` 结合使用: +It is also possible to use `select` with `ref.listen`: ```dart ref.listen( @@ -377,11 +423,12 @@ ref.listen( ); ``` -这样做只会在名称更改时调用监听器。 +Doing so will call the listener only when the name changes. ::: :::tip -你不需要返回对象的属性。任何可以使用==的值都可以工作。举个例子: +You don't have to return a property of the object. Any value that +overrides == will work. For example you could do: ```dart final label = ref.watch(userProvider.select((user) => 'Mr ${user.name}')); diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/consumer_stateful_widget/hooks.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/consumer_stateful_widget/hooks.dart index 518561f7e..7b9118cbc 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/consumer_stateful_widget/hooks.dart +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/consumer_stateful_widget/hooks.dart @@ -18,16 +18,16 @@ class HomeViewState extends ConsumerState { @override void initState() { super.initState(); - // “ref” 可以在 StatefulWidget 的所有的生命周期内使用。 + // "ref" can be used in all life-cycles of a StatefulWidget. ref.read(counterProvider); } @override Widget build(BuildContext context) { - // 我们可以在builder中使用钩子如同HookConsumerWidget使用的那样 + // Like HookConsumerWidget, we can use hooks inside the builder final state = useState(0); - // 我们也可以在build函数中使用“ref”监听provider + // We can also use "ref" to listen to a provider inside the build method final counter = ref.watch(counterProvider); return Text('$counter'); } diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/consumer_stateful_widget/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/consumer_stateful_widget/raw.dart index 0219623b1..98d7f3403 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/consumer_stateful_widget/raw.dart +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/consumer_stateful_widget/raw.dart @@ -15,13 +15,13 @@ class HomeViewState extends ConsumerState { @override void initState() { super.initState(); - // “ref” 可以在 StatefulWidget 的所有的生命周期内使用。 + // "ref" can be used in all life-cycles of a StatefulWidget. ref.read(counterProvider); } @override Widget build(BuildContext context) { - // 我们也可以在build函数中使用“ref”监听provider + // We can also use "ref" to listen to a provider inside the build method final counter = ref.watch(counterProvider); return Text('$counter'); } diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/consumer_widget/hooks.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/consumer_widget/hooks.dart index 8dd75334f..1c5547428 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/consumer_widget/hooks.dart +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/consumer_widget/hooks.dart @@ -12,10 +12,10 @@ class HomeView extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - // HookConsumerWidget允许在build方法中使用钩子 + // HookConsumerWidget allows using hooks inside the build method final state = useState(0); - // 我们也可以使用ref参数来监听provider。 + // We can also use the ref parameter to listen to providers. final counter = ref.watch(counterProvider); return Text('$counter'); } diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/consumer_widget/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/consumer_widget/raw.dart index a450ed37b..d2df7c5a7 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/consumer_widget/raw.dart +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/consumer_widget/raw.dart @@ -9,7 +9,7 @@ class HomeView extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - // 使用ref监听provider + // use ref to listen to a provider final counter = ref.watch(counterProvider); return Text('$counter'); } diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/counter/codegen.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/counter/codegen.dart index 75543d461..ca1edf42e 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/counter/codegen.dart +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/counter/codegen.dart @@ -16,7 +16,7 @@ class Counter extends _$Counter { int build() => 0; void increment() { - // Counter可以使用“ref”读取其他provider + // Counter can use the "ref" to read other providers final repository = ref.read(repositoryProvider); repository.post('...'); } diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/counter/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/counter/raw.dart index b0a11f77f..b97c93b00 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/counter/raw.dart +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/counter/raw.dart @@ -18,7 +18,7 @@ class Counter extends StateNotifier { final Ref ref; void increment() { - // Counter可以使用“ref”读取其他provider + // Counter can use the "ref" to read other providers final repository = ref.read(repositoryProvider); repository.post('...'); } diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/listen_build/codegen_hooks.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/listen_build/codegen_hooks.dart new file mode 100644 index 000000000..5b1147d28 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/listen_build/codegen_hooks.dart @@ -0,0 +1,34 @@ +// ignore_for_file: omit_local_variable_types, avoid_types_on_closure_parameters, avoid_print + +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen_hooks.g.dart'; + +/* SNIPPET START */ + +@riverpod +class Counter extends _$Counter { + @override + int build() => 0; +} + +class HomeView extends HookConsumerWidget { + const HomeView({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + ref.listen(counterProvider, (int? previousCount, int newCount) { + print('The counter changed $newCount'); + }); + + final greeting = useState('Hello'); + + return Container( + alignment: Alignment.center, + child: Text(greeting.value), + ); + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/listen_build/codegen_hooks.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/listen_build/codegen_hooks.g.dart new file mode 100644 index 000000000..6f4db9464 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/listen_build/codegen_hooks.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen_hooks.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$counterHash() => r'4320f811608c7a6e7342b83e3031965a34f7cb8e'; + +/// See also [Counter]. +@ProviderFor(Counter) +final counterProvider = AutoDisposeNotifierProvider.internal( + Counter.new, + name: r'counterProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$counterHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$Counter = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/listen_build/index.tsx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/listen_build/index.tsx index 339b89a56..a856c4980 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/listen_build/index.tsx +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/listen_build/index.tsx @@ -1,9 +1,11 @@ import raw from "!!raw-loader!./raw.dart"; import codegen from "!!raw-loader!./codegen.dart"; +import raw_hooks from "!!raw-loader!./raw_hooks.dart"; +import codegen_hooks from "!!raw-loader!./codegen_hooks.dart"; export default { raw, - hooks: raw, + hooks: raw_hooks, codegen, - hooksCodegen: codegen, + hooksCodegen: codegen_hooks, }; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/listen_build/raw_hooks.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/listen_build/raw_hooks.dart new file mode 100644 index 000000000..9f86fc19f --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/listen_build/raw_hooks.dart @@ -0,0 +1,29 @@ +// ignore_for_file: omit_local_variable_types, avoid_types_on_closure_parameters, avoid_print + +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import '../counter/raw.dart'; + +/* SNIPPET START */ + +final counterProvider = + StateNotifierProvider(Counter.new); + +class HomeView extends HookConsumerWidget { + const HomeView({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + ref.listen(counterProvider, (int? previousCount, int newCount) { + print('The counter changed $newCount'); + }); + + final greeting = useState('Hello'); + + return Container( + alignment: Alignment.center, + child: Text(greeting.value), + ); + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/provider/codegen.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/provider/codegen.dart index a3401479c..201c92f91 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/provider/codegen.dart +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/provider/codegen.dart @@ -14,7 +14,7 @@ Repository repository(RepositoryRef ref) => Repository(); @riverpod String value(ValueRef ref) { - // 使用ref获取其他provider + // use ref to obtain other providers final repository = ref.watch(repositoryProvider); return repository.get(); } diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/provider/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/provider/raw.dart index 27baf53bd..28fdfed97 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/provider/raw.dart +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/provider/raw.dart @@ -13,7 +13,7 @@ final repositoryProvider = Provider((ref) { /* SNIPPET START */ final valueProvider = Provider((ref) { - // 使用ref获取其他provider + // use ref to obtain other providers final repository = ref.watch(repositoryProvider); return repository.get(); }); diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/read/codegen.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/read/codegen.dart index 4e7f1e139..0742ed5e5 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/read/codegen.dart +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/read/codegen.dart @@ -23,7 +23,7 @@ class HomeView extends ConsumerWidget { return Scaffold( floatingActionButton: FloatingActionButton( onPressed: () { - // 对“Counter”类调用“increment()”方法 + // Call `increment()` on the `Counter` class ref.read(counterProvider.notifier).increment(); }, ), diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/read/codegen_hooks.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/read/codegen_hooks.dart new file mode 100644 index 000000000..341087cde --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/read/codegen_hooks.dart @@ -0,0 +1,36 @@ +// ignore_for_file: omit_local_variable_types, avoid_types_on_closure_parameters, avoid_print + +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen_hooks.g.dart'; + +/* SNIPPET START */ + +@riverpod +class Counter extends _$Counter { + @override + int build() => 0; + void increment() => state = state + 1; +} + +class HomeView extends HookConsumerWidget { + const HomeView({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final greeting = useState('Hello'); + + return Scaffold( + body: Center(child: Text(greeting.value)), + floatingActionButton: FloatingActionButton( + onPressed: () { + // Call `increment()` on the `Counter` class + ref.read(counterProvider.notifier).increment(); + }, + ), + ); + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/read/codegen_hooks.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/read/codegen_hooks.g.dart new file mode 100644 index 000000000..f5cbe5535 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/read/codegen_hooks.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen_hooks.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$counterHash() => r'600c6beb47b3732049b6955a9e49d7eef30c741a'; + +/// See also [Counter]. +@ProviderFor(Counter) +final counterProvider = AutoDisposeNotifierProvider.internal( + Counter.new, + name: r'counterProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$counterHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$Counter = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/read/index.tsx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/read/index.tsx index 339b89a56..d0caf89aa 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/read/index.tsx +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/read/index.tsx @@ -1,9 +1,11 @@ import raw from "!!raw-loader!./raw.dart"; +import raw_hooks from "!!raw-loader!./raw_hooks.dart"; import codegen from "!!raw-loader!./codegen.dart"; +import codegen_hooks from "!!raw-loader!./codegen_hooks.dart"; export default { raw, - hooks: raw, + hooks: raw_hooks, codegen, - hooksCodegen: codegen, + hooksCodegen: codegen_hooks, }; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/read/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/read/raw.dart index f191278b0..7d8685155 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/read/raw.dart +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/read/raw.dart @@ -7,8 +7,7 @@ import '../counter/raw.dart'; /* SNIPPET START */ -final counterProvider = - StateNotifierProvider(Counter.new); +final counterProvider = StateNotifierProvider(Counter.new); class HomeView extends ConsumerWidget { const HomeView({super.key}); @@ -18,7 +17,7 @@ class HomeView extends ConsumerWidget { return Scaffold( floatingActionButton: FloatingActionButton( onPressed: () { - // 对“Counter”类调用“increment()”方法 + // Call `increment()` on the `Counter` class ref.read(counterProvider.notifier).increment(); }, ), diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/read/raw_hooks.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/read/raw_hooks.dart new file mode 100644 index 000000000..acc6ba980 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/read/raw_hooks.dart @@ -0,0 +1,31 @@ +// ignore_for_file: omit_local_variable_types + +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +import '../counter/raw.dart'; + +/* SNIPPET START */ + +final counterProvider = + StateNotifierProvider(Counter.new); + +class HomeView extends HookConsumerWidget { + const HomeView({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final greeting = useState('Hello'); + + return Scaffold( + body: Center(child: Text(greeting.value)), + floatingActionButton: FloatingActionButton( + onPressed: () { + // Call `increment()` on the `Counter` class + ref.read(counterProvider.notifier).increment(); + }, + ), + ); + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/read_build/codegen.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/read_build/codegen.dart index 14915ab30..9a4347159 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/read_build/codegen.dart +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/read_build/codegen.dart @@ -16,7 +16,7 @@ class Counter extends _$Counter { } Widget build(BuildContext context, WidgetRef ref) { - // 使用 “read” 忽略provider的更新 + // use "read" to ignore updates on a provider final counter = ref.read(counterProvider.notifier); return ElevatedButton( onPressed: counter.increment, diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/read_build/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/read_build/raw.dart index 51a2a7ca3..266e00224 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/read_build/raw.dart +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/read_build/raw.dart @@ -8,7 +8,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; final counterProvider = StateProvider((ref) => 0); Widget build(BuildContext context, WidgetRef ref) { - // 使用 “read” 忽略provider的更新 + // use "read" to ignore updates on a provider final counter = ref.read(counterProvider.notifier); return ElevatedButton( onPressed: () => counter.state++, diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/watch/codegen.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/watch/codegen.dart index 0398da316..31de6da74 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/watch/codegen.dart +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/watch/codegen.dart @@ -30,16 +30,16 @@ class Todos extends _$Todos { @riverpod List filteredTodoList(FilteredTodoListRef ref) { - // 获取筛选器和待办清单列表 + // obtains both the filter and the list of todos final FilterType filter = ref.watch(filterTypeProvider); final List todos = ref.watch(todosProvider); switch (filter) { case FilterType.completed: - // 返回完成的待办清单 + // return the completed list of todos return todos.where((todo) => todo.isCompleted).toList(); case FilterType.none: - // 返回所有的待办清单 + // returns the unfiltered list of todos return todos; } } diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/watch/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/watch/raw.dart index 17b249eb2..625c0fe9a 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/watch/raw.dart +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/watch/raw.dart @@ -22,16 +22,16 @@ final todosProvider = StateNotifierProvider>((ref) => TodoList()); final filteredTodoListProvider = Provider((ref) { - // 获取筛选器和待办清单列表 + // obtains both the filter and the list of todos final FilterType filter = ref.watch(filterTypeProvider); final List todos = ref.watch(todosProvider); switch (filter) { case FilterType.completed: - // 返回完成的待办清单 + // return the completed list of todos return todos.where((todo) => todo.isCompleted).toList(); case FilterType.none: - // 返回所有的待办清单 + // returns the unfiltered list of todos return todos; } }); diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/watch_build/codegen.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/watch_build/codegen.dart index 5649470d1..80b63d216 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/watch_build/codegen.dart +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/watch_build/codegen.dart @@ -31,7 +31,7 @@ class HomeView extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - // 使用ref监听provider + // use ref to listen to a provider final counter = ref.watch(counterProvider); return Text('$counter'); diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/watch_build/codegen_hooks.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/watch_build/codegen_hooks.dart new file mode 100644 index 000000000..c82698304 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/watch_build/codegen_hooks.dart @@ -0,0 +1,43 @@ +// ignore_for_file: omit_local_variable_types, avoid_types_on_closure_parameters, avoid_print + +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen_hooks.g.dart'; + +enum FilterType { + none, + completed, +} + +abstract class Todo { + bool get isCompleted; +} + +@riverpod +class TodoList extends _$TodoList { + @override + List build() => []; +} + +/* SNIPPET START */ + +@riverpod +int counter(CounterRef ref) => 0; + +class HomeView extends HookConsumerWidget { + const HomeView({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + // You can use hooks inside a HookConsumerWidget + final greeting = useState('Hello'); + + // use ref to listen to a provider + final counter = ref.watch(counterProvider); + + return Text('${greeting.value} $counter'); + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/watch_build/codegen_hooks.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/watch_build/codegen_hooks.g.dart new file mode 100644 index 000000000..8a2f64454 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/watch_build/codegen_hooks.g.dart @@ -0,0 +1,41 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen_hooks.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$counterHash() => r'9b0db44ecc47057e79891e5ecd92d34b08637679'; + +/// See also [counter]. +@ProviderFor(counter) +final counterProvider = AutoDisposeProvider.internal( + counter, + name: r'counterProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$counterHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef CounterRef = AutoDisposeProviderRef; +String _$todoListHash() => r'77f007cd4f5105330a4c2ab8555ea0d1716945c1'; + +/// See also [TodoList]. +@ProviderFor(TodoList) +final todoListProvider = + AutoDisposeNotifierProvider>.internal( + TodoList.new, + name: r'todoListProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$todoListHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$TodoList = AutoDisposeNotifier>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/watch_build/index.tsx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/watch_build/index.tsx index 339b89a56..a856c4980 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/watch_build/index.tsx +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/watch_build/index.tsx @@ -1,9 +1,11 @@ import raw from "!!raw-loader!./raw.dart"; import codegen from "!!raw-loader!./codegen.dart"; +import raw_hooks from "!!raw-loader!./raw_hooks.dart"; +import codegen_hooks from "!!raw-loader!./codegen_hooks.dart"; export default { raw, - hooks: raw, + hooks: raw_hooks, codegen, - hooksCodegen: codegen, + hooksCodegen: codegen_hooks, }; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/watch_build/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/watch_build/raw.dart index 71886bdae..8d5d430eb 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/watch_build/raw.dart +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/watch_build/raw.dart @@ -25,7 +25,7 @@ class HomeView extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - // 使用ref监听provider + // use ref to listen to a provider final counter = ref.watch(counterProvider); return Text('$counter'); diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/watch_build/raw_hooks.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/watch_build/raw_hooks.dart new file mode 100644 index 000000000..5f297850a --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/reading/watch_build/raw_hooks.dart @@ -0,0 +1,37 @@ +// ignore_for_file: omit_local_variable_types + +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +enum FilterType { + none, + completed, +} + +abstract class Todo { + bool get isCompleted; +} + +class TodoList extends StateNotifier> { + TodoList() : super([]); +} + +/* SNIPPET START */ + +final counterProvider = StateProvider((ref) => 0); + +class HomeView extends HookConsumerWidget { + const HomeView({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + // You can use hooks inside a HookConsumerWidget + final greeting = useState('Hello'); + + // use ref to listen to a provider + final counter = ref.watch(counterProvider); + + return Text('${greeting.value} $counter'); + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/scopes.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/scopes.mdx index b4472bdec..9c8812bcf 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/scopes.mdx +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/scopes.mdx @@ -1,49 +1,60 @@ --- -title: 作用域 +title: Scopes --- -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; import CodeBlock from "@theme/CodeBlock"; import asyncInitialization from "!!raw-loader!/docs/concepts/async_initialization.dart"; import dialogScope from "!!raw-loader!/docs/concepts/dialog_scope.dart"; import themeScope from "!!raw-loader!/docs/concepts/theme_scope.dart"; import subtreeScope from "!!raw-loader!/docs/concepts/subtree_scope.dart"; -import { trimSnippet } from "../../../../../src/components/CodeSnippet"; +import { trimSnippet } from "@site/src/components/CodeSnippet"; +import { Link } from "@site/src/components/Link"; + +:::caution + +本页内容可能已经过时。 +今后会进行更新,但目前您可能需要参考侧边栏顶部的内容(介绍/要点/应用案例/......)。 +::: + +Scoping in Riverpod is a very powerful feature, but like all powerful features, it should be used wisely and intentionally. -Riverpod中的作用域是一个非常强大的功能,但像其他强大的功能一样,应该理智地使用它。 +A few of the things that scoping enables are: -作用域支持: -- 覆盖特定子树的provider状态(类似于主题和 `InheritedWidgets` 在flutter中的工作方式) [(查看示例)](#子树作用域) -- 为一般异步API创建同步provider [(查看示例)](#异步api的同步provider初始化) -- 允许 `对话框(Dialog)` 和 `覆盖层(Overlay)`从widget子树继承provider的状态以显示它们[(查看示例)](#展示对话框) -- 通过从widget的构造函数中删除参数来优化widget的重建,从而允许你将它们设置为`const` +- Override the state of providers for a specific subtree (similar to how theming and `InheritedWidgets` work in flutter) [(see example)](#subtree-scope) +- Creating synchronous providers for normally async APIs [(see example)](#initialization-of-synchronous-provider-for-async-apis) +- Allowing `Dialog`s and `Overlay`s to inherit the state of providers from the widget subtree that cause them to be shown [(see example)](#showing-dialogs) +- Optimizing rebuilds of widgets by removing parameters from Widget constructors allowing you to make them `const` -如果你想用作用域来表示上面的第一点,你也可以用family来代替。 -family的优点是允许你从widget树中的任何位置访问状态的每个实例,而不仅仅是从你所在的特定子树的状态范围访问。 +If you are wanting to use scope for the first point, chances are you can use families instead. +Families have the advantages of allowing you to access each of those instances of the state from anywhere in the widget tree rather than just the state scoped to the specific subtree that you are in. -使用作用域创建provider状态的多个实例与 `package:provider` 的工作方式类似。 +Using scope to create multiple instances of a provider's state is similar to how `package:provider` works. -但是,使用作用域来完成该任务的限制更大,因为你不能决定从该作用域访问其他实例。 +However, using scope to accomplish that task, is more restrictive, as you cannot decide to access other instances from that scope. -因此,在确定所使用的每个provider的作用域之前,请仔细考虑为什么要确定provider的作用域。 +As such, before scoping every provider you use, consider carefully why you want to scope the provider. -## ProviderScope 和 ProviderContainer +## ProviderScope and ProviderContainer -作用域由 [ProviderContainer] 引入。这个容器保存了所有provider的当前状态。它管理provider之间的查找和订阅功能。 +A scope is introduced by a [ProviderContainer]. This container holds the current state of all of your providers. +It manages the lookup and subscriptions between providers. -在Flutter中,你应该使用 [ProviderScope] widget, -它内部包含一个 [ProviderContainer],并提供了一种访问该容器到widget树其余部分的方法。 +In Flutter you should use the [ProviderScope] widget, which contains a [ProviderContainer] +internally, and provides a way to access that container to the rest of the widget tree. ```dart final valueProvider = StateProvider((ref) => 0); -// 这样做: +// DO this void main() { runApp(ProviderScope(child: MyApp())); } -// 不要这样做: +//DON'T do this: final myProviderContainer = ProviderContainer(); void main(){ runApp(MyApp()); @@ -51,118 +62,120 @@ void main(){ ``` :::warning -在了解它们的工作原理之前,不要使用多个 [ProviderContainer]。 -每个线程都有自己独立的状态线程,这些状态线程不能相互访问。 -拿测试举例,你可能希望使用单独的 [ProviderContainer],以便使每个测试的状态独立于其他测试。 +Do not use multiple [ProviderContainer]s, without an understanding of how they work. +Each will have it's own separate thread of states, which will not be able to access each other. +Tests are an example of when you might want to use separate [ProviderContainer]s +in order to make each test's state independent of the others. ::: -仅在纯Dart项目中和测试中创建并使用 [ProviderContainer] 且不需要 [ProviderScope]。 +Only create a [ProviderContainer] without a [ProviderScope] for testing and dart-only usage. -## How Riverpod如何找到一个Provider +## How Riverpod Finds a Provider -当一个widget或provider请求一个provider的值时,Riverpod在最近的ProviderScope widget中查找该provider的状态。 -如果provider和它显式列出的依赖项都没有在该范围内被覆盖到,Riverpod将继续查找widget树。 -如果provider没有在任何widget子树中被覆盖到,则默认查找到根 [ProviderScope] 中的 [ProviderContainer]。 +When a widget or provider requests the value of a provider, Riverpod looks up the state of that provider in the nearest +[ProviderScope] widget. If neither the provider nor one of it's explicitly listed dependencies is overridden in that scope Riverpod continues it's lookup up the widget tree. +If the provider has not been overridden in any Widget subtrees the lookup defaults to the [ProviderContainer] in the root [ProviderScope]. -一旦该进程定位了provider应该驻留的作用域,它就会确定provider是否已经创建。 -如果是,它将返回provider的状态。但是,如果provider已经失效或未初始化,它将使用provider的构建方法创建状态。 +Once this process locates the scope in which the provider should reside it determines if the provider has been created yet. +If so, it will return the state of the provider. +However, if the provider has been invalidated or is not currently initialized it will create the state using the provider's build method. -## 异步API的同步provider初始化 +## Initialization of Synchronous Provider for Async APIs -通常,你可能会对依赖项(如 `SharedPreferences` 或 `FirebaseApp`)进行一些异步初始化。 -许多其他provider可能依赖于此,在每个provider中处理错误/加载中状态是多余的。 +Often you might have some async initialization of a dependency such as `SharedPreferences` or `FirebaseApp`. +Many other providers might rely on this, and dealing with the error / loading states in each of those providers is redundant. -你可以保证这些provider不会有错误,并且在应用启动时可以快速加载。 +You might be able to guarantee that those providers will not have errors and will load quickly when the app is started. -那么,如何让这些provider状态同步可用呢? +So how do you makes these sorts of provider states available synchronously? -下面是一个示例,它展示了当异步API准备好时,作用域如何允许你覆盖一个形式上的provider。 +Here is an example that shows how scoping allows you override a dummy provider when your asynchronous API is ready. {trimSnippet(asyncInitialization)} -## 展示对话框 +## Showing Dialogs -当你显示一个`对话框(Dialog)` 或 `OverlayEntry`时,flutter会创建一个新的 `路由(Route)` 或添加到具有不同构建范围的 `Overlay` 中, -这样它就可以摆脱它的父布局,并可以显示在其他 `路由(Routes)` 之上。 -但这通常会给 `InheritedWidget` 带来一个问题,因为 [ProviderScope] 也是一个 `InheritedWidget`,所以它也会受到影响。 +When you show a `Dialog` or `OverlayEntry`, flutter creates a new `Route` or adds to an `Overlay` that has a different build scope, +so that it can escape the layout of it's parent, and can be shown above other `Routes`. +This presents a problem for `InheritedWidget`s in general, and since [ProviderScope] is an `InheritedWidget`, it is also affected. -为了解决这个问题,Riverpod允许你创建一个 `ProviderScope` ,它可以访问父作用域中所有provider的状态。 +To solve this problem, Riverpod allows you to create a `ProviderScope` that can access the state of all providers in a `parent` scope. -下面的示例展示了如何使用这个功能,它允许打开的`Dialog`从上下文(context)中访问计数器的状态。 +The following example shows how to use this, to allow a `Dialog` to access the state of a counter from the context that caused the `Dialog` to be shown. {trimSnippet(dialogScope)} -## 子树作用域 +## Subtree Scoping -作用域允许你覆盖widget树的特定子树的provider状态。 -通过这种方式,它可以提供类似于flutter中的 `InheritedWidget` 或 `package:provider` 中的provider机制。 +Scoping allows you to override the state of a provider for a specific subtree of your widget tree. +In this way it can provide a similar mechanism to `InheritedWidget` from flutter, or the providers from `package:provider`. For example, in flutter you can override the `Theme` for a particular subtree of your widget tree, by wrapping it in a `Theme` widget. -比如,在flutter中,通过将widget树的特定子树包装在Theme widget中,可以覆盖widget树的Theme。 {trimSnippet(themeScope)} -在底层,`Theme` 是一个 `InheritedWidget` ,当widget查找 `Theme` 时,它们从widget树中找到最近的 `Theme` widget 来获得主题。 +Under the hood, `Theme` is an `InheritedWidget` and when widgets look up the `Theme` they get the `Theme` from the nearest `Theme` widget above it in the widget tree. -Riverpod的工作方式不太一样,因为应用的所有状态通常存储在根 [ProviderScope] widget中。 -不要担心,当状态改变时,这不会导致整个应用程序重新构建,它只是允许你从widget树中的任何位置去访问状态。 +Riverpod works differently, since all of the state of your application is typically stored in a root [ProviderScope] widget. +Don't worry, this doesn't cause your whole application to rebuild when the state changes, it just allows you to access the state from anywhere in your widget tree. -如果根据所处的页面需要不同的provider该怎么办? +What if you want different providers depending on which page you are in? -你应该考虑的第一件事是它所提供的行为是否会以某种方式有所不同。 +The first thing that you should consider is whether the provided behavior will differ in any way. -如果不同 -> 只需创建一个不同名称的新的provider,并在该页面中使用它 +If so -> just create a new provider with a different name and use it in that page -如果相同 -> 考虑使用family[在这里了解更多关于family的内容](/docs/concepts/modifiers/family)。 +If not -> Consider using a . -通常,你开始时认为只需要在特定页面上使用provider,但最后却希望在稍后的另一个页面上也使用它。 -family可以让你不受这种可能性的影响,如果你是来自 `package:provider` 的开发者,你应该使用family来调整思维。 +Often you start by thinking that you only need a provider on a particular page, but end up wanting to use it in another page later on. +Families protect you against this eventuality, and are a major difference in how you should adjust your thinking if you are coming from `package:provider`. -但如果family确实不适合你的用例,下面的示例向你展示了如何覆盖特定子树的provider: +If families really do not fit your use case, the following example shows you how to override a provider for a particular subtree. {trimSnippet(subtreeScope)} -## 什么时候选择有作用域的Provider还是Family +## When to choose Scoped Providers or Families + +While scopes are important to understand, it is easy to get carried away when using scopes. + +If you want a different instance of a provider's state depending on where it is in the widget tree you have a few alternatives available to you: `Scoping`, `Families`, or a combination. +The appropriate choice depends on your use case. -虽然理解作用域很重要,但在使用作用域时很容易失去控制。 +Families: -如果你想要一个provider状态的不同实例,取决于它在widget树中的位置,你有几个可供选择的选项: `Scoping`, `Families`,或组合它们。 -请根据你的情况选择合适方案。 +- Pro: You can show multiple of the states no matter which subtree you are in +- Pro: This makes it a more flexible and scalable solution for many use cases -Family: -- 优点:无论你在哪个子树中,你都可以显示多个状态 -- 优点:这使得它成为许多用例的更灵活和可扩展的解决方案 +Scoping: -作用域: -- 缺点:你最终会在你的widget树中嵌套更多的[ProviderScope] widget -- 缺点:你只能访问一个覆盖住你部分的widget树 -- 缺点:你最终不得不显式地列出大多数provider的依赖关系 -- 优点:可以减少widget构造函数中的参数数量 -- 优点:你可以获得轻微的性能优势,并且可以潜在地使你的一些widget的构造函数为const +- Con: You end up with more nesting of [ProviderScope] widgets in your widget tree +- Con: You can only access the one override in your section of the widget tree +- Con: You end up having to explicitly list the dependencies of most of your providers +- Pro: You can reduce the number of parameters in your widget constructors +- Pro: You get a slight performance advantage, and can potentially make some of your widget constructors `const` -组合使用这两种方法,你可以同时获得这两种方法的优点,但你仍然必须解决作用域的缺点。 +Using a combination of the two approaches, you can get the pros of both approaches, but you still have to deal with the cons of scoping. :::warning -请记住,作用域为每个被覆盖的provider或列出了对被覆盖的provider的依赖项的provider引入了一个新的状态实例。 -如果你在应用程序的不同子树中覆盖相同的参数,它将**不会是**provider状态的相同实例。 -一般来说,family更加灵活,并且通过即将到来的代码生成特性,可以很容易地为一个family使用多个参数。 -一个很好的组合通常是同时使用family和作用域。使用一个family来提供对应用中任何地方的状态块的访问, -取决于你在widget树中的位置,然后使用作用域来提供一个特定的family状态实例。 +Remember that scopes introduce a new instance of the state of every provider that is overridden or has listed a dependency on a provider that was overridden. +If you override with the same parameter in a different subtree of the app, it will **not** be the same instance of the provider's state. +Families are more flexible in general, and with the upcoming code generation feature it is easy to use multiple parameters for a family. +Often a good combination is to use both families and scoping. Use a family to provide general access to a piece of state anywhere in your app, and then use scoping to +provide a specific instance of the family's state depending on where you are in the widget tree. ::: -### 作用域的不常见用法 +### Less common usages of Scopes -有时你可能想要覆盖应用特定子树中的所有provider。 -通过在每个provider的依赖项列表中列出一个公共provider, -你可以通过覆盖公共provider,轻松地一次性为所有这些provider创建新状态。 +Sometimes you may want to override a whole set of providers in a specific subtree of your app. +By listing a common provider in the dependencies list of each of those providers, you can easily create new states for all of them at once, by overriding the common one. -请注意,如果你尝试使用family来实现此功能,那么你将得到许多具有相同参数的family, -并且你可能会在整个widget树中传递该参数。在这种情况下,也可以使用作用域。 +Note that if you try to use families for this, you will end up with many families that all have the same parameter, and you could end up passing that parameter all over the widget tree. +In this case it is also acceptable to use scopes. :::warning -一旦开始使用作用域,请确保始终列出依赖项并保持最新状态,以防止运行时异常。 -为了解决这个问题,我们创建了 [riverpod_lint],它会在缺少依赖时警告你。 -另外,使用 [riverpod_generator] 这个代码生成器会自动为你生成依赖项列表。 +Once you start using scope, make sure to always list your dependencies and keep them up to date, to prevent runtime exceptions. +To help with this we have created [riverpod_lint] which will warn you if there is a missing dependency. +Additionally with [riverpod_generator] the code generator automatically generates the dependency list. ::: [ProviderContainer]: https://pub.dev/documentation/riverpod/latest/riverpod/ProviderContainer-class.html diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/subtree_scope.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/subtree_scope.dart new file mode 100644 index 000000000..9aa1903e5 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/subtree_scope.dart @@ -0,0 +1,78 @@ +// ignore_for_file: omit_local_variable_types, prefer_final_locals + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +/* SNIPPET START */ + +/// A counter that is being incremented by each [CounterDisplay]'s ElevatedButton +final counterProvider = StateProvider( + (ref) => 0, +); + +final adjustedCountProvider = Provider( + (ref) => ref.watch(counterProvider) * 2, + // Note that if a provider depends on a provider that is overridden for a subtree, + // you must explicitly list that provider in your dependencies list. + dependencies: [counterProvider], +); + +class Home extends ConsumerWidget { + const Home({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Scaffold( + body: Column( + children: [ + ProviderScope( + /// Just specify which provider you want to have a copy of in the subtree + /// + /// Note that dependant providers such as [adjustedCountProvider] will + /// also be copied for this subtree. If that is not the behavior you want, + /// consider using families instead + overrides: [counterProvider], + child: const CounterDisplay(), + ), + ProviderScope( + // You can change the provider's behavior in a particular subtree + overrides: [counterProvider.overrideWith((ref) => 1)], + child: const CounterDisplay(), + ), + ProviderScope( + overrides: [ + counterProvider, + // You can also change dependent provider's behaviors + adjustedCountProvider.overrideWith( + (ref) => ref.watch(counterProvider) * 3, + ), + ], + child: const CounterDisplay(), + ), + // This particular display will use the provider state from the root ProviderScope + const CounterDisplay(), + ], + )); + } +} + +class CounterDisplay extends ConsumerWidget { + const CounterDisplay({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final count = ref.watch(counterProvider); + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text('$count'), + ElevatedButton( + onPressed: () { + ref.read(counterProvider.notifier).state++; + }, + child: const Text('Increment Count'), + ), + ], + ); + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/theme_scope.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/theme_scope.dart new file mode 100644 index 000000000..d17f15f02 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/theme_scope.dart @@ -0,0 +1,68 @@ +// ignore_for_file: omit_local_variable_types, prefer_final_locals + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +/* SNIPPET START */ + +void main() { + runApp( + ProviderScope( + child: MaterialApp( + theme: ThemeData(primaryColor: Colors.blue), + home: const Home(), + ), + ), + ); +} + +// Have a counter that is being incremented +final counterProvider = StateProvider( + (ref) => 0, +); + +class Home extends ConsumerWidget { + const Home({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Scaffold( + body: Column( + children: [ + // This counter will have a primary color of green + Theme( + data: Theme.of(context).copyWith(primaryColor: Colors.green), + child: const CounterDisplay(), + ), + // This counter will have a primary color of blue + const CounterDisplay(), + ElevatedButton( + onPressed: () { + ref.read(counterProvider.notifier).state++; + }, + child: const Text('Increment Count'), + ), + ], + )); + } +} + +class CounterDisplay extends ConsumerWidget { + const CounterDisplay({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final count = ref.watch(counterProvider); + final theme = Theme.of(context); + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + '$count', + style: theme.textTheme.displayMedium + ?.copyWith(color: theme.primaryColor), + ), + ], + ); + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/why_immutability.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/why_immutability.mdx index 864abc633..3dfbe3741 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/why_immutability.mdx +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/why_immutability.mdx @@ -1,124 +1,98 @@ --- -title: 为何需要不可变性 +title: Why Immutability --- import Tabs from "@theme/Tabs"; import TabItem from "@theme/TabItem"; +import whyImmutability from "./why_immutability" +import { + trimSnippet, + AutoSnippet, + When, +} from "@site/src/components/CodeSnippet"; + +:::caution + +本页内容可能已经过时。 +今后会进行更新,但目前您可能需要参考侧边栏顶部的内容(介绍/要点/应用案例/......)。 +::: -## 什么是不可变性? +## What is Immutability? -不可变性指当一个`对象`中的所有域(属性)是final或late final的。 -它们只能通过构造器赋值一次。 +Immutability is when all fields of an `Object` are final or late final. +They are set exactly once upon construction. -出于许多原因,不可变性是比较理想的方式: +Immutability is desireable for many different reasons -- 值相等而不是引用相等 -- 关于代码的局部推理 - - 远处的一段代码不能从你的下面获得引用并改变对象 -- 对于异步和并行任务更容易推理 - - 其他代码不能在操作中改变对象 -- API安全 - - 你传递给方法的东西不会被访问者或被访问者改变 +- Value equality rather than reference equality +- Local reasoning about a piece of code + - A far distant piece of code can't obtain a reference and change the object from underneath you +- Easier to reason about for asynchronous and parallel tasks + - Other code can't mutate your object in between operations +- Safety of APIs + - What you pass into a method cannot be changed by the callee / caller -当创建了一个只改变几处的新对象时,copyWith方法可以帮助你减少冗余 +A copyWith method helps with reducing verbosity when creating a new object with just a few things changed. -拷贝的效率比你想的更高,dart可以重用对未更改的字对象的引用。 +Copying is more efficient than you might think, since dart can reuse any references to sub-objects that have not changed. :::warning -确保你的对象是深度不可变的,否则你必须实现某种深拷贝机制。 +Make sure your objects are deeply immutable, otherwise you'll have to implement some sort of deep copy mechanism. ::: -## 最佳实践 +## Best Practices -你可以用任何package来创建你想要的不可变状态。 +You can use any package you want to create immutable state. -对于不可变对象: +For immutable objects: - [package:freezed](https://pub.dev/packages/freezed) - [package:built_value](https://pub.dev/packages/built_value) -对于不可变集合 (Map, Set, List): +For immutable collections (Map, Set, List): - [package:fast_immutable_collections](https://pub.dev/packages/fast_immutable_collections) - [package:built_collection](https://pub.dev/packages/built_collection) - [package:kt_dart](https://pub.dev/packages/kt_dart) - [package:dartz](https://pub.dev/packages/dartz) -非常推荐[freezed]这个package,它除了创建不可变对象外,还提供了一些不错的附加功能,包括: +It is highly recommended to use [freezed], +since it has several nice additions beyond just making immutable objects including: -- 生成copyWith方法 -- 深拷贝 (在嵌套的freezed对象的copyWith方法) -- 联合类型 -- 联合映射函数 +- A generated copyWith method +- Deep copy (copyWith on nested freezed objects) +- Union types +- Union mapping functions -使用不可变状态不一定需要代码生成,但它能让这一过程变得更简单。 +You do not need to use code generation to work with immutable state, but it makes it much easier. :::warning -如果你想使用内置的集合,请确保实现更新集合时执行复制集合的规则。 -不复制集合的问题在于,riverpod会根据对对象的引用是否更改来确定是否发出新的状态。 -如果仅调用一个改变对象的方法,那么使用引用也是可行的。 +If you want to use the built-in collections, make sure to enforce a discipline of making copies of collections when updating them. +The issue with not copying a collection is that riverpod determines whether to emit a new state based on whether the reference to the object has changed. +If you just call a method that mutates an object, the reference is the same. ::: -### 使用不可变状态 - -不可变状态最适合 [StateNotifier] 和 [StateNotifierProvider] 结合使用。 -[StateNotifier]允许你暴露一个可以“改变”状态的接口。 -你不能从你定义的继承自 [StateNotifier] 的对象外面改变他的状态。 -这分离了你的关注点,并将业务逻辑保留在UI之外。 - -下面是一个例子,通过一个简单的不可变设置类来改变应用主题。 - -```dart -final themeProvider = StateNotifierProvider((ref) => ThemeNotifier()); - -class ThemeNotifier extends StateNotifier { - ThemeNotifier(): super( - ThemeSettings( - mode: ThemeMode.system, - primaryColor: Colors.blue, - )); - - void toggle() { - state = state.copyWith(mode: state.mode.toggle); - } - void setDarkTheme() { - state = state.copyWith(mode: ThemeMode.dark); - } - void setLightTheme() { - state = state.copyWith(mode: ThemeMode.light); - } - void setSystemTheme() { - state = state.copyWith(mode: ThemeMode.system); - } - void setPrimaryColor(Color color) { - state = state.copyWith(primaryColor: color); - } - -} - -@freezed -class ThemeSettings with _$ThemeSettings { - const factory ThemeSettings({ThemeMode mode, Color primaryColor}) = _ThemeSettings; -} - -extension ToggleTheme on ThemeMode { - ThemeMode get toggle { - switch (this){ - case ThemeMode.dark: - return ThemeMode.light; - case ThemeMode.light: - return ThemeMode.dark; - case ThemeMode.system: - return ThemeMode.system; - } - } -} -``` - -要使用这段代码,记住你需要引入 `freezed_annotation`这个package,并运行 [build_runner] 来构建freezed生成的类。 +### Using immutable state + +Immutable state is best fit for using a [Notifier] in combination with [NotifierProvider] . +A [Notifier] allows you to expose an interface through which you can 'mutate' the state. +You cannot mutate the state from outside the class you define that extends [Notifier]. +This enforces a separation of concerns and keeps business logic outside of your UI. + +Here is an example of a simple immutable settings class for changing an app theme. + + + +To use this code, remember to import `freezed_annotation`, add the part directive and run [build_runner] to generate the freezed classes! [changenotifier]: https://api.flutter.dev/flutter/foundation/ChangeNotifier-class.html [statenotifier]: https://pub.dev/documentation/riverpod/latest/riverpod/StateNotifier-class.html [statenotifierprovider]: https://pub.dev/documentation/riverpod/latest/riverpod/StateNotifierProvider-class.html +[notifier]: https://pub.dev/documentation/riverpod/latest/riverpod/Notifier-class.html +[notifierprovider]: https://pub.dev/documentation/riverpod/latest/riverpod/NotifierProvider.html [asyncvalue]: https://pub.dev/documentation/riverpod/latest/riverpod/AsyncValue-class.html [freezed]: https://pub.dev/packages/freezed [build_runner]: https://pub.dev/packages/build_runner diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/why_immutability/codegen.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/why_immutability/codegen.dart new file mode 100644 index 000000000..fd286a36c --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/why_immutability/codegen.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.freezed.dart'; +part 'codegen.g.dart'; + +/* SNIPPET START */ + +@riverpod +class ThemeNotifier extends _$ThemeNotifier { + @override + ThemeSettings build() => const ThemeSettings( + mode: ThemeMode.light, + primaryColor: Colors.blue, + ); + + void toggle() { + state = state.copyWith(mode: state.mode.toggle); + } + + void setDarkTheme() { + state = state.copyWith(mode: ThemeMode.dark); + } + + void setLightTheme() { + state = state.copyWith(mode: ThemeMode.light); + } + + void setSystemTheme() { + state = state.copyWith(mode: ThemeMode.system); + } + + void setPrimaryColor(Color color) { + state = state.copyWith(primaryColor: color); + } +} + +@freezed +class ThemeSettings with _$ThemeSettings { + const factory ThemeSettings({ + required ThemeMode mode, + required Color primaryColor, + }) = _ThemeSettings; +} + +extension ToggleTheme on ThemeMode { + ThemeMode get toggle { + switch (this) { + case ThemeMode.dark: + return ThemeMode.light; + case ThemeMode.light: + return ThemeMode.dark; + case ThemeMode.system: + return ThemeMode.system; + } + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/why_immutability/codegen.freezed.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/why_immutability/codegen.freezed.dart new file mode 100644 index 000000000..6e8c8082b --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/why_immutability/codegen.freezed.dart @@ -0,0 +1,151 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'codegen.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +/// @nodoc +mixin _$ThemeSettings { + ThemeMode get mode => throw _privateConstructorUsedError; + Color get primaryColor => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $ThemeSettingsCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ThemeSettingsCopyWith<$Res> { + factory $ThemeSettingsCopyWith( + ThemeSettings value, $Res Function(ThemeSettings) then) = + _$ThemeSettingsCopyWithImpl<$Res, ThemeSettings>; + @useResult + $Res call({ThemeMode mode, Color primaryColor}); +} + +/// @nodoc +class _$ThemeSettingsCopyWithImpl<$Res, $Val extends ThemeSettings> + implements $ThemeSettingsCopyWith<$Res> { + _$ThemeSettingsCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? mode = null, + Object? primaryColor = null, + }) { + return _then(_value.copyWith( + mode: null == mode + ? _value.mode + : mode // ignore: cast_nullable_to_non_nullable + as ThemeMode, + primaryColor: null == primaryColor + ? _value.primaryColor + : primaryColor // ignore: cast_nullable_to_non_nullable + as Color, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$ThemeSettingsImplCopyWith<$Res> + implements $ThemeSettingsCopyWith<$Res> { + factory _$$ThemeSettingsImplCopyWith( + _$ThemeSettingsImpl value, $Res Function(_$ThemeSettingsImpl) then) = + __$$ThemeSettingsImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ThemeMode mode, Color primaryColor}); +} + +/// @nodoc +class __$$ThemeSettingsImplCopyWithImpl<$Res> + extends _$ThemeSettingsCopyWithImpl<$Res, _$ThemeSettingsImpl> + implements _$$ThemeSettingsImplCopyWith<$Res> { + __$$ThemeSettingsImplCopyWithImpl( + _$ThemeSettingsImpl _value, $Res Function(_$ThemeSettingsImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? mode = null, + Object? primaryColor = null, + }) { + return _then(_$ThemeSettingsImpl( + mode: null == mode + ? _value.mode + : mode // ignore: cast_nullable_to_non_nullable + as ThemeMode, + primaryColor: null == primaryColor + ? _value.primaryColor + : primaryColor // ignore: cast_nullable_to_non_nullable + as Color, + )); + } +} + +/// @nodoc + +class _$ThemeSettingsImpl implements _ThemeSettings { + const _$ThemeSettingsImpl({required this.mode, required this.primaryColor}); + + @override + final ThemeMode mode; + @override + final Color primaryColor; + + @override + String toString() { + return 'ThemeSettings(mode: $mode, primaryColor: $primaryColor)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ThemeSettingsImpl && + (identical(other.mode, mode) || other.mode == mode) && + (identical(other.primaryColor, primaryColor) || + other.primaryColor == primaryColor)); + } + + @override + int get hashCode => Object.hash(runtimeType, mode, primaryColor); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$ThemeSettingsImplCopyWith<_$ThemeSettingsImpl> get copyWith => + __$$ThemeSettingsImplCopyWithImpl<_$ThemeSettingsImpl>(this, _$identity); +} + +abstract class _ThemeSettings implements ThemeSettings { + const factory _ThemeSettings( + {required final ThemeMode mode, + required final Color primaryColor}) = _$ThemeSettingsImpl; + + @override + ThemeMode get mode; + @override + Color get primaryColor; + @override + @JsonKey(ignore: true) + _$$ThemeSettingsImplCopyWith<_$ThemeSettingsImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/why_immutability/codegen.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/why_immutability/codegen.g.dart new file mode 100644 index 000000000..e423516f0 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/why_immutability/codegen.g.dart @@ -0,0 +1,28 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$themeNotifierHash() => r'e119d56d9bf8b8d7c19624997f99d116098b45e9'; + +/// See also [ThemeNotifier]. +@ProviderFor(ThemeNotifier) +final themeNotifierProvider = + AutoDisposeNotifierProvider.internal( + ThemeNotifier.new, + name: r'themeNotifierProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$themeNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$ThemeNotifier = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/why_immutability/index.tsx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/why_immutability/index.tsx new file mode 100644 index 000000000..339b89a56 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/why_immutability/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./codegen.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/why_immutability/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/why_immutability/raw.dart new file mode 100644 index 000000000..1e34c694c --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/why_immutability/raw.dart @@ -0,0 +1,67 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +/* SNIPPET START */ + +final themeProvider = + NotifierProvider(ThemeNotifier.new); + +class ThemeNotifier extends Notifier { + @override + ThemeSettings build() { + return ThemeSettings(mode: ThemeMode.system, primaryColor: Colors.blue); + } + + void toggle() { + state = state.copyWith(mode: state.mode.toggle); + } + + void setDarkTheme() { + state = state.copyWith(mode: ThemeMode.dark); + } + + void setLightTheme() { + state = state.copyWith(mode: ThemeMode.light); + } + + void setSystemTheme() { + state = state.copyWith(mode: ThemeMode.system); + } + + void setPrimaryColor(Color color) { + state = state.copyWith(primaryColor: color); + } +} + +class ThemeSettings { + ThemeSettings({ + required this.mode, + required this.primaryColor, + }); + + final ThemeMode mode; + final Color primaryColor; + + ThemeSettings copyWith({ + ThemeMode? mode, + Color? primaryColor, + }) { + return ThemeSettings( + mode: mode ?? this.mode, + primaryColor: primaryColor ?? this.primaryColor, + ); + } +} + +extension ToggleTheme on ThemeMode { + ThemeMode get toggle { + switch (this) { + case ThemeMode.dark: + return ThemeMode.light; + case ThemeMode.light: + return ThemeMode.dark; + case ThemeMode.system: + return ThemeMode.system; + } + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/cookbooks/search_as_we_type.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/cookbooks/search_as_we_type.mdx new file mode 100644 index 000000000..404f6a883 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/cookbooks/search_as_we_type.mdx @@ -0,0 +1,148 @@ +--- +title: Search as we type +--- + +:::caution + +本页内容可能已经过时。 +今后会进行更新,但目前您可能需要参考侧边栏顶部的内容(介绍/要点/应用案例/......)。 +::: + +A real world example could be to use `FutureProvider` to implement a searchbar. + +## Usage example: "Search as we type" searchbar + +Implementing a "search as we type" can seem daunting at first when using +conventional means. +There are lots of moving parts, such as: + +- handling errors. +- debouncing the user input to avoid making network requests on every keystroke. +- cancelling previously pending network requests when the search field changes. + +But the combination of `FutureProvider` and the power of [ref.watch] can +significantly simplify this task. + +A common pattern wanting to perform an asynchronous requests +is to split it into multiple providers: + +- a [StateNotifierProvider] or `StateProvider` for the parameters of your request + (or alternatively use [family]) +- a `FutureProvider`, which will do the request by reading the parameters from + the other providers/[family]. + +The first step would be to store the user input somewhere. For this example, +we will use `StateProvider` (as the search state is only a single `String`): + +```dart +final searchInputProvider = StateProvider((ref) => ''); +``` + +We can then connect this provider to a [TextField] by doing: + +```dart +Consumer( + builder: (context, ref, child) { + return TextField( + onChanged: (value) => ref.read(searchInputProvider.notifier).state = value, + ); + }, +) +``` + +Then, we can create our `FutureProvider` which will take care of the request: + +```dart +final searchProvider = FutureProvider< + + + +本页内容可能已经过时。 +今后会进行更新,但目前您可能需要参考侧边栏顶部的内容(介绍/要点/应用案例/......)。 +::: -对任何中型到大型应用,测试应用都是至关重要的。 +For any medium to large-scale applications, it is critical to test the application. -为了成功地测试我们的应用,我们需要以下东西: +To successfully test our application, we will want the following things: -- `test` 和`testWidgets` 之间不应该保留任何状态。 - 这意味着应用中没有全局状态,或者所有的全局状态都应该在每次测试后重置。 +- No state should be preserved between `test`/`testWidgets`. + That means no global state in the application, or all global states should reset after each test. -- 能够强制我们的provider具有特定的状态,无论是通过模拟还是通过操纵它们直到我们达到所需的状态。 +- Being able to force our providers to have a specific state, either through + mocking or by manipulating them until we reach the desired state. -让我们来逐个看看 [Riverpod] 是如何帮助你处理这些问题的。 +Let's see one by one how [Riverpod] helps you with these. -## `test` 和`testWidgets` 之间不应该保留任何状态。 +## No state should be preserved between `test`/`testWidgets`. -由于provider通常声明为全局变量,你可能会担心这一点。 -毕竟,全局状态使得测试非常困难,因为它可能需要漫长的 `配置(setUp)` 和 `销毁(tearDown)`。 +Since providers are usually declared as global variables, you might worry about +that one. +After all, global state makes testing very difficult, because it can require +lengthy `setUp`/`tearDown`. -但实际情况是虽然provider声明为全局的,但provider的状态却**不是**全局的。 +But the reality is: While providers are declared as globals, the state of a provider +is **not** global. -相反,它存储在一个名为 [ProviderContainer] 的对象中, -如果你查看Dart的示例,你可能已经看到了这个对象。 -如果你还没有看,请了解这个 [ProviderContainer] 对象是由 [ProviderScope] 隐式创建的, -这个widget可以让我们在Flutter项目中启用 [Riverpod]。 +Instead, it is stored in an object named [ProviderContainer], which you may have +seen if you looked at the dart-only examples. +If you haven't, know that this [ProviderContainer] object is implicitly created +by [ProviderScope], the widget that enables [Riverpod] on our project. -具体来说,这意味着两个使用provider的 `testWidgets` 不共享任何状态。 -因此,根本不需要任何 `配置(setUp)` 和 `销毁(tearDown)`。 +Concretely what this means is, two `testWidgets` using providers do not share +any state. +As such, there is no need for any `setUp`/`tearDown` at all. -解释这么多不如来一个例子: +But an example is better than lengthy explanations: -可以看到,虽然 `counterProvider` 被声明为全局变量,但测试间没有共享任何状态。 -因此,如果以不同的顺序执行,我们不必担心我们的测试可能表现不同,因为它们是在完全隔离的情况下运行的。 +As you can see, while `counterProvider` was declared as a global, no state was +shared between tests. +As such, we do not have to worry about our tests potentially behaving differently +if executed in a different order, since they are running in complete isolation. + +## Overriding the behavior of a provider during tests. -## 在测试期间重写provider的行为 +A common real-world application may have the following objects: -现实中,一个常见的应用可能有以下对象: +- It will have a `Repository` class, which provides a type-safe and simple API + to perform HTTP requests. -- 它将有一个 `Repository` 类,该类提供类型安全且简单的API来执行HTTP请求。 -- 一个管理应用程序状态的对象,可以使用 `Repository` 根据不同的因素来执行HTTP请求。 - 这可能是一个 `ChangeNotifier`, `Bloc`,甚至是一个provider。 +- An object that manages the application state, and may use `Repository` to perform + HTTP requests based on different factors. + This may be a `ChangeNotifier`, `Bloc`, or even a provider. -使用 [Riverpod],可以这样表示: +Using [Riverpod], this may be represented as follows: {trimSnippet(repositorySnippet)} -在这种情况下,当进行单元测试或widget测试时, -我们一般希望用一个返回预定义响应的伪实现来替换 `Repository` 实例,而不是发出真正的HTTP请求。 +In this situation, when making a unit/widget test, we will typically want to +replace our `Repository` instance with a fake implementation that returns +a pre-defined response instead of making a real HTTP request. -然后,我们希望我们的 `todoListProvider` 或类似的组件使用 `Repository` 的模拟实现。 +We will then want our `todoListProvider` or equivalent to use the mocked implementation +of `Repository`. -为了实现这一点,我们可以使用[ProviderScope] 或 [ProviderContainer]的 `overrides` 参数来覆盖 `repositoryProvider` 的行为: +To achieve this, we can use the `overrides` parameter of [ProviderScope]/[ProviderContainer] +to override the behavior of `repositoryProvider`: -正如您可以从高亮的代码中看到的,[ProviderScope]/[ProviderContainer] 允许用不同的行为替换provider的实现。 +As you can see by the highlighted code, [ProviderScope]/[ProviderContainer] +allows replacing the implementation of a provider with a different behavior. :::info -一些provider暴露了重写其行为的简化方法。 -例如,[FutureProvider] 允许使用 `AsyncValue` 重写provider: +Some providers expose simplified ways to override their behavior. +For example, [FutureProvider] allows overriding the provider with an `AsyncValue`: {trimSnippet(testOverrideInfo)} -**注意**:作为2.0.0版本的一部分, `overrideWithValue` 方法被暂时移除。 -它们将在未来的版本中重新添加。 +**Note**: As part of the 2.0.0 release, `overrideWithValue` methods are temporarily +removed. They will be added back in later versions. ::: :::info -使用 `family` 修饰符覆盖provider的语法略有不同。 +The syntax for overriding a provider with the `family` modifier is slightly different. -如果你像这样使用provider: +If you used a provider like this: ```dart final response = ref.watch(myProvider('12345')); ``` -你可以这样覆盖provider: +You could override the provider as: ```dart myProvider('12345').overrideWithValue(...)); @@ -132,9 +156,9 @@ myProvider('12345').overrideWithValue(...)); ::: -## 完整的widget测试用例 +## Full widget test example -最后,这里是我们Flutter测试的完整代码。 +Wrapping up, here is the entire full code for our Flutter test. {trimSnippet(testFull)} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/cookbooks/testing_dart.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/cookbooks/testing_dart.dart new file mode 100644 index 000000000..2d25bb8e8 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/cookbooks/testing_dart.dart @@ -0,0 +1,51 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; + +class FakeRepository {} + +final repositoryProvider = Provider((ref) => FakeRepository()); + +abstract class Todo { + String get id; + String get label; + bool get completed; +} + +final todoListProvider = FutureProvider>((ref) => []); + +void main() { +/* SNIPPET START */ + +test('override repositoryProvider', () async { + final container = ProviderContainer( + overrides: [ + // Override the behavior of repositoryProvider to return + // FakeRepository instead of Repository. + /* highlight-start */ + repositoryProvider.overrideWithValue(FakeRepository()) + /* highlight-end */ + // We do not have to override `todoListProvider`, it will automatically + // use the overridden repositoryProvider + ], + ); + + // The first read if the loading state + expect( + container.read(todoListProvider), + const AsyncValue>.loading(), + ); + + /// Wait for the request to finish + await container.read(todoListProvider.future); + + // Exposes the data fetched + expect(container.read(todoListProvider).value, [ + isA() + .having((s) => s.id, 'id', '42') + .having((s) => s.label, 'label', 'Hello world') + .having((s) => s.completed, 'completed', false), + ]); +}); + +/* SNIPPET END */ +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/cookbooks/testing_flutter.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/cookbooks/testing_flutter.dart new file mode 100644 index 000000000..6685a1bdb --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/cookbooks/testing_flutter.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; + +class MyApp extends StatelessWidget { + // ignore: prefer_const_constructors_in_immutables + MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return Container(); + } +} + +final repositoryProvider = Provider((ref) => FakeRepository()); + +class FakeRepository {} + +void main() { +/* SNIPPET START */ + +testWidgets('override repositoryProvider', (tester) async { + await tester.pumpWidget( + ProviderScope( + overrides: [ + // Override the behavior of repositoryProvider to return + // FakeRepository instead of Repository. + /* highlight-start */ + repositoryProvider.overrideWithValue(FakeRepository()) + /* highlight-end */ + // We do not have to override `todoListProvider`, it will automatically + // use the overridden repositoryProvider + ], + child: MyApp(), + ), + ); +}); + +/* SNIPPET END */ +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/cookbooks/testing_full.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/cookbooks/testing_full.dart new file mode 100644 index 000000000..0f79b4ee8 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/cookbooks/testing_full.dart @@ -0,0 +1,99 @@ + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; + +class Repository { + Future> fetchTodos() async => []; +} + +class Todo { + Todo({ + required this.id, + required this.label, + required this.completed, + }); + + final String id; + final String label; + final bool completed; +} + +// We expose our instance of Repository in a provider +final repositoryProvider = Provider((ref) => Repository()); + +/// The list of todos. Here, we are simply fetching them from the server using +/// [Repository] and doing nothing else. +final todoListProvider = FutureProvider((ref) async { + // Obtains the Repository instance + final repository = ref.read(repositoryProvider); + + // Fetch the todos and expose them to the UI. + return repository.fetchTodos(); +}); + +/// A mocked implementation of Repository that returns a pre-defined list of todos +class FakeRepository implements Repository { + @override + Future> fetchTodos() async { + return [ + Todo(id: '42', label: 'Hello world', completed: false), + ]; + } +} + +class TodoItem extends StatelessWidget { + const TodoItem({super.key, required this.todo}); + final Todo todo; + @override + Widget build(BuildContext context) { + return Text(todo.label); + } +} + +void main() { + testWidgets('override repositoryProvider', (tester) async { + await tester.pumpWidget( + ProviderScope( + overrides: [ + repositoryProvider.overrideWithValue(FakeRepository()) + ], + // Our application, which will read from todoListProvider to display the todo-list. + // You may extract this into a MyApp widget + child: MaterialApp( + home: Scaffold( + body: Consumer(builder: (context, ref, _) { + final todos = ref.watch(todoListProvider); + // The list of todos is loading or in error + if (todos.asData == null) { + return const CircularProgressIndicator(); + } + return ListView( + children: [ + for (final todo in todos.asData!.value) TodoItem(todo: todo) + ], + ); + }), + ), + ), + ), + ); + + // The first frame is a loading state. + expect(find.byType(CircularProgressIndicator), findsOneWidget); + + // Re-render. TodoListProvider should have finished fetching the todos by now + await tester.pump(); + + // No longer loading + expect(find.byType(CircularProgressIndicator), findsNothing); + + // Rendered one TodoItem with the data returned by FakeRepository + expect(tester.widgetList(find.byType(TodoItem)), [ + isA() + .having((s) => s.todo.id, 'todo.id', '42') + .having((s) => s.todo.label, 'todo.label', 'Hello world') + .having((s) => s.todo.completed, 'todo.completed', false), + ]); + }); +} \ No newline at end of file diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/cookbooks/testing_original_test_dart.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/cookbooks/testing_original_test_dart.dart new file mode 100644 index 000000000..6995de9b8 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/cookbooks/testing_original_test_dart.dart @@ -0,0 +1,63 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; +import 'package:riverpod/riverpod.dart'; + +/* SNIPPET START */ + +// A Counter implemented and tested with Dart only (no dependency on Flutter) + +// We declared a provider globally, and we will use it in two tests, to see +// if the state correctly resets to `0` between tests. + +final counterProvider = StateProvider((ref) => 0); + +// Using mockito to keep track of when a provider notify its listeners +class Listener extends Mock { + void call(int? previous, int value); +} + +void main() { + test('defaults to 0 and notify listeners when value changes', () { + // An object that will allow us to read providers + // Do not share this between tests. + final container = ProviderContainer(); + addTearDown(container.dispose); + final listener = Listener(); + + // Observe a provider and spy the changes. + container.listen( + counterProvider, + listener.call, + fireImmediately: true, + ); + + // the listener is called immediately with 0, the default value + verify(listener(null, 0)).called(1); + verifyNoMoreInteractions(listener); + + // We increment the value + container.read(counterProvider.notifier).state++; + + // The listener was called again, but with 1 this time + verify(listener(0, 1)).called(1); + verifyNoMoreInteractions(listener); + }); + + test('the counter state is not shared between tests', () { + // We use a different ProviderContainer to read our provider. + // This ensure that no state is reused between tests + final container = ProviderContainer(); + addTearDown(container.dispose); + final listener = Listener(); + + container.listen( + counterProvider, + listener.call, + fireImmediately: true, + ); + + // The new test correctly uses the default value: 0 + verify(listener(null, 0)).called(1); + verifyNoMoreInteractions(listener); + }); +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/cookbooks/testing_original_test_flutter.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/cookbooks/testing_original_test_flutter.dart new file mode 100644 index 000000000..f76abe181 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/cookbooks/testing_original_test_flutter.dart @@ -0,0 +1,56 @@ +// ignore_for_file: use_key_in_widget_constructors + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; + +/* SNIPPET START */ + +// A Counter implemented and tested using Flutter + +// We declared a provider globally, and we will use it in two tests, to see +// if the state correctly resets to `0` between tests. + +final counterProvider = StateProvider((ref) => 0); + +// Renders the current state and a button that allows incrementing the state +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Consumer(builder: (context, ref, _) { + final counter = ref.watch(counterProvider); + return ElevatedButton( + onPressed: () => ref.read(counterProvider.notifier).state++, + child: Text('$counter'), + ); + }), + ); + } +} + +void main() { + testWidgets('update the UI when incrementing the state', (tester) async { + await tester.pumpWidget(ProviderScope(child: MyApp())); + + // The default value is `0`, as declared in our provider + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Increment the state and re-render + await tester.tap(find.byType(ElevatedButton)); + await tester.pump(); + + // The state have properly incremented + expect(find.text('1'), findsOneWidget); + expect(find.text('0'), findsNothing); + }); + + testWidgets('the counter state is not shared between tests', (tester) async { + await tester.pumpWidget(ProviderScope(child: MyApp())); + + // The state is `0` once again, with no tearDown/setUp needed + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + }); +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/cookbooks/testing_override_info.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/cookbooks/testing_override_info.dart new file mode 100644 index 000000000..d71ae3c69 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/cookbooks/testing_override_info.dart @@ -0,0 +1,43 @@ +// ignore_for_file: avoid_unused_constructor_parameters + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +extension on ProviderBase { + // ignore: unused_element + Override overrideWithValue(Object? value) => throw UnimplementedError(); +} + +class Todo { + Todo({ + required String id, + required String label, + required bool completed, + }); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return Container(); + } +} + +/* SNIPPET START */ + +final todoListProvider = FutureProvider((ref) async => []); +// ... +/* SKIP */ +final foo = +/* SKIP END */ + ProviderScope( + overrides: [ + /// Allows overriding a FutureProvider to return a fixed value + todoListProvider.overrideWithValue( + AsyncValue.data([Todo(id: '42', label: 'Hello', completed: true)]), + ), + ], + child: const MyApp(), +); diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/cookbooks/testing_repository.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/cookbooks/testing_repository.dart new file mode 100644 index 000000000..c499279fc --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/cookbooks/testing_repository.dart @@ -0,0 +1,22 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +class Todo {} + +/* SNIPPET START */ + +class Repository { + Future> fetchTodos() async => []; +} + +// We expose our instance of Repository in a provider +final repositoryProvider = Provider((ref) => Repository()); + +/// The list of todos. Here, we are simply fetching them from the server using +/// [Repository] and doing nothing else. +final todoListProvider = FutureProvider((ref) async { + // Obtains the Repository instance + final repository = ref.watch(repositoryProvider); + + // Fetch the todos and expose them to the UI. + return repository.fetchTodos(); +}); diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose.mdx new file mode 100644 index 000000000..579ec40c4 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose.mdx @@ -0,0 +1,306 @@ +--- +title: 清除缓存并对状态处置做出反应 +--- + +import { Link } from "@site/src/components/Link"; +import { AutoSnippet, When } from "@site/src/components/CodeSnippet"; +import onDisposeExample from "./auto_dispose/on_dispose_example"; +import codegenKeepAlive from "!!raw-loader!./auto_dispose/codegen_keep_alive.dart"; +import rawAutoDispose from "!!raw-loader!./auto_dispose/raw_auto_dispose.dart"; +import invalidateExample from "!!raw-loader!./auto_dispose/invalidate_example.dart"; +import keepAlive from "./auto_dispose/keep_alive"; +import cacheForExtension from "!!raw-loader!./auto_dispose/cache_for_extension.dart"; +import cacheForUsage from "./auto_dispose/cache_for_usage"; +import invalidateFamilyExample from './auto_dispose/invalidate_family_example' + + +到目前为止,我们已经了解了如何创建/更新某些状态。 +但我们还没有谈论状态处置何时发生。 + + +Riverpod 提供了多种与状态处置进行交互的方法。 +这包括从延迟处置状态到对处置做出反应。 + + +## 何时状态被处置,如何改变这一点? + + + + +使用代码生成时,默认情况下,当提供者程序停止被监听时,状态将被处置。 +当监听器在整个帧中没有活动的监听器时,会发生这种情况。 +当这种情况发生时,状态将被处置。 + + +可以使用 `keepAlive: true` 选择禁用此行为。 +这样做可以防止在删除所有监听器时状态被处置。 + + + + + + + + +不使用代码生成时,默认情况下,当提供者程序停止监听时,状态不会被处置。 + + +您可以选择更改此行为并使用自动处置。 +执行此操作时,Riverpod 将跟踪提供者程序是否有监听器。 +然后,如果一帧的时间内提供者程序没有监听器,则状态将被处置。 + + +若要启用自动处置,可以在提供者程序类型旁边使用 `.autoDispose`: + + + + + +:::note + +启用/禁用自动处置在重新计算提供者程序时,对于是否处置状态没有影响。 +重新计算提供者程序时,状态将始终被处置。 +::: + +:::caution + +当提供者程序收到参数时,建议启用自动处置。 +这是因为否则,将为每个参数组合创建一个状态,这可能会导致内存泄漏。 +::: + + +## 对状态处置做出反应 + + +在 Riverpod 中,有几种内置的处置状态的方法: + + +- 提供者程序不再使用,并且处于“自动处置”模式(稍后会详细介绍)。 + 在这种情况下,与提供者程序的所有关联状态都将被处置。 +- 提供者程序将重新计算,例如 `ref.watch`。 + 在这种情况下,将处置以前的状态,并创建一个新状态。 + + +在这两种情况下。发生这种情况时,您可能希望执行一些逻辑。 +这可以通过 `ref.onDispose` 实现。 +此方法允许注册监听器,当状态被处置时回调。 + + +例如,您可能希望使用它来关闭任何活动 `StreamController`: + + + +:::caution + +不得触发副作用的 `ref.onDispose` 回调。 +修改提供者程序内部的 `onDispose` 可能会导致意外行为。 +::: + +:::info + +还有其他有用的生命周期,例如: + + +- `ref.onCancel` 当删除提供者程序的最后一个监听器时调用。 +- `ref.onResume` 当 `onCancel` 调用之后添加新的监听器时调用。 + +::: + +:::info + +您可以根据需要多次调用 `ref.onDispose`。 +在提供者程序中,每个可处置的对象可随意调用一次。 +这种做法使我们更容易发现我们何时忘记处置某些东西。 +::: + + +## 手动强制处置提供者程序,使用 `ref.invalidate` + + +有时,您可能希望强制处置提供者程序。 +这可以通过使用 `ref.invalidate` 来完成, +它可以从另一个提供者程序或小部件调用。 + + +使用 `ref.invalidate` 将处置当前提供者程序状态。 +然后有两种可能的结果: + + +- 如果监听提供者程序,则将创建一个新状态。 +- 如果提供者程序未被监听,则提供者程序将被完全处置。 + + + +:::info + +提供者程序可以使用 `ref.invalidateSelf` 使自己失效。 +尽管在这种情况下,这始终会导致创建新状态。 +::: + +:::tip + +当尝试使接收参数的提供者程序失效时, +可能会使一个特定的参数组合失效, +也可以同时使所有参数组合失效: + + +::: + + +## 使用 `ref.keepAlive` 微调处置 + + +如上所述,当自动处置启用时,如果在完整的一帧时间里提供者程序没有监听器,状态将被处置。 + + +但您可能希望对此行为有更多的控制权。例如, +您可能希望保留成功网络请求的状态,但不缓存失败的请求。 + + +这可以在启用自动处置后,使用通过 `ref.keepAlive` 来实现。 +使用它,您可以决定_何时_停止自动处置状态。 + + + +:::note + +如果重新计算提供者程序,将重新启用自动处置。 + + +也可以使用 `ref.keepAlive` 的返回值使其恢复到自动处置状态。 +::: + + +## 示例:在一段特定时间内保持状态 + + +目前,Riverpod 不提供在特定时间内保持状态的内置方法。 +但是,使用我们目前看到的工具,实现这样的功能很容易且可重用。 + + +通过使用 `Timer` + `ref.keepAlive`,我们可以在特定的时间内保持状态。 +为了使这个逻辑可重用,我们可以在扩展方法中实现它: + + + + +然后,我们可以这样使用它: + + + + +可以调整此逻辑以满足您的需求。 +例如,仅当提供者程序在特定时间内未被监听时, +才可以使用 `ref.onCancel`/`ref.onResume` 处置状态。 diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/cache_for_extension.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/cache_for_extension.dart new file mode 100644 index 000000000..cb7d14fe9 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/cache_for_extension.dart @@ -0,0 +1,18 @@ +import 'dart:async'; + +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +/* SNIPPET START */ +extension CacheForExtension on AutoDisposeRef { + /// 使提供者程序在 [duration] 内存活。 + void cacheFor(Duration duration) { + // 立即防止状态遭到破坏。 + final link = keepAlive(); + // 持续时间结束后,我们将重新启用自动处置功能。 + final timer = Timer(duration, link.close); + + // 可选项:重新计算提供者程序时(如使用 ref.watch) + // 我们取消待定计时器 + onDispose(timer.cancel); + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/cache_for_usage/codegen.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/cache_for_usage/codegen.dart new file mode 100644 index 000000000..b63cf3a46 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/cache_for_usage/codegen.dart @@ -0,0 +1,17 @@ +// ignore_for_file: unused_local_variable + +import 'package:http/http.dart' as http; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../cache_for_extension.dart'; + +part 'codegen.g.dart'; + +/* SNIPPET START */ +@riverpod +Future example(ExampleRef ref) async { + /// 保持状态 5 分钟 + ref.cacheFor(const Duration(minutes: 5)); + + return http.get(Uri.https('example.com')); +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/cache_for_usage/codegen.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/cache_for_usage/codegen.g.dart new file mode 100644 index 000000000..eb2dcfb0d --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/cache_for_usage/codegen.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$exampleHash() => r'3ff29b1cd8fa864286a2a04e39adf1c8589b4275'; + +/// See also [example]. +@ProviderFor(example) +final exampleProvider = AutoDisposeFutureProvider.internal( + example, + name: r'exampleProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$exampleHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ExampleRef = AutoDisposeFutureProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/cache_for_usage/index.ts b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/cache_for_usage/index.ts new file mode 100644 index 000000000..9d50c6614 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/cache_for_usage/index.ts @@ -0,0 +1,4 @@ +import raw from '!!raw-loader!./raw.dart'; +import codegen from '!!raw-loader!./codegen.dart'; + +export default { raw, codegen }; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/cache_for_usage/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/cache_for_usage/raw.dart new file mode 100644 index 000000000..a9c396eb7 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/cache_for_usage/raw.dart @@ -0,0 +1,15 @@ +// ignore_for_file: unused_local_variable + +import 'package:http/http.dart' as http; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../cache_for_extension.dart'; + +/* SNIPPET START */ +final provider = FutureProvider.autoDispose((ref) async { + /// 保持状态 5 分钟 + ref.cacheFor(const Duration(minutes: 5)); + + return http.get(Uri.https('example.com')); +}); +/* SNIPPET END */ diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/codegen_keep_alive.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/codegen_keep_alive.dart new file mode 100644 index 000000000..6a576d932 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/codegen_keep_alive.dart @@ -0,0 +1,10 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen_keep_alive.g.dart'; + +/* SNIPPET START */ +// 我们可以在注解中指定 "keepAlive" 来禁用状态自动处置功能 +@Riverpod(keepAlive: true) +int example(ExampleRef ref) { + return 0; +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/codegen_keep_alive.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/codegen_keep_alive.g.dart new file mode 100644 index 000000000..9f567e514 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/codegen_keep_alive.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen_keep_alive.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$exampleHash() => r'78f9426f6cbda80564387a9db8cd02368d890a85'; + +/// See also [example]. +@ProviderFor(example) +final exampleProvider = Provider.internal( + example, + name: r'exampleProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$exampleHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ExampleRef = ProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/invalidate_example.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/invalidate_example.dart new file mode 100644 index 000000000..db72c7279 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/invalidate_example.dart @@ -0,0 +1,23 @@ +// ignore_for_file: use_key_in_widget_constructors + +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +// We can specify autoDispose to enable automatic state destruction. +final someProvider = Provider.autoDispose((ref) { + return 0; +}); + +/* SNIPPET START */ +class MyWidget extends ConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + return ElevatedButton( + onPressed: () { + // 点击后,处置提供者程序。 + ref.invalidate(someProvider); + }, + child: const Text('dispose a provider'), + ); + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/invalidate_family_example/codegen.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/invalidate_family_example/codegen.dart new file mode 100644 index 000000000..4a93c3283 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/invalidate_family_example/codegen.dart @@ -0,0 +1,24 @@ +// ignore_for_file: unused_local_variable + +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +late WidgetRef ref; + +/* SNIPPET START */ +@riverpod +String label(LabelRef ref, String userName) { + return 'Hello $userName'; +} + +// ... + +void onTap() { + // 使该提供者程序所有可能的参数组合无效。 + ref.invalidate(labelProvider); + // 仅使特定组合无效 + ref.invalidate(labelProvider('John')); +} +/* SNIPPET END */ diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/invalidate_family_example/codegen.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/invalidate_family_example/codegen.g.dart new file mode 100644 index 000000000..ffe8961bd --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/invalidate_family_example/codegen.g.dart @@ -0,0 +1,159 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$labelHash() => r'20aa8ce0231205540f466f91259732bd86953c64'; + +/// Copied from Dart SDK +class _SystemHash { + _SystemHash._(); + + static int combine(int hash, int value) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + value); + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); + return hash ^ (hash >> 6); + } + + static int finish(int hash) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + // ignore: parameter_assignments + hash = hash ^ (hash >> 11); + return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); + } +} + +/// See also [label]. +@ProviderFor(label) +const labelProvider = LabelFamily(); + +/// See also [label]. +class LabelFamily extends Family { + /// See also [label]. + const LabelFamily(); + + /// See also [label]. + LabelProvider call( + String userName, + ) { + return LabelProvider( + userName, + ); + } + + @override + LabelProvider getProviderOverride( + covariant LabelProvider provider, + ) { + return call( + provider.userName, + ); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'labelProvider'; +} + +/// See also [label]. +class LabelProvider extends AutoDisposeProvider { + /// See also [label]. + LabelProvider( + String userName, + ) : this._internal( + (ref) => label( + ref as LabelRef, + userName, + ), + from: labelProvider, + name: r'labelProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$labelHash, + dependencies: LabelFamily._dependencies, + allTransitiveDependencies: LabelFamily._allTransitiveDependencies, + userName: userName, + ); + + LabelProvider._internal( + super._createNotifier, { + required super.name, + required super.dependencies, + required super.allTransitiveDependencies, + required super.debugGetCreateSourceHash, + required super.from, + required this.userName, + }) : super.internal(); + + final String userName; + + @override + Override overrideWith( + String Function(LabelRef provider) create, + ) { + return ProviderOverride( + origin: this, + override: LabelProvider._internal( + (ref) => create(ref as LabelRef), + from: from, + name: null, + dependencies: null, + allTransitiveDependencies: null, + debugGetCreateSourceHash: null, + userName: userName, + ), + ); + } + + @override + AutoDisposeProviderElement createElement() { + return _LabelProviderElement(this); + } + + @override + bool operator ==(Object other) { + return other is LabelProvider && other.userName == userName; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, userName.hashCode); + + return _SystemHash.finish(hash); + } +} + +mixin LabelRef on AutoDisposeProviderRef { + /// The parameter `userName` of this provider. + String get userName; +} + +class _LabelProviderElement extends AutoDisposeProviderElement + with LabelRef { + _LabelProviderElement(super.provider); + + @override + String get userName => (origin as LabelProvider).userName; +} +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/invalidate_family_example/index.ts b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/invalidate_family_example/index.ts new file mode 100644 index 000000000..9d50c6614 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/invalidate_family_example/index.ts @@ -0,0 +1,4 @@ +import raw from '!!raw-loader!./raw.dart'; +import codegen from '!!raw-loader!./codegen.dart'; + +export default { raw, codegen }; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/invalidate_family_example/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/invalidate_family_example/raw.dart new file mode 100644 index 000000000..6df871dbb --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/invalidate_family_example/raw.dart @@ -0,0 +1,20 @@ +// ignore_for_file: unused_local_variable + +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +late WidgetRef ref; + +/* SNIPPET START */ +final provider = Provider.autoDispose.family((ref, name) { + return 'Hello $name'; +}); + +// ... + +void onTap() { + // 使该提供者程序所有可能的参数组合无效。 + ref.invalidate(provider); + // 仅使特定组合无效 + ref.invalidate(provider('John')); +} +/* SNIPPET END */ diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/keep_alive/codegen.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/keep_alive/codegen.dart new file mode 100644 index 000000000..8f6bf9032 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/keep_alive/codegen.dart @@ -0,0 +1,21 @@ +// ignore_for_file: unused_local_variable + +import 'package:http/http.dart' as http; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +/* SNIPPET START */ +@riverpod +Future example(ExampleRef ref) async { + final response = await http.get(Uri.parse('https://example.com')); + // 只有在请求成功完成后,我们才会让提供者程序存活。 + // 如果请求失败(并抛出异常),那么当提供者程序停止被监听时, + // 状态就会被处置。 + ref.keepAlive(); + + // 我们可以使用 `link` 恢复自动处置行为: + // link.close(); + + return response.body; +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/keep_alive/codegen.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/keep_alive/codegen.g.dart new file mode 100644 index 000000000..dfe5d12cc --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/keep_alive/codegen.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$exampleHash() => r'4fa856c55e84da9525dcecfab1c897e61456325b'; + +/// See also [example]. +@ProviderFor(example) +final exampleProvider = AutoDisposeFutureProvider.internal( + example, + name: r'exampleProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$exampleHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ExampleRef = AutoDisposeFutureProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/keep_alive/index.ts b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/keep_alive/index.ts new file mode 100644 index 000000000..9d50c6614 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/keep_alive/index.ts @@ -0,0 +1,4 @@ +import raw from '!!raw-loader!./raw.dart'; +import codegen from '!!raw-loader!./codegen.dart'; + +export default { raw, codegen }; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/keep_alive/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/keep_alive/raw.dart new file mode 100644 index 000000000..375b52b6f --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/keep_alive/raw.dart @@ -0,0 +1,19 @@ +// ignore_for_file: unused_local_variable + +import 'package:http/http.dart' as http; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +/* SNIPPET START */ +final provider = FutureProvider.autoDispose((ref) async { + final response = await http.get(Uri.parse('https://example.com')); + // 只有在请求成功完成后,我们才会让提供者程序存活。 + // 如果请求失败(并抛出异常),那么当提供者程序停止被监听时, + // 状态就会被处置。 + final link = ref.keepAlive(); + + // 我们可以使用 `link` 恢复自动处置行为: + // link.close(); + + return response.body; +}); +/* SNIPPET END */ diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/on_dispose_example/codegen.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/on_dispose_example/codegen.dart new file mode 100644 index 000000000..d9ada052f --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/on_dispose_example/codegen.dart @@ -0,0 +1,23 @@ +// ignore_for_file: unused_local_variable + +import 'dart:async'; + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +@riverpod +int other(OtherRef ref) => 0; + +/* SNIPPET START */ +@riverpod +Stream example(ExampleRef ref) { + final controller = StreamController(); + + // 当状态被处置时,我们关闭 StreamController。 + ref.onDispose(controller.close); + + // TO-DO: 在 StreamController 中推送一些值 + return controller.stream; +} +/* SNIPPET END */ diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/on_dispose_example/codegen.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/on_dispose_example/codegen.g.dart new file mode 100644 index 000000000..5ae67d1d2 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/on_dispose_example/codegen.g.dart @@ -0,0 +1,40 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$otherHash() => r'b23696171643dfbab23d167ed9b5ab0639e6a86c'; + +/// See also [other]. +@ProviderFor(other) +final otherProvider = AutoDisposeProvider.internal( + other, + name: r'otherProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$otherHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef OtherRef = AutoDisposeProviderRef; +String _$exampleHash() => r'29f92958e0d0e3f13ac033e92cd2e4072339c7db'; + +/// See also [example]. +@ProviderFor(example) +final exampleProvider = AutoDisposeStreamProvider.internal( + example, + name: r'exampleProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$exampleHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ExampleRef = AutoDisposeStreamProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/on_dispose_example/index.ts b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/on_dispose_example/index.ts new file mode 100644 index 000000000..9d50c6614 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/on_dispose_example/index.ts @@ -0,0 +1,4 @@ +import raw from '!!raw-loader!./raw.dart'; +import codegen from '!!raw-loader!./codegen.dart'; + +export default { raw, codegen }; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/on_dispose_example/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/on_dispose_example/raw.dart new file mode 100644 index 000000000..aa1a4e59d --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/on_dispose_example/raw.dart @@ -0,0 +1,17 @@ +// ignore_for_file: unused_local_variable + +import 'dart:async'; + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +/* SNIPPET START */ +final provider = StreamProvider((ref) { + final controller = StreamController(); + + // 当状态被处置时,我们关闭 StreamController。 + ref.onDispose(controller.close); + + // TO-DO: 在 StreamController 中推送一些值 + return controller.stream; +}); +/* SNIPPET END */ diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/raw_auto_dispose.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/raw_auto_dispose.dart new file mode 100644 index 000000000..155623207 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/auto_dispose/raw_auto_dispose.dart @@ -0,0 +1,7 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +/* SNIPPET START */ +// 我们可以指定 autoDispose 来启用状态自动处置功能。 +final provider = Provider.autoDispose((ref) { + return 0; +}); diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests.mdx new file mode 100644 index 000000000..ab0a6a7f5 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests.mdx @@ -0,0 +1,275 @@ +--- +title: 组合请求 +version: 1 +--- + +import { Link } from "@site/src/components/Link"; +import { AutoSnippet } from "@site/src/components/CodeSnippet"; +import functionalRef from "./combining_requests/functional_ref"; +import notifierRef from "./combining_requests/notifier_ref"; +import watchExample from "./combining_requests/watch_example"; +import watchPlacement from "./combining_requests/watch_placement"; +import listenExample from "./combining_requests/listen_example"; +import readExample from './combining_requests/read_example' + + +到目前为止,我们只看到请求彼此独立的案例。 +但一个常见的用例是必须根据另一个请求的结果触发请求。 + + +我们可以使用机制来做到这一点, +方法是将一个提供者程序的结果作为参数传递给另一个提供者程序。 + + +但这种方法有一些缺点: + + +- 这泄露了实现细节。 + 现在,UI 需要了解所有被其他提供者程序使用的提供者程序。 +- 每当参数发生变化时,都会产生一个全新的状态。 + 通过传递参数,当参数更改时,无法保持以前的状态。 +- 它使合并请求变得更加困难。 +- 这使得开发工具的用处降低。devtool 不会知道提供者程序之间的关系。 + + +为了改善这一点,Riverpod 提供了一种不同的方法来合并请求。 + + +## 基础知识:获取 "ref" + + +组合请求的所有可能方法都有一个共同点:它们都基于 `Ref` 对象。 + + +该 `Ref` 对象是所有提供者程序都有权访问的对象。 +它允许他们访问各种生命周期监听器, +还可以使用各种方法来组合提供者程序。 + + +可以获取的位置 `Ref` 取决于提供者程序的类型。 + + +在函数提供者程序中,将 `Ref` 作为参数传递给提供者程序的函数: + + + + +在类变体中,`Ref` 是通知者程序类的一个属性: + + + + +## 使用 ref 读取提供者程序。 + + +### `ref.watch` 方法。 + + +现在我们已经获得了一个 `Ref`,我们可以用它来组合请求。 +执行此操作的主要方法是使用 `ref.watch`。 +通常建议对代码进行架构设计, +以便可以使用 `ref.watch` 的其他选项,因为它通常更易于维护。 + + +该 `ref.watch` 方法采用提供者程序,并返回其当前状态。 +然后,每当监听的提供者程序发生更改时,我们的提供者程序将在 +下一帧或下一次读取时失效并重新生成。 + + +通过使用 `ref.watch`,您的逻辑变得既是“反应式”又是“声明式”。 +这意味着您的逻辑将在需要时自动重新计算。 +并且更新机制不依赖于副作用,例如“更改”。 +这类似于 StatelessWidgets 的行为方式。 + + +例如,我们可以定义一个监听用户位置的提供者程序。 +然后,我们可以使用这个位置来获取用户附近的餐馆列表。 + + + +:::info + +当监听的提供者程序发生更改并且我们的请求重新计算时,将保留以前的状态,直到新请求完成。 +同时,当请求处于挂起状态时,将设置 "isLoading" 和 "isReloading" 标志。 + + +这使 UI 能够显示以前的状态或加载指示器,甚至两者兼而有之。 +::: + +:::info + +请注意我们如何使用 `ref.watch(locationProvider.future)` 来替代 `ref.watch(locationProvider)`。 +那是因为我们 `locationProvider` 是异步的。因此,我们希望等待初始值可用。 + + +如果我们省略了这一点 `.future`,我们将收到一个 `AsyncValue`, +它是 `locationProvider` 当前状态的快照。但是,如果还没有可用的位置, +我们将无能为力。 +::: + +:::caution + +调用 `ref.watch` “命令式”执行的内部代码被认为是不好的做法。 +这意味着在提供者程序的构建阶段可能未执行的任何代码。 +这包括通告程序上的“监听器”回调或方法: + + +::: + + +### `ref.listen`/`listenSelf` 方法。 + + +该 `ref.listen` 方法是 `ref.watch` 的替代方法。 +它类似于传统的 "listen"/"addListener" 方法。 +它接受一个提供者程序和一个回调, +并在提供者程序的内容发生更改时调用该回调。 + + +通常建议重构代码,您可以使用 `ref.watch` 替代 `ref.listen`, +因为后者由于其命令性质而更容易出错。 +但是 `ref.listen` 可以有助于添加一些快速逻辑而不必进行重大重构。 + + +我们可以重写 `ref.watch` 示例并使用 `ref.listen` 代替 + + + +:::info + +在提供者程序的构建阶段使用 `ref.listen` 是完全安全的。 +如果以某种方式重新计算提供者程序,则以前的监听器将被删除。 + + +或者,您可以根据需要使用 `ref.listen` 的返回值手动删除监听器。 +::: + + +### `ref.read` 方法 + + +最后一个可用选项是 `ref.read`。 +它类似于 `ref.watch` 返回提供者程序的当前状态。 +但与 `ref.watch` 不同的是,它不监听提供者程序。 + + +因此,`ref.read` 应该只被用在你不能使用 `ref.watch` 的地方, +比如通告程序的内部方法。 + + + +:::caution + +`ref.read` 在提供者程序上使用时要小心,因为它不监听提供者程序, +因此如果不监听提供者程序,则该提供者程序可能会决定处置其状态。 +::: diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/functional_ref/codegen.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/functional_ref/codegen.dart new file mode 100644 index 000000000..815283fa1 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/functional_ref/codegen.dart @@ -0,0 +1,18 @@ +// ignore_for_file: unused_local_variable + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +@riverpod +int other(OtherRef ref) => 0; + +/* SNIPPET START */ +@riverpod +int example(ExampleRef ref) { + // "Ref" 可以在这里用来阅读其他提供者程序 + final otherValue = ref.watch(otherProvider); + + return 0; +} +/* SNIPPET END */ diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/functional_ref/codegen.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/functional_ref/codegen.g.dart new file mode 100644 index 000000000..d7bab87bb --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/functional_ref/codegen.g.dart @@ -0,0 +1,40 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$otherHash() => r'b23696171643dfbab23d167ed9b5ab0639e6a86c'; + +/// See also [other]. +@ProviderFor(other) +final otherProvider = AutoDisposeProvider.internal( + other, + name: r'otherProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$otherHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef OtherRef = AutoDisposeProviderRef; +String _$exampleHash() => r'4429d7d3bb2b31fea4cc42c2f2af02d3f3d10693'; + +/// See also [example]. +@ProviderFor(example) +final exampleProvider = AutoDisposeProvider.internal( + example, + name: r'exampleProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$exampleHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ExampleRef = AutoDisposeProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/functional_ref/index.ts b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/functional_ref/index.ts new file mode 100644 index 000000000..9d50c6614 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/functional_ref/index.ts @@ -0,0 +1,4 @@ +import raw from '!!raw-loader!./raw.dart'; +import codegen from '!!raw-loader!./codegen.dart'; + +export default { raw, codegen }; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/functional_ref/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/functional_ref/raw.dart new file mode 100644 index 000000000..c4586d9f2 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/functional_ref/raw.dart @@ -0,0 +1,14 @@ +// ignore_for_file: unused_local_variable + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +final otherProvider = Provider((ref) => 0); + +/* SNIPPET START */ +final provider = Provider((ref) { + // "Ref" 可以在这里用来阅读其他提供者程序 + final otherValue = ref.watch(otherProvider); + + return 0; +}); +/* SNIPPET END */ diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/listen_example/codegen.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/listen_example/codegen.dart new file mode 100644 index 000000000..25c6a2f37 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/listen_example/codegen.dart @@ -0,0 +1,17 @@ +// ignore_for_file: unused_local_variable, avoid_print + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +final otherProvider = Provider((ref) => 0); + +/* SNIPPET START */ +@riverpod +int example(ExampleRef ref) { + ref.listen(otherProvider, (previous, next) { + print('Changed from: $previous, next: $next'); + }); + + return 0; +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/listen_example/codegen.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/listen_example/codegen.g.dart new file mode 100644 index 000000000..0e0b3489f --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/listen_example/codegen.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$exampleHash() => r'd614303f372e06e6ab96035affc4c07a53b28741'; + +/// See also [example]. +@ProviderFor(example) +final exampleProvider = AutoDisposeProvider.internal( + example, + name: r'exampleProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$exampleHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ExampleRef = AutoDisposeProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/listen_example/index.ts b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/listen_example/index.ts new file mode 100644 index 000000000..9d50c6614 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/listen_example/index.ts @@ -0,0 +1,4 @@ +import raw from '!!raw-loader!./raw.dart'; +import codegen from '!!raw-loader!./codegen.dart'; + +export default { raw, codegen }; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/listen_example/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/listen_example/raw.dart new file mode 100644 index 000000000..12b578dd0 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/listen_example/raw.dart @@ -0,0 +1,13 @@ +// ignore_for_file: unused_local_variable, avoid_print +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +final otherProvider = Provider((ref) => 0); + +/* SNIPPET START */ +final provider = Provider((ref) { + ref.listen(otherProvider, (previous, next) { + print('Changed from: $previous, next: $next'); + }); + + return 0; +}); diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/notifier_ref/codegen.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/notifier_ref/codegen.dart new file mode 100644 index 000000000..1d2139dbf --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/notifier_ref/codegen.dart @@ -0,0 +1,21 @@ +// ignore_for_file: unused_local_variable + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +@riverpod +int other(OtherRef ref) => 0; + +/* SNIPPET START */ +@riverpod +class Example extends _$Example { + @override + int build() { + // "Ref" 可以在这里用来阅读其他提供者程序 + final otherValue = ref.watch(otherProvider); + + return 0; + } +} +/* SNIPPET END */ diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/notifier_ref/codegen.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/notifier_ref/codegen.g.dart new file mode 100644 index 000000000..ca4e51873 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/notifier_ref/codegen.g.dart @@ -0,0 +1,40 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$otherHash() => r'b23696171643dfbab23d167ed9b5ab0639e6a86c'; + +/// See also [other]. +@ProviderFor(other) +final otherProvider = AutoDisposeProvider.internal( + other, + name: r'otherProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$otherHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef OtherRef = AutoDisposeProviderRef; +String _$exampleHash() => r'893db991b377b8e314e60c429043e5e81f1fd526'; + +/// See also [Example]. +@ProviderFor(Example) +final exampleProvider = AutoDisposeNotifierProvider.internal( + Example.new, + name: r'exampleProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$exampleHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$Example = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/notifier_ref/index.ts b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/notifier_ref/index.ts new file mode 100644 index 000000000..9d50c6614 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/notifier_ref/index.ts @@ -0,0 +1,4 @@ +import raw from '!!raw-loader!./raw.dart'; +import codegen from '!!raw-loader!./codegen.dart'; + +export default { raw, codegen }; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/notifier_ref/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/notifier_ref/raw.dart new file mode 100644 index 000000000..b68379fdc --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/notifier_ref/raw.dart @@ -0,0 +1,19 @@ +// ignore_for_file: unused_local_variable + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +final otherProvider = Provider((ref) => 0); + +/* SNIPPET START */ +final provider = NotifierProvider(MyNotifier.new); + +class MyNotifier extends Notifier { + @override + int build() { + // "Ref" 可以在这里用来阅读其他提供者程序 + final otherValue = ref.watch(otherProvider); + + return 0; + } +} +/* SNIPPET END */ diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/read_example/codegen.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/read_example/codegen.dart new file mode 100644 index 000000000..1469d1629 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/read_example/codegen.dart @@ -0,0 +1,23 @@ +// ignore_for_file: unused_local_variable + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +final otherProvider = Provider((ref) => 0); + +/* SNIPPET START */ +@riverpod +class MyNotifier extends _$MyNotifier { + @override + int build() { + // 糟糕的!不要在这里使用 "read",因为它不是反应性的 + ref.read(otherProvider); + + return 0; + } + + void increment() { + ref.read(otherProvider); // 这里使用 "read" 就可以了 + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/read_example/codegen.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/read_example/codegen.g.dart new file mode 100644 index 000000000..9c655cd8a --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/read_example/codegen.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$myNotifierHash() => r'353efe22dd5a91b2d036286211ac9e60c9de5f6d'; + +/// See also [MyNotifier]. +@ProviderFor(MyNotifier) +final myNotifierProvider = + AutoDisposeNotifierProvider.internal( + MyNotifier.new, + name: r'myNotifierProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$myNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$MyNotifier = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/read_example/index.ts b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/read_example/index.ts new file mode 100644 index 000000000..9d50c6614 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/read_example/index.ts @@ -0,0 +1,4 @@ +import raw from '!!raw-loader!./raw.dart'; +import codegen from '!!raw-loader!./codegen.dart'; + +export default { raw, codegen }; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/read_example/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/read_example/raw.dart new file mode 100644 index 000000000..8c173f806 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/read_example/raw.dart @@ -0,0 +1,22 @@ +// ignore_for_file: unused_local_variable + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +final otherProvider = Provider((ref) => 0); + +/* SNIPPET START */ +final notifierProvider = NotifierProvider(MyNotifier.new); + +class MyNotifier extends Notifier { + @override + int build() { + // 糟糕的!不要在这里使用 "read",因为它不是反应性的 + ref.read(otherProvider); + + return 0; + } + + void increment() { + ref.read(otherProvider); // 这里使用 "read" 就可以了 + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_example/codegen.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_example/codegen.dart new file mode 100644 index 000000000..afacd67bc --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_example/codegen.dart @@ -0,0 +1,44 @@ +// ignore_for_file: unused_local_variable + +import 'dart:convert'; + +import 'package:http/http.dart' as http; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +final otherProvider = Provider((ref) => 0); + +const someStream = Stream<({double longitude, double latitude})>.empty(); + +/* SNIPPET START */ +@riverpod +Stream<({double longitude, double latitude})> location(LocationRef ref) { + // TO-DO: 返回获取当前位置的流 + return someStream; +} + +@riverpod +Future> restaurantsNearMe(RestaurantsNearMeRef ref) async { + // 我们使用 "ref.watch" 来获取最新位置。 + // 通过在提供者程序之后指定 ".future", + // 我们的代码将在等待到至少一个位置信息后可用。 + final location = await ref.watch(locationProvider.future); + + // 我们现在可以根据该位置发出网络请求。 + // 例如,我们可以使用 Google Map API: + // https://developers.google.com/maps/documentation/places/web-service/search-nearby + final response = await http.get( + Uri.https('maps.googleapis.com', 'maps/api/place/nearbysearch/json', { + 'location': '${location.latitude},${location.longitude}', + 'radius': '1500', + 'type': 'restaurant', + 'key': '', + }), + ); + // 从 JSON 中获取餐厅名称 + final json = jsonDecode(response.body) as Map; + final results = (json['results'] as List).cast>(); + return results.map((e) => e['name']! as String).toList(); +} +/* SNIPPET END */ diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_example/codegen.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_example/codegen.g.dart new file mode 100644 index 000000000..910b079a1 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_example/codegen.g.dart @@ -0,0 +1,44 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$locationHash() => r'22e666f1e1ce04ce03d8f8d5652e25b54c1d1af3'; + +/// See also [location]. +@ProviderFor(location) +final locationProvider = + AutoDisposeStreamProvider<({double longitude, double latitude})>.internal( + location, + name: r'locationProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$locationHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef LocationRef + = AutoDisposeStreamProviderRef<({double longitude, double latitude})>; +String _$restaurantsNearMeHash() => r'dd49cc1e6f16abb34dd15286d171e322c06b93b8'; + +/// See also [restaurantsNearMe]. +@ProviderFor(restaurantsNearMe) +final restaurantsNearMeProvider = + AutoDisposeFutureProvider>.internal( + restaurantsNearMe, + name: r'restaurantsNearMeProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$restaurantsNearMeHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef RestaurantsNearMeRef = AutoDisposeFutureProviderRef>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_example/index.ts b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_example/index.ts new file mode 100644 index 000000000..9d50c6614 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_example/index.ts @@ -0,0 +1,4 @@ +import raw from '!!raw-loader!./raw.dart'; +import codegen from '!!raw-loader!./codegen.dart'; + +export default { raw, codegen }; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_example/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_example/raw.dart new file mode 100644 index 000000000..8852726ff --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_example/raw.dart @@ -0,0 +1,41 @@ +// ignore_for_file: unused_local_variable + +import 'dart:convert'; + +import 'package:http/http.dart' as http; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +final otherProvider = Provider((ref) => 0); + +const someStream = Stream<({double longitude, double latitude})>.empty(); + +/* SNIPPET START */ +final locationProvider = + StreamProvider<({double longitude, double latitude})>((ref) { + // TO-DO: 返回获取当前位置的流 + return someStream; +}); + +final restaurantsNearMeProvider = FutureProvider>((ref) async { + // 我们使用 "ref.watch" 来获取最新位置。 + // 通过在提供者程序之后指定 ".future", + // 我们的代码将在等待到至少一个位置信息后可用。 + final location = await ref.watch(locationProvider.future); + + // 我们现在可以根据该位置发出网络请求。 + // 例如,我们可以使用 Google Map API: + // https://developers.google.com/maps/documentation/places/web-service/search-nearby + final response = await http.get( + Uri.https('maps.googleapis.com', 'maps/api/place/nearbysearch/json', { + 'location': '${location.latitude},${location.longitude}', + 'radius': '1500', + 'type': 'restaurant', + 'key': '', + }), + ); + // 从 JSON 中获取餐厅名称 + final json = jsonDecode(response.body) as Map; + final results = (json['results'] as List).cast>(); + return results.map((e) => e['name']! as String).toList(); +}); +/* SNIPPET END */ diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_placement/codegen.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_placement/codegen.dart new file mode 100644 index 000000000..b5da254f6 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_placement/codegen.dart @@ -0,0 +1,37 @@ +// ignore_for_file: unused_local_variable + +import 'package:flutter/foundation.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +final otherProvider = Provider((ref) => 0); + +/* SNIPPET START */ +@riverpod +int example(ExampleRef ref) { + ref.watch(otherProvider); // 好! + ref.onDispose(() => ref.watch(otherProvider)); // 糟糕! + + final someListenable = ValueNotifier(0); + someListenable.addListener(() { + ref.watch(otherProvider); // 糟糕! + }); + + return 0; +} + +@riverpod +class MyNotifier extends _$MyNotifier { + @override + int build() { + ref.watch(otherProvider); // 好! + ref.onDispose(() => ref.watch(otherProvider)); // 糟糕! + + return 0; + } + + void increment() { + ref.watch(otherProvider); // 糟糕! + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_placement/codegen.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_placement/codegen.g.dart new file mode 100644 index 000000000..b1dcd7086 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_placement/codegen.g.dart @@ -0,0 +1,41 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$exampleHash() => r'd4d63f5cf1aaec5b7c6a19e6fee18ddf070147ec'; + +/// See also [example]. +@ProviderFor(example) +final exampleProvider = AutoDisposeProvider.internal( + example, + name: r'exampleProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$exampleHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ExampleRef = AutoDisposeProviderRef; +String _$myNotifierHash() => r'ad79fdb5b0e72a800fa03efc1e7157f0d1524844'; + +/// See also [MyNotifier]. +@ProviderFor(MyNotifier) +final myNotifierProvider = + AutoDisposeNotifierProvider.internal( + MyNotifier.new, + name: r'myNotifierProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$myNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$MyNotifier = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_placement/index.ts b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_placement/index.ts new file mode 100644 index 000000000..9d50c6614 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_placement/index.ts @@ -0,0 +1,4 @@ +import raw from '!!raw-loader!./raw.dart'; +import codegen from '!!raw-loader!./codegen.dart'; + +export default { raw, codegen }; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_placement/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_placement/raw.dart new file mode 100644 index 000000000..1eb02eea0 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/combining_requests/watch_placement/raw.dart @@ -0,0 +1,35 @@ +// ignore_for_file: unused_local_variable + +import 'package:flutter/foundation.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +final otherProvider = Provider((ref) => 0); + +/* SNIPPET START */ +final provider = Provider((ref) { + ref.watch(otherProvider); // 好! + ref.onDispose(() => ref.watch(otherProvider)); // 糟糕! + + final someListenable = ValueNotifier(0); + someListenable.addListener(() { + ref.watch(otherProvider); // 糟糕! + }); + + return 0; +}); + +final notifierProvider = NotifierProvider(MyNotifier.new); + +class MyNotifier extends Notifier { + @override + int build() { + ref.watch(otherProvider); // 好! + ref.onDispose(() => ref.watch(otherProvider)); // 糟糕! + + return 0; + } + + void increment() { + ref.watch(otherProvider); // 糟糕! + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/do_dont.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/do_dont.mdx new file mode 100644 index 000000000..d1b4fe452 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/do_dont.mdx @@ -0,0 +1,260 @@ +--- +title: 最佳实践 +--- + +import { Link } from "@site/src/components/Link"; +import { AutoSnippet, When } from "@site/src/components/CodeSnippet"; + + +为了确保代码具有良好的可维护性, +这里列出了您在使用 Riverpod 时应遵循的良好实践。 + + +此列表并不详尽,并且可能会发生变化。 +如果您有任何建议,请随时[提出问题](https://github.com/rrousselGit/riverpod/issues/new?assignees=rrousselGit&labels=documentation%2C+needs+triage&projects=&template=example_request.md&title=)。 + + +此列表中的项目没有任何特定的顺序。 + + +这些建议的很大一部分可以通过 [riverpod_lint](https://pub.dev/packages/riverpod_lint) 来执行。 +请参阅了解安装说明。 + + +## 避免!在小部件中初始化提供者程序​ + + +提供者程序应自行初始化。 +它们不应由外部元素(例如小部件)初始化。 + + +如果不这样做可能会导致可能的竞争条件和意外行为。 + + +**不要** + +```dart +class WidgetState extends State { + @override + void initState() { + super.initState(); + // 坏的:提供者程序应该自己初始化自己 + ref.read(provider).init(); + } +} +``` + + +**考虑** + + +对于这个问题,没有“一刀切”的解决方案。 +如果您的初始化逻辑取决于提供者程序的外部因素, +则放置此类逻辑的正确位置通常是触发导航的按钮的 `onPressed` 方法中: + +```dart +ElevatedButton( + onPressed: () { + ref.read(provider).init(); + Navigator.of(context).push(...); + }, + child: Text('Navigate'), +) +``` + + +## 避免!使用本地小部件状态的提供者程序。 + + +提供者程序被设计为共享业务状态。 +它们不适合用于本地小部件状态,例如: + + +- 存储表单状态 +- 当前选择的项目 +- 动画 +- Flutter 处理常见的 "controller" 相关的所有内容(例如 `TextEditingController` ) + + +如果您正在寻找一种处理本地小部件状态的方法,请考虑使用 +[flutter_hooks](https://pub.dev/packages/flutter_hooks) 代替。 + + +不鼓励这样做的一个原因是这种状态通常仅限于一条路由。 +如果不这样做,可能会破坏应用程序的后退按钮,因为新页面会覆盖上一页的状态。 + + +## 不要!在提供者程序初始化期间执行副作用​ + + +提供者程序通常应用于表示“读”操作。 +您不应该将它们用于“写”操作,例如提交表单。 + + +使用提供者程序进行此类操作可能会产生意外行为,例如 +如果执行了前一个操作,则跳过副作用。 + + +如果您正在寻找一种处理副作用的加载/错误状态的方法, +请参阅。 + + +**不要**: + +```dart +final submitProvider = FutureProvider((ref) async { + final formState = ref.watch(formState); + + // 坏的:提供者程序不应用于“写”操作。 + return http.post('https://my-api.com', body: formState.toJson()); +}); +``` + + +## 首选!ref.watch/read/listen(和类似的 API)以及静态已知的提供者程序​ + + +Riverpod 强烈建议启用 lint 规则(通过 riverpod_lint)。 +但为了使 lint 发挥作用,您的代码应该以可静态分析的方式编写。 + + +如果不这样做,可能会更难发现错误或导致 lints 误报。 + + +**应该**: + +```dart +final provider = Provider((ref) => 42); + +... + +// 好的,因为提供者程序是静态已知的 +ref.watch(provider); +``` + + +**不要**: + +```dart +class Example extends ConsumerWidget { + Example({required this.provider}); + final Provider provider; + + @override + Widget build(context, ref) { + // 不好,因为静态分析无法知道 `provider` 是什么 + ref.watch(provider); + } +} +``` + + +## 避免!动态创建提供者程序​ + + +提供者程序应该专门是顶级 final 变量。 + + +**应该**: + +```dart +final provider = Provider((ref) => 'Hello world'); +``` + + +**不要**: + +```dart +class Example { + // 不支持的操作。可能导致内存泄漏和意外行为。 + final provider = Provider((ref) => 'Hello world'); +} +``` + +:::info + +允许将提供者程序创建为 static final 变量, +但代码生成器不支持。 +::: diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/eager_initialization.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/eager_initialization.mdx new file mode 100644 index 000000000..35c85ad4b --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/eager_initialization.mdx @@ -0,0 +1,117 @@ +--- +title: 急切的初始化提供者程序 +version: 1 +--- + +import { Link } from "@site/src/components/Link"; +import { AutoSnippet } from "@site/src/components/CodeSnippet"; +import consumerExample from "!!raw-loader!./eager_initialization/consumer_example.dart"; +import asyncConsumerExample from "!!raw-loader!./eager_initialization/async_consumer_example.dart"; +import requireValue from "./eager_initialization/require_value"; + + +默认情况下,所有提供者程序都以延迟方式初始化。 +这意味着提供者程序仅在首次使用时进行初始化。 +这对于仅在应用程序的某些部分使用的提供者程序很有用。 + + +不幸的是,由于 Dart 的工作方式(出于摇树目的),没有办法将提供者程序标记为需要急切初始化。 +但是,一种解决方案是在应用程序的根组件下强制读取要急切初始化的提供者程序。 + + +推荐的方法是简单地 "watch" 位于 `ProviderScope` 下方的 Consumer 中的提供者程序: + + + +:::note + +请考虑将初始化使用者放在 "MyApp" 或公共小部件中。 +这使你的测试能够使用相同的行为,方法是从你的主数据中删除逻辑。 +::: + + +### 常见疑问 + + +#### 当提供者程序更改时,这不会重建我们的整个应用程序吗? + + +不,事实并非如此。 +在上面给出的示例中,负责急切初始化的消费者程序是一个单独的小部件,它只返回一个 `child` . + + +关键部分是它返回一个 `child` ,而不是实例化 `MaterialApp` 自身。 +这意味着,如果重新生成 `_EagerInitialization`,`child` 变量将不会更改。 +当一个小部件没有改变时,Flutter 不会重建它。 + + +因此,除非另一个小部件也在监听该提供者程序,否则只会 `_EagerInitialization` 重新构建。 + + +#### 使用此方法,如何处理加载和错误状态? + + +您可以像往常一样在 `Consumer` 中处理加载/错误状态。 +您可以 `_EagerInitialization` 检查提供者程序是否处于 "loading" 状态, +如果是则返回 `CircularProgressIndicator` 否则返回 `child`: + + + + +#### 我已经处理了加载/错误状态,但其他消费者程序仍然收到 AsyncValue!有没有办法不必处理每个小部件中的加载/错误状态? + + +与其试图让你的提供者程序不公开一个 `AsyncValue` ,不如让你的小部件使用 `AsyncValue.requireValue`。 +这将读取数据,而无需进行模式匹配。如果一个错误溜走了,它会抛出一个异常,并带有明确的信息。 + + + +:::note + +尽管有一些方法可以在这些情况下不公开加载/错误状态(依赖于范围),但通常不建议这样做。 +创建两个提供者程序并使用覆盖所增加的复杂性不值得麻烦。 +::: diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/eager_initialization/async_consumer_example.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/eager_initialization/async_consumer_example.dart new file mode 100644 index 000000000..9a64181b0 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/eager_initialization/async_consumer_example.dart @@ -0,0 +1,28 @@ +// ignore_for_file: unused_local_variable, use_key_in_widget_constructors, unused_element + +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +final myProvider = FutureProvider((ref) => 0); + +/* SNIPPET START */ +class _EagerInitialization extends ConsumerWidget { + const _EagerInitialization({required this.child}); + final Widget child; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final result = ref.watch(myProvider); + + // 处理错误状态和加载状态 + if (result.isLoading) { + return const CircularProgressIndicator(); + } else if (result.hasError) { + return const Text('Oopsy!'); + } + + return child; + } +} +/* SNIPPET END */ + diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/eager_initialization/consumer_example.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/eager_initialization/consumer_example.dart new file mode 100644 index 000000000..944e6e420 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/eager_initialization/consumer_example.dart @@ -0,0 +1,36 @@ +// ignore_for_file: unused_local_variable, use_key_in_widget_constructors + +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +final myProvider = Provider((ref) => 0); + +/* SNIPPET START */ +void main() { + runApp(ProviderScope(child: MyApp())); +} + +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return const _EagerInitialization( + // TODO: 在这里渲染你的 APP + child: MaterialApp(), + ); + } +} + +class _EagerInitialization extends ConsumerWidget { + const _EagerInitialization({required this.child}); + final Widget child; + + @override + Widget build(BuildContext context, WidgetRef ref) { + // 通过观察提供者程序,来初始化提供者程序。 + // 通过使用 "watch",提供者程序将保持存活,不会被处置。 + ref.watch(myProvider); + return child; + } +} +/* SNIPPET END */ + diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/eager_initialization/require_value/codegen.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/eager_initialization/require_value/codegen.dart new file mode 100644 index 000000000..a13c16b90 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/eager_initialization/require_value/codegen.dart @@ -0,0 +1,24 @@ +// ignore_for_file: unused_local_variable, use_key_in_widget_constructors + +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +/* SNIPPET START */ +// 一个急切需要初始化的 provider +@riverpod +Future example(ExampleRef ref) async => 'Hello world'; + +class MyConsumer extends ConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + final result = ref.watch(exampleProvider); + + /// 如果提供者程序被正确的急切初始化了, + /// 那么我们可以使用 "requireValue" 直接读取数据。 + return Text(result.requireValue); + } +} +/* SNIPPET END */ diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/eager_initialization/require_value/codegen.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/eager_initialization/require_value/codegen.g.dart new file mode 100644 index 000000000..739f3ea63 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/eager_initialization/require_value/codegen.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$exampleHash() => r'd421d08db0ee9d10af5521159561135d8c5fa57c'; + +/// See also [example]. +@ProviderFor(example) +final exampleProvider = AutoDisposeFutureProvider.internal( + example, + name: r'exampleProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$exampleHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ExampleRef = AutoDisposeFutureProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/eager_initialization/require_value/index.ts b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/eager_initialization/require_value/index.ts new file mode 100644 index 000000000..9d50c6614 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/eager_initialization/require_value/index.ts @@ -0,0 +1,4 @@ +import raw from '!!raw-loader!./raw.dart'; +import codegen from '!!raw-loader!./codegen.dart'; + +export default { raw, codegen }; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/eager_initialization/require_value/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/eager_initialization/require_value/raw.dart new file mode 100644 index 000000000..807db346a --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/eager_initialization/require_value/raw.dart @@ -0,0 +1,20 @@ +// ignore_for_file: unused_local_variable, use_key_in_widget_constructors + +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +/* SNIPPET START */ +// 一个急切需要初始化的 provider +final exampleProvider = FutureProvider((ref) async => 'Hello world'); + +class MyConsumer extends ConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + final result = ref.watch(exampleProvider); + + /// 如果提供者程序被正确的急切初始化了, + /// 那么我们可以使用 "requireValue" 直接读取数据。 + return Text(result.requireValue); + } +} +/* SNIPPET END */ diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/faq.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/faq.mdx new file mode 100644 index 000000000..4c6e25e26 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/faq.mdx @@ -0,0 +1,355 @@ +--- +title: FAQ 常见问题 +--- + +import { Link } from "@site/src/components/Link"; +import { AutoSnippet, When } from "@site/src/components/CodeSnippet"; + + +以下是社区中的一些常见问题: + + +## `ref.refresh` 和 `ref.invalidate` 之间有什么不同? + + +您可能已经注意到 `ref` 有两种方法可以强制提供者程序重新计算,并且想知道它们有何不同。 + + +它比你想象的要简单: `ref.refresh` 只是 `invalidate` + `read` 的语法糖: + +```dart +T refresh(provider) { + invalidate(provider); + return read(provider); +} +``` + + +如果您在重新计算后不关心提供者程序的新值, +那么 `invalidate` 就是正确的选择。 +如果这样做,请改用 `refresh`。 + +:::info + +该逻辑通过 lint 规则自动执行。 +如果您尝试使用 `ref.refresh` 而不使用返回值,您将收到警告。 +::: + + +行为上的主要区别在于,通过在使提供者程序失效后, +提供者程序会**立即**重新计算。 +然而,如果我们调用 `invalidate` 但没有立即读取它, +那么更新将稍后触发。 + + +“稍后”更新通常是在下一帧开始时。 +然而,如果当前被未监听的提供者程序失效, +则它在再次被监听之前都不会被更新。 + + +## 为什么 Ref 和 WidgetRef 之间没有共享接口?​ + + +Riverpod 自愿分离 `Ref` 和 `WidgetRef`。 +这样做的目的是为了避免编写有条件依赖其中之一的代码。 + + +一个问题是 `Ref` 和 `WidgetRef` 虽然看起来相似,但存在细微的差异。 +依赖于两者的代码将变得不可靠,并且难以发现。 + + +同时,依赖 `WidgetRef` 就相当于依赖 `BuildContext`。 +它实际上将您的逻辑放在 UI 层中,但不建议这样做。 + +--- + + +此类代码应重构为**始终**使用 `Ref`。 + + +此问题的解决方案通常是将您的逻辑移至 `Notifier` 中 +(请参阅 ), +然后让您的逻辑成为该 `Notifier` 的方法。 + + +这样,当您的小部件想要调用此逻辑时,它们可以编写如下内容: + +```dart +ref.read(yourNotifierProvider.notifier).yourMethod(); +``` + + +`yourMethod` 将使用 `Notifier` 的 `Ref` 与其他提供者程序交互。 + + +## 为什么我们需要扩展 ConsumerWidget 而不是使用原始的 StatelessWidget? + + +这是由于 `InheritedWidget` API 中的一个不幸的限制造成的。 + + +有几个问题: + + +- 无法使用 `InheritedWidget` 实现监听器的“当更改时”。 + 这意味着诸如 `ref.listen` 之类的内容不能与 `BuildContext` 一起使用。 + + `State.didChangeDependencies` 是最接近它的东西,但它并不可靠。 + 一个问题是,即使没有改变依赖关系,生命周期也可能被触发, + 特别是如果你的 widget 树使用 GlobalKeys(并且一些 Flutter widget 已经在内部这样做了)。 + + + +- 监听 `InheritedWidget` 的小部件永远不会停止监听它。 + 这通常适用于纯元数据,例如 "theme" 或 "media query"。 + + 对于业务逻辑来说,这是一个问题。 + 假设您使用提供者程序来表示分页 API。 + 当页面偏移量发生变化时,您不希望小部件继续监听先前可见的页面。 + + +- `InheritedWidget` 无法跟踪小部件何时停止监听它们。 + Riverpod 有时依赖于跟踪提供者程序是否被监听。 + + +此功能对于自动处置机制和将参数传递给提供者程序的能力至关重要。 +这些功能使 Riverpod 如此强大。 + + +也许在遥远的将来,这些问题将会得到解决。在这种情况下, +Riverpod 将迁移到使用 `BuildContext` 而不是 `Ref`。 +这将允许使用 `StatelessWidget` 而不是 `ConsumerWidget` 。 +但那是以后再说了! + + +## 为什么 hooks_riverpod 不导出 flutter_hooks? + + +这是为了尊重良好的版本控制实践。 + + +虽然您不能在没有 `flutter_hooks` 的情况下使用 `hooks_riverpod`, +但这两个包都是独立版本控制的。 +当其中一个可能会发生重大变化时,不会影响另一个。 + + +## 为什么 Riverpod 在某些情况下使用 `identical` 而不是 `==` 来过滤更新?​ + + +通知者程序使用 `identical` 而不是 `==` 来过滤更新。 + + +这是因为 Riverpod 用户为了实现 copyWith +而使用 Freezed/built_value 等代码生成器是很常见的。 +这些包重写 `==` 以深入比较对象。深度对象比较的成本相当高。 +“业务逻辑”模型往往具有很多属性。更糟糕的是,他们还有列表、地图等集合。 + + +同时,当使用复杂的“业务”对象时,大多数 `state = newState` 调用 +总是会产生通知(否则调用 setter 没有意义)。一般来说, +当当前状态和新状态相等时,我们调用 `state = newState` 的主要情况 +是对于原始对象(整数、枚举、字符串,但不是列表/类/...)。 +这些对象“默认被规范化”。如果这些对象是相等的, +那么它们通常也是“相同的(identical)”。 + + +因此,Riverpod 使用 `identical` 来过滤更新是一个两全其美的默认值尝试。 +对于复杂对象,我们并不真正关心过滤对象, +并且由于代码生成器默认生成 `==` 覆盖,因此 `==` 可能会很昂贵, +使用 `identical` 提供了一种通知监听器的有效方式。 +同时,对于简单对象,`identical` 确实正确过滤了冗余通知。 + + +最后且同样重要的一点是,您可以通过重写通知者程序上的方法 `updateShouldNotify` 来更改此行为。 + + +## 有没有办法一次性重置所有提供者程序 + + +不,没有办法立即重置所有提供者程序。 + + +这是故意的,因为它被认为是反模式。 +立即重置所有提供者程序通常会重置您不打算重置的提供者程序。 + + +当用户注销时想要重置应用程序状态的用户通常会询问此问题。 +如果这就是您所希望的,那么您应该将所有内容都 +通过 `ref.watch` 依赖于 "user" 提供者程序的用户状态。 + + +然后,当用户注销时,依赖于它的所有提供者程序将自动重置,但其他所有内容都将保持不变。 + + +## 我收到错误“在处理小部件后无法使用‘ref’”,这是怎么回事?​ + + +您可能还会看到 "Bad state: No ProviderScope found",这是同一问题的较旧错误消息。 + + +当您尝试在不再安装的小部件中使用 `ref` 时,会发生此错误。这通常发生在 `await` 之后: + +```dart +ElevatedButton( + onPressed: () async { + await future; + ref.read(...); // 可能抛出 "Cannot use "ref" after the widget was disposed" + } +) +``` + + +解决方案是,与 `BuildContext` 一样,在使用 `ref` 之前检查 `mounted`: + +```dart +ElevatedButton( + onPressed: () async { + await future; + if (!context.mounted) return; + ref.read(...); // 不再抛出 + } +) +``` diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request.mdx new file mode 100644 index 000000000..490a98fbf --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request.mdx @@ -0,0 +1,580 @@ +--- +title: 开始你的第一次 provider/network 请求 +pagination_prev: introduction/getting_started +version: 1 +--- + +import { Link } from "@site/src/components/Link"; +import { AutoSnippet, When } from "@site/src/components/CodeSnippet"; +import activity from "./first_request/activity"; +import provider from "./first_request/provider"; +import consumer from "./first_request/consumer"; +import consumerWidget from "./first_request/consumer_widget"; +import consumerStatefulWidget from "./first_request/consumer_stateful_widget"; +import hookConsumerWidget from "./first_request/hook_consumer_widget"; +import Legend from "./first_request/legend/legend"; + + +网络请求是任何应用程序的核心。但是,在发出网络请求时,需要考虑很多事项: + + +- UI 应在发出请求时呈现加载状态 +- 应妥善处理错误 +- 如果可能,应缓存请求 + + +在本节中,我们将看到 Riverpod 如何帮助我们自然地处理所有这些问题。 + + +## 配置 `ProviderScope` + + +在开始发出网络请求之前,请确保将其 `ProviderScope` 添加到应用程序的根目录。 + +```dart +void main() { + runApp( + // To install Riverpod, we need to add this widget above everything else. + // This should not be inside "MyApp" but as direct parameter to "runApp". + // 为了安装 Riverpod,我们需要将这个小组件添加到所有的小组件之上。 + // 它不应该在 “MyApp” 内部,而是作为 “runApp” 的直接参数。 + ProviderScope( + child: MyApp(), + ), + ); +} +``` + + +这样就可以为整个应用程序启用 Riverpod。 + +:::note + +有关完整的安装步骤(例如安装 [riverpod_lint](https://pub.dev/packages/riverpod_lint) +和运行代码生成器),请查看。 +::: + + +## 在 “provider” 中执行网络请求 + + +执行网络请求通常就是我们所说的“业务逻辑”。在 Riverpod 中,业务逻辑位于“providers”中。 +provider 是一种具有超能力的函数。它们的行为与正常函数类似,并具有以下额外好处: + + +- 保持缓存 +- 提供默认错误/加载处理 +- 可以被监听 +- 当某些数据发生变化时自动重新执行 + + +这使得 provider 非常适合 _GET_ 网络请求(与 _POST/etc_ 请求一样,请参阅)。 + + +举个例子,让我们做一个简单的应用程序,建议在无聊时做一个随机的活动。 +为此,我们将使用 [Bored API](https://boredapi.com/)。具体而言, +我们将在 `/api/activity` 端点上执行 _GET_ 请求。端点返回一个 JSON 对象,我们将把它解析为 Dart 类实例。 +然后,下一步是在 UI 中显示此活动。我们还将确保在发出请求时呈现加载状态,并优雅地处理错误。 + + +听起来不错?让我们开始吧! + + +### 定义数据模型 + + +在开始之前,我们需要定义从 API 接收的数据模型。 +该模型还需要一种方法将 JSON 对象解析为 Dart 类实例。 + + +通常,建议使用 [Freezed](https://pub.dev/packages/freezed) +或 [json_serializable](https://pub.dev/packages/json_serializable) 等代码生成器来处理 JSON 解码。 +虽然但是,也可以手动完成。 + + +无论如何,这是我们的模型: + + + + +### 创建 provider + + +现在我们有了模型,可以开始创建查询 API。 +为此,我们需要创建我们的第一个 provider。 + + +定义 provider 的语法如下: + + +((ref) { + <你的逻辑写这里> +}); +`} + annotations={[ + { + offset: 6, + length: 4, + label: "provider 的变量", + description: <> + + +此变量将用于与我们的提供者程序进行交互。 +变量必须是 final 和“顶级”(global)。 + + + }, + { + offset: 13, + length: 12, + label: "provider 的类型", + description: <> + + +通常为 `Provider`、`FutureProvider` 或 `StreamProvider`。 +使用提供者程序的类型取决于函数的返回值。 +例如,要创建一个 `Future`,您需要一个 `FutureProvider`。 + + +`FutureProvider` 是你最想用的那个。 + +:::tip + +不要从“我应该选择哪个提供者程序”的角度来思考。 +相反,从“我想返回什么”的角度来思考。 +提供者程序的类型将自然而然地遵循。 +::: + + + }, + { + offset: 25, + length: 13, + label: "修饰符(可选的)", + description: <> + + +通常,在提供者程序的类型之后,您可能会看到一个“修饰符”。 +修饰符是可选的,用于以类型安全的方式调整提供者程序的行为。 + + +目前有两种修饰符可用: + + +- `autoDispose`,这将在提供者程序停止使用时自动清除缓存。 + 另请参阅 +- `family`,这可以将参数传递给提供者程序。 + 另请参阅。 + + + }, + { + offset: 48, + length: 3, + label: "Ref 引用", + description: <> + + +用于与其他 provider 交互的对象。 +所有提供者程序都有一个 `Ref`;要么作为 `provider` 函数的参数,要么作为 `Notifier` 的属性。 + + + }, + { + offset: 57, + length: 17, + label: "provider 函数", + description: <> + + +这就是我们放置提供者程序逻辑的地方。首次读取提供者程序时将调用此函数。 +后续读取不会再次调用该函数,而是返回缓存的值。 + + + }, +]} +/> + + + + +} +`} + annotations={[ + { + offset: 0, + length: 9, + label: "注解", + description: <> + + +所有提供者程序都必须使用 `@riverpod` 或 `@Riverpod()` 进行注释。 +此注释可以放置在全局函数或类上。 +通过此注释,可以配置提供者程序。 + + +例如,我们可以通过编写 `@Riverpod(keepAlive: true)` 来禁用“自动处置”(我们将在后面看到)。 + + + }, + { + offset: 17, + length: 10, + label: "带注解的函数", + description: <> + + +带批注的函数的名称决定了如何与提供者程序进行交互。 +对于给定的函数 `myFunction` ,将生成一个生成的 `myFunctionProvider` 变量。 + + +带注释的函数**必须**指定“ref”作为第一个参数。 +除此之外,该函数可以具有任意数量的参数,包括泛型。 +如果愿意,该函数也可以自由返回 `Future`/`Stream`。 + + +首次读取提供者程序时将调用此函数。 +后续读取不会再次调用该函数,而是返回缓存的值。 + + + }, + { + offset: 28, + length: 17, + label: "Ref", + description: <> + + +用于与其他提供者程序交互的对象。 +所有提供者程序都有一个 `Ref`;要么作为 `provider` 函数的参数,要么作为 `Notifier` 的属性。 +此对象的类型由函数/类的名称确定。 + + + }, +]} +/> + + + +在我们的例子中,我们希望从 API 中 _GET_ 一个活动。 +由于 _GET_ 是异步操作,这意味着我们需要创建一个 `Future`。 + + +因此,使用前面定义的语法,我们可以按如下方式定义提供者程序: + + + + +在此代码片段中,我们定义了一个名为 `activityProvider` 的提供者程序, +我们的 UI 将能够使用该提供者程序来获取随机活动。值得一提的是: + + +- 在 UI 读取提供者程序至少一次之前,不会执行网络请求。 +- 后续读取不会重新执行网络请求,而是返回之前提取的活动。 +- 如果 UI 停止使用此提供者程序,则缓存将被处置。 + 然后,如果 UI 再次使用提供者程序,则会发出新的网络请求。 +- 我们没有捕获错误。这是自动的,因为提供者程序本身会处理错误。 + 如果网络请求或 JSON 解析抛出错误,则 Riverpod 将捕获该错误。 + 然后,UI 将自动包含呈现错误页面所需的信息。 + +:::info + +提供者程序是“懒惰的”。定义提供者程序不会执行网络请求。 +相反,网络请求将在首次读取提供者程序时执行。 +::: + + +### 在 UI 中呈现网络请求的响应 + + +现在我们已经定义了一个提供者程序,我们可以开始在 UI 中使用它来显示活动。 + + +为了与提供者程序交互,我们需要一个名为“ref”的对象。 +您之前可能在提供者程序定义中看到过它,因为提供者程序自然可以访问“ref”对象。 +但在我们的例子中,我们不是提供者程序,而是小部件。那么我们如何获得“ref”呢? + + +解决方案是使用名为 `Consumer` 的自定义小部件。 +`Consumer` 是一个类似于 `Builder` 的小部件,但还有一个额外的好处,那就是为我们提供了一个“ref”。 +这使我们的 UI 能够读取提供者程序。以下示例展示了如何使用 `Consumer`: + + + + +在该代码段中,我们使用了 `Consumer` 来读取和 `activityProvider` 显示活动。 +我们还优雅地处理了加载/错误状态。 +请注意 UI 如何能够处理加载/错误状态,而无需在提供者程序中执行任何特殊操作。 +同时,如果小部件要重建,则不会正确地重新执行网络请求。 +其他小部件也可以访问同一提供者程序,而无需重新执行网络请求。 + +:::info + +小部件可以根据需要,监听任意数量的提供者程序。为此,只需添加更多 ref.watch 调用即可。 +::: + +:::tip + +确保安装 linter。这将使您的 IDE 能够提供重构选项, +以自动添加 `Consumer` 或将 `StatelessWidget` 重构为 `ConsumerWidget`。 + + +有关安装步骤,请参阅。 +::: + + +## 更进一步:使用 `ConsumerWidget` 替代 `Consumer` 删除代码缩进。 + + +在前面的示例中,我们使用 `Consumer` 来读取提供者程序。 +尽管这种方法没有错,但添加的缩进会使代码更难阅读。 + + +Riverpod 提供了另一种实现相同结果的方法: +我们可以定义 `ConsumerWidget` / `ConsumerStatefulWidget` 来代替在 +`StatelessWidget` / `StatefulWidget` 返回 `Consumer` 小组件。 +`ConsumerWidget` 和 `ConsumerStatefulWidget` 实际上是 `StatelessWidget` / `StatefulWidget` 和 `Consumer` 的融合。 +它们的行为与原来的 couterpart 相同,但具有提供“ref”的额外好处。 + + +我们可以使用 `ConsumerWidget` 重写前面的例子,如下所示: + + + + +至于 `ConsumerStatefulWidget`,我们会这样写: + + + + +### Flutter_hooks 注意事项:结合 `HookWidget` 和 `ConsumerWidget` + +:::caution + +如果您以前从未听说过“钩子(hooks)”,请随时跳过本节。 +[Flutter_hooks](https://pub.dev/packages/flutter_hooks) 是一个独立于 Riverpod 的软件包, +但经常与 Riverpod 一起使用。如果您不熟悉 Riverpod,不建议使用“钩子”。 +有关详细信息,请参阅。 +::: + + +如果您正在使用 `flutter_hooks`,您可能想知道如何将 `ConsumerWidget` +和 `HookWidget` 组合在一起。毕竟,两者都涉及更改扩展的小部件类。 + + +Riverpod 为此问题提供了解决方案:`HookConsumerWidget` 和 `StatefulHookConsumerWidget`。 +类似于 `ConsumerWidget` 和 `ConsumerStatefulWidget` 是 `StatelessWidget` / `StatefulWidget` 和 `Consumer` 融合, +`HookConsumerWidget` 和 `StatefulHookConsumerWidget` 是 `HookWidget` / `HookStatefulWidget` 和 `Consumer` 的融合。 +因此,它们允许在同一个小部件中同时使用钩子和提供者程序。 + + +为了展示这一点,我们可以再次重写前面的例子: + + diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/activity.ts b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/activity.ts new file mode 100644 index 000000000..5cea035c7 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/activity.ts @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw/activity.dart"; +import codegen from "!!raw-loader!./codegen/activity.dart"; + +export default { + raw: raw, + hooks: raw, + codegen: codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/codegen/activity.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/codegen/activity.dart new file mode 100644 index 000000000..01cdb97f0 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/codegen/activity.dart @@ -0,0 +1,23 @@ +/* SNIPPET START */ import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'activity.freezed.dart'; +part 'activity.g.dart'; + +/// `GET /api/activity` 请求的响应。 +/// +/// 这个定义使用了 `freezed` 和 `json_serializable`。 +@freezed +class Activity with _$Activity { + factory Activity({ + required String key, + required String activity, + required String type, + required int participants, + required double price, + }) = _Activity; + + /// 将 JSON 对象转换为 [Activity] 实例。 + /// 这可以实现 API 响应的类型安全读取。 + factory Activity.fromJson(Map json) => + _$ActivityFromJson(json); +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/codegen/activity.freezed.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/codegen/activity.freezed.dart new file mode 100644 index 000000000..6ffa343c1 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/codegen/activity.freezed.dart @@ -0,0 +1,237 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'activity.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +Activity _$ActivityFromJson(Map json) { + return _Activity.fromJson(json); +} + +/// @nodoc +mixin _$Activity { + String get key => throw _privateConstructorUsedError; + String get activity => throw _privateConstructorUsedError; + String get type => throw _privateConstructorUsedError; + int get participants => throw _privateConstructorUsedError; + double get price => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $ActivityCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ActivityCopyWith<$Res> { + factory $ActivityCopyWith(Activity value, $Res Function(Activity) then) = + _$ActivityCopyWithImpl<$Res, Activity>; + @useResult + $Res call( + {String key, + String activity, + String type, + int participants, + double price}); +} + +/// @nodoc +class _$ActivityCopyWithImpl<$Res, $Val extends Activity> + implements $ActivityCopyWith<$Res> { + _$ActivityCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? key = null, + Object? activity = null, + Object? type = null, + Object? participants = null, + Object? price = null, + }) { + return _then(_value.copyWith( + key: null == key + ? _value.key + : key // ignore: cast_nullable_to_non_nullable + as String, + activity: null == activity + ? _value.activity + : activity // ignore: cast_nullable_to_non_nullable + as String, + type: null == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as String, + participants: null == participants + ? _value.participants + : participants // ignore: cast_nullable_to_non_nullable + as int, + price: null == price + ? _value.price + : price // ignore: cast_nullable_to_non_nullable + as double, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$ActivityImplCopyWith<$Res> + implements $ActivityCopyWith<$Res> { + factory _$$ActivityImplCopyWith( + _$ActivityImpl value, $Res Function(_$ActivityImpl) then) = + __$$ActivityImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String key, + String activity, + String type, + int participants, + double price}); +} + +/// @nodoc +class __$$ActivityImplCopyWithImpl<$Res> + extends _$ActivityCopyWithImpl<$Res, _$ActivityImpl> + implements _$$ActivityImplCopyWith<$Res> { + __$$ActivityImplCopyWithImpl( + _$ActivityImpl _value, $Res Function(_$ActivityImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? key = null, + Object? activity = null, + Object? type = null, + Object? participants = null, + Object? price = null, + }) { + return _then(_$ActivityImpl( + key: null == key + ? _value.key + : key // ignore: cast_nullable_to_non_nullable + as String, + activity: null == activity + ? _value.activity + : activity // ignore: cast_nullable_to_non_nullable + as String, + type: null == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as String, + participants: null == participants + ? _value.participants + : participants // ignore: cast_nullable_to_non_nullable + as int, + price: null == price + ? _value.price + : price // ignore: cast_nullable_to_non_nullable + as double, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$ActivityImpl implements _Activity { + _$ActivityImpl( + {required this.key, + required this.activity, + required this.type, + required this.participants, + required this.price}); + + factory _$ActivityImpl.fromJson(Map json) => + _$$ActivityImplFromJson(json); + + @override + final String key; + @override + final String activity; + @override + final String type; + @override + final int participants; + @override + final double price; + + @override + String toString() { + return 'Activity(key: $key, activity: $activity, type: $type, participants: $participants, price: $price)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ActivityImpl && + (identical(other.key, key) || other.key == key) && + (identical(other.activity, activity) || + other.activity == activity) && + (identical(other.type, type) || other.type == type) && + (identical(other.participants, participants) || + other.participants == participants) && + (identical(other.price, price) || other.price == price)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => + Object.hash(runtimeType, key, activity, type, participants, price); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$ActivityImplCopyWith<_$ActivityImpl> get copyWith => + __$$ActivityImplCopyWithImpl<_$ActivityImpl>(this, _$identity); + + @override + Map toJson() { + return _$$ActivityImplToJson( + this, + ); + } +} + +abstract class _Activity implements Activity { + factory _Activity( + {required final String key, + required final String activity, + required final String type, + required final int participants, + required final double price}) = _$ActivityImpl; + + factory _Activity.fromJson(Map json) = + _$ActivityImpl.fromJson; + + @override + String get key; + @override + String get activity; + @override + String get type; + @override + int get participants; + @override + double get price; + @override + @JsonKey(ignore: true) + _$$ActivityImplCopyWith<_$ActivityImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/codegen/activity.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/codegen/activity.g.dart new file mode 100644 index 000000000..55a7a5383 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/codegen/activity.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'activity.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$ActivityImpl _$$ActivityImplFromJson(Map json) => + _$ActivityImpl( + key: json['key'] as String, + activity: json['activity'] as String, + type: json['type'] as String, + participants: json['participants'] as int, + price: (json['price'] as num).toDouble(), + ); + +Map _$$ActivityImplToJson(_$ActivityImpl instance) => + { + 'key': instance.key, + 'activity': instance.activity, + 'type': instance.type, + 'participants': instance.participants, + 'price': instance.price, + }; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/codegen/provider.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/codegen/provider.dart new file mode 100644 index 000000000..0f70b54f6 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/codegen/provider.dart @@ -0,0 +1,19 @@ +/* SNIPPET START */ import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'activity.dart'; + +// 代码生成正常工作的必要条件 +part 'provider.g.dart'; + +/// 这将创建一个名为 `activityProvider` 的提供者程序 +/// 它可以缓存函数执行的结果 +@riverpod +Future activity(ActivityRef ref) async { + // 使用 package:http, 我们可以从 Bored API 获取一个随机的活动。 + final response = await http.get(Uri.https('boredapi.com', '/api/activity')); + // 使用 dart:convert, 然后我们将 JSON 有效负载解码为 Map 数据结构。 + final json = jsonDecode(response.body) as Map; + // 最后,我们将 Map 转换为 Activity 实例。 + return Activity.fromJson(json); +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/codegen/provider.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/codegen/provider.g.dart new file mode 100644 index 000000000..da8d6861d --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/codegen/provider.g.dart @@ -0,0 +1,29 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$activityHash() => r'636cd5510e09cbfc46f31b74a70d9e98c89e95a4'; + +/// 这将创建一个名为 `activityProvider` 的提供者程序 +/// 它可以缓存函数执行的结果 +/// +/// Copied from [activity]. +@ProviderFor(activity) +final activityProvider = AutoDisposeFutureProvider.internal( + activity, + name: r'activityProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$activityHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ActivityRef = AutoDisposeFutureProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/consumer.ts b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/consumer.ts new file mode 100644 index 000000000..a625b7073 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/consumer.ts @@ -0,0 +1,8 @@ +import raw from "!!raw-loader!./raw/consumer.dart"; + +export default { + raw: raw, + hooks: raw, + codegen: raw, + hooksCodegen: raw, +}; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/consumer_stateful_widget.ts b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/consumer_stateful_widget.ts new file mode 100644 index 000000000..5e6ac973a --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/consumer_stateful_widget.ts @@ -0,0 +1,8 @@ +import raw from "!!raw-loader!./raw/consumer_stateful_widget.dart"; + +export default { + raw: raw, + hooks: raw, + codegen: raw, + hooksCodegen: raw, +}; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/consumer_widget.ts b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/consumer_widget.ts new file mode 100644 index 000000000..56cb2331d --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/consumer_widget.ts @@ -0,0 +1,8 @@ +import raw from "!!raw-loader!./raw/consumer_widget.dart"; + +export default { + raw: raw, + hooks: raw, + codegen: raw, + hooksCodegen: raw, +}; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/hook_consumer_widget.ts b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/hook_consumer_widget.ts new file mode 100644 index 000000000..300ec6596 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/hook_consumer_widget.ts @@ -0,0 +1,8 @@ +import raw from "!!raw-loader!./raw/hook_consumer_widget.dart"; + +export default { + raw: raw, + hooks: raw, + codegen: raw, + hooksCodegen: raw, +}; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/legend/DocuCode.scss b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/legend/DocuCode.scss new file mode 100644 index 000000000..bda077971 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/legend/DocuCode.scss @@ -0,0 +1,18 @@ +.legend { + table, + td, + tr { + background: none !important; + border: none; + + vertical-align: top; + } + + td:first-child { + text-align: end; + } + + tr + tr { + border-top: solid 0.5px; + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/legend/legend.tsx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/legend/legend.tsx new file mode 100644 index 000000000..048817ec0 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/legend/legend.tsx @@ -0,0 +1,88 @@ +import React from "react"; +import PropTypes from "prop-types"; +import CodeBlock from "@theme/CodeBlock"; +import "./DocuCode.scss"; + +type AnnotatedCode = { + color?: string; + code: string; +}; + +export const colors = [ + "#2196f3", + "#4caf50", + "#f44336", + "#ff9800", + "#009688", + "#e91e63", + "#00bcd4", + "#8bc34a", +]; + +const Legend = ({ annotations, code }) => { + const fullAnnotations = new Array(); + + let annotationOffset = 0; + for (var codeOffset = 0; codeOffset < code.length; ) { + if (annotationOffset >= annotations.length) { + // Out of annotations, just add the rest of the code + fullAnnotations.push({ code: code.substring(codeOffset) }); + break; + } + + const annotation = annotations[annotationOffset]; + if (codeOffset < annotation.offset) { + /// There is an unannotated gap between the last annotation and this one. + const codeLength = annotation.offset - codeOffset; + fullAnnotations.push({ + code: code.substring(codeOffset, codeOffset + codeLength), + }); + codeOffset += codeLength; + } + + if (annotation.offset >= code.length) { + throw new Error("Annotation offset out of bounds"); + } + + annotationOffset++; + codeOffset = annotation.offset + annotation.length; + fullAnnotations.push({ + color: colors[annotationOffset - 1], + code: code.substring( + annotation.offset, + annotation.offset + annotation.length + ), + }); + } + + return ( +
+
+        {fullAnnotations.map(({ code, color }) => {
+          let underlineClass = color ? `underline` : "";
+          let style = color ? { color: color } : undefined;
+
+          return (
+            
+              {code}
+            
+          );
+        })}
+      
+ + + {annotations.map((annotation, index) => ( + + + + + ))} + +
+ {annotation.label} + {annotation.description}
+
+ ); +}; + +export default Legend; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/provider.ts b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/provider.ts new file mode 100644 index 000000000..42ce35bff --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/provider.ts @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw/provider.dart"; +import codegen from "!!raw-loader!./codegen/provider.dart"; + +export default { + raw: raw, + hooks: raw, + codegen: codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/raw/activity.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/raw/activity.dart new file mode 100644 index 000000000..c37aa2051 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/raw/activity.dart @@ -0,0 +1,28 @@ +/* SNIPPET START */ /// `GET /api/activity` 请求的响应。 +class Activity { + Activity({ + required this.key, + required this.activity, + required this.type, + required this.participants, + required this.price, + }); + + /// 将 JSON 对象转换为 [Activity] 实例。 + /// 这可以实现 API 响应的类型安全读取。 + factory Activity.fromJson(Map json) { + return Activity( + key: json['key'] as String, + activity: json['activity'] as String, + type: json['type'] as String, + participants: json['participants'] as int, + price: json['price'] as double, + ); + } + + final String key; + final String activity; + final String type; + final int participants; + final double price; +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/raw/consumer.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/raw/consumer.dart new file mode 100644 index 000000000..97a06178b --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/raw/consumer.dart @@ -0,0 +1,39 @@ +// ignore_for_file: omit_local_variable_types + +/* SNIPPET START */ import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'activity.dart'; +import 'provider.dart'; + +/// 我们应用程序主页 +class Home extends StatelessWidget { + const Home({super.key}); + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, ref, child) { + // 读取 activityProvider。如果没有准备开始,这将会开始一个网络请求。 + // 通过使用 ref.watch,小组件将会在 activityProvider 更新时重建。 + // 当下面的事情发生时,会更新小组件: + // - 响应从“正在加载”变为“数据/错误” + // - 请求重刷新 + // - 结果被本地修改(例如执行副作用时) + // ... + final AsyncValue activity = ref.watch(activityProvider); + + return Center( + /// 由于网络请求是异步的并且可能会失败,我们需要处理错误和加载的状态。 + /// 我们可以为此使用模式匹配。 + /// 我们也可以使用 `if (activity.isLoading) { ... } else if (...)` + child: switch (activity) { + AsyncData(:final value) => Text('Activity: ${value.activity}'), + AsyncError() => const Text('Oops, something unexpected happened'), + _ => const CircularProgressIndicator(), + }, + ); + }, + ); + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/raw/consumer_stateful_widget.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/raw/consumer_stateful_widget.dart new file mode 100644 index 000000000..ee427a42a --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/raw/consumer_stateful_widget.dart @@ -0,0 +1,42 @@ +// ignore_for_file: omit_local_variable_types, prefer_const_constructors, unused_local_variable, todo + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'activity.dart'; +import 'provider.dart'; + +/* SNIPPET START */ // 我们扩展了 ConsumerStatefulWidget。 +// 这等效于 "Consumer" + "StatefulWidget". +class Home extends ConsumerStatefulWidget { + const Home({super.key}); + + @override + ConsumerState createState() => _HomeState(); +} + +// 请注意,我们如何扩展“ConsumerState”而不是“State”。 +// 这和 "ConsumerWidget" 与 "StatelessWidget" 是相同的原理。 +class _HomeState extends ConsumerState { + @override + void initState() { + super.initState(); + + // 状态生命周期也可以访问“ref”。 + // 这使得在特定提供者程序上添加监听器,以便实现显示对话框/信息栏等功能。 + ref.listenManual(activityProvider, (previous, next) { + // TODO 显示一个 snackbar/dialog + }); + } + + @override + Widget build(BuildContext context) { + // "ref" is not passed as parameter anymore, but is instead a property of "ConsumerState". + // We can therefore keep using "ref.watch" inside "build". + // “ref”不再作为参数传递,而是作为“ConsumerState”的属性。 + // 因此,我们可以继续在“build”中使用“ref.watch”。 + final AsyncValue activity = ref.watch(activityProvider); + + return Center(/* ... */); + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/raw/consumer_widget.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/raw/consumer_widget.dart new file mode 100644 index 000000000..30d5a232e --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/raw/consumer_widget.dart @@ -0,0 +1,23 @@ +// ignore_for_file: omit_local_variable_types, prefer_const_constructors, unused_local_variable + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'activity.dart'; +import 'provider.dart'; + +/* SNIPPET START */ /// 我们将“ConsumerWidget”替代“StatelessWidget”进行子类化。 +/// 这相当于使用“StatelessWidget”并返回“Consumer”小组件。 +class Home extends ConsumerWidget { + const Home({super.key}); + + @override + // 请注意“build”现在如何接收一个额外的参数:“ref” + Widget build(BuildContext context, WidgetRef ref) { + // 我们可以像使用“Consumer”一样,在小部件中使用“ref.watch” + final AsyncValue activity = ref.watch(activityProvider); + + // 渲染逻辑保持不变 + return Center(/* ... */); + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/raw/hook_consumer_widget.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/raw/hook_consumer_widget.dart new file mode 100644 index 000000000..8f5a8473e --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/raw/hook_consumer_widget.dart @@ -0,0 +1,26 @@ +// ignore_for_file: omit_local_variable_types, unused_local_variable, prefer_const_constructors + +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +import 'activity.dart'; +import 'provider.dart'; + +/* SNIPPET START */ /// 我们子类化了 "HookConsumerWidget"。 +/// 这同时组合了 "StatelessWidget"、"Consumer"、"HookWidget"。 +class Home extends HookConsumerWidget { + const Home({super.key}); + + @override + // 请注意“build”现在如何接收一个额外的参数:“ref” + Widget build(BuildContext context, WidgetRef ref) { + // 可以在我们的小部件中使用诸如“useState”之类的钩子 + final counter = useState(0); + + // 我们还可以使用读取提供者程序 + final AsyncValue activity = ref.watch(activityProvider); + + return Center(/* ... */); + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/raw/provider.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/raw/provider.dart new file mode 100644 index 000000000..f417a5860 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/first_request/raw/provider.dart @@ -0,0 +1,13 @@ +/* SNIPPET START */ import 'dart:convert'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:http/http.dart' as http; +import 'activity.dart'; + +final activityProvider = FutureProvider.autoDispose((ref) async { + // 使用 package:http, 我们可以从 Bored API 获取一个随机的活动。 + final response = await http.get(Uri.https('boredapi.com', '/api/activity')); + // 使用 dart:convert, 然后我们将 JSON 有效负载解码为 Map 数据结构。 + final json = jsonDecode(response.body) as Map; + // 最后,我们将 Map 转换为 Activity 实例。 + return Activity.fromJson(json); +}); diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/passing_args.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/passing_args.mdx new file mode 100644 index 000000000..0f21cdd8e --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/passing_args.mdx @@ -0,0 +1,265 @@ +--- +title: 将参数传递给您的请求 +version: 1 +--- + +import { Link } from "@site/src/components/Link"; +import { AutoSnippet, When } from "@site/src/components/CodeSnippet"; +import noArgProvider from "./passing_args/no_arg_provider"; +import family from "!!raw-loader!./passing_args/raw/family.dart"; +import codegenFamily from "!!raw-loader!./passing_args/codegen/family.dart"; +import consumerProvider from "!!raw-loader!./passing_args/raw/consumer_provider.dart"; +import consumerFamily from "!!raw-loader!./passing_args/raw/consumer_family.dart"; +import consumerListFamily from "!!raw-loader!./passing_args/raw/consumer_list_family.dart"; +import multipleConsumerFamily from "!!raw-loader!./passing_args/raw/multiple_consumer_family.dart"; +import tupleFamily from "!!raw-loader!./passing_args/raw/tuple_family.dart"; +import consumerTupleFamily from "!!raw-loader!./passing_args/raw/consumer_tuple_family.dart"; + + +在上一篇文章中,我们看到了如何定义一个“provider”来发出一个简单的 _GET_ HTTP 请求。 +但通常,HTTP 请求依赖于外部参数。 + + +例如,以前我们使用 [Bored API](https://boredapi.com/) 向用户推荐随机活动。 +但也许用户想要过滤他们想做的活动类型,或者有价格要求,等等…… +这些参数事先是未知的。因此,我们需要一种方法将这些参数 +从我们的 UI 传递到我们的提供者程序。 + + +## 更新我们的提供者程序以接受参数 + + +提醒一下,以前我们是这样定义我们的提供者程序的: + + + + + + +当不依赖于代码生成时,我们需要稍微调整定义提供者程序的语法,以支持传递参数。 +这是通过依靠称为“family”的“修饰符”来完成的。 + + +简而言之,我们需要在提供者程序的类型之后添加 `.family` 一个额外的类型参数, +以及与参数类型相对应的类型参数。 +例如,我们可以更新提供者程序以接受与所需活动类型相对应的 String 参数: + + + + + + + + +要将参数传递给我们的提供者程序,我们只需在带注解的函数本身上添加参数即可。 +例如,我们可以更新提供者程序以接受与所需活动类型相对应的 `String` 参数: + + + + + +:::caution + +将参数传递给提供者程序时,强烈建议在提供者程序上启用“autoDispose”。 +否则可能会导致内存泄漏。 +有关详细信息,请参阅。 +::: + + +## 更新我们的 UI 以传递参数 + + +以前,小部件是这样消费我们的提供者程序的: + + + + +但是现在我们的提供者程序收到参数,使用它的语法略有不同。 +提供者程序现在是一个函数,需要使用请求的参数来调用它。 +我们可以更新我们的 UI 以传递硬编码类型的活动,如下所示: + + + + + + +传递给提供者程序的参数对应于带注解的函数的参数,减去“ref”参数。 + + + +:::info + +完全可以同时监听具有不同参数的同一提供者程序。 +例如,我们的 UI 可以同时呈现“娱乐(recreational)”_和_“烹饪(cooking)”活动: + + + + + + +这就是修饰符被称为“family”的原因: +因为把参数传递给提供者程序(译者注:作为不同状态的区分的 key 值), +可以有效地将提供者程序转换为一组具有相同逻辑的状态。 + + +::: + + +## 缓存注意事项和参数限制 + + +将参数传递给提供者程序时,仍会缓存计算。 +不同之处在于,计算现在是按参数缓存的。 + + +这意味着,如果两个小部件使用具有相同参数的同一提供者程序,则只会发出单个网络请求。 +但是,如果两个小部件使用具有不同参数的同一提供者程序,则将发出两个网络请求。 + + +为此,Riverpod 依赖于参数的 `==` 运算符。 +因此,传递给提供者程序的参数必须具有一致的相等性,这一点很重要。 + +:::caution + +一个常见的错误是直接实例化一个新对象作为提供者程序的参数,但该对象没有重写 `==`。 +例如,您可能很想去这样使用 `List`: + + + + +此代码的问题在于 `['recreational', 'cooking'] == ['recreational', 'cooking']` 的结果是 `false`。 +因此,Riverpod 将认为这两个参数不同,并尝试发出新的网络请求。 +这将导致网络请求的无限循环,一直向用户显示进度指示器。 + + +要解决此问题,您可以使用 `const` 列表 (`const ['recreational', 'cooking']`) +或使用重写了 `==` 的自定义列表实现。 + + +为了帮助发现此错误,建议使用 [riverpod_lint](https://pub.dev/packages/riverpod_lint) +并启用 [provider_parameters](https://github.com/rrousselGit/riverpod/tree/master/packages/riverpod_lint#provider_parameters) +的 lint 规则。这样做之后,上面的代码片段将显示警告。 +有关安装步骤,请参阅。 +::: + + + + +考虑到这一点,您可能想知道如何将多个参数传递给提供者程序。 +建议的解决方案是: + + +- 切换到代码生成,这样可以传递任意数量的参数 +- 使用 Dart 3 的记录 (record) 语法 + + +Dart 3 的记录之所以派上用场,是因为它们自然覆盖 `==` 并具有方便的语法。 +例如,我们可以更新我们的提供者程序,以同时接受一种活动类型和最高价格: + + + + +然后,我们可以像这样消费这个提供者程序: + + + + diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/passing_args/codegen/family.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/passing_args/codegen/family.dart new file mode 100644 index 000000000..7d5c4c260 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/passing_args/codegen/family.dart @@ -0,0 +1,29 @@ +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import '../../first_request/codegen/activity.dart'; + +part 'family.g.dart'; + +/* SNIPPET START */ +@riverpod +Future activity( + ActivityRef ref, + // 我们可以向提供者程序添加参数。 + // 参数的类型可以是您想要的任何类型。 + String activityType, +) async { + // 我们可以使用“activityType”参数来构建 URL。 + // 这将指向 "https://boredapi.com/api/activity?type=" + final response = await http.get( + Uri( + scheme: 'https', + host: 'boredapi.com', + path: '/api/activity', + // 无需手动编码查询参数,“Uri”类为我们完成了这一工作。 + queryParameters: {'type': activityType}, + ), + ); + final json = jsonDecode(response.body) as Map; + return Activity.fromJson(json); +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/passing_args/codegen/family.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/passing_args/codegen/family.g.dart new file mode 100644 index 000000000..f025f6159 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/passing_args/codegen/family.g.dart @@ -0,0 +1,159 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'family.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$activityHash() => r'cb76e67cd45f1823d3ed497a235be53819ce2eaf'; + +/// Copied from Dart SDK +class _SystemHash { + _SystemHash._(); + + static int combine(int hash, int value) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + value); + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); + return hash ^ (hash >> 6); + } + + static int finish(int hash) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + // ignore: parameter_assignments + hash = hash ^ (hash >> 11); + return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); + } +} + +/// See also [activity]. +@ProviderFor(activity) +const activityProvider = ActivityFamily(); + +/// See also [activity]. +class ActivityFamily extends Family> { + /// See also [activity]. + const ActivityFamily(); + + /// See also [activity]. + ActivityProvider call( + String activityType, + ) { + return ActivityProvider( + activityType, + ); + } + + @override + ActivityProvider getProviderOverride( + covariant ActivityProvider provider, + ) { + return call( + provider.activityType, + ); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'activityProvider'; +} + +/// See also [activity]. +class ActivityProvider extends AutoDisposeFutureProvider { + /// See also [activity]. + ActivityProvider( + String activityType, + ) : this._internal( + (ref) => activity( + ref as ActivityRef, + activityType, + ), + from: activityProvider, + name: r'activityProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$activityHash, + dependencies: ActivityFamily._dependencies, + allTransitiveDependencies: ActivityFamily._allTransitiveDependencies, + activityType: activityType, + ); + + ActivityProvider._internal( + super._createNotifier, { + required super.name, + required super.dependencies, + required super.allTransitiveDependencies, + required super.debugGetCreateSourceHash, + required super.from, + required this.activityType, + }) : super.internal(); + + final String activityType; + + @override + Override overrideWith( + FutureOr Function(ActivityRef provider) create, + ) { + return ProviderOverride( + origin: this, + override: ActivityProvider._internal( + (ref) => create(ref as ActivityRef), + from: from, + name: null, + dependencies: null, + allTransitiveDependencies: null, + debugGetCreateSourceHash: null, + activityType: activityType, + ), + ); + } + + @override + AutoDisposeFutureProviderElement createElement() { + return _ActivityProviderElement(this); + } + + @override + bool operator ==(Object other) { + return other is ActivityProvider && other.activityType == activityType; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, activityType.hashCode); + + return _SystemHash.finish(hash); + } +} + +mixin ActivityRef on AutoDisposeFutureProviderRef { + /// The parameter `activityType` of this provider. + String get activityType; +} + +class _ActivityProviderElement + extends AutoDisposeFutureProviderElement with ActivityRef { + _ActivityProviderElement(super.provider); + + @override + String get activityType => (origin as ActivityProvider).activityType; +} +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/passing_args/codegen/provider.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/passing_args/codegen/provider.dart new file mode 100644 index 000000000..d2cd2b635 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/passing_args/codegen/provider.dart @@ -0,0 +1,32 @@ +// ignore_for_file: avoid_print + +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import '../../first_request/codegen/activity.dart'; + +// Necessary for code-generation to work +part 'provider.g.dart'; + +FutureOr fetchActivity() => throw UnimplementedError(); + +/* SNIPPET START */ +// “函数型”提供者程序 +@riverpod +Future activity(ActivityRef ref) async { + // TODO: 执行网络请求以获取活动 + return fetchActivity(); +} + +// 或者替代方案,“通知者程序” +@riverpod +class ActivityNotifier2 extends _$ActivityNotifier2 { + /// 通知者程序参数在构建方法上指定。 + /// 可以有任意数量的通知者程序参数,可以是任意的变量名称,甚至可以是可选/命名的参数。 + @override + Future build(String activityType) async { + // 参数也可通过 "this." 使用 + print(this.activityType); + + // TODO: 执行网络请求以获取活动 + return fetchActivity(); + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/passing_args/codegen/provider.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/passing_args/codegen/provider.g.dart new file mode 100644 index 000000000..00390f801 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/passing_args/codegen/provider.g.dart @@ -0,0 +1,191 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$activityHash() => r'2f9496c5d70de9314c67e5c48ac44d8b149bc471'; + +/// See also [activity]. +@ProviderFor(activity) +final activityProvider = AutoDisposeFutureProvider.internal( + activity, + name: r'activityProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$activityHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ActivityRef = AutoDisposeFutureProviderRef; +String _$activityNotifier2Hash() => r'9e67c655d53a9f98c3b012a0534421385dde0339'; + +/// Copied from Dart SDK +class _SystemHash { + _SystemHash._(); + + static int combine(int hash, int value) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + value); + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); + return hash ^ (hash >> 6); + } + + static int finish(int hash) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + // ignore: parameter_assignments + hash = hash ^ (hash >> 11); + return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); + } +} + +abstract class _$ActivityNotifier2 + extends BuildlessAutoDisposeAsyncNotifier { + late final String activityType; + + FutureOr build( + String activityType, + ); +} + +/// See also [ActivityNotifier2]. +@ProviderFor(ActivityNotifier2) +const activityNotifier2Provider = ActivityNotifier2Family(); + +/// See also [ActivityNotifier2]. +class ActivityNotifier2Family extends Family> { + /// See also [ActivityNotifier2]. + const ActivityNotifier2Family(); + + /// See also [ActivityNotifier2]. + ActivityNotifier2Provider call( + String activityType, + ) { + return ActivityNotifier2Provider( + activityType, + ); + } + + @override + ActivityNotifier2Provider getProviderOverride( + covariant ActivityNotifier2Provider provider, + ) { + return call( + provider.activityType, + ); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'activityNotifier2Provider'; +} + +/// See also [ActivityNotifier2]. +class ActivityNotifier2Provider + extends AutoDisposeAsyncNotifierProviderImpl { + /// See also [ActivityNotifier2]. + ActivityNotifier2Provider( + String activityType, + ) : this._internal( + () => ActivityNotifier2()..activityType = activityType, + from: activityNotifier2Provider, + name: r'activityNotifier2Provider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$activityNotifier2Hash, + dependencies: ActivityNotifier2Family._dependencies, + allTransitiveDependencies: + ActivityNotifier2Family._allTransitiveDependencies, + activityType: activityType, + ); + + ActivityNotifier2Provider._internal( + super._createNotifier, { + required super.name, + required super.dependencies, + required super.allTransitiveDependencies, + required super.debugGetCreateSourceHash, + required super.from, + required this.activityType, + }) : super.internal(); + + final String activityType; + + @override + FutureOr runNotifierBuild( + covariant ActivityNotifier2 notifier, + ) { + return notifier.build( + activityType, + ); + } + + @override + Override overrideWith(ActivityNotifier2 Function() create) { + return ProviderOverride( + origin: this, + override: ActivityNotifier2Provider._internal( + () => create()..activityType = activityType, + from: from, + name: null, + dependencies: null, + allTransitiveDependencies: null, + debugGetCreateSourceHash: null, + activityType: activityType, + ), + ); + } + + @override + AutoDisposeAsyncNotifierProviderElement + createElement() { + return _ActivityNotifier2ProviderElement(this); + } + + @override + bool operator ==(Object other) { + return other is ActivityNotifier2Provider && + other.activityType == activityType; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, activityType.hashCode); + + return _SystemHash.finish(hash); + } +} + +mixin ActivityNotifier2Ref on AutoDisposeAsyncNotifierProviderRef { + /// The parameter `activityType` of this provider. + String get activityType; +} + +class _ActivityNotifier2ProviderElement + extends AutoDisposeAsyncNotifierProviderElement + with ActivityNotifier2Ref { + _ActivityNotifier2ProviderElement(super.provider); + + @override + String get activityType => (origin as ActivityNotifier2Provider).activityType; +} +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/passing_args/no_arg_provider.ts b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/passing_args/no_arg_provider.ts new file mode 100644 index 000000000..42ce35bff --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/passing_args/no_arg_provider.ts @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw/provider.dart"; +import codegen from "!!raw-loader!./codegen/provider.dart"; + +export default { + raw: raw, + hooks: raw, + codegen: codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/consumer_family.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/consumer_family.dart new file mode 100644 index 000000000..96c6fcec7 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/consumer_family.dart @@ -0,0 +1,24 @@ +// ignore_for_file: omit_local_variable_types, unused_local_variable, prefer_final_locals + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../first_request/raw/activity.dart'; +import 'family.dart'; + +class Example extends ConsumerWidget { + const Example({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { +/* SNIPPET START */ + AsyncValue activity = ref.watch( + // 提供者程序现在是一个需要活动类型的函数。 + // 为了简单起见,我们现在传递一个常量字符串。 + activityProvider('recreational'), + ); +/* SNIPPET END */ + + return Container(); + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/consumer_list_family.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/consumer_list_family.dart new file mode 100644 index 000000000..64433d625 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/consumer_list_family.dart @@ -0,0 +1,23 @@ +// ignore_for_file: omit_local_variable_types, unused_local_variable, prefer_final_locals + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +final activityProvider = Provider.family((ref, id) { + throw UnimplementedError(); +}); + +class Example extends ConsumerWidget { + const Example({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { +/* SNIPPET START */ + // 我们可以更新 ActivityProvider 接受字符串列表以替换之前的代码。 + // 然后尝试直接在 watch 调用中创建该列表。 + ref.watch(activityProvider(['recreational', 'cooking'])); +/* SNIPPET END */ + + return Container(); + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/consumer_provider.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/consumer_provider.dart new file mode 100644 index 000000000..337eab2d1 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/consumer_provider.dart @@ -0,0 +1,20 @@ +// ignore_for_file: omit_local_variable_types, unused_local_variable, prefer_final_locals + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../first_request/raw/activity.dart'; +import 'provider.dart'; + +class Example extends ConsumerWidget { + const Example({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { +/* SNIPPET START */ + AsyncValue activity = ref.watch(activityProvider); +/* SNIPPET END */ + + return Container(); + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/consumer_tuple_family.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/consumer_tuple_family.dart new file mode 100644 index 000000000..b63149bde --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/consumer_tuple_family.dart @@ -0,0 +1,23 @@ +// ignore_for_file: omit_local_variable_types, unused_local_variable, prefer_final_locals + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'tuple_family.dart'; + +class Example extends ConsumerWidget { + const Example({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { +/* SNIPPET START */ + ref.watch( + // 使用记录,我们可以传递参数。 + // 在 watch 调用中,可以实现直接创建带有覆盖 == 功能的记录。 + activityProvider((type: 'recreational', maxPrice: 40)), + ); +/* SNIPPET END */ + + return Container(); + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/family.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/family.dart new file mode 100644 index 000000000..7eb1af657 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/family.dart @@ -0,0 +1,42 @@ +// ignore_for_file: unnecessary_this, avoid_print + +import 'dart:async'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../../first_request/raw/activity.dart'; + +FutureOr fetchActivity(String activityType) => + throw UnimplementedError(); + +/* SNIPPET START */ +// “函数型”提供者 +final activityProvider = FutureProvider.autoDispose + // 我们使用 ".family" 修饰符。 + // 泛型类型 "String" 对应于参数的类型。 + // 我们的提供者程序现在在 "ref" 上收到一个额外的参数:activity 的类型。 + .family((ref, activityType) async { + // TODO: 使用 "activityType" 执行网络请求以获取活动 + return fetchActivity(activityType); +}); + +// “通知者程序”的提供者 +final activityProvider2 = AsyncNotifierProvider.autoDispose + // 再次,我们使用 ".family" 修饰符,并将参数指定为 "String" 类型。 + .family( + ActivityNotifier.new, +); + +// 当将 ".family" 与通知者程序一起使用时,我们需要更改通知者程序子类: +// AsyncNotifier -> FamilyAsyncNotifier +// AutoDisposeAsyncNotifier -> AutoDisposeFamilyAsyncNotifier +class ActivityNotifier + extends AutoDisposeFamilyAsyncNotifier { + /// Family 参数传递给构建方法并可通过 this.arg 访问 + @override + Future build(String activityType) async { + // 参数也可通过 "this.arg" 使用 + print(this.arg); + + // TODO: 执行网络请求以获取活动 + return fetchActivity(activityType); + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/multiple_consumer_family.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/multiple_consumer_family.dart new file mode 100644 index 000000000..68c9620db --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/multiple_consumer_family.dart @@ -0,0 +1,37 @@ +// ignore_for_file: omit_local_variable_types, unused_local_variable, prefer_final_locals + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../first_request/raw/activity.dart'; +import 'family.dart'; + +class Example extends ConsumerWidget { + const Example({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + AsyncValue activity = ref.watch( + // The provider is now a function expecting the activity type. + // Let's pass a constant string for now, for the sake of simplicity. + activityProvider('recreational'), + ); + /* SNIPPET START */ + return Consumer( + builder: (context, ref, child) { + final recreational = ref.watch(activityProvider('recreational')); + final cooking = ref.watch(activityProvider('cooking')); + + // 然后我们可以同时渲染这两个活动。 + // 两个请求将并行发生并正确缓存。 + return Column( + children: [ + Text(recreational.valueOrNull?.activity ?? ''), + Text(cooking.valueOrNull?.activity ?? ''), + ], + ); + }, + ); + /* SNIPPET END */ + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/provider.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/provider.dart new file mode 100644 index 000000000..0c4806b47 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/provider.dart @@ -0,0 +1,26 @@ +import 'dart:async'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../../first_request/raw/activity.dart'; + +/* SNIPPET START */ + +FutureOr fetchActivity() => throw UnimplementedError(); + +// “函数型”提供者程序 +final activityProvider = FutureProvider.autoDispose((ref) async { + // TODO: 执行网络请求以获取活动 + return fetchActivity(); +}); + +// 或者替代方案,“通知者程序” +final activityProvider2 = AsyncNotifierProvider( + ActivityNotifier.new, +); + +class ActivityNotifier extends AsyncNotifier { + @override + Future build() async { + // TODO: 执行网络请求以获取活动 + return fetchActivity(); + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/tuple_family.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/tuple_family.dart new file mode 100644 index 000000000..aa9b4476a --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/passing_args/raw/tuple_family.dart @@ -0,0 +1,32 @@ +// ignore_for_file: omit_local_variable_types, unused_local_variable, prefer_final_locals + +import 'dart:convert'; + +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:http/http.dart' as http; + +import '../../first_request/raw/activity.dart'; + +/* SNIPPET START */ +// 我们定义一条记录,表示我们想要传递给提供者程序的参数。 +// 创建 typedef 是可选的,但可以使代码更具可读性。 +typedef ActivityParameters = ({String type, int maxPrice}); + +final activityProvider = FutureProvider.autoDispose + // 我们现在使用新定义的记录作为参数类型。 + .family((ref, arguments) async { + final response = await http.get( + Uri( + scheme: 'https', + host: 'boredapi.com', + path: '/api/activity', + queryParameters: { + // 最后,我们可以使用参数来更新请求的查询参数。 + 'type': arguments.type, + 'price': arguments.maxPrice, + }, + ), + ); + final json = jsonDecode(response.body) as Map; + return Activity.fromJson(json); +}); diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/provider_observer.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/provider_observer.mdx new file mode 100644 index 000000000..ff240d37f --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/provider_observer.mdx @@ -0,0 +1,79 @@ +--- +title: 日志和错误报告 +--- + +import { Link } from "@site/src/components/Link"; +import { AutoSnippet, When } from "@site/src/components/CodeSnippet"; +import providerObserver from "!!raw-loader!./provider_observer/provider_observer.dart"; + + +Riverpod 本身提供了一种监听提供者程序树中发生的所有事件的方法。 +这可用于记录所有事件,或向远程服务报告错误。 + + +这是通过使用 `ProviderObserver` 类并将其传递给 +`ProviderScope`/`ProviderContainer` 来实现的。 + + +## 定义 ProviderObserver ​ + + +ProviderObserver 是一个应该被扩展的类。 +它提供了各种可以重写的方法来监听事件: + + +- `didAddProvider`,当提供者程序被添加到组件树时调用 +- `didUpdateProvider`,当提供者程序更新时调用 +- `didDisposeProvider`,当提供者程序被处置时调用 +- `providerDidFail`,当同步的提供者程序抛出错误时 + + + + +## 使用 ProviderObserver​ + + +现在我们已经定义了观察者,我们需要使用它。 +为此,我们应该将其传递给 `ProviderScope` 或 `ProviderContainer` : + +```dart +runApp( + ProviderScope( + observers: [ + MyObserver(), + ], + child: MyApp(), + ) +); +``` + +```dart +final container = ProviderContainer( + observers: [ + MyObserver(), + ], +); +``` diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/provider_observer/provider_observer.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/provider_observer/provider_observer.dart new file mode 100644 index 000000000..dd75afb01 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/provider_observer/provider_observer.dart @@ -0,0 +1,43 @@ +// ignore_for_file: avoid_print + +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +/* SNIPPET START */ +class MyObserver extends ProviderObserver { + @override + void didAddProvider( + ProviderBase provider, + Object? value, + ProviderContainer container, + ) { + print('Provider $provider was initialized with $value'); + } + + @override + void didDisposeProvider( + ProviderBase provider, + ProviderContainer container, + ) { + print('Provider $provider was disposed'); + } + + @override + void didUpdateProvider( + ProviderBase provider, + Object? previousValue, + Object? newValue, + ProviderContainer container, + ) { + print('Provider $provider updated from $previousValue to $newValue'); + } + + @override + void providerDidFail( + ProviderBase provider, + Object error, + StackTrace stackTrace, + ProviderContainer container, + ) { + print('Provider $provider threw $error at $stackTrace'); + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects.mdx new file mode 100644 index 000000000..9f76dcae9 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects.mdx @@ -0,0 +1,668 @@ +--- +title: 执行副作用 +version: 1 +--- + +import { Link } from "@site/src/components/Link"; +import { AutoSnippet, When } from "@site/src/components/CodeSnippet"; +import Legend, { colors } from "./first_request/legend/legend"; +import todoListProvider from "./side_effects/todo_list_provider"; +import todoListNotifier from "./side_effects/todo_list_notifier"; +import todoListNotifierAddTodo from "./side_effects/todo_list_notifier_add_todo"; +import consumerAddTodoCall from "!!raw-loader!./side_effects/raw/consumer_add_todo_call.dart"; +import restAddTodo from "!!raw-loader!./side_effects/raw/rest_add_todo.dart"; +import invalidateSelfAddTodo from "!!raw-loader!./side_effects/raw/invalidate_self_add_todo.dart"; +import manualAddTodo from "!!raw-loader!./side_effects/raw/manual_add_todo.dart"; +import mutableManualAddTodo from "!!raw-loader!./side_effects/raw/mutable_manual_add_todo.dart"; +import renderAddTodo from "./side_effects/render_add_todo"; + + +到目前为止,我们只看到了如何获取数据(也就是执行 _GET_ HTTP 请求)。 +但是副作用(例如 _POST_ 请求)呢? + + +应用程序通常实现 CRUD(创建、读取、更新、删除)API。 +执行此操作时,更新请求(通常是 _POST_)通常还应更新本地缓存,以使 UI 反映新状态。 + + +问题是,我们如何从消费者程序内部更新提供者程序的状态? +理所当然的,提供者程序不会公开修改其状态的方法。 +这是设计使然,以确保仅以受控方式修改状态并促进关注点分离。 +相反,提供者程序必须显式公开修改其状态的方法。 + + +为此,我们将使用一个新概念:通知者程序(Notifiers)。 +为了展示这个新概念,让我们使用一个更高级的例子:待办事项列表。 + + +## 定义通知者程序 + + +让我们从此时我们已经知道的内容开始:一个简单的 GET 请求。 +正如之前在中看到的那样, +我们可以通过编写以下内容来获取待办事项列表: + + + + +现在我们已经获取了待办事项列表,让我们看看如何添加新的待办事项。 +为此,我们需要修改我们的提供者程序,以便它们公开一个公共 API 来修改其状态。 +这是通过将我们的提供者程序转换为我们所说的“通知者程序”来完成的。 + + +通知者程序是提供者程序的“有状态小部件”。它们需要对定义提供者程序的语法稍作调整。 +此新语法如下: + + +(MyNotifier.new); + +class MyNotifier extends SomeNotifier { + @override + Result build() { + <你的业务逻辑在这里> + } + + <你的方法在这里> +}`} + annotations={[ + { + offset: 6, + length: 4, + label: "提供者程序变量", + description: <> + + +此变量将用于与我们的提供者程序进行交互。 +变量必须是 final 和“顶级”(全局)。 + + + }, + { + offset: 13, + length: 20, + label: "提供者程序类型", + description: <> + + +通常为 `NotifierProvider`、`AsyncNotifierProvider` 和 `StreamNotifierProvider`。 +使用的提供者程序类型取决于函数的返回值。 +例如,要创建一个 `Future` ,您需要一个 `AsyncNotifierProvider`。 + + +`AsyncNotifierProvider` 是你最想用的那个。 + +:::tip + +不要从“我应该选择哪个提供者程序”的角度来思考。 +相反,从“我想返回什么”的角度来思考。提供者程序类型将自然而然地遵循。 +::: + + + }, + { + offset: 33, + length: 13, + label: "修饰符(可选的)", + description: <> + + +通常,在提供者程序的类型之后,您可能会看到一个“修饰符”。 +修饰符是可选的,用于以类型安全的方式调整提供者程序的行为。 + + +目前有两种修饰符可用: + + +- `autoDispose`,这将在提供者程序停止使用时自动清除缓存。 + 另请参阅 +- `family`,这样就可以将参数传递给提供者程序。 + 另请参阅 + + + }, + { + offset: 67, + length: 14, + label: "通知者程序的构造函数", + description: <> + + +“notifier providers”的参数是一个函数,它应该实例化“notifier”。 +它通常应该是“构造函数撕裂”。 + + + }, + { + offset: 86, + length: 16, + label: "通知者程序", + description: <> + + +如果 `NotifierProvider` 是 “StatefulWidget” 类,则此部分就是该 `State` 类。 + + +此类负责公开修改提供者程序状态的方法。 +使用者可以使用 `ref.read(yourProvider.notifier).yourMethod()` 此类上的公共方法。 + +:::note + +除了内置的 `state` 之外,通知者程序不应具有公共属性,因为 UI 无法知道状态已更改。 +::: + + + }, + { + offset: 111, + length: 12, + label: "通知者程序类型", + description: <> + + +通知者程序扩展的基类应与提供者程序 + 修饰符的基类匹配。一些例子是: + +- NotifierProvider -> Notifier +- AsyncNotifierProvider -> AsyncNotifier +- AsyncNotifierProvider. + autoDispose -> + AutoDispose + + AsyncNotifier +- AsyncNotifierProvider. + autoDispose. + family + -> AutoDispose + Family + AsyncNotifier + + +为了简化此操作,建议使用代码生成器,因为它会自动推断正确的类型。 + + + }, + { + offset: 136, + length: 54, + label: "build 方法", + description: <> + + +所有通知者程序都必须重写该 `build` 方法。 +此方法等效于通常将逻辑放在非通知者程序提供者程序中的位置。 + + +不应直接调用此方法。 + + + }, +]} +/> + + + + + + + } + + <你的方法在这里> +}`} + annotations={[ + { + offset: 0, + length: 9, + label: "注解", + description: <> + + +所有提供者程序都必须使用 `@riverpod` 或 `@Riverpod()` 进行注解。 +此注解可以放置在全局函数或类上。 +通过此注解,可以配置提供者程序。 + + +例如,我们可以通过编写 `@Riverpod(keepAlive: true)` 来禁用“自动处置”(我们将在后面看到)。 + + + }, + { + offset: 10, + length: 16, + label: "通知者程序", + description: <> + + +当 `@riverpod` 注解被放置在一个类上时,该类被称为“通知者程序”。 +类必须扩展 `_$NotifierName` ,其中 `NotifierName` 是类名。 + + +通知者程序负责公开修改提供者程序状态的方法。 +使用者可以使用 `ref.read(yourProvider.notifier).yourMethod()` 此类上的公共方法。 + +:::note + +除了内置的 `state` 之外,通知者程序不应具有公共属性,因为 UI 无法知道状态已更改。 +::: + + + }, + { + offset: 52, + length: 54, + label: "build 方法", + description: <> + + +所有通知者程序都必须重写该 `build` 方法。 +此方法等效于通常将逻辑放在非通知者程序提供者程序中的位置。 + + +不应直接调用此方法。 + + + }, +]} +/> + + + +作为参考,您可能需要查看,将这里的新语法与之前看到的语法进行比较。 + +:::info + +除了 `build` 以外,没有其他方法的通知者程序与使用前面看到的语法相同。 +中显示的语法可以被视为通知者程序的简写,无法从 UI 进行修改。 +::: + + +现在我们已经了解了语法,让我们看看如何将之前定义的提供者程序转换为通知者程序: + + + + +请注意,在小部件中读取提供者程序的方式保持不变。 +您仍然可以像以前的语法一样使用 `ref.watch(todoListProvider)`。 + +:::caution + +不要将逻辑放在通知者程序的构造函数中。 +通知者程序不应具有构造函数,因为 `ref` 此时其他属性尚不可用。 +相反,将您的逻辑放在方法中 `build`。 + +```dart +class MyNotifier extends ... { + MyNotifier() { + // ❌ 别这样做 + // 这将会抛出一个异常 + state = AsyncValue.data(42); + } + + @override + Result build() { + // ✅ 应该这样做 + state = AsyncValue.data(42); + } +} +``` + +::: + + +## 公开用于执行 _POST_ 请求的方法 + + +现在我们有了通知者程序,我们可以开始添加方法来执行副作用。 +其中一个副作用是让客户端 _POST_ 一个新的待办事项。 +我们可以通过在通知者程序上添加一个 `addTodo` 方法来做到这一点: + + + + +然后,我们可以在 UI 中使用我们在中看到的相同 `Consumer`/`ConsumerWidget` 调用此方法: + + + +:::info + +请注意我们如何使用 `ref.read` 而不是调用 `ref.watch` 的方法。 +虽然在技术上可以工作,但 `ref.watch` 建议在事件处理(如“onPressed”)中执行逻辑时使用 `ref.read`。 +::: + + +我们现在有一个按钮,按下时会发出 _POST_ 请求。 +但是,目前,我们的 UI 不会更新以返回新的待办事项列表。 +我们希望本地缓存与服务器的状态相匹配。 + + +有几种方法可以做到这一点,下面说说优点和缺点。 + + +### 更新本地缓存以匹配 API 响应 + + +一种常见的后端做法是让 _POST_ 请求返回资源的新状态。 +特别是,我们的 API 将在添加新的待办事项后返回新的待办事项列表。 +一种方法是编写 `state = AsyncData(response)`: + + + +:::tip 优点 + + +- UI 将尽可能具有最新状态。如果其他用户添加了待办事项,我们也会看到它。 +- 服务器是事实的来源。使用这种方法,客户端不需要知道需要在列表的哪个位置插入新的待办事项。 +- 只需要一个网络请求。 + +::: + +:::danger 缺点 + + +- 仅当服务器以特定方式实现时,此方法才有效。如果服务器不返回新状态,则此方法将不起作用。 +- 如果关联的 _GET_ 请求更复杂,例如如果它具有过滤/排序的功能,则可能仍然不可行。 + +::: + + +### 使用 `ref.invalidateSelf()` 刷新提供者程序。 + + +一种选择是让我们的提供者程序重新执行 _GET_ 请求。 +这可以通过在 _POST_ 请求之后调用 `ref.invalidateSelf()` 来完成: + + + +:::tip 优点 + + +- UI 将尽可能具有最新状态。如果其他用户添加了待办事项,我们也会看到它。 +- 服务器是事实的来源。使用这种方法,客户端不需要知道需要在列表的哪个位置插入新的待办事项。 +- 无论服务器实现如何,此方法都应该有效。如果您的 _GET_ 请求更复杂,例如它具有过滤器/排序,则它可能特别有用。 + +::: + +:::danger 缺点 + + +- 此方法将执行额外的 _GET_ 请求,这可能效率低下。 + +::: + + +### 手动更新本地缓存 + + +另一种选择是手动更新本地缓存。 +这将涉及尝试模仿后端的行为。例如,我们需要知道后端是在开头还是结尾插入新项目。 + + + +:::info + +此示例使用不可变状态。这不是必需的,但建议这样做。 +有关更多详细信息,请参阅。 +如果要改用可变状态,也可以执行以下操作: + + + +::: + +:::tip 优点 + + +- 无论服务器实现如何,此方法都应该有效。 +- 只需要一个网络请求。 + +::: + +:::danger 缺点 + + +- 本地缓存可能与服务器的状态不匹配。如果其他用户添加了待办事项,我们将看不到它。 +- 这种方法的实现和有效地复制后端的逻辑可能更复杂。 + +::: + + +## 更进一步:显示下拉加载器和错误处理 + + +到目前为止我们所看到的一切,我们有一个按钮,当按下时会发出 _POST_ 请求; +请求完成后,UI 会更新以反映更改。 +但目前,没有迹象表明请求正在执行,如果失败,也没有任何信息。 + + +一种方法是将返回 `addTodo` 的异步结果存储在本地小部件状态中, +然后监听该异步状态以显示下拉加载器或错误消息。 +这时[flutter_hooks](https://pub.dev/packages/flutter_hooks)派上用场的一种情况。 +但是,当然,您也可以使用 `StatefulWidget` 代替。 + + +以下代码片段显示了当处于加载状态时,进度指示器和操作处于挂起状态。 +如果失败,则将按钮呈现为红色: + +![A button which turns red when the operation failed](/img/essentials/side_effects/spinner.gif) + + diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier.dart new file mode 100644 index 000000000..901cd926e --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier.dart @@ -0,0 +1,28 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'todo_list_notifier.freezed.dart'; +part 'todo_list_notifier.g.dart'; + +@freezed +class Todo with _$Todo { + factory Todo({ + required String description, + @Default(false) bool completed, + }) = _Todo; + + factory Todo.fromJson(Map json) => _$TodoFromJson(json); +} + +/* SNIPPET START */ +@riverpod +class TodoList extends _$TodoList { + @override + Future> build() async { + // 我们之前在 FutureProvider 中的业务逻辑现在位于 build 方法中。 + return [ + Todo(description: 'Learn Flutter', completed: true), + Todo(description: 'Learn Riverpod'), + ]; + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier.freezed.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier.freezed.dart new file mode 100644 index 000000000..3326f9ffa --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier.freezed.dart @@ -0,0 +1,166 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'todo_list_notifier.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +Todo _$TodoFromJson(Map json) { + return _Todo.fromJson(json); +} + +/// @nodoc +mixin _$Todo { + String get description => throw _privateConstructorUsedError; + bool get completed => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $TodoCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $TodoCopyWith<$Res> { + factory $TodoCopyWith(Todo value, $Res Function(Todo) then) = + _$TodoCopyWithImpl<$Res, Todo>; + @useResult + $Res call({String description, bool completed}); +} + +/// @nodoc +class _$TodoCopyWithImpl<$Res, $Val extends Todo> + implements $TodoCopyWith<$Res> { + _$TodoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? description = null, + Object? completed = null, + }) { + return _then(_value.copyWith( + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + completed: null == completed + ? _value.completed + : completed // ignore: cast_nullable_to_non_nullable + as bool, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$TodoImplCopyWith<$Res> implements $TodoCopyWith<$Res> { + factory _$$TodoImplCopyWith( + _$TodoImpl value, $Res Function(_$TodoImpl) then) = + __$$TodoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String description, bool completed}); +} + +/// @nodoc +class __$$TodoImplCopyWithImpl<$Res> + extends _$TodoCopyWithImpl<$Res, _$TodoImpl> + implements _$$TodoImplCopyWith<$Res> { + __$$TodoImplCopyWithImpl(_$TodoImpl _value, $Res Function(_$TodoImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? description = null, + Object? completed = null, + }) { + return _then(_$TodoImpl( + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + completed: null == completed + ? _value.completed + : completed // ignore: cast_nullable_to_non_nullable + as bool, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$TodoImpl implements _Todo { + _$TodoImpl({required this.description, this.completed = false}); + + factory _$TodoImpl.fromJson(Map json) => + _$$TodoImplFromJson(json); + + @override + final String description; + @override + @JsonKey() + final bool completed; + + @override + String toString() { + return 'Todo(description: $description, completed: $completed)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$TodoImpl && + (identical(other.description, description) || + other.description == description) && + (identical(other.completed, completed) || + other.completed == completed)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, description, completed); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$TodoImplCopyWith<_$TodoImpl> get copyWith => + __$$TodoImplCopyWithImpl<_$TodoImpl>(this, _$identity); + + @override + Map toJson() { + return _$$TodoImplToJson( + this, + ); + } +} + +abstract class _Todo implements Todo { + factory _Todo({required final String description, final bool completed}) = + _$TodoImpl; + + factory _Todo.fromJson(Map json) = _$TodoImpl.fromJson; + + @override + String get description; + @override + bool get completed; + @override + @JsonKey(ignore: true) + _$$TodoImplCopyWith<_$TodoImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier.g.dart new file mode 100644 index 000000000..9d12d50c6 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier.g.dart @@ -0,0 +1,42 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'todo_list_notifier.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$TodoImpl _$$TodoImplFromJson(Map json) => _$TodoImpl( + description: json['description'] as String, + completed: json['completed'] as bool? ?? false, + ); + +Map _$$TodoImplToJson(_$TodoImpl instance) => + { + 'description': instance.description, + 'completed': instance.completed, + }; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$todoListHash() => r'c939d438b07da6065dbbcfab86c27ef363bdb76c'; + +/// See also [TodoList]. +@ProviderFor(TodoList) +final todoListProvider = + AutoDisposeAsyncNotifierProvider>.internal( + TodoList.new, + name: r'todoListProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$todoListHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$TodoList = AutoDisposeAsyncNotifier>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier_add_todo.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier_add_todo.dart new file mode 100644 index 000000000..0d6a9a7bd --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier_add_todo.dart @@ -0,0 +1,26 @@ +// ignore_for_file: avoid_print + +import 'dart:convert'; + +import 'package:http/http.dart' as http; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import 'todo_list_notifier.dart'; + +part 'todo_list_notifier_add_todo.g.dart'; + +/* SNIPPET START */ +@riverpod +class TodoList extends _$TodoList { + @override + Future> build() async => [/* ... */]; + + Future addTodo(Todo todo) async { + await http.post( + Uri.https('your_api.com', '/todos'), + // 我们序列化 Todo 对象并将其 POST 到服务器。 + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(todo.toJson()), + ); + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier_add_todo.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier_add_todo.g.dart new file mode 100644 index 000000000..a6c2ce847 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_notifier_add_todo.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'todo_list_notifier_add_todo.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$todoListHash() => r'4008395aaca8f55312f668c0b2a32e7599f82349'; + +/// See also [TodoList]. +@ProviderFor(TodoList) +final todoListProvider = + AutoDisposeAsyncNotifierProvider>.internal( + TodoList.new, + name: r'todoListProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$todoListHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$TodoList = AutoDisposeAsyncNotifier>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_provider.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_provider.dart new file mode 100644 index 000000000..88eb06c0e --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_provider.dart @@ -0,0 +1,23 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'todo_list_provider.freezed.dart'; +part 'todo_list_provider.g.dart'; + +/* SNIPPET START */ +@freezed +class Todo with _$Todo { + factory Todo({ + required String description, + @Default(false) bool completed, + }) = _Todo; +} + +@riverpod +Future> todoList(TodoListRef ref) async { + // 模拟一个网络请求。这通常来自真实的 API + return [ + Todo(description: 'Learn Flutter', completed: true), + Todo(description: 'Learn Riverpod'), + ]; +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_provider.freezed.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_provider.freezed.dart new file mode 100644 index 000000000..26a2d640c --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_provider.freezed.dart @@ -0,0 +1,148 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'todo_list_provider.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +/// @nodoc +mixin _$Todo { + String get description => throw _privateConstructorUsedError; + bool get completed => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $TodoCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $TodoCopyWith<$Res> { + factory $TodoCopyWith(Todo value, $Res Function(Todo) then) = + _$TodoCopyWithImpl<$Res, Todo>; + @useResult + $Res call({String description, bool completed}); +} + +/// @nodoc +class _$TodoCopyWithImpl<$Res, $Val extends Todo> + implements $TodoCopyWith<$Res> { + _$TodoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? description = null, + Object? completed = null, + }) { + return _then(_value.copyWith( + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + completed: null == completed + ? _value.completed + : completed // ignore: cast_nullable_to_non_nullable + as bool, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$TodoImplCopyWith<$Res> implements $TodoCopyWith<$Res> { + factory _$$TodoImplCopyWith( + _$TodoImpl value, $Res Function(_$TodoImpl) then) = + __$$TodoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String description, bool completed}); +} + +/// @nodoc +class __$$TodoImplCopyWithImpl<$Res> + extends _$TodoCopyWithImpl<$Res, _$TodoImpl> + implements _$$TodoImplCopyWith<$Res> { + __$$TodoImplCopyWithImpl(_$TodoImpl _value, $Res Function(_$TodoImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? description = null, + Object? completed = null, + }) { + return _then(_$TodoImpl( + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + completed: null == completed + ? _value.completed + : completed // ignore: cast_nullable_to_non_nullable + as bool, + )); + } +} + +/// @nodoc + +class _$TodoImpl implements _Todo { + _$TodoImpl({required this.description, this.completed = false}); + + @override + final String description; + @override + @JsonKey() + final bool completed; + + @override + String toString() { + return 'Todo(description: $description, completed: $completed)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$TodoImpl && + (identical(other.description, description) || + other.description == description) && + (identical(other.completed, completed) || + other.completed == completed)); + } + + @override + int get hashCode => Object.hash(runtimeType, description, completed); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$TodoImplCopyWith<_$TodoImpl> get copyWith => + __$$TodoImplCopyWithImpl<_$TodoImpl>(this, _$identity); +} + +abstract class _Todo implements Todo { + factory _Todo({required final String description, final bool completed}) = + _$TodoImpl; + + @override + String get description; + @override + bool get completed; + @override + @JsonKey(ignore: true) + _$$TodoImplCopyWith<_$TodoImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_provider.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_provider.g.dart new file mode 100644 index 000000000..455d9a64b --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/codegen/todo_list_provider.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'todo_list_provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$todoListHash() => r'26b30307668c8feefa7cbe3c400b73e6edccbc39'; + +/// See also [todoList]. +@ProviderFor(todoList) +final todoListProvider = AutoDisposeFutureProvider>.internal( + todoList, + name: r'todoListProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$todoListHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef TodoListRef = AutoDisposeFutureProviderRef>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/consumer_add_todo_call.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/consumer_add_todo_call.dart new file mode 100644 index 000000000..133545651 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/consumer_add_todo_call.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../raw/todo_list_notifier.dart' show Todo; +import '../raw/todo_list_notifier_add_todo.dart'; + +/* SNIPPET START */ +class Example extends ConsumerWidget { + const Example({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return ElevatedButton( + onPressed: () { + // 使用“ref.read”与“myProvider.notifier”结合, + // 我们可以获得通知者程序的类实例。 + // 这使我们能够调用“addTodo”方法。 + ref + .read(todoListProvider.notifier) + .addTodo(Todo(description: 'This is a new todo')); + }, + child: const Text('Add todo'), + ); + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/invalidate_self_add_todo.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/invalidate_self_add_todo.dart new file mode 100644 index 000000000..af28999d4 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/invalidate_self_add_todo.dart @@ -0,0 +1,38 @@ +// ignore_for_file: avoid_print, prefer_final_locals, omit_local_variable_types + +import 'dart:convert'; + +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:http/http.dart' as http; + +import 'todo_list_notifier.dart'; + +final todoListProvider = + AsyncNotifierProvider.autoDispose>( + TodoList.new, +); + +class TodoList extends AutoDisposeAsyncNotifier> { + @override + Future> build() async => [/* ... */]; + + /* SNIPPET START */ + Future addTodo(Todo todo) async { + // 我们不关心 API 响应 + await http.post( + Uri.https('your_api.com', '/todos'), + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(todo.toJson()), + ); + + // 一旦post请求完成,我们就可以将本地缓存标记为脏。 + // 这将导致我们的通知者程序上的“build”再次异步调用, + // 并在执行此操作时通知监听者。 + ref.invalidateSelf(); + + // (可选)然后我们可以等待新状态的计算。 + // 这确保了“addTodo”在新状态可用之前不会完成。 + await future; + } +/* SNIPPET END */ +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/manual_add_todo.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/manual_add_todo.dart new file mode 100644 index 000000000..30c168a4f --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/manual_add_todo.dart @@ -0,0 +1,39 @@ +// ignore_for_file: avoid_print, prefer_final_locals, omit_local_variable_types + +import 'dart:convert'; + +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:http/http.dart' as http; + +import 'todo_list_notifier.dart'; + +final todoListProvider = + AsyncNotifierProvider.autoDispose>( + TodoList.new, +); + +class TodoList extends AutoDisposeAsyncNotifier> { + @override + Future> build() async => [/* ... */]; + + /* SNIPPET START */ + Future addTodo(Todo todo) async { + // 我们不关心 API 响应 + await http.post( + Uri.https('your_api.com', '/todos'), + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(todo.toJson()), + ); + + // 然后我们可以手动更新本地缓存。为此,我们需要获取之前的状态。 + // 注意:之前的状态可能仍在加载或处于错误状态。 + // 处理此问题的一种优雅方法是读取“this.future”而不是“this.state”, + // 这将允许等待加载状态,并在状态处于错误状态时抛出错误。 + final previousState = await future; + + // 然后我们可以通过创建一个新的状态对象来更新状态。 + // 这将通知所有监听者。 + state = AsyncData([...previousState, todo]); + } +/* SNIPPET END */ +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/mutable_manual_add_todo.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/mutable_manual_add_todo.dart new file mode 100644 index 000000000..ad40ed42a --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/mutable_manual_add_todo.dart @@ -0,0 +1,35 @@ +// ignore_for_file: avoid_print, prefer_final_locals, omit_local_variable_types + +import 'dart:convert'; + +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:http/http.dart' as http; + +import 'todo_list_notifier.dart'; + +final todoListProvider = + AsyncNotifierProvider.autoDispose>( + TodoList.new, +); + +class TodoList extends AutoDisposeAsyncNotifier> { + @override + Future> build() async => [/* ... */]; + + Future addTodo(Todo todo) async { + // We don't care about the API response + await http.post( + Uri.https('your_api.com', '/todos'), + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(todo.toJson()), + ); + + /* SNIPPET START */ + final previousState = await future; + // 改变之前的待办事项列表。 + previousState.add(todo); + // 手动通知监听者。 + ref.notifyListeners(); +/* SNIPPET END */ + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/render_add_todo.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/render_add_todo.dart new file mode 100644 index 000000000..2d1bd4521 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/render_add_todo.dart @@ -0,0 +1,78 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +import 'rest_add_todo.dart'; +import 'todo_list_notifier.dart' show Todo; + +void main() { + runApp( + const ProviderScope(child: MyApp()), + ); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return const MaterialApp(home: Example()); + } +} + +/* SNIPPET START */ +class Example extends ConsumerStatefulWidget { + const Example({super.key}); + + @override + ConsumerState createState() => _ExampleState(); +} + +class _ExampleState extends ConsumerState { + // 待处理的 addTodo 操作。如果没有待处理的,则为 null。 + Future? _pendingAddTodo; + + @override + Widget build(BuildContext context) { + return FutureBuilder( + // 我们监听待处理的操作,以相应地更新 UI。 + future: _pendingAddTodo, + builder: (context, snapshot) { + // 计算是否存在错误状态。 + // 检查 connectionState 用于在重试操作时进行处理。 + final isErrored = snapshot.hasError && + snapshot.connectionState != ConnectionState.waiting; + + return Row( + children: [ + ElevatedButton( + style: ButtonStyle( + // 如果出现错误,我们会将该按钮显示为红色 + backgroundColor: MaterialStateProperty.all( + isErrored ? Colors.red : null, + ), + ), + onPressed: () { + // 我们将 addTodo 返回的 future 保存在变量中 + final future = ref + .read(todoListProvider.notifier) + .addTodo(Todo(description: 'This is a new todo')); + + // 我们将这个 future 存储在本地的状态中 + setState(() { + _pendingAddTodo = future; + }); + }, + child: const Text('Add todo'), + ), + // 操作正在等待,让我们显示一个进度指示器 + if (snapshot.connectionState == ConnectionState.waiting) ...[ + const SizedBox(width: 8), + const CircularProgressIndicator(), + ] + ], + ); + }, + ); + } +} +/* SNIPPET END */ \ No newline at end of file diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/render_add_todo_hooks.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/render_add_todo_hooks.dart new file mode 100644 index 000000000..959000261 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/render_add_todo_hooks.dart @@ -0,0 +1,68 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +import 'rest_add_todo.dart'; +import 'todo_list_notifier.dart' show Todo; + +void main() { + runApp( + const ProviderScope(child: MyApp()), + ); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return const MaterialApp(home: Example()); + } +} + +/* SNIPPET START */ +class Example extends HookConsumerWidget { + const Example({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + // 待处理的 addTodo 操作。如果没有待处理的,则为 null。 + final pendingAddTodo = useState?>(null); + // 我们监听待处理的操作,以相应地更新 UI。 + final snapshot = useFuture(pendingAddTodo.value); + + // 计算是否存在错误状态。 + // 检查 connectionState 用于在重试操作时进行处理。 + final isErrored = snapshot.hasError && + snapshot.connectionState != ConnectionState.waiting; + + return Row( + children: [ + ElevatedButton( + style: ButtonStyle( + // 如果出现错误,我们会将该按钮显示为红色 + backgroundColor: MaterialStateProperty.all( + isErrored ? Colors.red : null, + ), + ), + onPressed: () async { + // 我们将 addTodo 返回的 future 保存在变量中 + final future = ref + .read(todoListProvider.notifier) + .addTodo(Todo(description: 'This is a new todo')); + + // 我们将这个 future 存储在本地的状态中 + pendingAddTodo.value = future; + }, + child: const Text('Add todo'), + ), + // 操作正在等待,让我们显示一个进度指示器 + if (snapshot.connectionState == ConnectionState.waiting) ...[ + const SizedBox(width: 8), + const CircularProgressIndicator(), + ] + ], + ); + } +} +/* SNIPPET END */ \ No newline at end of file diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/rest_add_todo.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/rest_add_todo.dart new file mode 100644 index 000000000..8136265cd --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/rest_add_todo.dart @@ -0,0 +1,39 @@ +// ignore_for_file: avoid_print, prefer_final_locals, omit_local_variable_types + +import 'dart:convert'; + +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:http/http.dart' as http; + +import 'todo_list_notifier.dart'; + +final todoListProvider = + AsyncNotifierProvider.autoDispose>( + TodoList.new, +); + +class TodoList extends AutoDisposeAsyncNotifier> { + @override + Future> build() async => [/* ... */]; + + /* SNIPPET START */ + Future addTodo(Todo todo) async { + // POST 请求将返回与新应用程序状态匹配的 List + final response = await http.post( + Uri.https('your_api.com', '/todos'), + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(todo.toJson()), + ); + + // 我们解码 API 响应并将其转换为 List + List newTodos = (jsonDecode(response.body) as List) + .cast>() + .map(Todo.fromJson) + .toList(); + + // 我们更新本地缓存以匹配新状态。 + // 这将通知所有的监听程序。 + state = AsyncData(newTodos); + } +/* SNIPPET END */ +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/todo_list_notifier.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/todo_list_notifier.dart new file mode 100644 index 000000000..c3f0aff34 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/todo_list_notifier.dart @@ -0,0 +1,44 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +class Todo { + Todo({ + required this.description, + this.completed = false, + }); + + factory Todo.fromJson(Map json) { + return Todo( + description: json['description']! as String, + completed: json['completed']! as bool, + ); + } + + final String description; + final bool completed; + + Map toJson() => { + 'description': description, + 'completed': completed, + }; +} + +/* SNIPPET START */ +// 我们现在使用 AsyncNotifierProvider 替代 FutureProvider +final todoListProvider = + AsyncNotifierProvider.autoDispose>( + TodoList.new, +); + +// 因为我们的逻辑是异步的,所以我们需要使用 AsyncNotifier。 +// 特别的,由于使用“autoDispose”修饰符, +// 我们需要 AutoDisposeAsyncNotifier。 +class TodoList extends AutoDisposeAsyncNotifier> { + @override + Future> build() async { + // 我们之前在 FutureProvider 中的业务逻辑现在位于 build 方法中。 + return [ + Todo(description: 'Learn Flutter', completed: true), + Todo(description: 'Learn Riverpod'), + ]; + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/todo_list_notifier_add_todo.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/todo_list_notifier_add_todo.dart new file mode 100644 index 000000000..81e32048f --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/todo_list_notifier_add_todo.dart @@ -0,0 +1,28 @@ +// ignore_for_file: avoid_print + +import 'dart:convert'; + +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:http/http.dart' as http; + +import 'todo_list_notifier.dart'; + +final todoListProvider = + AsyncNotifierProvider.autoDispose>( + TodoList.new, +); + +/* SNIPPET START */ +class TodoList extends AutoDisposeAsyncNotifier> { + @override + Future> build() async => [/* ... */]; + + Future addTodo(Todo todo) async { + await http.post( + Uri.https('your_api.com', '/todos'), + // 我们序列化 Todo 对象并将其 POST 到服务器。 + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(todo.toJson()), + ); + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/todo_list_provider.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/todo_list_provider.dart new file mode 100644 index 000000000..a01c4195c --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/raw/todo_list_provider.dart @@ -0,0 +1,27 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +/* SNIPPET START */ +class Todo { + Todo({ + required this.description, + this.completed = false, + }); + + factory Todo.fromJson(Map json) { + return Todo( + description: json['description'] as String, + completed: json['completed'] as bool, + ); + } + + final String description; + final bool completed; +} + +final todoListProvider = FutureProvider.autoDispose>((ref) async { + // 模拟一个网络请求。这通常来自真实的 API + return [ + Todo(description: 'Learn Flutter', completed: true), + Todo(description: 'Learn Riverpod'), + ]; +}); diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/render_add_todo.ts b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/render_add_todo.ts new file mode 100644 index 000000000..798feb107 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/render_add_todo.ts @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw/render_add_todo.dart"; +import hooks from "!!raw-loader!./raw/render_add_todo_hooks.dart"; + +export default { + raw: raw, + hooks: hooks, + codegen: raw, + hooksCodegen: hooks, +}; \ No newline at end of file diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/todo_list_notifier.ts b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/todo_list_notifier.ts new file mode 100644 index 000000000..b32ae3b9b --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/todo_list_notifier.ts @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw/todo_list_notifier.dart"; +import codegen from "!!raw-loader!./codegen/todo_list_notifier.dart"; + +export default { + raw: raw, + hooks: raw, + codegen: codegen, + hooksCodegen: codegen, +}; \ No newline at end of file diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/todo_list_notifier_add_todo.ts b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/todo_list_notifier_add_todo.ts new file mode 100644 index 000000000..62fb0e0fc --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/todo_list_notifier_add_todo.ts @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw/todo_list_notifier_add_todo.dart"; +import codegen from "!!raw-loader!./codegen/todo_list_notifier_add_todo.dart"; + +export default { + raw: raw, + hooks: raw, + codegen: codegen, + hooksCodegen: codegen, +}; \ No newline at end of file diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/todo_list_provider.ts b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/todo_list_provider.ts new file mode 100644 index 000000000..de7789c0b --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/side_effects/todo_list_provider.ts @@ -0,0 +1,7 @@ +import raw from "!!raw-loader!./raw/todo_list_provider.dart"; +import codegen from "!!raw-loader!./codegen/todo_list_provider.dart"; + +export default { + raw: raw, + codegen: codegen, +}; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing.mdx new file mode 100644 index 000000000..65e4c1945 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing.mdx @@ -0,0 +1,303 @@ +--- +title: 测试你的提供者程序 +--- + +import { AutoSnippet, When } from "@site/src/components/CodeSnippet"; +import createContainer from "!!raw-loader!./testing/create_container.dart"; +import unitTest from "!!raw-loader!./testing/unit_test.dart"; +import widgetTest from "!!raw-loader!./testing/widget_test.dart"; +import fullWidgetTest from "!!raw-loader!./testing/full_widget_test.dart"; +import widgetContainerOf from "!!raw-loader!./testing/widget_container_of.dart"; +import providerToMock from "./testing/provider_to_mock"; +import mockProvider from "!!raw-loader!./testing/mock_provider.dart"; +import autoDisposeListen from "!!raw-loader!./testing/auto_dispose_listen.dart"; +import listenProvider from "!!raw-loader!./testing/listen_provider.dart"; +import awaitFuture from "!!raw-loader!./testing/await_future.dart"; +import notifierMock from "./testing/notifier_mock"; + + +Riverpod API 的核心部分是能够单独测试提供者程序。 + + +对于一个合适的测试套件,有几个挑战需要克服: + + +- 测试不应共享状态。这意味着新测试不应受到先前测试的影响。 +- 测试应该使我们能够模拟某些功能以达到所需的状态。 +- 测试环境应尽可能接近真实环境。 + + +幸运的是,Riverpod 可以轻松实现所有这些目标。 + + +## 设置测试 + + +使用 Riverpod 定义测试时,主要有两种情况: + + +- 单元测试,通常没有 Flutter 依赖。 + 这对于单独测试提供者程序的行为非常有用。 +- Widget 测试,通常带有 Flutter 依赖项。 + 这对于测试使用提供者程序的小部件的行为非常有用。 + + +### 单元测试 + + +单元测试是使用 [package:test](https://pub.dev/packages/test) 中的 `test` 函数定义的。 + + +与任何其他测试的主要区别在于,我们想要创建一个 `ProviderContainer` 对象。 +此对象将使我们的测试能够与提供者程序进行交互。 + + +建议创建一个测试实用程序来创建和处置对象 `ProviderContainer`: + + + + +然后,我们可以使用此实用程序定义一个 `test`: + + + + +现在我们有了 ProviderContainer,我们可以使用它来读取提供者程序,使用: + + +- `container.read`,以读取提供者程序的当前值。 +- `container.listen`,以监听提供者程序并接收更改的通知。 + +:::caution + +在自动处置提供者程序时使用 `container.read` 时要小心。 +如果您的提供者程序没有被监听,其状态可能会在测试过程中被破坏。 + + +在这种情况下,请考虑使用 `container.listen`。 +无论如何,它的返回值都能读取提供者程序的当前值, +同时还能确保提供者程序不会在测试过程中被弃置: + + +::: + + +### 小部件测试 + + +小部件测试是使用 [package:flutter_test](https://pub.dev/packages/flutter_test) 中的 `testWidgets` 函数定义的。 + + +在这种情况下,与通常的 Widget 测试的主要区别在于, +我们必须添加一个 `ProviderScope` 在 `tester.pumpWidget` 的根组件上: + + + + +这类似于我们在 Flutter 应用程序中启用 Riverpod 时所做的。 + + +然后,我们可以用 `tester` 来与我们的小部件进行交互。 +或者,如果要与提供者程序交互,可以获取 `ProviderContainer`。 +也可以使用 `ProviderScope.containerOf(buildContext)` 获得一个。 +因此,通过使用 `tester` ,我们可以编写以下内容: + + + + +然后,我们可以使用它来读取提供者程序。下面是一个完整的示例: + + + + +## 模拟提供者程序 + + +到目前为止,我们已经了解了如何设置测试以及与提供者程序的基本交互。 +但是,在某些情况下,我们可能想要模拟一个提供者程序。 + + +很酷的部分:默认情况下可以模拟所有提供者程序,无需任何额外设置。 +这可以通过在 `ProviderScope` 或 `ProviderContainer` 上指定 `overrides` 参数来实现。 + + +请考虑以下提供者程序: + + + + +我们可以模拟它通过以下方式: + + + + +## 监视提供者程序中的更改 + + +由于我们在测试中获得了一个 `ProviderContainer`,因此可以使用它来“监听”提供者程序: + + + + +然后,您可以将其与 [mockito](https://pub.dev/packages/mockito) +或 [mocktail](https://pub.dev/packages/mocktail) 等包结合使用,以使用它们的 `verify` API。 +或者更简单地说,您可以在列表中添加所有更改并对其进行断言。 + + +## 等待异步提供者程序 + + +在 Riverpod 中,提供者程序返回 Future/Stream 是很常见的。 +在这种情况下,我们的测试可能需要等待异步操作完成。 + + +一种方法是读取提供者程序的 `.future`: + + + + +## 模拟通知者程序 + + +通常不鼓励嘲笑通知者程序。 +相反,您可能应该在通告程序的逻辑中引入一个抽象级别,以便您可以模拟该抽象。 +例如,与其模拟通告程序,不如模拟通告程序用来从中获取数据的“存储库”。 + + +如果您坚持要模拟通告程序,那么创建这样的通告程序需要特别注意: +您的模拟必须对原始通告程序基类进行子类化: +您不能“实现”通告程序,因为这会破坏接口。 + + +因此,在模拟通告程序时,不要编写以下模拟代码: + +```dart +class MyNotifierMock with Mock implements MyNotifier {} +``` + + +你应该改写: + + + + + + +为此,您的模拟必须与您模拟的通知者程序放在同一个文件中。 +否则,您将无法访问该 `_$MyNotifier` 类。 + + diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/auto_dispose_listen.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/auto_dispose_listen.dart new file mode 100644 index 000000000..4c1582c91 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/auto_dispose_listen.dart @@ -0,0 +1,24 @@ +// ignore_for_file: unused_local_variable, avoid_print + +import 'package:flutter_test/flutter_test.dart'; +import 'package:riverpod/riverpod.dart'; + +import 'create_container.dart'; + +final provider = Provider((_) => 'Hello world'); + +void main() { + test('Some description', () { + final container = createContainer(); + /* SNIPPET START */ + final subscription = container.listen(provider, (_, __) {}); + + expect( + // 等同于 `container.read(provider)` + // 但除非处置了 "subscription",否则不会处置提供者程序。 + subscription.read(), + 'Some value', + ); + /* SNIPPET END */ + }); +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/await_future.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/await_future.dart new file mode 100644 index 000000000..0d5fd8438 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/await_future.dart @@ -0,0 +1,32 @@ +// ignore_for_file: unused_local_variable + +import 'package:flutter_test/flutter_test.dart'; +import 'package:riverpod/riverpod.dart'; + +import 'create_container.dart'; + +final provider = FutureProvider((_) async => 42); + +void main() { + test('Some description', () async { + // 为该测试创建一个 ProviderContainer。 + // 切勿!在测试之间共享 ProviderContainer。 + final container = createContainer(); + + /* SNIPPET START */ + // TODO: 使用容器来测试您的应用程序。 + // 我们的期望是异步的,所以应该使用 "expectLater"(期望稍后)。 + await expectLater( + // 我们读取的是 "provider.future",而不是 "provider"。 + // 这在异步提供者程序上是可能发生的, + // 并返回一个携带了提供者程序的值的 Future。 + container.read(provider.future), + // We can verify that the future resolves with the expected value. + // Alternatively we can use "throwsA" for errors. + // 我们可以验证 Future 是否按预期值解析。 + // 或者,我们可以使用 "throwsA" 来处理错误。 + completion('some value'), + ); + /* SNIPPET END */ + }); +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/create_container.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/create_container.dart new file mode 100644 index 000000000..1d4faf8f1 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/create_container.dart @@ -0,0 +1,22 @@ +import 'package:riverpod/riverpod.dart'; +import 'package:test/test.dart'; + +/// 一种测试工具,用于创建一个 [ProviderContainer], +/// 并在测试结束时自动将其处置。 +ProviderContainer createContainer({ + ProviderContainer? parent, + List overrides = const [], + List? observers, +}) { + // 创建一个 ProviderContainer,并可选的允许指定参数。 + final container = ProviderContainer( + parent: parent, + overrides: overrides, + observers: observers, + ); + + // 测试结束后,处置容器。 + addTearDown(container.dispose); + + return container; +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/full_widget_test.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/full_widget_test.dart new file mode 100644 index 000000000..ce0766bc3 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/full_widget_test.dart @@ -0,0 +1,33 @@ +// ignore_for_file: unused_local_variable + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; + +final provider = Provider((_) => 'some value'); + +class YourWidgetYouWantToTest extends StatelessWidget { + const YourWidgetYouWantToTest({super.key}); + + @override + Widget build(BuildContext context) => const Placeholder(); +} + +/* SNIPPET START */ +void main() { + testWidgets('Some description', (tester) async { + await tester.pumpWidget( + const ProviderScope(child: YourWidgetYouWantToTest()), + ); + + final element = tester.element(find.byType(YourWidgetYouWantToTest)); + final container = ProviderScope.containerOf(element); + + // TODO 与你的提供者程序交互 + expect( + container.read(provider), + 'some value', + ); + }); +} +/* SNIPPET END */ diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/listen_provider.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/listen_provider.dart new file mode 100644 index 000000000..d2e840d9b --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/listen_provider.dart @@ -0,0 +1,22 @@ +// ignore_for_file: unused_local_variable, avoid_print + +import 'package:flutter_test/flutter_test.dart'; +import 'package:riverpod/riverpod.dart'; + +import 'create_container.dart'; + +final provider = Provider((_) => 'Hello world'); + +void main() { + test('Some description', () { + final container = createContainer(); + /* SNIPPET START */ + container.listen( + provider, + (previous, next) { + print('The provider changed from $previous to $next'); + }, + ); + /* SNIPPET END */ + }); +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/mock_provider.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/mock_provider.dart new file mode 100644 index 000000000..284094b48 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/mock_provider.dart @@ -0,0 +1,45 @@ +// ignore_for_file: unused_local_variable + +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'create_container.dart'; +import 'full_widget_test.dart'; +import 'provider_to_mock/raw.dart'; + +void main() { + testWidgets('Some description', (tester) async { + await tester.pumpWidget( + const ProviderScope(child: YourWidgetYouWantToTest()), + ); + /* SNIPPET START */ + // 在单元测试中,重用我们之前的 "createContainer "工具。 + final container = createContainer( + // 我们可以指定要模拟的提供者程序列表: + overrides: [ + // 在本例中,我们模拟的是 "exampleProvider"。 + exampleProvider.overrideWith((ref) { + // 该函数是典型的提供者程序初始化函数。 + // 通常在此调用 "ref.watch "并返回初始状态。 + + // 让我们用自定义值替换默认的 "Hello world"。 + // 然后,与 `exampleProvider` 交互时将返回此值。 + return 'Hello from tests'; + }), + ], + ); + + // 我们还可以使用 ProviderScope 在 widget 测试中做同样的事情: + await tester.pumpWidget( + ProviderScope( + // ProviderScopes 具有完全相同的 "overrides" 参数 + overrides: [ + // 和之前一样 + exampleProvider.overrideWith((ref) => 'Hello from tests'), + ], + child: const YourWidgetYouWantToTest(), + ), + ); + /* SNIPPET END */ + }); +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/notifier_mock/codegen.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/notifier_mock/codegen.dart new file mode 100644 index 000000000..53e71e99e --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/notifier_mock/codegen.dart @@ -0,0 +1,17 @@ +// ignore_for_file: prefer_mixin + +import 'package:mockito/mockito.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +/* SNIPPET START */ +@riverpod +class MyNotifier extends _$MyNotifier { + @override + int build() => throw UnimplementedError(); +} + +// 您的模拟类需要作为 Notifier 的子类,与您的通知者程序使用的基类相对应 +class MyNotifierMock extends _$MyNotifier with Mock implements MyNotifier {} +/* SNIPPET END */ diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/notifier_mock/codegen.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/notifier_mock/codegen.g.dart new file mode 100644 index 000000000..7efc9fc64 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/notifier_mock/codegen.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$myNotifierHash() => r'912fa35c2296626fc0825bcbcfc6b6c85958be02'; + +/// See also [MyNotifier]. +@ProviderFor(MyNotifier) +final myNotifierProvider = + AutoDisposeNotifierProvider.internal( + MyNotifier.new, + name: r'myNotifierProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$myNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$MyNotifier = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/notifier_mock/index.ts b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/notifier_mock/index.ts new file mode 100644 index 000000000..9d50c6614 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/notifier_mock/index.ts @@ -0,0 +1,4 @@ +import raw from '!!raw-loader!./raw.dart'; +import codegen from '!!raw-loader!./codegen.dart'; + +export default { raw, codegen }; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/notifier_mock/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/notifier_mock/raw.dart new file mode 100644 index 000000000..404e67734 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/notifier_mock/raw.dart @@ -0,0 +1,14 @@ +// ignore_for_file: prefer_mixin + +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:mockito/mockito.dart'; + +/* SNIPPET START */ +class MyNotifier extends Notifier { + @override + int build() => throw UnimplementedError(); +} + +// 您的模拟类需要作为 Notifier 的子类,与您的通知者程序使用的基类相对应 +class MyNotifierMock extends Notifier with Mock implements MyNotifier {} +/* SNIPPET END */ diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/provider_to_mock/codegen.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/provider_to_mock/codegen.dart new file mode 100644 index 000000000..1a5fd4543 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/provider_to_mock/codegen.dart @@ -0,0 +1,9 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +/* SNIPPET START */ +// 一个急于初始化的提供者程序。 +@riverpod +Future example(ExampleRef ref) async => 'Hello world'; +/* SNIPPET END */ diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/provider_to_mock/codegen.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/provider_to_mock/codegen.g.dart new file mode 100644 index 000000000..739f3ea63 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/provider_to_mock/codegen.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$exampleHash() => r'd421d08db0ee9d10af5521159561135d8c5fa57c'; + +/// See also [example]. +@ProviderFor(example) +final exampleProvider = AutoDisposeFutureProvider.internal( + example, + name: r'exampleProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$exampleHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ExampleRef = AutoDisposeFutureProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/provider_to_mock/index.ts b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/provider_to_mock/index.ts new file mode 100644 index 000000000..9d50c6614 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/provider_to_mock/index.ts @@ -0,0 +1,4 @@ +import raw from '!!raw-loader!./raw.dart'; +import codegen from '!!raw-loader!./codegen.dart'; + +export default { raw, codegen }; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/provider_to_mock/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/provider_to_mock/raw.dart new file mode 100644 index 000000000..1491a0b60 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/provider_to_mock/raw.dart @@ -0,0 +1,6 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +/* SNIPPET START */ +// 一个急于初始化的提供者程序。 +final exampleProvider = FutureProvider((ref) async => 'Hello world'); +/* SNIPPET END */ diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/unit_test.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/unit_test.dart new file mode 100644 index 000000000..40261e13e --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/unit_test.dart @@ -0,0 +1,23 @@ +// ignore_for_file: unused_local_variable + +import 'package:flutter_test/flutter_test.dart'; +import 'package:riverpod/riverpod.dart'; + +import 'create_container.dart'; + +final provider = Provider((_) => 42); + +/* SNIPPET START */ +void main() { + test('Some description', () { + // 为该测试创建一个 ProviderContainer。 + // 切勿!在测试之间共享 ProviderContainer。 + final container = createContainer(); + + // TODO: 使用容器测试你的应用程序。 + expect( + container.read(provider), + equals('some value'), + ); + }); +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/widget_container_of.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/widget_container_of.dart new file mode 100644 index 000000000..61b2ca36b --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/widget_container_of.dart @@ -0,0 +1,15 @@ +// ignore_for_file: unused_local_variable + +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'widget_test.dart'; + +void main() { + testWidgets('Some description', (tester) async { + /* SNIPPET START */ + final element = tester.element(find.byType(YourWidgetYouWantToTest)); + final container = ProviderScope.containerOf(element); + /* SNIPPET END */ + }); +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/widget_test.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/widget_test.dart new file mode 100644 index 000000000..b4afc835c --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/testing/widget_test.dart @@ -0,0 +1,22 @@ +// ignore_for_file: unused_local_variable + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; + +class YourWidgetYouWantToTest extends StatelessWidget { + const YourWidgetYouWantToTest({super.key}); + + @override + Widget build(BuildContext context) => const Placeholder(); +} + +/* SNIPPET START */ +void main() { + testWidgets('Some description', (tester) async { + await tester.pumpWidget( + const ProviderScope(child: YourWidgetYouWantToTest()), + ); + }); +} +/* SNIPPET END */ diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync.mdx new file mode 100644 index 000000000..7fe276f71 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync.mdx @@ -0,0 +1,197 @@ +--- +title: Websocket 和同步执行 +--- + +import { + trimSnippet, + AutoSnippet, + When, +} from "@site/src/components/CodeSnippet"; +import syncDefinition from "./websockets_sync/sync_definition"; +import streamProvider from "./websockets_sync/stream_provider"; +import syncConsumer from "!!raw-loader!./websockets_sync/sync_consumer.dart"; +import rawUsage from "!!raw-loader!./websockets_sync/raw_usage.dart"; +import pipeChangeNotifier from "!!raw-loader!./websockets_sync/pipe_change_notifier.dart"; +import sharedPipeChangeNotifier from "!!raw-loader!./websockets_sync/shared_pipe_change_notifier.dart"; +import changeNotifierProvider from "!!raw-loader!./websockets_sync/change_notifier_provider.dart"; + + +到目前为止,我们只介绍了如何创建一个 `Future`。 +这是有意为之的,因为 `Future` 是 Riverpod 应用程序构建方式的核心。 +_但是_,如有必要,Riverpod 还支持其他格式。 + + +特别是,除了 `Future` 类型的提供者程序,还存在灵活的类型: + + +- 同步返回一个对象,例如创建“存储库”。 +- 返回一个 `Stream`,例如监听 websockets。 + + +返回 `Future` 和返回 `Stream` 或 object 总体上非常相似。 +本页将解释这些用例的细微差别和各种提示。 + + +## 同步返回对象 + + +若要同步创建对象,请确保提供者程序不返回 Future: + + + + +当提供者程序同步创建对象时,这会影响对象的使用方式。 +具体而言,同步值不会被包装在“AsyncValue”中: + + + + +这种差异的后果是,如果提供者程序抛出异常,尝试读取该值会重新抛出错误。 +或者,当使用 `ref.listen` 时,将调用 “onError” 回调。 + + +### 可监听对象的注意事项 + + + + +可监听对象,例如 `ChangeNotifier` 或 `StateNotifier` 是不受支持的。 +如果出于兼容性原因,您需要与其中一个对象进行交互, +一种解决方法是将其通知机制通过管道传递给 Riverpod。 + + + +:::info + +如果你多次需要这样的逻辑,值得注意的是, +逻辑是共享的!"ref" 对象被设计为可组合的。 +这样就可以从提供者程序中提取处置/监听逻辑: + + +::: + + + + + + +当不使用代码生成时,Riverpod 提供了“传统”的提供者程序来支持 +`ChangeNotifier` 和 `StateNotifier` 开箱即用: +`ChangeNotifierProvider` 和 `StateNotifierProvider`。 +使用它们就像使用其他类型的提供者程序一样。 +主要区别在于它们将自动监听和处置返回的对象。 + + +不建议将这些提供者程序用于新的业务逻辑。 +但是,在与旧代码混合编写时(例如从 `pkg:provider` 迁移到 Riverpod 时), +它们可能会有所帮助。 + + + + + + +## 监听一个流 + + +现代应用程序的一个常见用例是与 Websocket 交互, +例如与 Firebase 或 GraphQL 订阅进行交互。 +与这些 API 的交互通常是通过监听一个 `Stream`。 + + +为了帮助实现这一点,Riverpod 自然地支持 `Stream` 对象。 +与 `Future` 一样,该对象将转换为 `AsyncValue`: + + + +:::info + +Riverpod 不知道自定义 `Stream` 实现,例如 RX 的 `BehaviorSubject`。 +因此,返回 `BehaviorSubject` 不会同步向小部件公开,`value` 即使在创建时已经可用。 +::: + + +## 禁用从 `Stream` / `Future` 转换到 `AsyncValue` + + +默认情况下,Riverpod 会将 `Stream` 和 `Future` 转换为 `AsyncValue`。 +尽管很少需要,但可以通过将返回类型包装在 `Raw` 泛型中来禁用此行为。 + +:::caution + +通常不建议禁用转换 `AsyncValue`。 +只有当您知道自己在做什么时才这样做。 +::: + + diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/change_notifier_provider.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/change_notifier_provider.dart new file mode 100644 index 000000000..58d56d96c --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/change_notifier_provider.dart @@ -0,0 +1,11 @@ +// ignore_for_file: omit_local_variable_types + +import 'package:flutter/widgets.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +/* SNIPPET START */ +final myProvider = ChangeNotifierProvider>((ref) { + // 将监听并处置 ValueNotifier。 + // 然后,小部件可以使用 "ref.watch" 对提供者程序监听更新。 + return ValueNotifier(0); +}); diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/pipe_change_notifier.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/pipe_change_notifier.dart new file mode 100644 index 000000000..1506310fb --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/pipe_change_notifier.dart @@ -0,0 +1,21 @@ +// ignore_for_file: omit_local_variable_types + +import 'package:flutter/widgets.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'pipe_change_notifier.g.dart'; + +/* SNIPPET START */ +/// 一个提供者程序,它创建 ValueNotifier 并在值更改时更新其监听器。 +@riverpod +ValueNotifier myListenable(MyListenableRef ref) { + final notifier = ValueNotifier(0); + + // 当提供者程序被处置时处置通知者程序 + ref.onDispose(notifier.dispose); + + // 每当 ValueNotifier 更新时通知此提供者程序的监听器。 + notifier.addListener(ref.notifyListeners); + + return notifier; +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/pipe_change_notifier.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/pipe_change_notifier.g.dart new file mode 100644 index 000000000..585765770 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/pipe_change_notifier.g.dart @@ -0,0 +1,28 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'pipe_change_notifier.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$myListenableHash() => r'4cc07df2f47050c4aa761e5467f341ab6c312d09'; + +/// 一个提供者程序,它创建 ValueNotifier 并在值更改时更新其监听器。 +/// +/// Copied from [myListenable]. +@ProviderFor(myListenable) +final myListenableProvider = AutoDisposeProvider>.internal( + myListenable, + name: r'myListenableProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$myListenableHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef MyListenableRef = AutoDisposeProviderRef>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/raw_usage.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/raw_usage.dart new file mode 100644 index 000000000..496408e74 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/raw_usage.dart @@ -0,0 +1,30 @@ +// ignore_for_file: omit_local_variable_types, prefer_final_locals, use_key_in_widget_constructors + +import 'package:flutter/widgets.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'raw_usage.g.dart'; + +/* SNIPPET START */ +@riverpod +Raw> rawStream(RawStreamRef ref) { + // “Raw”是一个 typedef。 + // 无需包装返回值的“原始”构造函数中的值。 + return const Stream.empty(); +} + +class Consumer extends ConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + // 该值不再转换为 AsyncValue, + // 并且按原样返回创建的流。 + Stream stream = ref.watch(rawStreamProvider); + return StreamBuilder( + stream: stream, + builder: (context, snapshot) { + return Text('${snapshot.data}'); + }, + ); + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/raw_usage.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/raw_usage.g.dart new file mode 100644 index 000000000..5b898587c --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/raw_usage.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'raw_usage.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$rawStreamHash() => r'7e7c2e8f4f08d33a4d86d60449e143c419ca4822'; + +/// See also [rawStream]. +@ProviderFor(rawStream) +final rawStreamProvider = AutoDisposeProvider>>.internal( + rawStream, + name: r'rawStreamProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$rawStreamHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef RawStreamRef = AutoDisposeProviderRef>>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/shared_pipe_change_notifier.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/shared_pipe_change_notifier.dart new file mode 100644 index 000000000..faa8fb018 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/shared_pipe_change_notifier.dart @@ -0,0 +1,29 @@ +// ignore_for_file: omit_local_variable_types + +import 'package:flutter/widgets.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'shared_pipe_change_notifier.g.dart'; + +/* SNIPPET START */ +extension on Ref { + // 我们可以将之前的逻辑移至 Ref 扩展。 + // 这使得能够重用提供者程序之间的逻辑 + T disposeAndListenChangeNotifier(T notifier) { + onDispose(notifier.dispose); + notifier.addListener(notifyListeners); + // 我们返回通知者程序以稍微简化使用 + return notifier; + } +} + +@riverpod +ValueNotifier myListenable(MyListenableRef ref) { + return ref.disposeAndListenChangeNotifier(ValueNotifier(0)); +} + +@riverpod +ValueNotifier anotherListenable(AnotherListenableRef ref) { + return ref.disposeAndListenChangeNotifier(ValueNotifier(42)); +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/shared_pipe_change_notifier.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/shared_pipe_change_notifier.g.dart new file mode 100644 index 000000000..66cb3d39c --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/shared_pipe_change_notifier.g.dart @@ -0,0 +1,42 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'shared_pipe_change_notifier.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$myListenableHash() => r'7096094cd24ed50dbabb9fb9ab64b340176c04bf'; + +/// See also [myListenable]. +@ProviderFor(myListenable) +final myListenableProvider = AutoDisposeProvider>.internal( + myListenable, + name: r'myListenableProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$myListenableHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef MyListenableRef = AutoDisposeProviderRef>; +String _$anotherListenableHash() => r'38bfe5dbf5f148819b3671ad69d15c8e05264c23'; + +/// See also [anotherListenable]. +@ProviderFor(anotherListenable) +final anotherListenableProvider = + AutoDisposeProvider>.internal( + anotherListenable, + name: r'anotherListenableProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$anotherListenableHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef AnotherListenableRef = AutoDisposeProviderRef>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/stream_provider/codegen.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/stream_provider/codegen.dart new file mode 100644 index 000000000..7fdf1c3a2 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/stream_provider/codegen.dart @@ -0,0 +1,34 @@ +// ignore_for_file: omit_local_variable_types, prefer_final_locals, use_key_in_widget_constructors + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +/* SNIPPET START */ +@riverpod +Stream streamExample(StreamExampleRef ref) async* { + // 每 1 秒产生一个 0 到 41 之间的数字。 + // 这可以替换为来自 Firestore 或 GraphQL 或其他任何东西的 Stream。 + for (var i = 0; i < 42; i++) { + yield i; + await Future.delayed(const Duration(seconds: 1)); + } +} + +class Consumer extends ConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + // 该流被监听并转换为 AsyncValue。 + AsyncValue value = ref.watch(streamExampleProvider); + + // 我们可以使用 AsyncValue 来处理加载/错误状态并显示数据。 + return switch (value) { + AsyncValue(:final error?) => Text('Error: $error'), + AsyncValue(:final valueOrNull?) => Text('$valueOrNull'), + _ => const CircularProgressIndicator(), + }; + } +} +/* SNIPPET END */ \ No newline at end of file diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/stream_provider/codegen.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/stream_provider/codegen.g.dart new file mode 100644 index 000000000..fec43b43a --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/stream_provider/codegen.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$streamExampleHash() => r'ca9993b22f6d587b20c041133cacd28d01933074'; + +/// See also [streamExample]. +@ProviderFor(streamExample) +final streamExampleProvider = AutoDisposeStreamProvider.internal( + streamExample, + name: r'streamExampleProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$streamExampleHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef StreamExampleRef = AutoDisposeStreamProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/stream_provider/index.ts b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/stream_provider/index.ts new file mode 100644 index 000000000..4ee159de8 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/stream_provider/index.ts @@ -0,0 +1,4 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./codegen.dart"; + +export default { raw, codegen }; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/stream_provider/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/stream_provider/raw.dart new file mode 100644 index 000000000..bdec702f7 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/stream_provider/raw.dart @@ -0,0 +1,30 @@ +// ignore_for_file: omit_local_variable_types, prefer_final_locals, use_key_in_widget_constructors + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +/* SNIPPET START */ +final streamExampleProvider = StreamProvider.autoDispose((ref) async* { + // 每 1 秒产生一个 0 到 41 之间的数字。 + // 这可以替换为来自 Firestore 或 GraphQL 或其他任何东西的 Stream。 + for (var i = 0; i < 42; i++) { + yield i; + await Future.delayed(const Duration(seconds: 1)); + } +}); + +class Consumer extends ConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + // 该流被监听并转换为 AsyncValue。 + AsyncValue value = ref.watch(streamExampleProvider); + + // 我们可以使用 AsyncValue 来处理加载/错误状态并显示数据。 + return switch (value) { + AsyncValue(:final error?) => Text('Error: $error'), + AsyncValue(:final valueOrNull?) => Text('$valueOrNull'), + _ => const CircularProgressIndicator(), + }; + } +} +/* SNIPPET END */ \ No newline at end of file diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_consumer.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_consumer.dart new file mode 100644 index 000000000..83bc56e66 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_consumer.dart @@ -0,0 +1,19 @@ +// ignore_for_file: omit_local_variable_types, prefer_final_locals + +import 'package:flutter/widgets.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'sync_definition/raw.dart'; + +void main() { +/* SNIPPET START */ + Consumer( + builder: (context, ref, child) { + // value 没有使用 "AsyncValue "包装 + int value = ref.watch(synchronousExampleProvider); + + return Text('$value'); + }, + ); +/* SNIPPET END */ +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_definition/codegen.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_definition/codegen.dart new file mode 100644 index 000000000..b18b8f76e --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_definition/codegen.dart @@ -0,0 +1,10 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +/* SNIPPET START */ +@riverpod +int synchronousExample(SynchronousExampleRef ref) { + return 0; +} +/* SNIPPET END */ \ No newline at end of file diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_definition/codegen.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_definition/codegen.g.dart new file mode 100644 index 000000000..9d331d63e --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_definition/codegen.g.dart @@ -0,0 +1,28 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$synchronousExampleHash() => + r'98df96e07d554683041f668c06b36f183ff534c1'; + +/// See also [synchronousExample]. +@ProviderFor(synchronousExample) +final synchronousExampleProvider = AutoDisposeProvider.internal( + synchronousExample, + name: r'synchronousExampleProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$synchronousExampleHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef SynchronousExampleRef = AutoDisposeProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_definition/index.ts b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_definition/index.ts new file mode 100644 index 000000000..4ee159de8 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_definition/index.ts @@ -0,0 +1,4 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./codegen.dart"; + +export default { raw, codegen }; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_definition/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_definition/raw.dart new file mode 100644 index 000000000..9c64294a0 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/essentials/websockets_sync/sync_definition/raw.dart @@ -0,0 +1,7 @@ +import 'package:riverpod/riverpod.dart'; + +/* SNIPPET START */ +final synchronousExampleProvider = Provider.autoDispose((ref) { + return 0; +}); +/* SNIPPET END */ \ No newline at end of file diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/family/family.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/family/family.dart new file mode 100644 index 000000000..4d49c552a --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/family/family.dart @@ -0,0 +1,11 @@ +import 'dart:math'; + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'family.g.dart'; +/* SNIPPET START */ + +@riverpod +int random(RandomRef ref, {required int seed, required int max}) { + return Random(seed).nextInt(max); +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/family/family.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/family/family.g.dart new file mode 100644 index 000000000..528729ff0 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/family/family.g.dart @@ -0,0 +1,174 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'family.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$randomHash() => r'517b12aad4df7b31f8872b89af74e7880377b2ea'; + +/// Copied from Dart SDK +class _SystemHash { + _SystemHash._(); + + static int combine(int hash, int value) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + value); + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); + return hash ^ (hash >> 6); + } + + static int finish(int hash) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + // ignore: parameter_assignments + hash = hash ^ (hash >> 11); + return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); + } +} + +/// See also [random]. +@ProviderFor(random) +const randomProvider = RandomFamily(); + +/// See also [random]. +class RandomFamily extends Family { + /// See also [random]. + const RandomFamily(); + + /// See also [random]. + RandomProvider call({ + required int seed, + required int max, + }) { + return RandomProvider( + seed: seed, + max: max, + ); + } + + @override + RandomProvider getProviderOverride( + covariant RandomProvider provider, + ) { + return call( + seed: provider.seed, + max: provider.max, + ); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'randomProvider'; +} + +/// See also [random]. +class RandomProvider extends AutoDisposeProvider { + /// See also [random]. + RandomProvider({ + required int seed, + required int max, + }) : this._internal( + (ref) => random( + ref as RandomRef, + seed: seed, + max: max, + ), + from: randomProvider, + name: r'randomProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$randomHash, + dependencies: RandomFamily._dependencies, + allTransitiveDependencies: RandomFamily._allTransitiveDependencies, + seed: seed, + max: max, + ); + + RandomProvider._internal( + super._createNotifier, { + required super.name, + required super.dependencies, + required super.allTransitiveDependencies, + required super.debugGetCreateSourceHash, + required super.from, + required this.seed, + required this.max, + }) : super.internal(); + + final int seed; + final int max; + + @override + Override overrideWith( + int Function(RandomRef provider) create, + ) { + return ProviderOverride( + origin: this, + override: RandomProvider._internal( + (ref) => create(ref as RandomRef), + from: from, + name: null, + dependencies: null, + allTransitiveDependencies: null, + debugGetCreateSourceHash: null, + seed: seed, + max: max, + ), + ); + } + + @override + AutoDisposeProviderElement createElement() { + return _RandomProviderElement(this); + } + + @override + bool operator ==(Object other) { + return other is RandomProvider && other.seed == seed && other.max == max; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, seed.hashCode); + hash = _SystemHash.combine(hash, max.hashCode); + + return _SystemHash.finish(hash); + } +} + +mixin RandomRef on AutoDisposeProviderRef { + /// The parameter `seed` of this provider. + int get seed; + + /// The parameter `max` of this provider. + int get max; +} + +class _RandomProviderElement extends AutoDisposeProviderElement + with RandomRef { + _RandomProviderElement(super.provider); + + @override + int get seed => (origin as RandomProvider).seed; + @override + int get max => (origin as RandomProvider).max; +} +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/family/index.tsx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/family/index.tsx new file mode 100644 index 000000000..fa391f61a --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/family/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./family.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/family/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/family/raw.dart new file mode 100644 index 000000000..68b84d40d --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/family/raw.dart @@ -0,0 +1,27 @@ +import 'dart:math'; + +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +@immutable +abstract class Equatable { + const Equatable(); + + List get props; +} + +/* SNIPPET START */ +class ParamsType extends Equatable { + const ParamsType({required this.seed, required this.max}); + + final int seed; + final int max; + + @override + List get props => [seed, max]; +} + +final randomProvider = + Provider.family.autoDispose((ref, params) { + return Random(params.seed).nextInt(params.max); +}); diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/helpers/item.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/helpers/item.dart new file mode 100644 index 000000000..1082ef6f3 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/helpers/item.dart @@ -0,0 +1,12 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +import 'json.dart'; + +part 'item.freezed.dart'; +part 'item.g.dart'; + +@freezed +class Item with _$Item { + const factory Item({required int id}) = _Item; + factory Item.fromJson(Json json) => _$ItemFromJson(json); +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/helpers/item.freezed.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/helpers/item.freezed.dart new file mode 100644 index 000000000..e578c8154 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/helpers/item.freezed.dart @@ -0,0 +1,146 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'item.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +Item _$ItemFromJson(Map json) { + return _Item.fromJson(json); +} + +/// @nodoc +mixin _$Item { + int get id => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $ItemCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ItemCopyWith<$Res> { + factory $ItemCopyWith(Item value, $Res Function(Item) then) = + _$ItemCopyWithImpl<$Res, Item>; + @useResult + $Res call({int id}); +} + +/// @nodoc +class _$ItemCopyWithImpl<$Res, $Val extends Item> + implements $ItemCopyWith<$Res> { + _$ItemCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + }) { + return _then(_value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$ItemImplCopyWith<$Res> implements $ItemCopyWith<$Res> { + factory _$$ItemImplCopyWith( + _$ItemImpl value, $Res Function(_$ItemImpl) then) = + __$$ItemImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({int id}); +} + +/// @nodoc +class __$$ItemImplCopyWithImpl<$Res> + extends _$ItemCopyWithImpl<$Res, _$ItemImpl> + implements _$$ItemImplCopyWith<$Res> { + __$$ItemImplCopyWithImpl(_$ItemImpl _value, $Res Function(_$ItemImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + }) { + return _then(_$ItemImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$ItemImpl implements _Item { + const _$ItemImpl({required this.id}); + + factory _$ItemImpl.fromJson(Map json) => + _$$ItemImplFromJson(json); + + @override + final int id; + + @override + String toString() { + return 'Item(id: $id)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ItemImpl && + (identical(other.id, id) || other.id == id)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, id); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$ItemImplCopyWith<_$ItemImpl> get copyWith => + __$$ItemImplCopyWithImpl<_$ItemImpl>(this, _$identity); + + @override + Map toJson() { + return _$$ItemImplToJson( + this, + ); + } +} + +abstract class _Item implements Item { + const factory _Item({required final int id}) = _$ItemImpl; + + factory _Item.fromJson(Map json) = _$ItemImpl.fromJson; + + @override + int get id; + @override + @JsonKey(ignore: true) + _$$ItemImplCopyWith<_$ItemImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/helpers/item.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/helpers/item.g.dart new file mode 100644 index 000000000..3c653e18c --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/helpers/item.g.dart @@ -0,0 +1,18 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'item.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$ItemImpl _$$ItemImplFromJson(Map json) => _$ItemImpl( + id: json['id'] as int, + ); + +Map _$$ItemImplToJson(_$ItemImpl instance) => + { + 'id': instance.id, + }; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/helpers/json.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/helpers/json.dart new file mode 100644 index 000000000..17cfb1c01 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/helpers/json.dart @@ -0,0 +1 @@ +typedef Json = Map; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/async_values/async_values.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/async_values/async_values.dart new file mode 100644 index 000000000..802a23150 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/async_values/async_values.dart @@ -0,0 +1,29 @@ +import 'package:collection/collection.dart'; +import 'package:dio/dio.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../../helpers/item.dart'; +import '../../helpers/json.dart'; + +part 'async_values.g.dart'; + +/* SNIPPET START */ + +@riverpod +Future> itemsApi(ItemsApiRef ref) async { + final client = Dio(); + final result = await client.get>('your-favorite-api'); + final parsed = [...result.data!.map((e) => Item.fromJson(e as Json))]; + return parsed; +} + +@riverpod +List evenItems(EvenItemsRef ref) { + final asyncValue = ref.watch(itemsApiProvider); + if (asyncValue.isReloading) return []; + if (asyncValue.hasError) return const [Item(id: -1)]; + + final items = asyncValue.requireValue; + + return [...items.whereIndexed((index, element) => index.isEven)]; +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/async_values/async_values.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/async_values/async_values.g.dart new file mode 100644 index 000000000..09f07382c --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/async_values/async_values.g.dart @@ -0,0 +1,40 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'async_values.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$itemsApiHash() => r'b32ccb7b85305e361d8ed752cbe11d9524c96190'; + +/// See also [itemsApi]. +@ProviderFor(itemsApi) +final itemsApiProvider = AutoDisposeFutureProvider>.internal( + itemsApi, + name: r'itemsApiProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$itemsApiHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ItemsApiRef = AutoDisposeFutureProviderRef>; +String _$evenItemsHash() => r'55ae98f9b6108203dfc4a139f1ade9fbd8ba8ddd'; + +/// See also [evenItems]. +@ProviderFor(evenItems) +final evenItemsProvider = AutoDisposeProvider>.internal( + evenItems, + name: r'evenItemsProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$evenItemsHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef EvenItemsRef = AutoDisposeProviderRef>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/async_values/index.tsx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/async_values/index.tsx new file mode 100644 index 000000000..526f2dffe --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/async_values/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./async_values.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/async_values/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/async_values/raw.dart new file mode 100644 index 000000000..1ca8987ef --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/async_values/raw.dart @@ -0,0 +1,25 @@ +import 'package:collection/collection.dart'; +import 'package:dio/dio.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../../helpers/item.dart'; +import '../../helpers/json.dart'; + +/* SNIPPET START */ + +final itemsApiProvider = FutureProvider.autoDispose((ref) async { + final client = Dio(); + final result = await client.get>('your-favorite-api'); + final parsed = [...result.data!.map((e) => Item.fromJson(e as Json))]; + return parsed; +}); + +final evenItemsProvider = Provider.autoDispose((ref) { + final asyncValue = ref.watch(itemsApiProvider); + if (asyncValue.isLoading) return []; + if (asyncValue.hasError) return const [Item(id: -1)]; + + final items = asyncValue.requireValue; + + return [...items.whereIndexed((index, element) => index.isEven)]; +}); diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/auto_dispose/auto_dispose.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/auto_dispose/auto_dispose.dart new file mode 100644 index 000000000..9510dc2b8 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/auto_dispose/auto_dispose.dart @@ -0,0 +1,24 @@ +import 'dart:math'; + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'auto_dispose.g.dart'; + +/* SNIPPET START */ // 对于代码生成,.autoDispose 是默认值 +@riverpod +int diceRoll(DiceRollRef ref) { + // 由于此提供者程序是 .autoDispose,因此取消监听将处置其当前公开的状态。 + // 然后,每当再次监听该提供者程序时,就会掷出新的骰子并再次暴露。 + final dice = Random().nextInt(10); + return dice; +} + +@riverpod +int cachedDiceRoll(CachedDiceRollRef ref) { + final coin = Random().nextInt(10); + if (coin > 5) throw Exception('Way too large.'); + // 上述条件可能会失败; + // 如果没有,以下指令会告诉提供者程序保持其缓存状态,即使没有人再监听它。 + ref.keepAlive(); + return coin; +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/auto_dispose/auto_dispose.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/auto_dispose/auto_dispose.g.dart new file mode 100644 index 000000000..376279010 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/auto_dispose/auto_dispose.g.dart @@ -0,0 +1,41 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'auto_dispose.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$diceRollHash() => r'dfd5ac8b74351a0076da9d131c10277f53ff11b9'; + +/// See also [diceRoll]. +@ProviderFor(diceRoll) +final diceRollProvider = AutoDisposeProvider.internal( + diceRoll, + name: r'diceRollProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$diceRollHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef DiceRollRef = AutoDisposeProviderRef; +String _$cachedDiceRollHash() => r'fc31fcb804f10360d75362e56329976343ee7abb'; + +/// See also [cachedDiceRoll]. +@ProviderFor(cachedDiceRoll) +final cachedDiceRollProvider = AutoDisposeProvider.internal( + cachedDiceRoll, + name: r'cachedDiceRollProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$cachedDiceRollHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef CachedDiceRollRef = AutoDisposeProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/auto_dispose/index.tsx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/auto_dispose/index.tsx new file mode 100644 index 000000000..6c57cfffd --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/auto_dispose/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./auto_dispose.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/auto_dispose/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/auto_dispose/raw.dart new file mode 100644 index 000000000..f7230ee09 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/auto_dispose/raw.dart @@ -0,0 +1,20 @@ +import 'dart:math'; + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +/* SNIPPET START */ +final diceRollProvider = Provider.autoDispose((ref) { + // 由于此提供者程序是 .autoDispose,因此取消监听将处置其当前公开的状态。 + // 然后,每当再次监听该提供者程序时,就会掷出新的骰子并再次暴露。 + final dice = Random().nextInt(10); + return dice.isEven; +}); + +final cachedDiceRollProvider = Provider.autoDispose((ref) { + final coin = Random().nextInt(10); + if (coin > 5) throw Exception('Way too large.'); + // 上述条件可能会失败; + // 如果没有,以下指令会告诉提供者程序保持其缓存状态,即使没有人再监听它。 + ref.keepAlive(); + return coin.isEven; +}); diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/combine/combine.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/combine/combine.dart new file mode 100644 index 000000000..ecd1915da --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/combine/combine.dart @@ -0,0 +1,19 @@ +import 'dart:math'; + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'combine.g.dart'; + +/* SNIPPET START */ + +@riverpod +int number(NumberRef ref) { + return Random().nextInt(10); +} + +@riverpod +int doubled(DoubledRef ref) { + final number = ref.watch(numberProvider); + + return number * 2; +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/combine/combine.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/combine/combine.g.dart new file mode 100644 index 000000000..7df3df2f7 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/combine/combine.g.dart @@ -0,0 +1,40 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'combine.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$numberHash() => r'725e25be57b9cc2bd914752f156e26a214596b63'; + +/// See also [number]. +@ProviderFor(number) +final numberProvider = AutoDisposeProvider.internal( + number, + name: r'numberProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$numberHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef NumberRef = AutoDisposeProviderRef; +String _$doubledHash() => r'ddc640c876bdbe49fe72fe1632b5ff48687c9279'; + +/// See also [doubled]. +@ProviderFor(doubled) +final doubledProvider = AutoDisposeProvider.internal( + doubled, + name: r'doubledProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$doubledHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef DoubledRef = AutoDisposeProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/combine/index.tsx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/combine/index.tsx new file mode 100644 index 000000000..2ff7dfbaa --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/combine/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./combine.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/combine/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/combine/raw.dart new file mode 100644 index 000000000..ad33636e7 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/combine/raw.dart @@ -0,0 +1,15 @@ +import 'dart:math'; + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +/* SNIPPET START */ + +final numberProvider = Provider.autoDispose((ref) { + return Random().nextInt(10); +}); + +final doubledProvider = Provider.autoDispose((ref) { + final number = ref.watch(numberProvider); + + return number * 2; +}); diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/motivation.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/motivation.mdx new file mode 100644 index 000000000..4951f9b1a --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/motivation.mdx @@ -0,0 +1,434 @@ +--- +title: 动机 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; +import CodeBlock from "@theme/CodeBlock"; +import sameType from "./same_type"; +import combine from "./combine"; +import asyncValues from "./async_values"; +import autoDispose from "./auto_dispose"; +import override from "./override"; +import sideEffects from "./side_effects"; +import { + trimSnippet, + AutoSnippet, + When, +} from "@site/src/components/CodeSnippet"; + + +这篇深入的文章旨在说明为什么需要 Riverpod 的动机。 + + +特别是,本节应回答以下问题: + - 既然 Provider 广受欢迎,为什么要迁移到 Riverpod? + - 我能获得哪些具体优势? + - 如何迁移到 Riverpod? + - 我可以增量迁移吗? + - 等等…… + + +在本节结束时,您应该确信 Riverpod 优先于 Provider。 + + +**与 Provider 相比,Riverpod 确实是一种更现代、更推荐和更可靠的方法。** + + +Riverpod 提供更好的状态管理功能、更好的缓存策略和简化的响应式模型。 +然而,Provider 目前在许多领域都缺乏,没有前进的道路。 + + +## Provider 的局限性 + + +Provider 存在根本问题是由于受到 InheritedWidget API 的限制。 +从本质上讲,Provider 是一个“更简单的 `InheritedWidget`”; +Provider 只是一个 InheritedWidget 包装器,因此它受到它的限制。 + + +下面是已知的提供者程序问题列表。 + + +### 提供者程序不能保留两个(或多个)相同“类型”的提供者程序 + + +声明两个 `Provider` 将导致不可靠的行为: +`InheritedWidget` 的 API 只能获取*两者中的一个*:即最接近 `Provider` 的祖先。 +虽然 Provider 的文档中解释了[解决方法],但 Riverpod 根本没有这个问题。 + + +通过消除这个限制,我们可以自由地将逻辑拆分为小块,如下所示: + + + + + +### 提供者程序一次合理地只发出一个值 + + +读取外部 RESTful API 时,通常会显示上次读取值,而新调用会加载下一个读取值。 +Riverpod 通过其 `AsyncValue` 的 API 一次发出两个值(即一个前一个数据值和一个传入的新加载值)来允许这种行为: + + + + +在前面的代码片段中,观察 `evenItemsProvider` 将产生以下效果: +1. 最初,正在发出请求。我们得到一个空列表; +1. 然后,假设发生错误。我们获得 `[Item(id: -1)]`; +1. 然后,我们使用拉取刷新逻辑重试请求(例如,通过 `ref.invalidate`); +1. 当我们重新加载第一个提供者程序时,第二个提供者程序仍然公开 `[Item(id: -1)]`; +1. 这一次,一些解析后的数据被正确接收:我们的偶数项被正确返回。 + + +使用 Provider,上述功能无法远程实现,甚至更难解决。 + + +### 合并提供者程序很困难且容易出错 + + +对于 Provider,我们可能很想在 provider 的 `create` 中使用 `context.watch`。 +这将是不可靠的,因为即使没有依赖项发生更改(例如,当小部件树中涉及 GlobalKey 时), +`didChangeDependencies` 也可能被触发。 + + +尽管如此,Provider 有一个名为 `ProxyProvider` 的临时解决方案,但它被认为是乏味且容易出错的。 + + +合并状态是 Riverpod 的核心机制,因为我们可以使用简单而强大的方法(如 [ref.watch] 和 [ref.listen])以零开销组合和缓存值: + + + + +使用 Riverpod 组合值感觉很自然:依赖项是可读的,并且 API 保持不变。 + + + +### 缺乏安全性 + + +使用 Provider,在重构和/或大型更改期间以 `ProviderNotFoundException` 结束是很常见的。 +事实上,这个运行时异常*是*最初创建 Riverpod 的主要原因之一。 + + +尽管它带来了比这更多的实用性,但 Riverpod 根本无法抛出此异常。 + + +### 处置状态很困难 + + +`InheritedWidget` [无法对消费者程序停止监听他们的情况做出反应]。 +这可以防止提供者程序在不再使用时自动处置其提供者程序的状态。 +在使用 Provider 的情况下,[我们必须]依靠作用域提供者程序在停止使用状态时对其进行处置。 +但这并不容易,因为当在页面之间共享状态时,它会变得棘手。 + + +Riverpod 通过易于理解的 API(如 [autodispose] 和 [keepAlive])解决了这个问题。 +这两个 API 支持灵活且创造性的缓存策略(例如基于时间的缓存): + + + + + +不幸的是,没有办法用原始 `InheritedWidget` 的来实现这一点,因此没有办法用 Provider 来实现。 + + +### 缺乏可靠的参数化机制 + + +Riverpod 允许其用户使用 [`.family` 修饰符]声明“参数化”提供者程序。 +事实上,这是 Riverpod 最强大的功能之一,也是其创新的核心, +例如,`.family` 它能够极大地[简化逻辑]。 + + +如果我们想使用 Provider 实现类似的东西, +我们将不得不放弃这些参数的易用性*和*类型安全性。 + + +此外,无法使用 Provider 实现类似的 `.autoDispose` 机制 +本身就阻止了 `.family` 的任何等效实现,[因为这两个功能是齐头并进的]。 + + +最后,如前所述,[事实证明],小部件*永远*不会停止收听 `InheritedWidget`。 +这意味着如果某些提供者程序状态是“动态挂载”的, +即当使用参数构建提供者程序时,则会出现严重的内存泄漏,而这正是 `.family` 这样做的。 +因此,目前从根本上不可能获得 Provider 的 `.family` 等价物。 + + +### 测试很乏味 + + +为了能够编写测试,您*必须*在每个测试中重新定义提供者程序。 + + +默认情况下,借助 Riverpod,提供者程序已准备好在内部测试中使用。 +此外,Riverpod 还公开了一组方便的“覆盖”的工具,这些实用程序在模拟提供者程序时至关重要。 + + +测试上面的组合状态代码段非常简单,如下所示: + + + + +有关测试的详细信息,请参阅[测试]。 + + + +### 引发副作用并不简单 + + +由于 `InheritedWidget` 没有 `onChange` 回调,因此 Provider 也没有回调。 +这对于导航来说是有问题的,例如小吃栏、模态等。 + + +相反,Riverpod 只是提供 `ref.listen`,它[与 Flutter 很好地集成在一起]。 + + + + +## 转向 Riverpod + + +从概念上讲,Riverpod 和 Provider 非常相似。 +这两个包都扮演着类似的角色。两者都尝试: + + +- 缓存和处置一些有状态对象; +- 提供一种在测试期间模拟这些对象的方法; +- 为 Widget 提供了一种以简单的方式监听这些对象的方法。 + + +你可以把 Riverpod 想象成 Provider 在几年内继续成熟时的样子。 + + +### 为什么要单独建包? + + +最初,计划发布 Provider 的主要版本,以解决上述问题。 +但随后决定反对它,因为由于新的 `ConsumerWidget` API,这将“太麻烦”甚至有争议。 +由于 Provider 仍然是最常用的 Flutter 包之一,因此决定创建一个单独的包,因此创建了 Riverpod。 + + +启用创建单独的包: + - 通过*同时*临时使用这两种方法,为任何想要迁移的人提供便利; + - 如果人们原则上不喜欢 Riverpod,或者他们觉得它还不可靠,请允许他们坚持使用 Provider; + - 实验,允许 Riverpod 搜索生产就绪的解决方案,以应对各种提供者程序的技术限制。 + + +事实上,Riverpod 旨在成为 Provider 的精神继承者。因此得名“Riverpod”(它是“Provider”的字谜,异位词)。 + + +### 破坏性变化 + + +Riverpod 唯一真正的缺点是它需要更改小部件类型才能工作: + + +- 使用 Riverpod,您应该扩展 `ConsumerWidget`,而不是扩展 `StatelessWidget`。 +- 使用 Riverpod,您应该扩展 `ConsumerStatefulWidget`,而不是扩展 `StatefulWidget`。 + + +但这种不便在宏伟的计划中是相当小的。有朝一日,这种要求可能会消失。 + + +### 选择正确的库 + + +您可能会问自己:*“那么,作为 Provider 用户,我应该使用 Provider 还是 Riverpod?”* + + +我们想非常清楚地回答这个问题: + + + 您可能应该使用 Riverpod + + +Riverpod 总体上设计得更好,可以大大简化您的逻辑。 + +[ref.watch]: /docs/concepts/reading#using-refwatch-to-observe-a-provider +[ref.listen]: /docs/concepts/reading#using-reflisten-to-react-to-a-provider-change +[autodispose]: /docs/concepts/modifiers/auto_dispose +[workaround]: https://pub.dev/packages/provider#can-i-obtain-two-different-providers-using-the-same-type +[.family modifier]: /docs/concepts/modifiers/family +[keepAlive]: /docs/concepts/modifiers/auto_dispose#refkeepalive +[as these two features go hand-in-hand]: /docs/concepts/modifiers/family#prefer-using-autodispose-when-the-parameter-is-not-constant +[simplification of logic]: /docs/concepts/modifiers/family#usage +[we have to]: https://github.com/flutter/flutter/issues/128432 +[it turns out]: https://github.com/flutter/flutter/issues/106549 +[can't react when a consumer stops listening to them]: https://github.com/flutter/flutter/issues/106546 +[Testing]: /docs/cookbooks/testing +[integrates well with Flutter]: /docs/concepts/reading#using-reflisten-to-react-to-a-provider-change + +[解决方法]: https://pub.dev/packages/provider#can-i-obtain-two-different-providers-using-the-same-type +[无法对消费者程序停止监听他们的情况做出反应]: https://github.com/flutter/flutter/issues/106546 +[我们必须]: https://github.com/flutter/flutter/issues/128432 +[简化逻辑]: /docs/concepts/modifiers/family#usage +[`.family` 修饰符]: /docs/concepts/modifiers/family +[因为这两个功能是齐头并进的]: /docs/concepts/modifiers/family#prefer-using-autodispose-when-the-parameter-is-not-constant +[事实证明]: https://github.com/flutter/flutter/issues/106549 +[测试]: /docs/cookbooks/testing +[与 Flutter 很好地集成在一起]: /docs/concepts/reading#using-reflisten-to-react-to-a-provider-change diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/override/index.tsx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/override/index.tsx new file mode 100644 index 000000000..43ec56b51 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/override/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./override.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/override/override.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/override/override.dart new file mode 100644 index 000000000..860020903 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/override/override.dart @@ -0,0 +1,16 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +import '../combine/combine.dart'; + +/* SNIPPET START */ + +void main() { + test('it doubles the value correctly', () async { + final container = ProviderContainer( + overrides: [numberProvider.overrideWith((ref) => 9)], + ); + final doubled = container.read(doubledProvider); + expect(doubled, 9 * 2); + }); +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/override/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/override/raw.dart new file mode 100644 index 000000000..4a7e2062f --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/override/raw.dart @@ -0,0 +1,15 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +import '../combine/combine.dart'; + +/* SNIPPET START */ +void main() { + test('这个值将正确的翻倍', () async { + final container = ProviderContainer( + overrides: [numberProvider.overrideWith((ref) => 9)], + ); + final doubled = container.read(doubledProvider); + expect(doubled, 9 * 2); + }); +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/same_type/index.tsx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/same_type/index.tsx new file mode 100644 index 000000000..8569e8316 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/same_type/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./same_type.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/same_type/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/same_type/raw.dart new file mode 100644 index 000000000..dacfe9b9d --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/same_type/raw.dart @@ -0,0 +1,15 @@ +import 'package:collection/collection.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../../helpers/item.dart'; + +/* SNIPPET START */ + +final itemsProvider = Provider.autoDispose( + (ref) => [], // ... +); + +final evenItemsProvider = Provider.autoDispose((ref) { + final items = ref.watch(itemsProvider); + return [...items.whereIndexed((index, element) => index.isEven)]; +}); diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/same_type/same_type.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/same_type/same_type.dart new file mode 100644 index 000000000..94a4ab086 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/same_type/same_type.dart @@ -0,0 +1,19 @@ +import 'package:collection/collection.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../../helpers/item.dart'; + +part 'same_type.g.dart'; + +/* SNIPPET START */ + +@riverpod +List items(ItemsRef ref) { + return []; // ... +} + +@riverpod +List evenItems(EvenItemsRef ref) { + final items = ref.watch(itemsProvider); + return [...items.whereIndexed((index, element) => index.isEven)]; +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/same_type/same_type.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/same_type/same_type.g.dart new file mode 100644 index 000000000..db84c17ad --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/same_type/same_type.g.dart @@ -0,0 +1,40 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'same_type.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$itemsHash() => r'f0a8fa6874f4868db9ead31e82c75d976f9d2033'; + +/// See also [items]. +@ProviderFor(items) +final itemsProvider = AutoDisposeProvider>.internal( + items, + name: r'itemsProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$itemsHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ItemsRef = AutoDisposeProviderRef>; +String _$evenItemsHash() => r'82b4525e91604745f2b4664531b32d4aff5717d4'; + +/// See also [evenItems]. +@ProviderFor(evenItems) +final evenItemsProvider = AutoDisposeProvider>.internal( + evenItems, + name: r'evenItemsProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$evenItemsHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef EvenItemsRef = AutoDisposeProviderRef>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/side_effects/index.tsx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/side_effects/index.tsx new file mode 100644 index 000000000..f4797a94f --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/side_effects/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./side_effects.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/side_effects/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/side_effects/raw.dart new file mode 100644 index 000000000..61f016870 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/side_effects/raw.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +import '../auto_dispose/auto_dispose.dart'; + +/* SNIPPET START */ + +class DiceRollWidget extends ConsumerWidget { + const DiceRollWidget({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + ref.listen(diceRollProvider, (previous, next) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Dice roll! We got: $next')), + ); + }); + return TextButton.icon( + onPressed: () => ref.invalidate(diceRollProvider), + icon: const Icon(Icons.casino), + label: const Text('Roll a dice'), + ); + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/side_effects/side_effects.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/side_effects/side_effects.dart new file mode 100644 index 000000000..61f016870 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/motivation/side_effects/side_effects.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +import '../auto_dispose/auto_dispose.dart'; + +/* SNIPPET START */ + +class DiceRollWidget extends ConsumerWidget { + const DiceRollWidget({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + ref.listen(diceRollProvider, (previous, next) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Dice roll! We got: $next')), + ); + }); + return TextButton.icon( + onPressed: () => ref.invalidate(diceRollProvider), + icon: const Icon(Icons.casino), + label: const Text('Roll a dice'), + ); + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/provider_vs_riverpod.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/provider_vs_riverpod.mdx new file mode 100644 index 000000000..5b940ff80 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/provider_vs_riverpod.mdx @@ -0,0 +1,653 @@ +--- +title: Provider 对比 Riverpod +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; +import CodeBlock from "@theme/CodeBlock"; +import family from "./family"; +import { + trimSnippet, + AutoSnippet, + When, +} from "@site/src/components/CodeSnippet"; + + + +本文将介绍 Provider 和 Riverpod 之间的差异和相似之处。 + + +## 定义提供者程序 + + +这两个包之间的主要区别在于如何定义“提供者程序”。 + + +对于 [Provider],提供者程序是小部件,因此放置在小部件树中,通常位于 `MultiProvider`: + +```dart +class Counter extends ChangeNotifier { + ... +} + +void main() { + runApp( + MultiProvider( + providers: [ + ChangeNotifierProvider(create: (context) => Counter()), + ], + child: MyApp(), + ) + ); +} +``` + + +使用 Riverpod,提供者程序**不是**小部件。相反,它们是普通的 Dart 对象。 +同样,提供者程序在小部件树之外定义,而且声明为全局 final 变量。 + + +此外,要使 Riverpod 正常工作,必须在整个应用程序上方添加一个小 `ProviderScope` 部件。 +因此,使用 Riverpod 和 Provider 示例等效的版本为: + +```dart +// provider 现在是顶级变量 +final counterProvider = ChangeNotifierProvider((ref) => Counter()); + +void main() { + runApp( + // 该小部件为整个项目启用了 Riverpod + ProviderScope( + child: MyApp(), + ), + ); +} +``` + + +请注意,这个 ChangeNotifierProvider 的定义只是向上移动了几行。 + +:::info + +由于 Riverpod 的提供者程序是普通的 Dart 对象,因此可以在没有 Flutter 的情况下使用 Riverpod。 +例如,Riverpod 可用于编写命令行应用程序。 +::: + + +## 读取提供者程序:使用 BuildContext + + +使用 Provider 库,读取提供者程序的一种方法是使用 Widget 的 `BuildContext`。 + + +例如,如果 provider 定义为: + +```dart +Provider(...); +``` + + +然后使用 [Provider] 读取它就可以这样做: + +```dart +class Example extends StatelessWidget { + @override + Widget build(BuildContext context) { + Model model = context.watch(); + + } +} +``` + + +在 Riverpod 中的等效代码是: + +```dart +final modelProvider = Provider(...); + +class Example extends ConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + Model model = ref.watch(modelProvider); + + } +} +``` + + +请注意: + + +- Riverpod 的代码片段是扩展 `ConsumerWidget` 的,而不是 `StatelessWidget`。 + 不同的小部件类型为我们的 `build` 函数添加了一个额外的参数:`WidgetRef`。 + + +- 在 Riverpod 中我们使用 `WidgetRef.watch` 代替 `BuildContext.watch`, + `WidgetRef` 是我们从 `ConsumerWidget` 拿到的。 + + +- Riverpod 不依赖于泛型类型。相反,它依赖于使用提供者程序定义创建的变量。 + + +还要注意措辞的相似程度。Provider 和 Riverpod 都使用关键字“watch”来描述“当值更改时,这里的小部件应重新生成”。 + +:::info + +Riverpod 使用与 Provider 相同的术语来读取提供者程序。 + +- `BuildContext.watch` -> `WidgetRef.watch` +- `BuildContext.read` -> `WidgetRef.read` +- `BuildContext.select` -> `WidgetRef.watch(myProvider.select)` + + +`context.watch` 相对于 `context.read` 的规则也适用于 Riverpod: +在 `build` 方法中,使用 “watch”。在单击处理程序和其他事件中,使用 “read”。 +当需要过滤掉值并重新生成时,请使用 “select”。 +::: + + +## 读取提供者程序:使用 Consumer + + +Provider 可以选择附带一个名为 `Consumer`(以及名为 `Consumer2` 的变体)的小部件,用于读取提供者程序。 + + +`Consumer` 作为性能优化很有帮助,它允许对小部件树进行更精细的重建 - 在状态更改时仅更新相关的小部件: + + +因此,如果一个 provider 被定义为: + +```dart +Provider(...); +``` + + +Provider 允许使用 `Consumer` 读取这个 provider: + +```dart +Consumer( + builder: (BuildContext context, Model model, Widget? child) { + + } +) +``` + + +Riverpod 也有同样的原理。Riverpod 也有一个以完全相同功能的 `Consumer` 小部件。 + + +如果我们将一个 provider 定义为: + +```dart +final modelProvider = Provider(...); +``` + + +然后我们可以使用 `Consumer` 实现: + +```dart +Consumer( + builder: (BuildContext context, WidgetRef ref, Widget? child) { + Model model = ref.watch(modelProvider); + + } +) +``` + + +注意 `Consumer` 是如何给我们一个 `WidgetRef` 对象。这与我们在上一部分中看到的 `ConsumerWidget` 与相关的对象相同。 + + +### Riverpod 中没有 `ConsumerN` 等效的类 + + +请注意,在 Riverpod 中不需要 pkg:Provider 的 `Consumer2`、`Consumer3` 等,也不要遗漏重构它们。 + + +使用 Riverpod,如果要从多个提供者程序读取值,只需编写多个 ref.watch 语句即可,如下所示: + +```dart +Consumer( + builder: (context, ref, child) { + Model1 model = ref.watch(model1Provider); + Model2 model = ref.watch(model2Provider); + Model3 model = ref.watch(model3Provider); + // ... + } +) +``` + + +与 pkg:Provider 的 `ConsumerN` API 相比,上述解决方案感觉不那么沉重,应该更容易理解。 + + +## 组合提供者程序:ProxyProvider 与无状态对象 + + +使用 Provider 时,组合提供者程序的官方方法是使用 `ProxyProvider` widget(或变体,例如 `ProxyProvider2`)。 + + +例如,我们可以定义: + +```dart +class UserIdNotifier extends ChangeNotifier { + String? userId; +} + +// ... + +ChangeNotifierProvider(create: (context) => UserIdNotifier()), +``` + + +在这里我们有两个选择。我们可以组合 `UserIdNotifier` 创建一个新的“无状态”提供者程序 +(通常是一个可能覆盖 == 的不可变值)。如: + +```dart +ProxyProvider( + update: (context, userIdNotifier, _) { + return 'The user ID of the the user is ${userIdNotifier.userId}'; + } +) +``` + + +每当 `UserIdNotifier.userId` 发生更改时,这个提供者程序都会自动返回新的 `String` 值。 + + +我们可以在 Riverpod 中做类似的事情,但语法不同。 +首先,在 Riverpod 中,我们的 `UserIdNotifier` 定义是: + +```dart +class UserIdNotifier extends ChangeNotifier { + String? userId; +} + +// ... + +final userIdNotifierProvider = ChangeNotifierProvider( + (ref) => UserIdNotifier(), +); +``` + + +那样的话,要基于 `userId` 生成 `String`,我们可以这样做: + +```dart +final labelProvider = Provider((ref) { + UserIdNotifier userIdNotifier = ref.watch(userIdNotifierProvider); + return 'The user ID of the the user is ${userIdNotifier.userId}'; +}); +``` + + +请注意这行 ref.watch(userIdNotifierProvider) 做的事。 + + +这行代码告诉 Riverpod 获取 `userIdNotifierProvider` 的内容, +并且每当该值发生变化时, `labelProvider` 也会重新计算。 +因此,每当 `userId` 更改时, +我们 `labelProvider` 发出的 `String` 都会自动更新。 + + +这行 `ref.watch` 应该感觉很熟悉。 +之前在解释[如何在小部件中读取提供者程序](#读取-providersbuildcontext)时已经介绍了这个模式。 +事实上,提供者程序现在能够与小部件以相同的方式监听其他提供者程序的改变。 + + +## 组合提供者程序:ProxyProvider 与有状态对象 + + +组合提供者程序时,另一个替代用例是公开有状态对象,例如 `ChangeNotifier` 实例。 + + +为此,我们可以使用 `ChangeNotifierProxyProvider`(或变体,例如 `ChangeNotifierProxyProvider2`)。 +例如,我们可以定义: + +```dart +class UserIdNotifier extends ChangeNotifier { + String? userId; +} + +// ... + +ChangeNotifierProvider(create: (context) => UserIdNotifier()), +``` + + +然后,我们可以定义基于 `UserIdNotifier.userId` 的一个 `ChangeNotifier`。 +例如,我们可以这样做: + +```dart +class UserNotifier extends ChangeNotifier { + String? _userId; + + void setUserId(String? userId) { + if (userId != _userId) { + print('The user ID changed from $_userId to $userId'); + _userId = userId; + } + } +} + +// ... + +ChangeNotifierProxyProvider( + create: (context) => UserNotifier(), + update: (context, userIdNotifier, userNotifier) { + return userNotifier! + ..setUserId(userIdNotifier.userId); + }, +); +``` + + +这个新提供者程序会创建一个 `UserNotifier` 实例(它永远不会重新构造), +并在用户 ID 更改时打印一个字符串。 + + +在提供者程序中执行相同的操作是以不同的方式实现的。 +首先,在 Riverpod 中,我们的 `UserIdNotifier` 是这样定义的: + +```dart +class UserIdNotifier extends ChangeNotifier { + String? userId; +} + +// ... + +final userIdNotifierProvider = ChangeNotifierProvider( + (ref) => UserIdNotifier(), +), +``` + + +相比于上面 `ChangeNotifierProxyProvider` 的等价代码将是: + +```dart +class UserNotifier extends ChangeNotifier { + String? _userId; + + void setUserId(String? userId) { + if (userId != _userId) { + print('The user ID changed from $_userId to $userId'); + _userId = userId; + } + } +} + +// ... + +final userNotifierProvider = ChangeNotifierProvider((ref) { + final userNotifier = UserNotifier(); + ref.listen( + userIdNotifierProvider, + (previous, next) { + if (previous?.userId != next.userId) { + userNotifier.setUserId(next.userId); + } + }, + ); + + return userNotifier; +}); +``` + + +这个片段的核心是 `ref.listen` 这行代码。 +这里的 `ref.listen` 函数是一个实用程序,它允许监听一个提供者程序, +并在提供者程序更改时执行函数。 + + +该函数的 `previous` 和 `next` 参数对应于提供者程序更改前的最后一个值和更改后的新值。 + + +## 作用域提供者程序与 `.family` + `.autoDispose` + + +在 pkg:Provider 中,作用域用于两件事: + - 离开页面时处置状态 + - 每页具有自定义状态 + + +仅使用作用域来破坏状态并不理想。 +问题在于,作用域在大型应用程序上效果不佳。 +例如,状态通常在一个页面中创建,但在导航后稍后在另一个页面中处置。 +这不允许多个缓存在不同的页面上处于活动状态。 + + +同样,如果需要与小部件树的另一部分共享状态, +“自定义每个页面状态”的方法很快就会变得难以处理, +就像你需要模态或多步骤表单一样。 + + +Riverpod 采取了不同的方法:首先,不鼓励使用作用域提供者; +其次, `.family` 和 `.autoDispose` 是完整的替代解决方案。 + + +在 Riverpod 中,当一个提供者程序标记为 `.autoDispose` 在不再使用时会自动处置的状态。 +当卸载最后一个删除提供者程序的小部件时,Riverpod 将检测到卸载并处置提供者程序。 +尝试在提供者程序中使用以下两种生命周期方法来测试此行为: + +```dart +ref.onCancel((){ + print("我一个监听程序都没有了!"); +}); +ref.onDispose((){ + print("如果我已经被定义为 `.autoDispose`,我将被处置!"); +}); +``` + + +这从本质上解决了“破坏状态”问题。 + + +此外,还可以将提供者程序标记为 `.family`(同时,也可以标记为 `.autoDispose`)。 +这样就可以将参数传递给提供者程序,从而在内部生成和跟踪多个提供者程序。 +换句话说,在传递参数时,*会为每个唯一参数创建一个唯一状态*。 + + + + + +这解决了“每页自定义状态”问题。实际上,还有另一个优点:这种状态不再绑定到一个特定的页面。 +相反,如果不同的页面尝试访问相同的状态,则该页面只需重用参数即可实现。 + + +在许多方面,将参数传递给提供者程序等同于 Map 的键。 +如果键相同,则获取的值相同。如果是不同的键,则将获得不同的状态。 + +[provider]: https://pub.dev/packages/provider +[ref.watch]: /docs/concepts/reading#using-refwatch-to-observe-a-provider +[ref.listen]: /docs/concepts/reading#using-reflisten-to-react-to-a-provider-change +[autodispose]: /docs/concepts/modifiers/auto_dispose +[workaround]: https://pub.dev/packages/provider#can-i-obtain-two-different-providers-using-the-same-type +[.family modifier]: /docs/concepts/modifiers/family +[keepAlive]: /docs/concepts/modifiers/auto_dispose#refkeepalive +[as these two features go hand-in-hand]: /docs/concepts/modifiers/family#prefer-using-autodispose-when-the-parameter-is-not-constant +[simplification of logic]: /docs/concepts/modifiers/family#usage +[we have to]: https://github.com/flutter/flutter/issues/128432 +[it turns out]: https://github.com/flutter/flutter/issues/106549 +[*can't* react when a consumer stops listening to them]: https://github.com/flutter/flutter/issues/106546 +[integrates well with Flutter]: /docs/concepts/reading#using-reflisten-to-react-to-a-provider-change +[ChangeNotifierProvider]: /docs/providers/change_notifier_provider +[Code generation]: /docs/about_code_generation +[AsyncNotifiers]: /docs/providers/notifier_provider +[combining Providers]: /docs/concepts/combining_providers +[global final variable]: /docs/concepts/providers#creating-a-provider diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/quickstart.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/quickstart.mdx new file mode 100644 index 000000000..d98512916 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/from_provider/quickstart.mdx @@ -0,0 +1,399 @@ +--- +title: 快速开始 +--- + +:::info +译者注:在此章节中,由于 Provider 库,Provider 类,Provider 及其相关的提供者程序类容易造成混淆,一般的,我们约定: +- Provider 库 -> `pkg:Provider` / `Provider`,和 Riverpod 一样首字母大写。 +- Provider 类 -> `provider`,小写,特指单纯的提供者程序类 Provider。 +- Provider 泛指 -> `提供者程序`,表示各种不同的提供者程序类都适用。 + +其他地方的代指某个 provider,会被具体翻译,避免不必要的误解,请放心阅读。 +如有翻译错误还请 pr 指正。 +::: + + +本部分专为熟悉 [Provider] 包并希望了解 Riverpod 的人员而设计。 + + +首先,请阅读简短的[入门指南]文章,并尝试使用小型[沙盒]示例来测试 Riverpod 的功能。 +如果您喜欢那里看到的内容,那么您应该明确考虑迁移。 + + +事实上,从 Provider 迁移到 Riverpod 可以非常简单。 + + +迁移基本上包括几个步骤,这些步骤可以以*增量*方式完成。 + + +## 从 `ChangeNotifierProvider` 开始 + + +在过渡到 Riverpod 时是可以的继续使用,而不是尽快使用 `ChangeNotifier` 其最新的花哨功能。 + + +事实上,可以从以下几点着手开始: + +```dart +// 如果你有这个…… +class MyNotifier extends ChangeNotifier { + int state = 0; + + void increment() { + state++; + notifyListeners(); + } +} + +// ……只需要加上这个! +final myNotifierProvider = ChangeNotifierProvider((ref) { + return MyNotifier(); +}); +``` + + +正如你所看到的,Riverpod 公开了一个 [ChangeNotifierProvider] 类, +该类正是为了支持从 pkg:Provider 迁移。 + + +请记住,在编写新代码时不建议使用 `ChangeNotifierProvider`,因为它不是使用 Riverpod 的最佳方式, +但它是开始迁移的一种温和且非常简单的方法。 + +:::tip + +不要急于*立即*尝试将您的 `ChangeNotifier` 迁移到更现代的 [Riverpod 的提供者程序]。 +有些地方会进行一些泛型的转变,因此最初可能很难做到。 + + +慢慢来,因为首先熟悉 Riverpod 很重要; +你很快就会发现,几乎所有来自 pkg:provider 的提供者程序在 pkg:riverpod 中都有严格等价的代码。 +::: + + +## 从*叶子*开始 + + +从不依赖于其他任何内容的提供者程序开始,即从依赖项树中的*叶子*开始。 +迁移完所有叶子后,可以转到依赖于叶子的提供者程序。 + + +换言之,首先要避免迁移 `ProxyProvider`;当迁移了它们的所有依赖项,就可以开始处理它们了。 + + +这应该促进和简化迁移过程,同时还可以最大限度地减少/跟踪任何错误。 + + + +## Riverpod 和 Provider 可以共存 + + + +*请记住,完全可以同时使用 Provider 和 Riverpod。* + + +事实上,使用导入别名,可以同时使用两个 API 了。 +这也非常有助于提高可用性,并消除了任何模棱两可的 API 用法。 + + +如果计划执行此操作,请考虑对代码库中的每个提供者程序导入使用导入别名。 + +:::info + +关于如何有效实现导入别名的完整指南即将发布。 +::: + + + +## 您*不必*立即使用 `Consumer` + + +请务必记住,无需*立即*使用 [Riverpod 的 `Consumer` API]。 +如果您刚刚开始迁移,[如上所述],您可能应该从 `ChangeNotifierProvider` 开始。 + + +考虑上面定义的 `myNotifierProvider`。 + + +由于您的内部代码可能依赖于 pkg:Provider 的 API,因此请使用以下代码开始使用 pkg:Riverpod 的 `ChangeNotifier`。 + +```dart +MultiProvider( + providers: [ + ChangeNotifierProvider.value(value: ref.watch(myNotifierProvider.notifier)), + ] +) +``` + + +这样,最初只需将根 Widget 转换为 `ConsumerWidget`。 +这应该会进一步简化向 pkg:Riverpod 的迁移。 + + + +## 一次迁移一个提供者程序 + + +如果您已有应用,请不要尝试一次迁移所有的提供者程序! + + +虽然从长远来看,您应该努力将所有应用程序迁移到 Riverpod,**但不要让自己筋疲力尽**。 +一次只迁移一个提供者程序。 + + +以上面的例子为例。将其 `myNotifierProvider` **完全**迁移到 Riverpod 意味着需要编写以下内容: + +```dart +class MyNotifier extends Notifier { + @override + int build() => 0; + + void increment() => state++; +} + +final myNotifierProvider = NotifierProvider(MyNotifier.new); +``` + + +……并且_还_需要更改这个 `myNotifierProvider` 的使用方式,即每个 `context.watch` 的位置替换为 `ref.watch`。 + + +此操作可能需要一些时间,并可能导致一些错误,因此不要急于一次完成所有操作。 + + +## 迁移 `ProxyProvider` + + +在 pkg:Provider 中,`ProxyProvider` 用于组合来自其他提供者程序的值; +它的构建被动地取决于其他提供者程序的价值。 + + +相反,在 Riverpod 中,提供者程序[默认是可组合的]; +因此,在迁移 `ProxyProvider` 时,如果要声明从一个提供者程序到另一个提供者程序的直接依赖项,则只需编写 `ref.watch` 即可。 + + +如果有的话,将值与 Riverpod 相结合应该感觉更简单明了;因此,迁移应该大大简化代码。 + + +此外,将两个以上的提供者程序组合在一起没有任何障碍:只需添加另一个 `ref.watch`,就可以了。 + + +## 预先初始化 + + +由于 Riverpod 的提供者程序是全局 final 变量,因此它们[默认是懒加载的]。 + + +如果您需要在启动时初始化一些预热数据或有用的服务, +最好的做法是首次读取提供者程序的时候,您手动去设置 `MultiProvider`。 + + +换句话说,由于 Riverpod 不能被强制预先初始化,因此可以在启动阶段读取和缓存它们, +以便在应用程序的其余部分需要时它们完成了初始化并准备就绪。 + + +有关 pkg:Riverpod 提供者程序的快速初始化的完整指南 [可在此处获得]。 + + +## 代码生成 + + +建议使用[代码生成],以便以*面向未来*的方式使用 Riverpod。 +顺便说一句,当元编程成为现实的时候,代码生成很可能是 Riverpod 的默认代码。 + + +不幸的是, `@riverpod` 无法为 `ChangeNotifierProvider` 生成代码。 +要解决此问题,您可以使用以下实用程序扩展方法: + +```dart +extension ChangeNotifierWithCodeGenExtension on Ref { + T listenAndDisposeChangeNotifier(T notifier) { + notifier.addListener(notifyListeners); + onDispose(() => notifier.removeListener(notifyListeners)); + onDispose(notifier.dispose); + return notifier; + } +} +``` + + +然后,您可以使用以下代码生成语法公开您的 `ChangeNotifier`: + +```dart +// ignore_for_file: unsupported_provider_value +@riverpod +MyNotifier example(ExampleRef ref) { + return ref.listenAndDisposeChangeNotifier(MyNotifier()); +} +``` + + +完成“基本”迁移后,您可以将 `ChangeNotifier` 更改为 `Notifier`,从而消除了临时扩展的需要。 +以前面的示例为例,“完全迁移” `Notifier` 变为: + +```dart +@riverpod +class MyNotifier extends _$MyNotifier { + @override + int build() => 0; + + void increment() => state++; +} +``` + + +一旦完成此操作,并且您确信代码库中不再有 `ChangeNotifierProvider`,您就可以彻底摆脱临时扩展。 + + +请记住,虽然是推荐的,但代码生成不是*强制性的*。 +以增量方式进行迁移是个好的选择: +如果您觉得在一次转换到代码生成语法的*同时*实现此迁移可能太麻烦了,*别勉强*。 + + +按照本指南,您*可以*稍后做进一步的迁移到代码生成。 + +[getting started]: /docs/introduction/getting_started +[sandbox]: https://dartpad.dev/?null_safety=true&id=ef06ab3ce0b822e6cc5db0575248e6e2 +[provider]: https://pub.dev/packages/provider +[ChangeNotifierProvider]: /docs/providers/change_notifier_provider +[Code generation]: /docs/concepts/about_code_generation +[Riverpod's providers]: /docs/providers/notifier_provider +[are composable by default]: /docs/from_provider/motivation#combining-providers-is-hard-and-error-prone +[as mentioned above]: /docs/from_provider/quickstart#start-with-changenotifierprovider +[Riverpod's `Consumer` APIs]: /docs/concepts/reading +[lazy by default]: /docs/concepts/provider_lifecycles + +[入门指南]: /docs/introduction/getting_started +[沙盒]: https://dartpad.dev/?null_safety=true&id=ef06ab3ce0b822e6cc5db0575248e6e2 + +[默认是可组合的]: /docs/from_provider/motivation#combining-providers-is-hard-and-error-prone +[如上所述]: /docs/from_provider/quickstart#从-changenotifierprovider-开始 +[Riverpod 的 `Consumer` API]: /docs/concepts/reading +[默认是懒加载的]: /docs/concepts/provider_lifecycles +[代码生成]: /docs/concepts/about_code_generation +[Riverpod 的提供者程序]: /docs/providers/notifier_provider \ No newline at end of file diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/getting_started.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/getting_started.mdx deleted file mode 100644 index 9287f10af..000000000 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/getting_started.mdx +++ /dev/null @@ -1,189 +0,0 @@ ---- -title: 开始上手 -version: 3 ---- - -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; -import CodeBlock from "@theme/CodeBlock"; -import pubspec from "./getting_started/pubspec"; -import dartHelloWorld from "./getting_started/dart_hello_world"; -import pubadd from "./getting_started/pub_add"; -import helloWorld from "./getting_started/hello_world"; -import dartPubspec from "./getting_started/dart_pubspec"; -import dartPubadd from "./getting_started/dart_pub_add"; -import { - trimSnippet, - AutoSnippet, - When, -} from "../../../../src/components/CodeSnippet"; - ---- - -## 在线尝试 Riverpod - -在 [Dartpad](https://dartpad.dev/?null_safety=true&id=ef06ab3ce0b822e6cc5db0575248e6e2) 上感受 Riverpod 的魅力。 - -## 安装包文件 - -当你确定好你想要安装的包文件后,像这样继续将依赖添加到你的应用中: - - - - - - - - - - - - - - - - -当然,你也可以手动将依赖添加到应用中的`pubspec.yaml`: - - - - - - -然后使用 `flutter pub get` 命令来安装包文件。 - - - 最后,运行代码生成器 {" "} - flutter pub run build_runner watch - - - - - - - -然后使用 `dart pub get` 命令来安装包文件。 - - - 最后,运行代码生成器 {" "} - flutter pub run build_runner watch - - - - - -就是这样,你已成功将 [Riverpod] 添加到你的应用当中! - -## 启用 riverpod_lint/custom_lint - -Riverpod 附带一个可选的 [riverpod_lint] 包, -它提供了lint规则来帮助你编写更好的代码且提供了一些自定义重构选项。 - -如果遵循了上一步骤,那你应该已经安装好这个包了,但需要额外的步骤来启用它。 - -为了开启 [riverpod_lint],你需要向你的 `pubspc.yaml` 旁边新建一个 `analysis_options.yaml` 配置 -并包含下面的内容: - - - {`analyzer: - plugins: - - custom_lint`} - - -如果你使用riverpod并在代码中写错了,那么就会在IDE中看到一些警告信息。 - -前往 [riverpod_lint] 页面查看更多关于警告信息和重构的内容。 - -:::note -用 `dart analyze` 命令这些警告不会显示。 -如果你想在CI或者终端中检查这些警告信息,你可以运行: - -```sh -dart run custom_lint -``` - -::: - -## 使用示例: Hello world - -安装完 [Riverpod],我们就可以使用了。 - -下面的代码片段展示了如何使用我们的新依赖来创建一个 "Hello world": - -export const foo = 42; - - - - - - -你可以使用 `flutter run` 命令来运行。 -这会在你的设备上渲染 "Hello world" 的文字。 - - - - - - -你可以使用 `dart lib/main.dart` 命令来运行。 -这会在你的控制台打印 "Hello world" 的字样。 - - - - -## 更进一步:安装代码片段插件 - -如果你使用 `Flutter` 和 `VS Code`,推荐使用 [Flutter Riverpod Snippets](https://marketplace.visualstudio.com/items?itemName=robert-brunhage.flutter-riverpod-snippets) - -如果你使用 `Flutter` 和 `Android Studio` 或 `IntelliJ`,推荐使用 [Flutter Riverpod Snippets](https://plugins.jetbrains.com/plugin/14641-flutter-riverpod-snippets) - -![img](/img/snippets/greetingProvider.gif) - -## 选择你的下一步 - -学习一些基础的概念: - -- [了解更多关于 provider 的内容](/docs/concepts/providers) - -遵循专题手册: - -- [如何测试 provider](/docs/cookbooks/testing) - -[riverpod]: https://github.com/rrousselgit/riverpod -[hooks_riverpod]: https://pub.dev/packages/hooks_riverpod -[flutter_riverpod]: https://pub.dev/packages/flutter_riverpod -[flutter_hooks]: https://github.com/rrousselGit/flutter_hooks -[riverpod_lint]: https://pub.dev/packages/riverpod_lint diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/getting_started/dart_pub_add.tsx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/getting_started/dart_pub_add.tsx deleted file mode 100644 index 73bf922c8..000000000 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/getting_started/dart_pub_add.tsx +++ /dev/null @@ -1,10 +0,0 @@ -const raw = "dart pub add riverpod dev:custom_lint dev:riverpod_lint"; - -const codegen = "dart pub add riverpod dev:custom_lint dev:riverpod_lint riverpod_annotation dev:build_runner dev:riverpod_generator"; - -export default { - raw, - hooks: raw, - codegen, - hooksCodegen: codegen, -}; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/getting_started/pub_add.tsx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/getting_started/pub_add.tsx deleted file mode 100644 index e72171523..000000000 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/getting_started/pub_add.tsx +++ /dev/null @@ -1,14 +0,0 @@ -const raw = "flutter pub add flutter_riverpod dev:custom_lint dev:riverpod_lint"; - -const codegen = "flutter pub add flutter_riverpod dev:custom_lint dev:riverpod_lint riverpod_annotation dev:build_runner dev:riverpod_generator"; - -const hooks = "flutter pub add hooks_riverpod dev:custom_lint dev:riverpod_lint"; - -const hooksCodegen = "flutter pub add hooks_riverpod dev:custom_lint dev:riverpod_lint riverpod_annotation dev:build_runner dev:riverpod_generator"; - -export default { - raw: raw, - hooks: hooks, - codegen: codegen, - hooksCodegen: hooksCodegen -}; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction.mdx deleted file mode 100644 index c0d1c81dd..000000000 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction.mdx +++ /dev/null @@ -1,65 +0,0 @@ ---- -title: 介绍 ---- - ---- - -## 什么是 Riverpod? - -Riverpod ([Provider](https://pub.dev/packages/provider) 的回文) 是 Flutter / Dart 平台的一个响应式缓存框架。它能自动获取、缓存、合并和计算网络请求,同时还会为你处理错误。 - -## 动机 - -现代的应用程序很少带有用户界面所需要的所有信息。一般来说,那些数据会从服务器异步获取。 - -但问题是,处理异步代码是比较困难的。虽然说 Flutter 本身提供了一些存储状态的方法,但除此之外, -它并没有其他额外的功能。因此,有许多问题仍然没有解决方案: - -- 异步请求需要在本地缓存,因为只要 UI 刷新就重新执行请求的做法是不合理的。 -- 由于缓存的存在,如果我们不小心可能会导致缓存过期。 -- 我们也需要处理错误和加载中的各种状态。 - -大规模解决问题会比较困难,而且它们受到了许多功能的影响,比如: - -- 下拉刷新 -- 列表滚动加载 -- 在键入时搜索 -- 异步请求防抖 -- 当不再需要时取消异步请求 -- 积极的界面更新 -- 离线模式 -- ... - -虽然实现这些功能很棘手,但这对一个良好的用户体验至关重要。 -然而很少有其他包文件试图直接解决这些问题,而且大量的样板代码需要手工完成。 - -这就是 Riverpod 的由来。 -受 Flutter 组件的启发,Riverpod 尝试通过一种独特的方式来写业务逻辑,进而来解决这些问题。 -对于状态来说,在许多方面 Riverpod 都可以类比于组件。 - -采用这种新的方式,上述复杂的功能大多都已默认被实现。你要做的只不过是关注 UI 实现。 - -不信?这里有一个例子。下面的代码片段是使用 Riverpod 的一个简化的 [Pub.dev](https://github.com/rrousselGit/riverpod/tree/master/examples/pub) -客户端应用。 - -```dart -// 从 pub.dev 获取包文件列表 -@riverpod -Future> fetchPackages( - FetchPackagesRef ref, { - required int page, - String search = '', -}) async { - final dio = Dio(); - // 请求 API。这里我们用了 dio,当然你也可以使用任何其他的网络请求库。 - final response = await dio.get( - 'https://pub.dartlang.org/api/search?page=$page&q=${Uri.encodeQueryComponent(search)}', - ); - - // 将返回的 JSON 解码到 Dart 的对象。 - final json = response.data as List; - return json.map(Package.fromJson).toList(); -} -``` - -这段代码就是你要实现 “在键入时搜索” + “下拉刷新” + “无限列表” 功能所需的全部业务逻辑,同时还可以处理错误及加载中的状态。 diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/getting_started.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/getting_started.mdx new file mode 100644 index 000000000..42aadaa34 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/getting_started.mdx @@ -0,0 +1,296 @@ +--- +title: 入门指南 +pagination_next: essentials/first_request +version: 4 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; +import CodeBlock from "@theme/CodeBlock"; +import pubspec from "./getting_started/pubspec"; +import dartHelloWorld from "./getting_started/dart_hello_world"; +import pubadd from "./getting_started/pub_add"; +import helloWorld from "./getting_started/hello_world"; +import dartPubspec from "./getting_started/dart_pubspec"; +import dartPubadd from "./getting_started/dart_pub_add"; +import { + AutoSnippet, + When, +} from "@site/src/components/CodeSnippet"; +import { Link } from "@site/src/components/Link"; + + +## 在线尝试 Riverpod + + +要尝试 Riverpod,请在 [Dartpad](https://dartpad.dev/?null_safety=true&id=ef06ab3ce0b822e6cc5db0575248e6e2) +或 [Zapp](https://zapp.run/new) 上在线试用: + + + + +## 安装包 + + +知道要安装哪个包后,请继续在一行中将依赖项添加到应用,如下所示: + + + + + + + + + + + + + + + + + +或者,您可以从以下 pubspec.yaml 位置手动将依赖项添加到您的应用: + + + + + + + +然后,使用 `flutter pub get` 安装包。 + + + + 现在你可以运行代码生成工具 {" "} + dart run build_runner watch. + + + + + + + + +然后,使用 `dart pub get` 安装包。 + + + + 现在你可以运行代码生成工具 {" "} + dart run build_runner watch. + + + + + + +就是这样。你已将 [Riverpod] 添加到你的应用中! + + +## 启用 riverpod_lint/custom_lint + + +Riverpod 附带一个可选的 riverpod_lint 包, +该包提供 lint 规则以帮助您编写更好的代码, +并提供自定义重构选项。 + + +如果按照前面的步骤操作,则应该已经安装了该包, +但需要单独的步骤才能启用它。 + + +要启用riverpod_lint,您需要在 `pubspec.yaml` 文件的旁边添加一个 +`analysis_options.yaml` 文件,并包含以下内容: + + + {`analyzer: + plugins: + - custom_lint`} + + + +现在,如果在代码库中使用 Riverpod 时出错,您应该会在 IDE 中看到警告。 + + +要查看警告和重构的完整列表,请前往 [riverpod_lint] 页面。 + + +:::note +这些警告不会显示在 `dart analyze` 命令的结果中。 +如果要在 CI 或终端中检查这些警告,可以运行以下命令: + +```sh +dart run custom_lint +``` + +::: + + +## 使用示例:Hello world + + +现在我们已经安装了 [Riverpod],我们可以开始使用它了。 + + +以下代码片段展示了如何使用我们的新依赖项来创建 “Hello world”: + +export const foo = 42; + + + + + + + +然后,使用 `flutter run` 启动应用程序。 +这将在您的设备上呈现 “Hello world”。 + + + + + + + +然后,使用 `dart lib/main.dart` 启动应用程序。 +这将在控制台中打印 “Hello world”。 + + + + + +## 更进一步:安装代码片段 + + +如果你使用的 `Flutter` 是 `VS Code` ,请考虑使用 [Flutter Riverpod Snippets](https://marketplace.visualstudio.com/items?itemName=robert-brunhage.flutter-riverpod-snippets) + + +如果您使用的 `Flutter` 是 `Android Studio` 或 `IntelliJ` ,请考虑使用 [Flutter Riverpod Snippets](https://plugins.jetbrains.com/plugin/14641-flutter-riverpod-snippets) + +![img](/img/snippets/greetingProvider.gif) + + +## 选择你的下一步 + + +了解一些基本概念: + +- + + +跟着教程走: + +- + +[riverpod]: https://github.com/rrousselgit/riverpod +[hooks_riverpod]: https://pub.dev/packages/hooks_riverpod +[flutter_riverpod]: https://pub.dev/packages/flutter_riverpod +[flutter_hooks]: https://github.com/rrousselGit/flutter_hooks +[riverpod_lint]: https://pub.dev/packages/riverpod_lint diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/getting_started/dart_hello_world/index.tsx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/getting_started/dart_hello_world/index.tsx similarity index 100% rename from website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/getting_started/dart_hello_world/index.tsx rename to website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/getting_started/dart_hello_world/index.tsx diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/getting_started/dart_hello_world/main.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/getting_started/dart_hello_world/main.dart new file mode 100644 index 000000000..87b2e0cd8 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/getting_started/dart_hello_world/main.dart @@ -0,0 +1,25 @@ +// ignore_for_file: avoid_print + +/* SNIPPET START */ + +import 'package:riverpod/riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'main.g.dart'; + +// 我们创建了一个 "provider",它可以存储一个值(这里是 "Hello world")。 +// 通过使用提供者程序,这可以允许我们模拟或者覆盖一个暴露的值。 +@riverpod +String helloWorld(HelloWorldRef ref) { + return 'Hello world'; +} + +void main() { + // 这个对象是我们的提供者程序的状态将被存储的地方。 + final container = ProviderContainer(); + + // 多亏有了 "container",我们可以读取我们的提供者程序。 + final value = container.read(helloWorldProvider); + + print(value); // Hello world +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/getting_started/hello_world/main.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/getting_started/dart_hello_world/main.g.dart similarity index 100% rename from website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/getting_started/hello_world/main.g.dart rename to website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/getting_started/dart_hello_world/main.g.dart diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/getting_started/dart_hello_world/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/getting_started/dart_hello_world/raw.dart new file mode 100644 index 000000000..4efa17abd --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/getting_started/dart_hello_world/raw.dart @@ -0,0 +1,19 @@ +// ignore_for_file: avoid_print + +/* SNIPPET START */ + +import 'package:riverpod/riverpod.dart'; + +// 我们创建了一个 "provider",它可以存储一个值(这里是 "Hello world")。 +// 通过使用提供者程序,这可以允许我们模拟或者覆盖一个暴露的值。 +final helloWorldProvider = Provider((_) => 'Hello world'); + +void main() { + // 这个对象是我们的提供者程序的状态将被存储的地方。 + final container = ProviderContainer(); + + // 多亏有了 "container",我们可以读取我们的提供者程序。 + final value = container.read(helloWorldProvider); + + print(value); // Hello world +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/getting_started/dart_pub_add.tsx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/getting_started/dart_pub_add.tsx new file mode 100644 index 000000000..07c03cf6b --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/getting_started/dart_pub_add.tsx @@ -0,0 +1,32 @@ +export function buildDeps({ + deps = [], + devDeps = [], +}: { + deps?: string[]; + devDeps?: string[]; +}) { + var result = ""; + for (const dep of deps) { + result += `dart pub add ${dep}\n`; + } + + for (const dep of [...devDeps, "custom_lint", "riverpod_lint"]) { + result += `dart pub add dev:${dep}\n`; + } + + return result; +} + +const raw = buildDeps({ deps: ["riverpod"] }); + +const codegen = buildDeps({ + deps: ["riverpod", "riverpod_annotation"], + devDeps: ["riverpod_generator", "build_runner"], +}); + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/getting_started/dart_pubspec.tsx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/getting_started/dart_pubspec.tsx new file mode 100644 index 000000000..7860548a1 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/getting_started/dart_pubspec.tsx @@ -0,0 +1,40 @@ +import { + riverpodVersion, + riverpodAnnotationVersion, + riverpodGeneratorVersion, + riverpodLintVersion, +} from "@site/src/versions"; + +const codegen = `name: my_app_name +environment: + sdk: ">=3.0.0 <4.0.0" + +dependencies: + riverpod: ^${riverpodVersion} + riverpod_annotation: ^${riverpodAnnotationVersion} + +dev_dependencies: + build_runner: + custom_lint: + riverpod_generator: ^${riverpodGeneratorVersion} + riverpod_lint: ^${riverpodLintVersion} +`; + +const raw = `name: my_app_name +environment: + sdk: ">=3.0.0 <4.0.0" + +dependencies: + riverpod: ^${riverpodVersion} + +dev_dependencies: + custom_lint: + riverpod_lint: ^${riverpodLintVersion} +`; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/hooks_codegen/main.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/hooks_codegen/main.dart new file mode 100644 index 000000000..d605ea202 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/hooks_codegen/main.dart @@ -0,0 +1,46 @@ +// ignore_for_file: use_key_in_widget_constructors, omit_local_variable_types + +/* SNIPPET START */ import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'main.g.dart'; + +// 我们创建了一个 "provider",它可以存储一个值(这里是 "Hello world")。 +// 通过使用提供者程序,这可以允许我们模拟或者覆盖一个暴露的值。 +@riverpod +String helloWorld(HelloWorldRef ref) { + return 'Hello world'; +} + +void main() { + runApp( + // 为了使小组件可以读取提供者程序, + // 我们需要将整个应用程序包装在“ProviderScope”小部件中。 + // 这是我们的提供者程序的状态将被存储的地方。 + ProviderScope( + child: MyApp(), + ), + ); +} + +// 继承父类使用 HookConsumerWidget 替代 HookWidget,这样可以获取到提供者程序的引用 +class MyApp extends HookConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + // 我们可以在 HookConsumerWidget 中使用钩子函数 + final counter = useState(0); + + final String value = ref.watch(helloWorldProvider); + + return MaterialApp( + home: Scaffold( + appBar: AppBar(title: const Text('Example')), + body: Center( + child: Text('$value ${counter.value}'), + ), + ), + ); + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/getting_started/hello_world/main_hooks.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/hooks_codegen/main.g.dart similarity index 97% rename from website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/getting_started/hello_world/main_hooks.g.dart rename to website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/hooks_codegen/main.g.dart index e44b1758b..a7cc420dc 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/getting_started/hello_world/main_hooks.g.dart +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/hooks_codegen/main.g.dart @@ -2,7 +2,7 @@ // ignore_for_file: non_constant_identifier_names -part of 'main_hooks.dart'; +part of 'main.dart'; // ************************************************************************** // RiverpodGenerator diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/index.tsx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/index.tsx new file mode 100644 index 000000000..3f10c2a94 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/index.tsx @@ -0,0 +1,11 @@ +import raw from "!!raw-loader!./raw.dart"; +import raw_hooks from "!!raw-loader!./raw_hooks.dart"; +import codegen from "!!raw-loader!./main.dart"; +import hooksCodegen from "!!raw-loader!./hooks_codegen/main.dart"; + +export default { + raw, + hooks: raw_hooks, + codegen, + hooksCodegen: hooksCodegen, +}; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/main.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/main.dart new file mode 100644 index 000000000..6ee5a4bdf --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/main.dart @@ -0,0 +1,42 @@ +// ignore_for_file: use_key_in_widget_constructors, omit_local_variable_types + +/* SNIPPET START */ import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'main.g.dart'; + +// 我们创建了一个 "provider",它可以存储一个值(这里是 "Hello world")。 +// 通过使用提供者程序,这可以允许我们模拟或者覆盖一个暴露的值。 +@riverpod +String helloWorld(HelloWorldRef ref) { + return 'Hello world'; +} + +void main() { + runApp( + // 为了使小组件可以读取提供者程序, + // 我们需要将整个应用程序包装在“ProviderScope”小部件中。 + // 这是我们的提供者程序的状态将被存储的地方。 + ProviderScope( + child: MyApp(), + ), + ); +} + +// 继承父类使用 ConsumerWidget 替代 StatelessWidget,这样可以获取到提供者程序的引用 +class MyApp extends ConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + final String value = ref.watch(helloWorldProvider); + + return MaterialApp( + home: Scaffold( + appBar: AppBar(title: const Text('Example')), + body: Center( + child: Text(value), + ), + ), + ); + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/main.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/main.g.dart new file mode 100644 index 000000000..180bedb23 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/main.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'main.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$helloWorldHash() => r'8bbe6cff2b7b1f4e1f7be3d1820da793259f7bfc'; + +/// See also [helloWorld]. +@ProviderFor(helloWorld) +final helloWorldProvider = AutoDisposeProvider.internal( + helloWorld, + name: r'helloWorldProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$helloWorldHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef HelloWorldRef = AutoDisposeProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/ko/docusaurus-plugin-content-docs/current/getting_started/hello_world/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/raw.dart similarity index 52% rename from website/i18n/ko/docusaurus-plugin-content-docs/current/getting_started/hello_world/raw.dart rename to website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/raw.dart index 307a104e5..7ad09874f 100644 --- a/website/i18n/ko/docusaurus-plugin-content-docs/current/getting_started/hello_world/raw.dart +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/raw.dart @@ -1,26 +1,24 @@ // ignore_for_file: use_key_in_widget_constructors, omit_local_variable_types -/* SNIPPET START */ - -import 'package:flutter/material.dart'; +/* SNIPPET START */ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -// 우리는 값을 저장할 "provider"를 만들겁니다(여기서 값은 "Hello world"를 의미합니다). -// 프로바이더를 사용하는 것으로 값의 mock/override가 가능하게 됩니다. +// 我们创建了一个 "provider",它可以存储一个值(这里是 "Hello world")。 +// 通过使用提供者程序,这可以允许我们模拟或者覆盖一个暴露的值。 final helloWorldProvider = Provider((_) => 'Hello world'); void main() { runApp( - // 위젯에서 프로바이더를 사용하고 읽기위해 - // 앱 전체적으로 "ProviderScope" 위젯을 감싸줘야 합니다. - // 여기에 프로바이더의 상태가 저장됩니다. + // 为了使小组件可以读取提供者程序, + // 我们需要将整个应用程序包装在“ProviderScope”小部件中。 + // 这是我们的提供者程序的状态将被存储的地方。 ProviderScope( child: MyApp(), ), ); } -// StatelessWidget 대신에 Riverpod의 ConsumerWidget을 상속받아 사용합니다. +// 继承父类使用 ConsumerWidget 替代 StatelessWidget,这样可以获取到提供者程序的引用 class MyApp extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/raw_hooks.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/raw_hooks.dart new file mode 100644 index 000000000..d2d085319 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/getting_started/hello_world/raw_hooks.dart @@ -0,0 +1,40 @@ +// ignore_for_file: use_key_in_widget_constructors, omit_local_variable_types + +/* SNIPPET START */ import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +// 我们创建了一个 "provider",它可以存储一个值(这里是 "Hello world")。 +// 通过使用提供者程序,这可以允许我们模拟或者覆盖一个暴露的值。 +final helloWorldProvider = Provider((_) => 'Hello world'); + +void main() { + runApp( + // 为了使小组件可以读取提供者程序, + // 我们需要将整个应用程序包装在“ProviderScope”小部件中。 + // 这是我们的提供者程序的状态将被存储的地方。 + ProviderScope( + child: MyApp(), + ), + ); +} + +// 继承父类使用 HookConsumerWidget 替代 HookWidget,这样可以获取到提供者程序的引用 +class MyApp extends HookConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + // 我们可以在 HookConsumerWidget 中使用钩子函数 + final counter = useState(0); + + final String value = ref.watch(helloWorldProvider); + + return MaterialApp( + home: Scaffold( + appBar: AppBar(title: const Text('Example')), + body: Center( + child: Text('$value ${counter.value}'), + ), + ), + ); + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/getting_started/pub_add.tsx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/getting_started/pub_add.tsx new file mode 100644 index 000000000..65c7be1bd --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/getting_started/pub_add.tsx @@ -0,0 +1,39 @@ +export function buildDeps({ + deps = [], + devDeps = [], +}: { + deps?: string[]; + devDeps?: string[]; +}) { + var result = ''; + for (const dep of deps) { + result += `flutter pub add ${dep}\n`; + } + + for (const dep of [...devDeps, "custom_lint", "riverpod_lint"]) { + result += `flutter pub add dev:${dep}\n`; + } + + return result; +} + +const raw = buildDeps({ deps: ["flutter_riverpod"] }); + +const codegen = buildDeps({ + deps: ["flutter_riverpod", "riverpod_annotation"], + devDeps: ["riverpod_generator", "build_runner"], +}); + +const hooks = buildDeps({ deps: ["hooks_riverpod", "flutter_hooks"] }); + +const hooksCodegen = buildDeps({ + deps: ["hooks_riverpod", "flutter_hooks", "riverpod_annotation"], + devDeps: ["riverpod_generator", "build_runner"], +}); + +export default { + raw: raw, + hooks: hooks, + codegen: codegen, + hooksCodegen: hooksCodegen, +}; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/getting_started/pubspec.tsx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/getting_started/pubspec.tsx similarity index 79% rename from website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/getting_started/pubspec.tsx rename to website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/getting_started/pubspec.tsx index 0f182e500..151a203a6 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/getting_started/pubspec.tsx +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/getting_started/pubspec.tsx @@ -4,13 +4,12 @@ import { riverpodAnnotationVersion, riverpodGeneratorVersion, riverpodLintVersion, -} from "../../../../../src/versions"; +} from "@site/src/versions"; function plain(riverpod: string) { - return ` -name: my_app_name + return `name: my_app_name environment: - sdk: ">=2.17.0 <3.0.0" + sdk: ">=3.0.0 <4.0.0" flutter: ">=3.0.0" dependencies: @@ -25,10 +24,9 @@ dev_dependencies: } function codegen(riverpod: string) { - return ` -name: my_app_name + return `name: my_app_name environment: - sdk: ">=2.17.0 <3.0.0" + sdk: ">=3.0.0 <4.0.0" flutter: ">=3.0.0" dependencies: @@ -47,7 +45,7 @@ dev_dependencies: export default { raw: plain(`flutter_riverpod: ^${flutterRiverpodVersion}`), - hooks: plain(`hooks_riverpod: ^${hooksRiverpodVersion}`), + hooks: plain(`hooks_riverpod: ^${hooksRiverpodVersion}\n flutter_hooks:`), codegen: codegen(`flutter_riverpod: ^${flutterRiverpodVersion}`), - hooksCodegen: codegen(`hooks_riverpod: ^${hooksRiverpodVersion}`), + hooksCodegen: codegen(`hooks_riverpod: ^${hooksRiverpodVersion}\n flutter_hooks:`), }; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/why_riverpod.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/why_riverpod.mdx new file mode 100644 index 000000000..942359f25 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/why_riverpod.mdx @@ -0,0 +1,125 @@ +--- +title: 为什么选择 Riverpod? +version: 1 +--- + +import whyRiverpod from "./why_riverpod"; +import { AutoSnippet } from "@site/src/components/CodeSnippet"; + + +## 什么是 Riverpod? + + +Riverpod([Provider](https://pub.dev/packages/provider) 的异序词)是 Flutter/Dart 的反应式缓存框架。 + + +Riverpod 使用声明式和反应式编程,为您处理应用程序的大部分逻辑。 +它可以通过内置的错误处理和缓存来执行网络请求,同时在必要时自动重新获取数据。 + + +## 动机 + + +现代应用程序很少提供呈现其用户界面所需的所有信息。 +相反,数据通常是从服务器异步获取的。 + + +问题是,使用异步代码很困难。 +尽管 Flutter 提供了一些创建状态变量并在更改时刷新 UI 的方法,但它仍然相当有限。 +许多挑战仍未解决: + + +- 异步请求需要在本地缓存,因为每当 UI 更新时就重新执行异步请求是不合理的。 +- 由于我们有一个缓存,如果我们不关心的话,我们的缓存可能会过时。 +- 我们还需要处理错误和加载状态。 + + +大规模解决这些问题可能很困难,并且它们会受到大量功能的影响,例如: + + +- 下拉刷新 +- 无限列表/上划加载 +- 键入时搜索 +- 对异步请求进行去抖动 +- 不再使用异步请求时取消异步请求 +- 乐观 UI (注:主动积极的更新 UI 以实现良好的用户体验) +- 离线模式 +- …… + + +这些功能可能很难实现,但对于良好的用户体验至关重要。 +然而,很少有软件包尝试直接解决这些问题,而且很多工作必须手动完成。 + + +这就是 Riverpod 的用武之地。 +Riverpod 试图通过提供一种新的、独特的业务逻辑编写方式来解决这些问题, +这种方式的灵感来自 Flutter 小部件。 +在许多方面,Riverpod 可以与小部件相媲美,但仅限于状态管理。 + + +使用这种新方法,这些复杂的功能大多是默认完成的。 +剩下的就是专注于你的 UI。 + + +不信?举个例子。 +以下代码片段是使用 Riverpod 实现的 Pub.dev 客户端应用程序的简化版本。 + + + + +此代码片段是在处理错误/加载状态时,“键入时搜索”+“下拉刷新”+“无限列表”所需的所有业务逻辑。 diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/why_riverpod/codegen.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/why_riverpod/codegen.dart new file mode 100644 index 000000000..0e0853f30 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/why_riverpod/codegen.dart @@ -0,0 +1,29 @@ +// ignore_for_file: use_key_in_widget_constructors, omit_local_variable_types + +import 'package:dio/dio.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +class Package { + static Package fromJson(dynamic json) { + throw UnimplementedError(); + } +} + +/* SNIPPET START */ // 从 pub.dev 获取包列表 +@riverpod +Future> fetchPackages( + FetchPackagesRef ref, { + required int page, + String search = '', +}) async { + final dio = Dio(); + // 获取 API。 这里我们使用 package:dio,但我们可以使用其他任何东西。 + final response = await dio.get>( + 'https://pub.dartlang.org/api/search?page=$page&q=${Uri.encodeQueryComponent(search)}', + ); + + // 将 JSON 响应解码为 Dart 类。 + return response.data?.map(Package.fromJson).toList() ?? const []; +}/* SNIPPET END */ diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/why_riverpod/codegen.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/why_riverpod/codegen.g.dart new file mode 100644 index 000000000..5c271e243 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/why_riverpod/codegen.g.dart @@ -0,0 +1,178 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$fetchPackagesHash() => r'eebf7d838a57f493fffebfd2c8d8ab76d3233165'; + +/// Copied from Dart SDK +class _SystemHash { + _SystemHash._(); + + static int combine(int hash, int value) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + value); + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); + return hash ^ (hash >> 6); + } + + static int finish(int hash) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + // ignore: parameter_assignments + hash = hash ^ (hash >> 11); + return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); + } +} + +/// See also [fetchPackages]. +@ProviderFor(fetchPackages) +const fetchPackagesProvider = FetchPackagesFamily(); + +/// See also [fetchPackages]. +class FetchPackagesFamily extends Family>> { + /// See also [fetchPackages]. + const FetchPackagesFamily(); + + /// See also [fetchPackages]. + FetchPackagesProvider call({ + required int page, + String search = '', + }) { + return FetchPackagesProvider( + page: page, + search: search, + ); + } + + @override + FetchPackagesProvider getProviderOverride( + covariant FetchPackagesProvider provider, + ) { + return call( + page: provider.page, + search: provider.search, + ); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'fetchPackagesProvider'; +} + +/// See also [fetchPackages]. +class FetchPackagesProvider extends AutoDisposeFutureProvider> { + /// See also [fetchPackages]. + FetchPackagesProvider({ + required int page, + String search = '', + }) : this._internal( + (ref) => fetchPackages( + ref as FetchPackagesRef, + page: page, + search: search, + ), + from: fetchPackagesProvider, + name: r'fetchPackagesProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$fetchPackagesHash, + dependencies: FetchPackagesFamily._dependencies, + allTransitiveDependencies: + FetchPackagesFamily._allTransitiveDependencies, + page: page, + search: search, + ); + + FetchPackagesProvider._internal( + super._createNotifier, { + required super.name, + required super.dependencies, + required super.allTransitiveDependencies, + required super.debugGetCreateSourceHash, + required super.from, + required this.page, + required this.search, + }) : super.internal(); + + final int page; + final String search; + + @override + Override overrideWith( + FutureOr> Function(FetchPackagesRef provider) create, + ) { + return ProviderOverride( + origin: this, + override: FetchPackagesProvider._internal( + (ref) => create(ref as FetchPackagesRef), + from: from, + name: null, + dependencies: null, + allTransitiveDependencies: null, + debugGetCreateSourceHash: null, + page: page, + search: search, + ), + ); + } + + @override + AutoDisposeFutureProviderElement> createElement() { + return _FetchPackagesProviderElement(this); + } + + @override + bool operator ==(Object other) { + return other is FetchPackagesProvider && + other.page == page && + other.search == search; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, page.hashCode); + hash = _SystemHash.combine(hash, search.hashCode); + + return _SystemHash.finish(hash); + } +} + +mixin FetchPackagesRef on AutoDisposeFutureProviderRef> { + /// The parameter `page` of this provider. + int get page; + + /// The parameter `search` of this provider. + String get search; +} + +class _FetchPackagesProviderElement + extends AutoDisposeFutureProviderElement> + with FetchPackagesRef { + _FetchPackagesProviderElement(super.provider); + + @override + int get page => (origin as FetchPackagesProvider).page; + @override + String get search => (origin as FetchPackagesProvider).search; +} +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/why_riverpod/index.tsx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/why_riverpod/index.tsx new file mode 100644 index 000000000..339b89a56 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/why_riverpod/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./codegen.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/why_riverpod/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/why_riverpod/raw.dart new file mode 100644 index 000000000..a6e6df4aa --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/introduction/why_riverpod/raw.dart @@ -0,0 +1,25 @@ +// ignore_for_file: use_key_in_widget_constructors, omit_local_variable_types + +import 'package:dio/dio.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +class Package { + static Package fromJson(dynamic json) { + throw UnimplementedError(); + } +} + +/* SNIPPET START */ // 从 pub.dev 获取包列表 +final fetchPackagesProvider = FutureProvider.autoDispose + .family, ({int page, String? search})>((ref, params) async { + final page = params.page; + final search = params.search ?? ''; + final dio = Dio(); + // 获取 API。 这里我们使用 package:dio,但我们可以使用其他任何东西。 + final response = await dio.get>( + 'https://pub.dartlang.org/api/search?page=$page&q=${Uri.encodeQueryComponent(search)}', + ); + + // 将 JSON 响应解码为 Dart 类。 + return response.data?.map(Package.fromJson).toList() ?? const []; +});/* SNIPPET END */ diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/0.13.0_to_0.14.0.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/0.13.0_to_0.14.0.mdx new file mode 100644 index 000000000..132db7d1b --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/0.13.0_to_0.14.0.mdx @@ -0,0 +1,120 @@ +--- +title: ^0.13.0 到 ^0.14.0 +--- + +随着 Riverpod 版本 0.14.0 的发布,使用 [StateNotifierProvider] 的语法发生了变化 +(请参阅 https://github.com/rrousselGit/riverpod/issues/341 了解详细解释)。 + +在整篇文章中,请考虑以下的 [StateNotifier]: + +```dart +class MyModel {} + +class MyStateNotifier extends StateNotifier { + MyStateNotifier(): super(MyModel()); +} +``` + +## The changes + +- [StateNotifierProvider] 添加了一个额外的泛型参数,该参数应该是您的 [StateNotifier]状态的类型。 + + 之前: + + ```dart + final provider = StateNotifierProvider((ref) { + return MyStateNotifier(); + }); + ``` + + 之后: + + ```dart + final provider = StateNotifierProvider((ref) { + return MyStateNotifier(); + }); + ``` + +- 为了获取 [StateNotifier] ,您现在应该使用 `myProvider.notifier` 而不是仅仅使用 `myProvider`: + + 之前: + + ```dart + Widget build(BuildContext context, ScopedReader watch) { + MyStateNotifier notifier = watch(provider); + } + ``` + + 之后: + + ```dart + Widget build(BuildContext context, ScopedReader watch) { + MyStateNotifier notifier = watch(provider.notifier); + } + ``` + +- 为了监听 [StateNotifier] 的状态,您现在应该使用 `myProvider` 而不是 `myProvider.state`: + + Before: + + ```dart + Widget build(BuildContext context, ScopedReader watch) { + MyModel state = watch(provider.state); + } + ``` + + After: + + ```dart + Widget build(BuildContext context, ScopedReader watch) { + MyModel state = watch(provider); + } + ``` + +## 使用迁移工具自动升级项目到新的语法 + +随着 0.14.0 版本的发布,Riverpod 推出了一个命令行工具,可以帮助您迁移项目。 + +### 安装命令行工具 + +要安装迁移工具,请运行: + +```sh +dart pub global activate riverpod_cli +``` + +现在您应该能够运行: + +```sh +riverpod --help +``` + +### 使用说明 + +既然命令行工具已经安装,我们可以开始使用它了。 + +- 首先,在终端中打开您想要迁移的项目。 +- **不要** 升级 Riverpod. + 迁移工具会为您升级 Riverpod 的版本。 +- 确保您的项目不包含错误。 +- 执行: + ```sh + riverpod migrate + ``` + +然后,该工具将分析您的项目并提出更改建议。例如,您可能会看到: + +```diff +Widget build(BuildContext context, ScopedReader watch) { +- MyModel state = watch(provider.state); ++ MyModel state = watch(provider); +} + +Accept change (y = yes, n = no [default], A = yes to all, q = quit)? +``` + +要接受更改,只需按 y 键。否则,要拒绝更改,按 n 键。 + + +[statenotifierprovider]: ../providers/state_notifier_provider +[statenotifier]: https://pub.dev/documentation/state_notifier/latest/state_notifier/StateNotifier-class.html diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/0.14.0_to_1.0.0.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/0.14.0_to_1.0.0.mdx new file mode 100644 index 000000000..30f078a19 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/0.14.0_to_1.0.0.mdx @@ -0,0 +1,226 @@ +--- +title: ^0.14.0 到 ^1.0.0 +--- + +import { Link } from "@site/src/components/Link"; + + +经过漫长的等待,Riverpod 迎来了首个稳定版本的发布 👏 + +要查看完整的变更列表,请参阅 [Changelog](https://pub.dev/packages/flutter_riverpod/changelog#100). +在本页面中,我们将重点介绍如何将现有的 Riverpod 应用程序从版本 0.14.x 迁移到版本 1.0.0。 + +## 使用迁移工具自动升级项目到新的语法 + +在解释各种变更之前,值得注意的是,Riverpod 提供了一个命令行工具,可以自动为您的项目进行迁移。 + +### 安装命令行工具 + +要安装迁移工具,请运行: + +```sh +dart pub global activate riverpod_cli +``` + +现在您应该能够运行: + +```sh +riverpod --help +``` + +### 使用说明 + +既然命令行工具已经安装,我们可以开始使用它了。 + +- 首先,在终端中打开您想要迁移的项目。 +- **不用** 升级 Riverpod. + 迁移工具将会为您升级 Riverpod 的版本。 + + :::danger + 不升级 Riverpod 非常重要。 + 如果您已经安装了版本 1.0.0,该工具将无法正常执行。因此,请确保在启动工具之前正确使用较旧的版本。 + ::: + +- 确保您的项目不包含错误。 +- 执行 + ```sh + riverpod migrate + ``` + +然后,该工具将分析您的项目并提出更改建议。例如,您可能会看到: + +```diff +-Widget build(BuildContext context, ScopedReader watch) { ++Widget build(BuildContext context, Widget ref) { +- MyModel state = watch(provider); ++ MyModel state = ref.watch(provider); +} + +Accept change (y = yes, n = no [default], A = yes to all, q = quit)? +``` + +要接受更改,只需按 y 键。否则,要拒绝更改,按 n 键. + +## 变更内容 + +现在我们已经了解了如何使用命令行工具自动升级项目,让我们详细看看所需的更改。 + + +### 语法统一 + +Riverpod 1.0.0 版本主要关注与提供程序交互的语法统一。 +在此之前,Riverpod 对于读取提供程序有许多类似但不同的语法, +例如 `ref.watch(provider)` 与 `useProvider(provider)` 与 `watch(provider)`。 +在版本 1.0.0 中,只剩下一种语法: `ref.watch(provider)`。其他的语法已经被移除。 + +例如: + +- `useProvider` 已被移除,而推荐使用 `HookConsumerWidget`。 + + 之前: + + ```dart + class Example extends HookWidget { + @override + Widget build(BuildContext context) { + useState(...); + int count = useProvider(counterProvider); + ... + } + } + ``` + + 之后: + + ```dart + class Example extends HookConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + useState(...); + int count = ref.watch(counterProvider); + ... + } + } + ``` + +- 在 `ConsumerWidget` 的 `build` 方法和 `Consumer` 的 `builder` 方法原型发生了变化. + + 之前: + + ```dart + class Example extends ConsumerWidget { + @override + Widget build(BuildContext context, ScopedReader watch) { + int count = watch(counterProvider); + ... + } + } + + Consumer( + builder: (context, watch, child) { + int count = watch(counterProvider); + ... + } + ) + ``` + + 之后: + + ```dart + class Example extends ConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + int count = ref.watch(counterProvider); + ... + } + } + + Consumer( + builder: (context, ref, child) { + int count = ref.watch(counterProvider); + ... + } + ) + ``` + +- `context.read` 被移除,取而代之的是 `ref.read` 。 + + 之前: + + ```dart + class Example extends StatelessWidget { + @override + Widget build(BuildContext context) { + SomeButton( + onPressed: () => context.read(provider.notifier).doSomething(), + ); + } + } + ``` + + 之后: + + ```dart + class Example extends ConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + SomeButton( + onPressed: () => ref.read(provider.notifier).doSomething(), + ); + } + } + ``` + +### StateProvider 更新 + +[StateProvider] 已更新以匹配 [StateNotifierProvider]。 + +在更新之前,使用 `ref.watch(StateProvider)` 会返回一个 `StateController` 实例。 +现在它只返回 `StateController` 的状态。 + +要进行迁移,有几种解决方案。 +如果您的代码仅获取状态而不修改它,可以从: + +```dart +final provider = StateProvider(...); + +Consumer( + builder: (context, ref, child) { + StateController count = ref.watch(provider); + + return Text('${count.state}'); + } +) +``` + +变到: + +```dart +final provider = StateProvider(...); + +Consumer( + builder: (context, ref, child) { + int count = ref.watch(provider); + + return Text('${count}'); + } +) +``` + +或者,您可以使用新的 `StateProvider.state` 来保持旧的行为。 + +```dart +final provider = StateProvider(...); + +Consumer( + builder: (context, ref, child) { + StateController count = ref.watch(provider.state); + + return Text('${count.state}'); + } +) +``` + +[statenotifierprovider]: ../providers/state_notifier_provider +[stateprovider]: ../providers/state_provider +[statenotifier]: https://pub.dev/documentation/state_notifier/latest/state_notifier/StateNotifier-class.html diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_change_notifier.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_change_notifier.mdx new file mode 100644 index 000000000..ba586aa46 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_change_notifier.mdx @@ -0,0 +1,259 @@ +--- +title: 从 `ChangeNotifier` 迁移 +--- + +import old from "!!raw-loader!./from_change_notifier/old.dart"; +import declaration from "./from_change_notifier/declaration"; +import initialization from "./from_change_notifier/initialization"; +import migrated from "./from_change_notifier/migrated"; + +import { Link } from "@site/src/components/Link"; +import { AutoSnippet } from "@site/src/components/CodeSnippet"; + + + +在 Riverpod 中,`ChangeNotifierProvider` 用于提供从 pkg:provider 的平滑过渡。 + + +如果您刚刚开始迁移到 pkg:riverpod,请务必阅读专用指南 +(请参阅)。 +本文适用于已经过渡到 Riverpod,但想要彻底放弃 `ChangeNotifier` 的人们。 + + +总而言之,从 `ChangeNotifier` 迁移到 `AsyncNotifer` 需要范式转换, +但它极大地简化了迁移后的代码。 +另请参阅。 + + +以这个(错误的)例子为例: + + + +该实现显示了几个薄弱的设计选择,例如: +- 使用 `isLoading` 和 `hasError` 处理不同的异步情况 +- 需要仔细处理带有繁琐的 `try`/`catch`/`finally` 表达式的请求 +- 需要在正确的时间调用 `notifyListeners` 才能使此实现发挥作用 +- 存在不一致或可能不需要的状态,例如使用空列表进行初始化 + + +请注意这个示例是如何精心设计的,以向新手开发人员展示 `ChangeNotifier` +如何导致错误的设计方案;此外,另一个要点是可变状态可能比最初承诺的要困难得多。 + + +`Notifier`/`AsyncNotifer` 与不可变状态相结合, +可以带来更好的设计方案和更少的错误。 + + +让我们看看如何将上述代码片段一步一步迁移到最新的 API。 + + + +## 开始迁移​ + +首先,我们应该声明新的提供者程序/通知者程序:这需要一些思维过程,这取决于您独特的业务逻辑。 + + +我们总结一下上面的要求: +- 状态用 `List` 表示,通过网络调用获得,不带参数 +- 状态还应该公开有关其 `loading`、`error` 和 `data` 状态的信息 +- 状态可以通过一些公开的方法进行改变,因此一个函数是不够的 + +:::tip + +上述思考过程归结为回答以下问题: +1. 是否需要一些副作用? + - `y`:使用 Riverpod 的基于类的 API + - `n`:使用 Riverpod 的基于函数的 API +1. State 需要异步加载吗? + - `y`:让 `build` 返回 `Future` + - `n`:让 `build` 简单地返回 `T` +1. 是否需要一些参数? + - `y`:让 `build` (或你的函数)接受它们 + - `n`:让 `build` (或你的函数)不接受额外的参数 +::: + +:::info + +如果您使用的是 codegen,上述思考过程就足够了。 +无需考虑正确的类名及其*特定*的 API。 +`@riverpod` 仅要求您编写一个具有返回类型的类,然后就可以开始了。 +::: + + +从技术上讲,这里最合适的是定义一个 `AutoDisposeAsyncNotifier>`, +它满足上述所有要求。让我们先写一些伪代码。 + + + +:::tip + +请记住:在 IDE 中使用代码片段可以获得一些指导,或者只是为了加快代码编写速度。 +请参阅。 +::: + + +考虑到 `ChangeNotifier` 的实现,我们不再需要声明 `todos`; +这样的变量是 `state`,它是用 `build` 隐式加载的。 + + +事实上,Riverpod 的通知者程序一次可以暴露*一个*实体。 + +:::tip + +Riverpod 的 API 是细粒度的;尽管如此,在迁移时, +您仍然可以定义自定义实体来保存多个值。首先考虑使用 [Dart 3 的记录](https://dart.dev/language/records) +来平滑迁移。 +::: + + + +### 初始化​ + +初始化通知者程序很简单:只需在 `build` 内编写初始化逻辑即可。 +我们现在可以摆脱旧的 `_init` 函数。 + + + + +相对于旧的 `_init` ,新的 `build` 没有丢失任何内容: +不需要初始化 `isLoading` 或 `hasError` + + +Riverpod 将通过公开 `AsyncValue>` 自动转换任何异步提供者程序, +并比两个简单的布尔标志更好地处理异步状态的复杂性。 + + +事实上,任何 `AsyncNotifier` 都会有效地使编写额外的 `try`/`catch`/`finally` 成为处理异步状态的反模式。 + + + +### 突变和副作用​ + +就像初始化一样,执行副作用时,无需操作布尔标志, +例如 `hasError`,或编写额外的 `try`/`catch`/`finally` + + +下面,我们删除了所有样板文件并成功完全迁移了上面的示例: + + +:::tip + +语法和设计方案可能会有所不同,但最终我们只需要编写我们的请求并随后更新状态。 +请参阅。 +::: + + +## 迁移过程总结 + + +让我们从操作的角度回顾一下上面应用的整个迁移过程。 + + +1. 我们已将初始化从构造函数中调用的自定义方法移至 `build` +1. 我们删除了 `todos`、`isLoading` 和 `hasError` 属性:内部 `state` 就足够了 +1. 我们已经删除了所有 `try`-`catch`-`finally` 块:返回 Future 就足够了 +1. 我们对副作用应用了相同的简化(`addTodo`) +1. 我们已经通过简单地重新分配 `state` 应用了突变 diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_change_notifier/declaration/declaration.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_change_notifier/declaration/declaration.dart new file mode 100644 index 000000000..b3ca06ef2 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_change_notifier/declaration/declaration.dart @@ -0,0 +1,33 @@ +// ignore_for_file: avoid_print, avoid_unused_constructor_parameters + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'declaration.g.dart'; + +class Todo { + const Todo(this.id); + Todo.fromJson(Object obj) : id = 0; + + final int id; +} + +class Http { + Future> get(String str) async => [str]; + Future> post(String str) async => [str]; +} + +final http = Http(); + +/* SNIPPET START */ +@riverpod +class MyNotifier extends _$MyNotifier { + @override + FutureOr> build() { + // TODO ... + return []; + } + + Future addTodo(Todo todo) async { + // TODO + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_change_notifier/declaration/declaration.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_change_notifier/declaration/declaration.g.dart new file mode 100644 index 000000000..6a0307fb2 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_change_notifier/declaration/declaration.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'declaration.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$myNotifierHash() => r'fc9a07f8ef9f792da2ac660d76ea0a809335ba18'; + +/// See also [MyNotifier]. +@ProviderFor(MyNotifier) +final myNotifierProvider = + AutoDisposeAsyncNotifierProvider>.internal( + MyNotifier.new, + name: r'myNotifierProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$myNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$MyNotifier = AutoDisposeAsyncNotifier>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_change_notifier/declaration/index.tsx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_change_notifier/declaration/index.tsx new file mode 100644 index 000000000..1ad659c31 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_change_notifier/declaration/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./declaration.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_change_notifier/declaration/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_change_notifier/declaration/raw.dart new file mode 100644 index 000000000..c7485bbba --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_change_notifier/declaration/raw.dart @@ -0,0 +1,33 @@ +// ignore_for_file: avoid_print, avoid_unused_constructor_parameters + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +class Todo { + const Todo(this.id); + Todo.fromJson(Object obj) : id = 0; + + final int id; +} + +class Http { + Future> get(String str) async => [str]; + Future> post(String str) async => [str]; +} + +final http = Http(); + +/* SNIPPET START */ +@riverpod +class MyNotifier extends AutoDisposeAsyncNotifier> { + @override + FutureOr> build() { + // TODO ... + return []; + } + + Future addTodo(Todo todo) async { + // TODO + } +} + +final myNotifierProvider = AsyncNotifierProvider.autoDispose(MyNotifier.new); diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_change_notifier/initialization/index.tsx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_change_notifier/initialization/index.tsx new file mode 100644 index 000000000..3b3fbd2cb --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_change_notifier/initialization/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./initialization.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_change_notifier/initialization/initialization.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_change_notifier/initialization/initialization.dart new file mode 100644 index 000000000..4da805e13 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_change_notifier/initialization/initialization.dart @@ -0,0 +1,29 @@ +// ignore_for_file: avoid_print, avoid_unused_constructor_parameters + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'initialization.g.dart'; + +class Todo { + const Todo(this.id); + Todo.fromJson(Object obj) : id = 0; + + final int id; +} + +class Http { + Future> get(String str) async => [str]; + Future> post(String str) async => [str]; +} + +final http = Http(); + +/* SNIPPET START */ +@riverpod +class MyNotifier extends _$MyNotifier { + @override + FutureOr> build() async { + final json = await http.get('api/todos'); + return [...json.map(Todo.fromJson)]; + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_change_notifier/initialization/initialization.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_change_notifier/initialization/initialization.g.dart new file mode 100644 index 000000000..8fd8b75ce --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_change_notifier/initialization/initialization.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'initialization.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$myNotifierHash() => r'1c67c12443102cf8c43efbf6c630d3028d9847c3'; + +/// See also [MyNotifier]. +@ProviderFor(MyNotifier) +final myNotifierProvider = + AutoDisposeAsyncNotifierProvider>.internal( + MyNotifier.new, + name: r'myNotifierProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$myNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$MyNotifier = AutoDisposeAsyncNotifier>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_change_notifier/initialization/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_change_notifier/initialization/raw.dart new file mode 100644 index 000000000..24ab265f7 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_change_notifier/initialization/raw.dart @@ -0,0 +1,29 @@ +// ignore_for_file: avoid_print, avoid_unused_constructor_parameters + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +class Todo { + const Todo(this.id); + Todo.fromJson(Object obj) : id = 0; + + final int id; +} + +class Http { + Future> get(String str) async => [str]; + Future> post(String str) async => [str]; +} + +final http = Http(); + +/* SNIPPET START */ +@riverpod +class MyNotifier extends AutoDisposeAsyncNotifier> { + @override + FutureOr> build() async { + final json = await http.get('api/todos'); + return [...json.map(Todo.fromJson)]; + } +} + +final myNotifierProvider = AsyncNotifierProvider.autoDispose(MyNotifier.new); diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_change_notifier/migrated/index.tsx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_change_notifier/migrated/index.tsx new file mode 100644 index 000000000..075bfbdf5 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_change_notifier/migrated/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./migrated.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_change_notifier/migrated/migrated.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_change_notifier/migrated/migrated.dart new file mode 100644 index 000000000..a90cee0fa --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_change_notifier/migrated/migrated.dart @@ -0,0 +1,37 @@ +// ignore_for_file: avoid_print, avoid_unused_constructor_parameters + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'migrated.g.dart'; + +class Todo { + const Todo(this.id); + Todo.fromJson(Object obj) : id = 0; + + final int id; +} + +class Http { + Future> get(String str) async => [str]; + Future> post(String str) async => [str]; +} + +final http = Http(); + +/* SNIPPET START */ +@riverpod +class MyNotifier extends _$MyNotifier { + @override + FutureOr> build() async { + final json = await http.get('api/todos'); + + return [...json.map(Todo.fromJson)]; + } + + Future addTodo(Todo todo) async { + // 可选的: state = const AsyncLoading(); + final json = await http.post('api/todos'); + final newTodos = [...json.map(Todo.fromJson)]; + state = AsyncData(newTodos); + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_change_notifier/migrated/migrated.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_change_notifier/migrated/migrated.g.dart new file mode 100644 index 000000000..737a9ce7f --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_change_notifier/migrated/migrated.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'migrated.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$myNotifierHash() => r'bde95c56aa12eff7c8c01ede57ae4ad2b616c225'; + +/// See also [MyNotifier]. +@ProviderFor(MyNotifier) +final myNotifierProvider = + AutoDisposeAsyncNotifierProvider>.internal( + MyNotifier.new, + name: r'myNotifierProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$myNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$MyNotifier = AutoDisposeAsyncNotifier>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_change_notifier/migrated/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_change_notifier/migrated/raw.dart new file mode 100644 index 000000000..3bea206b7 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_change_notifier/migrated/raw.dart @@ -0,0 +1,37 @@ +// ignore_for_file: avoid_print, avoid_unused_constructor_parameters + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +class Todo { + const Todo(this.id); + Todo.fromJson(Object obj) : id = 0; + + final int id; +} + +class Http { + Future> get(String str) async => [str]; + Future> post(String str) async => [str]; +} + +final http = Http(); + +/* SNIPPET START */ +@riverpod +class MyNotifier extends AutoDisposeAsyncNotifier> { + @override + FutureOr> build() async { + final json = await http.get('api/todos'); + + return [...json.map(Todo.fromJson)]; + } + + Future addTodo(Todo todo) async { + // 可选的: state = const AsyncLoading(); + final json = await http.post('api/todos'); + final newTodos = [...json.map(Todo.fromJson)]; + state = AsyncData(newTodos); + } +} + +final myNotifierProvider = AsyncNotifierProvider.autoDispose(MyNotifier.new); diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_change_notifier/old.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_change_notifier/old.dart new file mode 100644 index 000000000..4e65e823d --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_change_notifier/old.dart @@ -0,0 +1,60 @@ +// ignore_for_file: avoid_print, avoid_unused_constructor_parameters + +import 'package:flutter/foundation.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +class Todo { + const Todo(this.id); + Todo.fromJson(Object obj) : id = 0; + + final int id; +} + +class Http { + Future> get(String str) async => [str]; + Future> post(String str) async => [str]; +} + +final http = Http(); + +/* SNIPPET START */ +class MyChangeNotifier extends ChangeNotifier { + MyChangeNotifier() { + _init(); + } + List todos = []; + bool isLoading = true; + bool hasError = false; + + Future _init() async { + try { + final json = await http.get('api/todos'); + todos = [...json.map(Todo.fromJson)]; + } on Exception { + hasError = true; + } finally { + isLoading = false; + notifyListeners(); + } + } + + Future addTodo(int id) async { + isLoading = true; + notifyListeners(); + + try { + final json = await http.post('api/todos'); + todos = [...json.map(Todo.fromJson)]; + hasError = false; + } on Exception { + hasError = true; + } finally { + isLoading = false; + notifyListeners(); + } + } +} + +final myChangeProvider = ChangeNotifierProvider((ref) { + return MyChangeNotifier(); +}); diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier.mdx new file mode 100644 index 000000000..15e22eace --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier.mdx @@ -0,0 +1,547 @@ +--- +title: 从 `StateNotifier` 迁移 +--- + +import buildInit from "./from_state_notifier/build_init"; +import buildInitOld from "!!raw-loader!./from_state_notifier/build_init_old.dart"; +import consumersDontChange from "!!raw-loader!./from_state_notifier/consumers_dont_change.dart"; +import familyAndDispose from "./from_state_notifier/family_and_dispose"; +import familyAndDisposeOld from "!!raw-loader!./from_state_notifier/family_and_dispose_old.dart"; +import asyncNotifier from "./from_state_notifier/async_notifier"; +import asyncNotifierOld from "!!raw-loader!./from_state_notifier/async_notifier_old.dart"; +import addListener from "./from_state_notifier/add_listener"; +import addListenerOld from "!!raw-loader!./from_state_notifier/add_listener_old.dart"; +import fromStateProvider from "./from_state_notifier/from_state_provider"; +import fromStateProviderOld from "!!raw-loader!./from_state_notifier/from_state_provider_old.dart"; +import oldLifecycles from "./from_state_notifier/old_lifecycles"; +import oldLifecyclesOld from "!!raw-loader!./from_state_notifier/old_lifecycles_old.dart"; +import oldLifecyclesFinal from "./from_state_notifier/old_lifecycles_final"; +import obtainNotifierOnTests from "!!raw-loader!./from_state_notifier/obtain_notifier_on_tests.dart"; + +import { Link } from "@site/src/components/Link"; +import { AutoSnippet } from "@site/src/components/CodeSnippet"; + + +与 [Riverpod 2.0](https://pub.dev/packages/flutter_riverpod/changelog#200) +一起引入了新类: `Notifier`/`AsyncNotifer`。 +现在不鼓励使用 `StateNotifier`,转而使用这些新 API。 + + +本页展示如何从已弃用的 `StateNotifier` 迁移到新的 API。 + + +`AsyncNotifier` 带来的主要好处是更好的 `async` 支持;事实上, +`AsyncNotifier` 可以被认为是 `FutureProvider` ,并且具备从 UI 修改的公开方法。 + + +此外,新的 (Async)Notifier: + + +- 在其类中公开 `Ref` 对象 +- 在代码生成和非代码生成方法之间提供类似的语法 +- 在同步和异步版本之间提供类似的语法 +- 将逻辑从提供者程序中移开,并将其集中到通知者程序本身中 + + +让我们看看如何定义 `Notifier`、它与 `StateNotifier` 的比较以及如何迁移新的 +`AsyncNotifier` 以获得异步状态。 + + +## 新语法比较​ + + +在进行比较之前,请务必了解如何定义 `Notifier`。 +请参阅。 + + +让我们使用旧的 `StateNotifier` 语法编写一个示例: + + + +这是使用新的 `Notifier` API 构建的相同示例,大致可翻译为: + + + +比较 `Notifier` 与 `StateNotifier`,可以观察到以下主要区别: + + +- `StateNotifier` 的反应式依赖项在其提供者程序中声明,而 `Notifier` + 将此逻辑集中在其 `build` 方法中 +- `StateNotifier` 的整个初始化过程分为其提供者程序和构造函数, + 而 `Notifier` 保留一个位置来放置此类逻辑 +- 请注意,与 `StateNotifier` 不同,没有任何逻辑被写入 `Notifier` 的构造函数中 + + +使用 `AsyncNotifer`、`Notifier` 的异步等效项可以得出类似的结论。 + + +## 迁移异步 `StateNotifier` + + +新 API 语法的主要吸引力在于改进了异步数据的开发体验。 +举个例子: + + + + +下面是用新的 `AsyncNotifier` API 重写的上面的示例: + + + + +`AsyncNotifer` 与 `Notifier` 一样,带来了更简单、更统一的 API。 +在这里,很容易将 `AsyncNotifer` 视为带有方法的 `FutureProvider`。 + + +`AsyncNotifer` 附带了一组 `StateNotifier` 没有的实用程序和 getter,例如 +[`future`](https://pub.dev/documentation/riverpod/latest/riverpod/AutoDisposeAsyncNotifier/future.html) +和 [`update`](https://pub.dev/documentation/riverpod/latest/riverpod/AutoDisposeAsyncNotifier/update.html)。 +这使我们能够在处理异步突变和副作用时编写更简单的逻辑。 +另请参阅。 +:::tip + +从 `StateNotifier>` 迁移到 `AsyncNotifer` 归结为: + +- 将初始化逻辑放入 `build` +- 删除初始化或副作用方法中的任何 `catch`/`try` 块 +- 从 `build` 中删除任何 `AsyncValue.guard` ,因为它将 `Future` 转换为 `AsyncValue` +::: + + + +## 优点​ + + +在这几个示例之后,现在让我们重点介绍 `Notifier` 和 `AsyncNotifer` 的主要优点: +- 新语法应该感觉更简单、更具可读性,特别是对于异步状态 +- 一般来说,新 API 的样板代码可能会更少 +- 无论您正在编写哪种类型的提供者程序,语法现在都是统一的,从而支持代码生成 + (请参阅) + + +让我们进一步深入并强调更多的差异和相似之处。 + + +## 显式 `.family` 和 `.autoDispose` 修改​ + + +另一个重要的区别是新 API 处理系列和自动处置的方式。 + + +`Notifier`,有其自己的 `.family` 和 `.autoDispose` 对应项, +例如 `FamilyNotifier` 和 `AutoDisposeNotifier`。 +与往常一样,此类修改可以组合使用(又名 `AutoDisposeFamilyNotifier`)。 +`AsyncNotifer` 也有其异步等效项(例如 `AutoDisposeFamilyAsyncNotifier`)。 + + +修改在类中明确说明;所有参数都直接注入 `build` 方法中,以便初始化逻辑可以使用它们。 +这应该会带来更好的可读性、更简洁、总体上更少的错误。 + + +以下面的示例为例,其中定义了 `StateNotifierProvider.family`。 + + + +`BugsEncounteredNotifier` 感觉...沉重/难以阅读。 +让我们看一下它的迁移后的 `AsyncNotifier` 对应部分: + + + + +其迁移后的版本应该是一本轻松的读物。 + +:::info + +`(Async)Notifier` 的 `.family` 参数可通过 `this.arg` 获取(或使用代码生成时的 `this.paramName`) +::: + + +## 生命周期有不同的行为​ + + +`Notifier`/`AsyncNotifier` 和 `StateNotifier` 之间的生命周期有很大不同。 + + +这个例子再次展示了旧 API 如何具有稀疏逻辑: + + + + +在这里,如果 `durationProvider` 更新,`MyNotifier` 会进行处置: +然后重新实例化其实例,然后重新初始化其内部状态。 +此外,与其他提供者程序不同的是,`dispose` 回调将在类中单独定义。 +最后,仍然可以在其 _provider_ 中编写 `ref.onDispose`, +再次显示此 API 的逻辑是多么稀疏;潜在地,开发人员可能必须研究八 (8!) +个不同的地方才能理解此通知者程序行为! + + +这些歧义可以通过 `Riverpod 2.0` 解决。 + + +### 旧 `dispose` 与 `ref.onDispose` + +`StateNotifier` 的 `dispose` 方法指的是通知者程序本身的 dispose 事件, +也就是*在自行处置之前*调用的回调。 + + +`(Async)Notifier` 没有此属性,因为*它们在重建时不会被处置*;只有他们的内部状态是。 +在新的通知者程序中,处置生命周期仅在_一个_地方处理,通过 `ref.onDispose` +(和其他),就像任何其他提供者程序一样。 +这简化了 API,希望也提高了开发体验,这样只需查看_一个_地方 +即可了解生命周期的副作用:它的 `build` 方法。 + + +简而言之:要注册在*内部状态*重建之前触发的回调, +我们可以像其他提供者程序一样使用 `ref.onDispose`。 + + +您可以像这样迁移上面的代码片段: + + + + +在最后一个片段中,肯定有一些简化,但仍然存在一个未解决的问题: +我们现在无法了解我们的通知者程序在执行 `update` 时是否仍然存在。 +这可能会出现不需要的 `StateError`。 + + +### 不再 `mounted` + +发生这种情况是因为 `(Async)Notifier` 缺少 `mounted` 属性, +而该属性在 `StateNotifier` 上可用。 +考虑到它们生命周期的差异,这是完全有道理的;尽管只是可能,`mounted` +属性可能会误导新通知者程序:`mounted` *几乎总是* `true` 。 + + +虽然可以制定[自定义解决方法](https://github.com/rrousselGit/riverpod/issues/1879#issuecomment-1303189191), +但建议通过取消异步操作来解决此问题。 + + +可以使用自定义[完成器](https://api.flutter.dev/flutter/dart-async/Completer-class.html) +或任何自定义派生程序来取消操作。 + + +例如,如果您使用 `Dio` 执行网络请求,请考虑使用[取消令牌](https://pub.dev/documentation/dio/latest/dio/CancelToken-class.html) +(另请参阅)。 + + +因此,上面的示例迁移到以下内容: + + + + +## 突变 API 与之前相同​ + + +到目前为止,我们已经展示了 `StateNotifier` 和新 API 之间的差异。 +相反, `Notifier`、`AsyncNotifer` 和 `StateNotifier` 共享的一件事是 +如何使用和改变它们的状态。 + + +消费者程序可以使用相同的语法从这三个提供者程序获取数据, +这在您从 `StateNotifier` 迁移时非常有用;这也适用于通知者程序方法。 + + + + +## 其他迁移​ + + +让我们探讨一下 `StateNotifier` 和 `Notifier`(或 `AsyncNotifier`)之间影响较小的差异 + + +### 从 `.addListener` 和 `.stream` 迁移​ + + +`StateNotifier` 的 `.addListener` 和 `.stream` 可用于监听状态更改。 +这两个 API 现在被认为已经过时了。 + + +这是有意为之,因为我们希望与 `Notifier`、`AsyncNotifier` 和其他提供者程序实现完全的 API 统一。 +事实上,使用 `Notifier` 或 `AsyncNotifier` 应该与任何其他提供者程序没有任何不同。 + + +因此: + + + +就变成这样了: + + + +简而言之:如果你想监听 `Notifier`/`AsyncNotifer`,只需使用 `ref.listen`。 +请参阅。 + + +### 从测试中的 `.debugState` 迁移​ + + +`StateNotifier` 公开 `.debugState`:此属性供 pkg:state_notifier +用户在开发模式下启用从类外部进行状态访问,以用于测试目的。 + + +如果您在测试中使用 `.debugState` 访问状态,则您很可能需要放弃这种方法。 + + +`Notifier`/`AsyncNotifer` 没有 `.debugState`;相反,它们直接公开 `.state`, +即 `@visibleForTesting` 。 + +:::danger + +避免!从测试中访问 `.state`;如果必须的话,_当且仅当_您已经正确实例化了 +`Notifier`/`AsyncNotifer` 时才执行此操作; +然后,您可以在测试中自由访问 `.state`。 + + +事实上,`Notifier`/`AsyncNotifier` _不应该_手动实例化;相反, +它们应该通过使用其提供者程序进行交互:如果不这样做将会*破坏*通知者程序, +因为 ref 和 family 参数没有被初始化。 +::: + + +没有 `Notifier` 实例? +没问题,您可以使用 `ref.read` 获取一个,就像您读取其暴露状态一样: + + + + +在其专用指南中了解有关测试的更多信息。请参阅。 + + +### 从 `StateProvider` 迁移​ + + +`StateProvider` 自发布以来就被 Riverpod 暴露出来, +它是为了节省一些代码行数(LoC)来简化 `StateNotifierProvider` 的版本。 +由于 `StateNotifierProvider` 已被弃用,因此 `StateProvider` 也应避免使用。 +此外,到目前为止,新 API 还没有等效的 `StateProvider`。 + + +尽管如此,从 `StateProvider` 迁移到 `Notifier` 很简单。 + + +这样: + + + +变成: + + + +尽管它花费了我们更多的代码行数(LoC),但从 `StateProvider` +迁移使我们能够明确地归档 `StateNotifier` 。 diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener/add_listener.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener/add_listener.dart new file mode 100644 index 000000000..ead1c72d8 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener/add_listener.dart @@ -0,0 +1,18 @@ +// ignore_for_file: avoid_print + +import 'package:flutter/material.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'add_listener.g.dart'; + +/* SNIPPET START */ +@riverpod +class MyNotifier extends _$MyNotifier { + @override + int build() { + ref.listenSelf((_, next) => debugPrint('$next')); + return 0; + } + + void add() => state++; +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener/add_listener.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener/add_listener.g.dart new file mode 100644 index 000000000..3d55dd0bb --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener/add_listener.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'add_listener.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$myNotifierHash() => r'9acd382ed579c545ace755687b155e28eba01d22'; + +/// See also [MyNotifier]. +@ProviderFor(MyNotifier) +final myNotifierProvider = + AutoDisposeNotifierProvider.internal( + MyNotifier.new, + name: r'myNotifierProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$myNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$MyNotifier = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener/index.tsx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener/index.tsx new file mode 100644 index 000000000..6d7ac6d37 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./add_listener.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener/raw.dart new file mode 100644 index 000000000..e25b4a181 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener/raw.dart @@ -0,0 +1,17 @@ +// ignore_for_file: avoid_print + +import 'package:flutter/material.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +/* SNIPPET START */ +class MyNotifier extends Notifier { + @override + int build() { + ref.listenSelf((_, next) => debugPrint('$next')); + return 0; + } + + void add() => state++; +} + +final myNotifierProvider = NotifierProvider(MyNotifier.new); diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener_old.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener_old.dart new file mode 100644 index 000000000..f9177df65 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/add_listener_old.dart @@ -0,0 +1,24 @@ +// ignore_for_file: avoid_print + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +/* SNIPPET START */ +class MyNotifier extends StateNotifier { + MyNotifier() : super(0); + + void add() => state++; +} + +final myNotifierProvider = StateNotifierProvider((ref) { + final notifier = MyNotifier(); + + final cleanup = notifier.addListener((state) => debugPrint('$state')); + ref.onDispose(cleanup); + + // 或者,等效为: + // final listener = notifier.stream.listen((event) => debugPrint('$event')); + // ref.onDispose(listener.cancel); + + return notifier; +}); diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier/async_notifier.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier/async_notifier.dart new file mode 100644 index 000000000..5ead76378 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier/async_notifier.dart @@ -0,0 +1,28 @@ +// ignore_for_file: avoid_print, avoid_unused_constructor_parameters + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'async_notifier.g.dart'; + +class Todo { + Todo.fromJson(Object obj); +} + +class Http { + Future> get(String str) async => [str]; +} + +final http = Http(); + +/* SNIPPET START */ +@riverpod +class AsyncTodosNotifier extends _$AsyncTodosNotifier { + @override + FutureOr> build() async { + final json = await http.get('api/todos'); + + return [...json.map(Todo.fromJson)]; + } + + // ... +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier/async_notifier.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier/async_notifier.g.dart new file mode 100644 index 000000000..5f72d5d1c --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier/async_notifier.g.dart @@ -0,0 +1,29 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'async_notifier.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$asyncTodosNotifierHash() => + r'10207327c7dee180e9da8beece5bfffedcf86e98'; + +/// See also [AsyncTodosNotifier]. +@ProviderFor(AsyncTodosNotifier) +final asyncTodosNotifierProvider = + AutoDisposeAsyncNotifierProvider>.internal( + AsyncTodosNotifier.new, + name: r'asyncTodosNotifierProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$asyncTodosNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$AsyncTodosNotifier = AutoDisposeAsyncNotifier>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier/index.tsx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier/index.tsx new file mode 100644 index 000000000..a0ff513c3 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./async_notifier.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier/raw.dart new file mode 100644 index 000000000..52da61d76 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier/raw.dart @@ -0,0 +1,29 @@ +// ignore_for_file: avoid_print, avoid_unused_constructor_parameters + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +class Todo { + Todo.fromJson(Object obj); +} + +class Http { + Future> get(String str) async => [str]; +} + +final http = Http(); + +/* SNIPPET START */ +class AsyncTodosNotifier extends AsyncNotifier> { + @override + FutureOr> build() async { + final json = await http.get('api/todos'); + + return [...json.map(Todo.fromJson)]; + } + + // ... +} + +final asyncTodosNotifier = AsyncNotifierProvider>( + AsyncTodosNotifier.new, +); diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier_old.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier_old.dart new file mode 100644 index 000000000..50d7f4aed --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/async_notifier_old.dart @@ -0,0 +1,30 @@ +// ignore_for_file: avoid_print, avoid_unused_constructor_parameters + +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +class Todo { + Todo.fromJson(Object obj); +} + +class Http { + Future> get(String str) async => [str]; +} + +final http = Http(); + +/* SNIPPET START */ +class AsyncTodosNotifier extends StateNotifier>> { + AsyncTodosNotifier() : super(const AsyncLoading()) { + _postInit(); + } + + Future _postInit() async { + state = await AsyncValue.guard(() async { + final json = await http.get('api/todos'); + + return [...json.map(Todo.fromJson)]; + }); + } + + // ... +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init/build_init.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init/build_init.dart new file mode 100644 index 000000000..3e2303b7a --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init/build_init.dart @@ -0,0 +1,15 @@ +// ignore_for_file: avoid_print + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'build_init.g.dart'; + +/* SNIPPET START */ +@riverpod +class CounterNotifier extends _$CounterNotifier { + @override + int build() => 0; + + void increment() => state++; + void decrement() => state++; +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init/build_init.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init/build_init.g.dart new file mode 100644 index 000000000..9b4dc05a7 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init/build_init.g.dart @@ -0,0 +1,28 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'build_init.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$counterNotifierHash() => r'8d4e4011da15a0ef79af9622336839a0c9e406ab'; + +/// See also [CounterNotifier]. +@ProviderFor(CounterNotifier) +final counterNotifierProvider = + AutoDisposeNotifierProvider.internal( + CounterNotifier.new, + name: r'counterNotifierProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$counterNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$CounterNotifier = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init/index.tsx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init/index.tsx new file mode 100644 index 000000000..276a143ac --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./build_init.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init/raw.dart new file mode 100644 index 000000000..0ba8eebed --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init/raw.dart @@ -0,0 +1,15 @@ +// ignore_for_file: avoid_print + +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +/* SNIPPET START */ +class CounterNotifier extends Notifier { + @override + int build() => 0; + + void increment() => state++; + void decrement() => state++; +} + +final counterNotifierProvider = NotifierProvider(CounterNotifier.new); diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init_old.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init_old.dart new file mode 100644 index 000000000..82a8c54bd --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/build_init_old.dart @@ -0,0 +1,13 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +/* SNIPPET START */ +class CounterNotifier extends StateNotifier { + CounterNotifier() : super(0); + + void increment() => state++; + void decrement() => state++; +} + +final counterNotifierProvider = StateNotifierProvider((ref) { + return CounterNotifier(); +}); diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/consumers_dont_change.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/consumers_dont_change.dart new file mode 100644 index 000000000..6a964a5a6 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/consumers_dont_change.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +class CounterNotifier extends StateNotifier { + CounterNotifier() : super(0); + + void increment() => state++; + void decrement() => state++; +} + +final counterNotifierProvider = StateNotifierProvider((ref) { + return CounterNotifier(); +}); + +/* SNIPPET START */ +class SomeConsumer extends ConsumerWidget { + const SomeConsumer({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + /* highlight-start */ + final counter = ref.watch(counterNotifierProvider); + /* highlight-end */ + return Column( + children: [ + Text("You've counted up until $counter, good job!"), + TextButton( + /* highlight-start */ + onPressed: ref.read(counterNotifierProvider.notifier).increment, + /* highlight-end */ + child: const Text('Count even more!'), + ) + ], + ); + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose/family_and_dispose.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose/family_and_dispose.dart new file mode 100644 index 000000000..9a695614c --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose/family_and_dispose.dart @@ -0,0 +1,24 @@ +// ignore_for_file: unnecessary_this + +import 'dart:math'; + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../../utils.dart'; + +part 'family_and_dispose.g.dart'; + +/* SNIPPET START */ +@riverpod +class BugsEncounteredNotifier extends _$BugsEncounteredNotifier { + @override + FutureOr build(String featureId) { + return 99; + } + + Future fix(int amount) async { + final old = await future; + final result = await ref.read(taskTrackerProvider).fix(id: this.featureId, fixed: amount); + state = AsyncData(max(old - result, 0)); + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose/family_and_dispose.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose/family_and_dispose.g.dart new file mode 100644 index 000000000..36e1f1144 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose/family_and_dispose.g.dart @@ -0,0 +1,178 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'family_and_dispose.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$bugsEncounteredNotifierHash() => + r'c76e924f84db91c57d226896b062d9f4e8ab79e5'; + +/// Copied from Dart SDK +class _SystemHash { + _SystemHash._(); + + static int combine(int hash, int value) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + value); + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); + return hash ^ (hash >> 6); + } + + static int finish(int hash) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + // ignore: parameter_assignments + hash = hash ^ (hash >> 11); + return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); + } +} + +abstract class _$BugsEncounteredNotifier + extends BuildlessAutoDisposeAsyncNotifier { + late final String featureId; + + FutureOr build( + String featureId, + ); +} + +/// See also [BugsEncounteredNotifier]. +@ProviderFor(BugsEncounteredNotifier) +const bugsEncounteredNotifierProvider = BugsEncounteredNotifierFamily(); + +/// See also [BugsEncounteredNotifier]. +class BugsEncounteredNotifierFamily extends Family> { + /// See also [BugsEncounteredNotifier]. + const BugsEncounteredNotifierFamily(); + + /// See also [BugsEncounteredNotifier]. + BugsEncounteredNotifierProvider call( + String featureId, + ) { + return BugsEncounteredNotifierProvider( + featureId, + ); + } + + @override + BugsEncounteredNotifierProvider getProviderOverride( + covariant BugsEncounteredNotifierProvider provider, + ) { + return call( + provider.featureId, + ); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'bugsEncounteredNotifierProvider'; +} + +/// See also [BugsEncounteredNotifier]. +class BugsEncounteredNotifierProvider + extends AutoDisposeAsyncNotifierProviderImpl { + /// See also [BugsEncounteredNotifier]. + BugsEncounteredNotifierProvider( + String featureId, + ) : this._internal( + () => BugsEncounteredNotifier()..featureId = featureId, + from: bugsEncounteredNotifierProvider, + name: r'bugsEncounteredNotifierProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$bugsEncounteredNotifierHash, + dependencies: BugsEncounteredNotifierFamily._dependencies, + allTransitiveDependencies: + BugsEncounteredNotifierFamily._allTransitiveDependencies, + featureId: featureId, + ); + + BugsEncounteredNotifierProvider._internal( + super._createNotifier, { + required super.name, + required super.dependencies, + required super.allTransitiveDependencies, + required super.debugGetCreateSourceHash, + required super.from, + required this.featureId, + }) : super.internal(); + + final String featureId; + + @override + FutureOr runNotifierBuild( + covariant BugsEncounteredNotifier notifier, + ) { + return notifier.build( + featureId, + ); + } + + @override + Override overrideWith(BugsEncounteredNotifier Function() create) { + return ProviderOverride( + origin: this, + override: BugsEncounteredNotifierProvider._internal( + () => create()..featureId = featureId, + from: from, + name: null, + dependencies: null, + allTransitiveDependencies: null, + debugGetCreateSourceHash: null, + featureId: featureId, + ), + ); + } + + @override + AutoDisposeAsyncNotifierProviderElement + createElement() { + return _BugsEncounteredNotifierProviderElement(this); + } + + @override + bool operator ==(Object other) { + return other is BugsEncounteredNotifierProvider && + other.featureId == featureId; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, featureId.hashCode); + + return _SystemHash.finish(hash); + } +} + +mixin BugsEncounteredNotifierRef on AutoDisposeAsyncNotifierProviderRef { + /// The parameter `featureId` of this provider. + String get featureId; +} + +class _BugsEncounteredNotifierProviderElement + extends AutoDisposeAsyncNotifierProviderElement with BugsEncounteredNotifierRef { + _BugsEncounteredNotifierProviderElement(super.provider); + + @override + String get featureId => (origin as BugsEncounteredNotifierProvider).featureId; +} +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose/index.tsx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose/index.tsx new file mode 100644 index 000000000..0780f2135 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./family_and_dispose.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose/raw.dart new file mode 100644 index 000000000..8dfce9447 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose/raw.dart @@ -0,0 +1,27 @@ +// ignore_for_file: unnecessary_this + +import 'dart:math'; + +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../../utils.dart'; + +/* SNIPPET START */ +class BugsEncounteredNotifier extends AutoDisposeFamilyAsyncNotifier { + @override + FutureOr build(String featureId) { + return 99; + } + + Future fix(int amount) async { + final old = await future; + final result = await ref.read(taskTrackerProvider).fix(id: this.arg, fixed: amount); + state = AsyncData(max(old - result, 0)); + } +} + +final bugsEncounteredNotifierProvider = + AsyncNotifierProvider.family.autoDispose( + BugsEncounteredNotifier.new, +); diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose_old.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose_old.dart new file mode 100644 index 000000000..28f93a65c --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/family_and_dispose_old.dart @@ -0,0 +1,30 @@ +// ignore_for_file: unnecessary_this + +import 'dart:math'; + +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +import '../utils.dart'; + +/* SNIPPET START */ +class BugsEncounteredNotifier extends StateNotifier> { + BugsEncounteredNotifier({ + required this.ref, + required this.featureId, + }) : super(const AsyncData(99)); + final String featureId; + final Ref ref; + + Future fix(int amount) async { + state = await AsyncValue.guard(() async { + final old = state.requireValue; + final result = await ref.read(taskTrackerProvider).fix(id: featureId, fixed: amount); + return max(old - result, 0); + }); + } +} + +final bugsEncounteredNotifierProvider = + StateNotifierProvider.family.autoDispose((ref, id) { + return BugsEncounteredNotifier(ref: ref, featureId: id); +}); diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider/from_state_provider.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider/from_state_provider.dart new file mode 100644 index 000000000..2c71d6144 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider/from_state_provider.dart @@ -0,0 +1,14 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'from_state_provider.g.dart'; + +/* SNIPPET START */ +@riverpod +class CounterNotifier extends _$CounterNotifier { + @override + int build() => 0; + + @override + set state(int newState) => super.state = newState; + int update(int Function(int state) cb) => state = cb(state); +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider/from_state_provider.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider/from_state_provider.g.dart new file mode 100644 index 000000000..e9bbec271 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider/from_state_provider.g.dart @@ -0,0 +1,28 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'from_state_provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$counterNotifierHash() => r'b32033040f0fff627f1a6dfd9cfb4e93a842390b'; + +/// See also [CounterNotifier]. +@ProviderFor(CounterNotifier) +final counterNotifierProvider = + AutoDisposeNotifierProvider.internal( + CounterNotifier.new, + name: r'counterNotifierProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$counterNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$CounterNotifier = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider/index.tsx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider/index.tsx new file mode 100644 index 000000000..f59794999 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./from_state_provider.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider/raw.dart new file mode 100644 index 000000000..97e4564f3 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider/raw.dart @@ -0,0 +1,13 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +/* SNIPPET START */ +class CounterNotifier extends Notifier { + @override + int build() => 0; + + @override + set state(int newState) => super.state = newState; + int update(int Function(int state) cb) => state = cb(state); +} + +final counterNotifierProvider = NotifierProvider(CounterNotifier.new); diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider_old.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider_old.dart new file mode 100644 index 000000000..246f44a0c --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/from_state_provider_old.dart @@ -0,0 +1,6 @@ +import 'package:riverpod/riverpod.dart'; + +/* SNIPPET START */ +final counterProvider = StateProvider((ref) { + return 0; +}); diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/obtain_notifier_on_tests.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/obtain_notifier_on_tests.dart new file mode 100644 index 000000000..53f142d5d --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/obtain_notifier_on_tests.dart @@ -0,0 +1,35 @@ +// ignore_for_file: unused_local_variable,omit_local_variable_types + +import 'package:flutter_test/flutter_test.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +class MyNotifier extends AutoDisposeNotifier { + @override + int build() { + return 0; + } +} + +final myNotifierProvider = + NotifierProvider.autoDispose(MyNotifier.new); + +/* SNIPPET START */ +void main(List args) { + test('my test', () { + final container = ProviderContainer(); + addTearDown(container.dispose); + + // 获取通知者程序 + /* highlight-start */ + final AutoDisposeNotifier notifier = + container.read(myNotifierProvider.notifier); + /* highlight-end */ + + // 获取其暴露状态 + /* highlight-start */ + final int state = container.read(myNotifierProvider); + /* highlight-end */ + + // TODO 编写您的测试 + }); +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles/index.tsx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles/index.tsx new file mode 100644 index 000000000..9b77f551a --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./old_lifecycles.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles/old_lifecycles.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles/old_lifecycles.dart new file mode 100644 index 000000000..deceee786 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles/old_lifecycles.dart @@ -0,0 +1,36 @@ +import 'dart:async'; + +import 'package:dio/dio.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../../utils.dart'; + +part 'old_lifecycles.g.dart'; + +final repositoryProvider = Provider<_MyRepo>((ref) { + return _MyRepo(); +}); + +class _MyRepo { + Future update(int i, {CancelToken? token}) async {} +} + +/* SNIPPET START */ +@riverpod +class MyNotifier extends _$MyNotifier { + @override + int build() { + // 只需在此处读取/写入代码,一目了然 + final period = ref.watch(durationProvider); + final timer = Timer.periodic(period, (t) => update()); + ref.onDispose(timer.cancel); + + return 0; + } + + Future update() async { + await ref.read(repositoryProvider).update(state + 1); + // `mounted` 已不复存在! + state++; // 这可能会抛出。 + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles/old_lifecycles.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles/old_lifecycles.g.dart new file mode 100644 index 000000000..9d1158e62 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles/old_lifecycles.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'old_lifecycles.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$myNotifierHash() => r'0495c52ce893ee0304d4d5ac5648c634ed4a241e'; + +/// See also [MyNotifier]. +@ProviderFor(MyNotifier) +final myNotifierProvider = + AutoDisposeNotifierProvider.internal( + MyNotifier.new, + name: r'myNotifierProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$myNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$MyNotifier = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles/raw.dart new file mode 100644 index 000000000..2e0718348 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles/raw.dart @@ -0,0 +1,35 @@ +import 'dart:async'; + +import 'package:dio/dio.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../../utils.dart'; + +final repositoryProvider = Provider<_MyRepo>((ref) { + return _MyRepo(); +}); + +class _MyRepo { + Future update(int i, {CancelToken? token}) async {} +} + +/* SNIPPET START */ +class MyNotifier extends Notifier { + @override + int build() { + // 只需在此处读取/写入代码,一目了然 + final period = ref.watch(durationProvider); + final timer = Timer.periodic(period, (t) => update()); + ref.onDispose(timer.cancel); + + return 0; + } + + Future update() async { + await ref.read(repositoryProvider).update(state + 1); + // `mounted` 已不复存在! + state++; // 这可能会抛出。 + } +} + +final myNotifierProvider = NotifierProvider(MyNotifier.new); diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_final/index.tsx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_final/index.tsx new file mode 100644 index 000000000..9823b1564 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_final/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./old_lifecycles_final.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_final/old_lifecycles_final.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_final/old_lifecycles_final.dart new file mode 100644 index 000000000..64a7481f3 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_final/old_lifecycles_final.dart @@ -0,0 +1,38 @@ +import 'dart:async'; + +import 'package:dio/dio.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../../utils.dart'; + +part 'old_lifecycles_final.g.dart'; + +final repositoryProvider = Provider<_MyRepo>((ref) { + return _MyRepo(); +}); + +class _MyRepo { + Future update(int i, {CancelToken? token}) async {} +} + +/* SNIPPET START */ +@riverpod +class MyNotifier extends _$MyNotifier { + @override + int build() { + // 只需在此处读取/写入代码,一目了然 + final period = ref.watch(durationProvider); + final timer = Timer.periodic(period, (t) => update()); + ref.onDispose(timer.cancel); + + return 0; + } + + Future update() async { + final cancelToken = CancelToken(); + ref.onDispose(cancelToken.cancel); + await ref.read(repositoryProvider).update(state + 1, token: cancelToken); + // 调用 `cancelToken.cancel` 时,会抛出一个自定义异常 + state++; + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_final/old_lifecycles_final.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_final/old_lifecycles_final.g.dart new file mode 100644 index 000000000..3600948dc --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_final/old_lifecycles_final.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'old_lifecycles_final.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$myNotifierHash() => r'8ea2586ea29d12306efd4b8b847142136dd20338'; + +/// See also [MyNotifier]. +@ProviderFor(MyNotifier) +final myNotifierProvider = + AutoDisposeNotifierProvider.internal( + MyNotifier.new, + name: r'myNotifierProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$myNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$MyNotifier = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_final/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_final/raw.dart new file mode 100644 index 000000000..5a9e6329c --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_final/raw.dart @@ -0,0 +1,37 @@ +import 'dart:async'; + +import 'package:dio/dio.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../../utils.dart'; + +final repositoryProvider = Provider<_MyRepo>((ref) { + return _MyRepo(); +}); + +class _MyRepo { + Future update(int i, {CancelToken? token}) async {} +} + +/* SNIPPET START */ +class MyNotifier extends Notifier { + @override + int build() { + // 只需在此处读取/写入代码,一目了然 + final period = ref.watch(durationProvider); + final timer = Timer.periodic(period, (t) => update()); + ref.onDispose(timer.cancel); + + return 0; + } + + Future update() async { + final cancelToken = CancelToken(); + ref.onDispose(cancelToken.cancel); + await ref.read(repositoryProvider).update(state + 1, token: cancelToken); + // 调用 `cancelToken.cancel` 时,会抛出一个自定义异常 + state++; + } +} + +final myNotifierProvider = NotifierProvider(MyNotifier.new); diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_old.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_old.dart new file mode 100644 index 000000000..4d5251b5b --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/from_state_notifier/old_lifecycles_old.dart @@ -0,0 +1,42 @@ +import 'dart:async'; + +import 'package:dio/dio.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../utils.dart'; + +final repositoryProvider = Provider<_MyRepo>((ref) { + return _MyRepo(); +}); + +class _MyRepo { + Future update(int i, {CancelToken? token}) async {} +} + +/* SNIPPET START */ +class MyNotifier extends StateNotifier { + MyNotifier(this.ref, this.period) : super(0) { + // 1 初始化逻辑 + _timer = Timer.periodic(period, (t) => update()); // 2 初始化副作用 + } + final Duration period; + final Ref ref; + late final Timer _timer; + + Future update() async { + await ref.read(repositoryProvider).update(state + 1); // 3 发生突变 + if (mounted) state++; // 4 检测挂载属性 + } + + @override + void dispose() { + _timer.cancel(); // 5 自定义处置逻辑 + super.dispose(); + } +} + +final myNotifierProvider = StateNotifierProvider((ref) { + // 6 提供者程序定义 + final period = ref.watch(durationProvider); // 7 反应式依赖逻辑 + return MyNotifier(ref, period); // 8 传递 `ref` +}); diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/utils.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/utils.dart new file mode 100644 index 000000000..e515b6f94 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/migration/utils.dart @@ -0,0 +1,23 @@ +import 'dart:math' as math; + +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +final randomProvider = Provider((ref) { + return math.Random().nextInt(6); +}); + +final taskTrackerProvider = Provider((ref) { + return TaskTrackerRepo(); +}); + +class TaskTrackerRepo { + Future fix({required String id, required int fixed}) async => 0; +} + +final durationProvider = Provider((ref) { + return Duration.zero; +}); + +final availableWaterProvider = Provider((ref) { + return 40; +}); diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/change_notifier_provider.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/change_notifier_provider.mdx index 86f9f374f..9d8f62db8 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/change_notifier_provider.mdx +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/change_notifier_provider.mdx @@ -2,47 +2,58 @@ title: ChangeNotifierProvider --- -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; import CodeBlock from "@theme/CodeBlock"; import todos from "!!raw-loader!/docs/providers/change_notifier_provider/todos.dart"; import todosConsumer from "!!raw-loader!/docs/providers/change_notifier_provider/todos_consumer.dart"; -import { trimSnippet, Foo } from "../../../../../src/components/CodeSnippet"; +import { trimSnippet } from "@site/src/components/CodeSnippet"; + +:::caution + +本页内容可能已经过时。 +今后会进行更新,但目前您可能需要参考侧边栏顶部的内容(介绍/要点/应用案例/......)。 +::: -`ChangeNotifierProvider` (来自 flutter_riverpod/hooks_riverpod) -是一个用于监听和暴露Flutter本身的 [ChangeNotifier] 的provider。 +`ChangeNotifierProvider` (flutter_riverpod/hooks_riverpod only) is a provider that +is used to listen to and expose a [ChangeNotifier] from Flutter itself. -Riverpod不鼓励使用 `ChangeNotifierProvider` ,它的存在主要是为了: +Using `ChangeNotifierProvider` is discouraged by Riverpod and exists primarily for: - an easy transition from `package:provider` when using its `ChangeNotifierProvider` -- 当使用它的 `ChangeNotifierProvider` 时,简单地从 `package:provider` 迁移。 -- 支持可变状态,即使不可变的状态更好。 +- supporting mutable state, even though immutable state is preferred :::info -更倾向于使用 [StateNotifierProvider]。 -只有在绝对需要可变状态时才考虑使用 `ChangeNotifierProvider`。 +Prefer using [NotifierProvider] instead. +Consider using `ChangeNotifierProvider` only if you are absolutely certain +that you want mutable state. ::: -使用可变状态而不是不可变状态有时会更高效。 -但缺点是它可能更难维护,并可能破坏各种功能。 -比如说如果状态是可变的, -用于优化widget重新构建的 `provider.select` 可能不起作用, -因为Select会认为值没有改变。 -因此,使用不可变的数据结构有时会更快。 -制定特定用例的基准测试非常重要,以确保通过使用 `ChangeNotifierProvider` 时能真正获得性能。 +Using mutable state instead of immutable state can sometimes be more efficient. +The downside is, it can be harder to maintain and may break various features. +For example, using `provider.select` to optimize rebuilds of your widgets +may not work if your state is mutable, as `select` will think that the value +hasn't changed. +As such, using immutable data structures can sometimes be faster. Therefore +it is important to make benchmarks specific to your use-case, to make sure +that you are truly gaining performance by using `ChangeNotifierProvider`. -下面是用法示例,我们可以使用 `ChangeNotifierProvider` 来实现待办清单。 -这样做将允许我们公开 `addTodo` 等方法,让UI修改用户交互中的待办清单: +As a usage example, we could use `ChangeNotifierProvider` to implement a todo-list. +Doing so would allow us to expose methods such as `addTodo` to let the UI +modify the list of todos on user interactions: {trimSnippet(todos)} -现在我们已经定义了一个 `ChangeNotifierProvider`, -我们可以用它来与UI中的待办清单交互: +Now that we have defined a `ChangeNotifierProvider`, we can use it to interact +with the list of todos in our UI: {trimSnippet(todosConsumer)} [state_notifier]: https://pub.dev/packages/state_notifier [statenotifierprovider]: ./state_notifier_provider +[notifierprovider]: ./notifier_provider [changenotifier]: https://api.flutter.dev/flutter/foundation/ChangeNotifier-class.html [provider]: ./provider [futureprovider]: ./future_provider diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/change_notifier_provider/todos.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/change_notifier_provider/todos.dart index c181f8fd7..d766920b1 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/change_notifier_provider/todos.dart +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/change_notifier_provider/todos.dart @@ -32,16 +32,13 @@ class TodosNotifier extends ChangeNotifier { // Let's mark a todo as completed void toggle(String todoId) { - for (final todo in todos) { - if (todo.id == todoId) { - todo.completed = !todo.completed; - notifyListeners(); - } - } + final todo = todos.firstWhere((todo) => todo.id == todoId); + todo.completed = !todo.completed; + notifyListeners(); } } -// Finally, we are using StateNotifierProvider to allow the UI to interact with +// Finally, we are using ChangeNotifierProvider to allow the UI to interact with // our TodosNotifier class. final todosProvider = ChangeNotifierProvider((ref) { return TodosNotifier(); diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/future_provider.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/future_provider.mdx index 34af86f85..6feb07d83 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/future_provider.mdx +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/future_provider.mdx @@ -3,51 +3,63 @@ title: FutureProvider version: 1 --- -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; -import CodeBlock from "@theme/CodeBlock"; import configProvider from "./future_provider/config_provider"; import configConsumer from "./future_provider/config_consumer"; -import { trimSnippet,AutoSnippet} from "../../../../../src/components/CodeSnippet"; +import { AutoSnippet} from "@site/src/components/CodeSnippet"; + +:::caution + +本页内容可能已经过时。 +今后会进行更新,但目前您可能需要参考侧边栏顶部的内容(介绍/要点/应用案例/......)。 +::: -`FutureProvider` 与 [Provider] 类似,但用在异步代码中。 +`FutureProvider` is the equivalent of [Provider] but for asynchronous code. -`FutureProvider` 一般用于: +`FutureProvider` is typically used for: -- 执行和缓存异步操作 (比如网络请求) -- 能很好地处理异步操作中的错误/加载中的状态 -- 将多个异步的值组合为另一个异步的值 +- performing and caching asynchronous operations (such as network requests) +- nicely handling error/loading states of asynchronous operations +- combining multiple asynchronous values into another value -`FutureProvider` 与 [ref.watch] 结合使用有很多好处。 -这种组合允许在某些变量改变时自动重新获取一些数据,确保我们总是拥有最新的值。 +`FutureProvider` gains a lot when combined with [ref.watch]. This combination +allows automatic re-fetching of some data when some variables change, +ensuring that we always have the most up-to-date value. :::info -`FutureProvider` 不提供给用户交互后直接修改计算结果的方法。它被设计用来解决简单的用例。 -对于更高级的场景请考虑使用 [StateNotifierProvider] 。 +`FutureProvider` does not offer a way of directly modifying the computation after +a user interaction. It is designed to solve simple use-cases. +For more advanced scenarios, consider using [AsyncNotifierProvider]. ::: -## 用法示例:读取配置文件 +## Usage example: reading a configuration file -`FutureProvider` 可以是一种通过读取JSON文件创建的 `Configuration` 对象的便捷方法。 +`FutureProvider` can be a convenient way to expose a `Configuration` object +created by reading a JSON file. -在provider内部使用的async/await语法创建配置。 -再加上使用Flutter的资产系统,举个例子: +Creating the configuration would be done with your typical async/await +syntax, but inside the provider. +Using Flutter's asset system, this would be: -接着,UI可以像这样监听配置: +Then, the UI can listen to configurations like so: -这将在 [Future] 完成时自动重新构建UI。 -同时,如果多个widget需要配置,Flutter资产将只载入一次。 +This will automatically rebuild the UI when the [Future] completes. +At the same time, if multiple widgets want the configurations, +the asset will be decoded only once. -如你所见,在widget中监听 `FutureProvider` 会返回一个 [AsyncValue] , -它允许你处理错误/加载中的状态。 +As you can see, listening to a `FutureProvider` inside a widget returns +an [AsyncValue] – which allows handling the error/loading states. [ref.watch]: ../concepts/reading#using-refwatch-to-observe-a-provider -[statenotifierprovider]: ./state_notifier_provider +[asyncnotifierprovider]: ./notifier_provider [provider]: ./provider [asyncvalue]: https://pub.dev/documentation/riverpod/latest/riverpod/AsyncValue-class.html [future]: https://api.dart.dev/dart-async/Future-class.html diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/future_provider/config_consumer/codegen.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/future_provider/config_consumer/codegen.dart index 7f8e854f2..a96f35e72 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/future_provider/config_consumer/codegen.dart +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/future_provider/config_consumer/codegen.dart @@ -3,17 +3,14 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import '../config_provider/codegen.dart'; - /* SNIPPET START */ Widget build(BuildContext context, WidgetRef ref) { final config = ref.watch(fetchConfigurationProvider); - return config.when( - loading: () => const CircularProgressIndicator(), - error: (err, stack) => Text('Error: $err'), - data: (config) { - return Text(config.host); - }, - ); + return switch (config) { + AsyncError(:final error) => Text('Error: $error'), + AsyncData(:final value) => Text(value.host), + _ => const CircularProgressIndicator(), + }; } diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/future_provider/config_consumer/hooks.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/future_provider/config_consumer/hooks.dart index 3f9e2231c..44d9b4df9 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/future_provider/config_consumer/hooks.dart +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/future_provider/config_consumer/hooks.dart @@ -11,12 +11,11 @@ class MyConfiguration extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final config = ref.watch(configProvider); return Scaffold( - body: config.when( - loading: () => const Center(child: CircularProgressIndicator()), - error: (err, stack) => Center(child: Text('Error: $err')), - data: (config) { - return Center(child: Text(config.host)); + body: switch (config) { + AsyncError(:final error) => Center(child: Text('Error: $error')), + AsyncData(:final value) => Center(child: Text(value.host)), + _ => const Center(child: CircularProgressIndicator()), }, - )); + ); } } diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/future_provider/config_consumer/hooks_codegen.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/future_provider/config_consumer/hooks_codegen.dart index ece077f72..ade0628fd 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/future_provider/config_consumer/hooks_codegen.dart +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/future_provider/config_consumer/hooks_codegen.dart @@ -3,23 +3,20 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import '../config_provider/codegen.dart'; - - /* SNIPPET START */ -class MyConfiguration extends HookConsumerWidget { +class MyConfiguration extends HookConsumerWidget { const MyConfiguration({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final config = ref.watch(fetchConfigurationProvider); return Scaffold( - body: config.when( - loading: () => const Center(child: CircularProgressIndicator()), - error: (err, stack) => Center(child: Text('Error: $err')), - data: (config) { - return Center(child: Text(config.host)); + body: switch (config) { + AsyncError(:final error) => Center(child: Text('Error: $error')), + AsyncData(:final value) => Center(child: Text(value.host)), + _ => const Center(child: CircularProgressIndicator()), }, - )); + ); } -} \ No newline at end of file +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/future_provider/config_consumer/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/future_provider/config_consumer/raw.dart index 4176a3866..9d39f3bcd 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/future_provider/config_consumer/raw.dart +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/future_provider/config_consumer/raw.dart @@ -10,11 +10,9 @@ import '../config_provider/raw.dart'; Widget build(BuildContext context, WidgetRef ref) { AsyncValue config = ref.watch(configProvider); - return config.when( - loading: () => const CircularProgressIndicator(), - error: (err, stack) => Text('Error: $err'), - data: (config) { - return Text(config.host); - }, - ); + return switch (config) { + AsyncData(:final value) => Text(value.host), + AsyncError(:final error) => Text('Error: $error'), + _ => const CircularProgressIndicator(), + }; } diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/notifier_provider.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/notifier_provider.mdx index 54468b905..601f06da1 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/notifier_provider.mdx +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/notifier_provider.mdx @@ -2,41 +2,54 @@ title: (Async)NotifierProvider --- -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; import CodeBlock from "@theme/CodeBlock"; import todos from "./notifier_provider/todos"; import todosConsumer from "!!raw-loader!/docs/providers/notifier_provider/todos/todos_consumer.dart"; import remoteTodos from "./notifier_provider/remote_todos"; import remoteTodosConsumer from "!!raw-loader!/docs/providers/notifier_provider/remote_todos/todos_consumer.dart"; -import { trimSnippet, AutoSnippet } from "../../../../../src/components/CodeSnippet"; - -[NotifierProvider] 是一个监听和暴露 [Notifier] 的provider。 -[AsyncNotifier] 是一个可以异步初始化的 [Notifier]。 -[AsyncNotifierProvider] 是一个用于监听和公开 [AsyncNotifier] 的provider。 -`(Async)NotifierProvider` 和 `(Async)Notifier` 是Riverpod推荐的管理状态的方案, -这些状态可能会因用户交互而发生变化。 - -它一般用于: - -- 暴露在对自定义事件做出反应后可以随时间推移变化的状态。 -- 修改某些状态的逻辑(又名“业务逻辑”)集中在一个地方,随着时间的推移也能提高可维护性。 - -作为使用示例,我们使用 [NotifierProvider] 来实现一个待办清单。 -这样做将允许我们公开 `addTodo` 等方法,让UI在用户交互时修改待办清单的列表: +import { trimSnippet, AutoSnippet } from "@site/src/components/CodeSnippet"; + +:::caution + +本页内容可能已经过时。 +今后会进行更新,但目前您可能需要参考侧边栏顶部的内容(介绍/要点/应用案例/......)。 +::: + +[NotifierProvider] is a provider that is used to listen to and expose a [Notifier]. +[AsyncNotifierProvider] is a provider that is used to listen to and expose an [AsyncNotifier]. +[AsyncNotifier] is a [Notifier] that can be asynchronously initialized. +`(Async)NotifierProvider` along with `(Async)Notifier` is Riverpod's recommended solution +for managing state which may change in reaction to a user interaction. + +It is typically used for: + +- exposing a state which can change over time after reacting to custom events. +- centralizing the logic for modifying some state (aka "business logic") in a + single place, improving maintainability over time. + +As a usage example, we could use [NotifierProvider] to implement a todo-list. +Doing so would allow us to expose methods such as `addTodo` to let the UI +modify the list of todos on user interactions: -现在我们定义了一个 [NotifierProvider] ,我们可以使用它来与UI中的待办清单列表交互: +Now that we have defined a [NotifierProvider], we can use it to interact +with the list of todos in our UI: {trimSnippet(todosConsumer)} -下面的使用示例,我们可以使用 [AsyncNotifierProvider] 来实现一个远程待办清单列表。 -这样做将允许我们暴露 `addTodo` 等方法,让UI在用户交互时修改待办清单列表: +As a usage example, we could use [AsyncNotifierProvider] to implement a remote todo-list. +Doing so would allow us to expose methods such as `addTodo` to let the UI +modify the list of todos on user interactions: -现在我们已经定义了一个 [AsyncNotifierProvider] ,我们可以使用它来与UI中的待办清单交互: +Now that we have defined a [AsyncNotifierProvider], we can use it to interact +with the list of todos in our UI: {trimSnippet(remoteTodosConsumer)} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/codegen.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/codegen.dart index 8e3e06122..e02195f87 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/codegen.dart +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/codegen.dart @@ -28,11 +28,13 @@ class Todo with _$Todo { factory Todo.fromJson(Map json) => _$TodoFromJson(json); } -// 这会生成一个 AsyncNotifier 和 AsyncNotifierProvider。 -// Notifier类将会被传递给我们的 AsyncNotifierProvider。 -// 这个类不应该在其“state”属性之外暴露状态,也就是说没有公共的获取属性的方法! -// 这个类上的公共方法将允许UI修改它的状态。 -// 最后我们使用asyncTodosProvider(AsyncNotifierProvider)来允许UI与我们的Todos类进行交互。 +// This will generates a AsyncNotifier and AsyncNotifierProvider. +// The AsyncNotifier class that will be passed to our AsyncNotifierProvider. +// This class should not expose state outside of its "state" property, which means +// no public getters/properties! +// The public methods on this class will be what allow the UI to modify the state. +// Finally, we are using asyncTodosProvider(AsyncNotifierProvider) to allow the UI to +// interact with our Todos class. @riverpod class AsyncTodos extends _$AsyncTodos { Future> _fetchTodo() async { @@ -43,21 +45,21 @@ class AsyncTodos extends _$AsyncTodos { @override FutureOr> build() async { - // 从远程仓库获取初始的待办清单 + // Load initial todo list from the remote repository return _fetchTodo(); } Future addTodo(Todo todo) async { - // 将当前状态设置为加载中 + // Set the state to loading state = const AsyncValue.loading(); - // 将新的待办清单添加到远程仓库 + // Add the new todo and reload the todo list from the remote repository state = await AsyncValue.guard(() async { await http.post('api/todos', todo.toJson()); return _fetchTodo(); }); } - // 让我们允许删除待办清单 + // Let's allow removing todos Future removeTodo(String todoId) async { state = const AsyncValue.loading(); state = await AsyncValue.guard(() async { @@ -66,7 +68,7 @@ class AsyncTodos extends _$AsyncTodos { }); } - // 让我们把待办清单标记为已完成 + // Let's mark a todo as completed Future toggle(String todoId) async { state = const AsyncValue.loading(); state = await AsyncValue.guard(() async { diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/codegen.freezed.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/codegen.freezed.dart index 315bc924e..d1b8a01d5 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/codegen.freezed.dart +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/codegen.freezed.dart @@ -12,7 +12,7 @@ part of 'codegen.dart'; T _$identity(T value) => value; final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); Todo _$TodoFromJson(Map json) { return _Todo.fromJson(json); @@ -134,7 +134,7 @@ class _$TodoImpl implements _Todo { } @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && other is _$TodoImpl && diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/raw.dart index e91f812e1..f4455a338 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/raw.dart +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/raw.dart @@ -14,8 +14,8 @@ final http = Http(); /* SNIPPET START */ -// 最好使用不可变状态。 -// 我们还可以使用像 freezed 这样的package来帮助实现不可变。 +// An immutable state is preferred. +// We could also use packages like Freezed to help with the implementation. @immutable class Todo { const Todo({ @@ -32,7 +32,7 @@ class Todo { ); } - // 在我们的类中所有的属性都应该是 `final` 的。 + // All properties should be `final` on our class. final String id; final String description; final bool completed; @@ -44,9 +44,10 @@ class Todo { }; } -// Notifier类将会被传递给我们的NotifierProvider。 -// 这个类不应该在其“state”属性之外暴露状态,也就是说没有公共的获取属性的方法! -// 这个类上的公共方法将允许UI修改它的状态。 +// The Notifier class that will be passed to our NotifierProvider. +// This class should not expose state outside of its "state" property, which means +// no public getters/properties! +// The public methods on this class will be what allow the UI to modify the state. class AsyncTodosNotifier extends AsyncNotifier> { Future> _fetchTodo() async { final json = await http.get('api/todos'); @@ -56,21 +57,21 @@ class AsyncTodosNotifier extends AsyncNotifier> { @override Future> build() async { - // 从远程仓库获取初始的待办清单 + // Load initial todo list from the remote repository return _fetchTodo(); } Future addTodo(Todo todo) async { - // 将当前状态设置为加载中 + // Set the state to loading state = const AsyncValue.loading(); - // 将新的待办清单添加到远程仓库 + // Add the new todo and reload the todo list from the remote repository state = await AsyncValue.guard(() async { await http.post('api/todos', todo.toJson()); return _fetchTodo(); }); } - // 让我们允许删除待办清单 + // Let's allow removing todos Future removeTodo(String todoId) async { state = const AsyncValue.loading(); state = await AsyncValue.guard(() async { @@ -79,7 +80,7 @@ class AsyncTodosNotifier extends AsyncNotifier> { }); } - // 让我们把待办清单标记为已完成 + // Let's mark a todo as completed Future toggle(String todoId) async { state = const AsyncValue.loading(); state = await AsyncValue.guard(() async { @@ -92,7 +93,8 @@ class AsyncTodosNotifier extends AsyncNotifier> { } } -// 最后,我们使用NotifierProvider来允许UI与我们的TodosNotifier类交互。 +// Finally, we are using NotifierProvider to allow the UI to interact with +// our TodosNotifier class. final asyncTodosProvider = AsyncNotifierProvider>(() { return AsyncTodosNotifier(); diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/todos_consumer.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/todos_consumer.dart index ae829eee4..5d00d05d9 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/todos_consumer.dart +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/notifier_provider/remote_todos/todos_consumer.dart @@ -16,23 +16,22 @@ class TodoListView extends ConsumerWidget { final asyncTodos = ref.watch(asyncTodosProvider); // Let's render the todos in a scrollable list view - return asyncTodos.when( - data: (todos) => ListView( - children: [ - for (final todo in todos) - CheckboxListTile( - value: todo.completed, - // When tapping on the todo, change its completed status - onChanged: (value) => - ref.read(asyncTodosProvider.notifier).toggle(todo.id), - title: Text(todo.description), - ), - ], - ), - loading: () => const Center( - child: CircularProgressIndicator(), - ), - error: (err, stack) => Text('Error: $err'), - ); + return switch (asyncTodos) { + AsyncData(:final value) => ListView( + children: [ + for (final todo in value) + CheckboxListTile( + value: todo.completed, + // When tapping on the todo, change its completed status + onChanged: (value) { + ref.read(asyncTodosProvider.notifier).toggle(todo.id); + }, + title: Text(todo.description), + ), + ], + ), + AsyncError(:final error) => Text('Error: $error'), + _ => const Center(child: CircularProgressIndicator()), + }; } } diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/codegen.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/codegen.dart index b1999c2ea..866ed37bb 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/codegen.dart +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/codegen.dart @@ -15,11 +15,13 @@ class Todo with _$Todo { }) = _Todo; } -// 这会生成一个Notifier 和 NotifierProvider。 -// Notifier类将会被传递给我们的NotifierProvider。 -// 这个类不应该在其“state”属性之外暴露状态,也就是说没有公共的获取属性的方法! -// 这个类上的公共方法将允许UI修改它的状态。 -// 最后我们使用todosProvider(NotifierProvider)来允许UI与我们的Todos类进行交互。 +// This will generates a Notifier and NotifierProvider. +// The Notifier class that will be passed to our NotifierProvider. +// This class should not expose state outside of its "state" property, which means +// no public getters/properties! +// The public methods on this class will be what allow the UI to modify the state. +// Finally, we are using todosProvider(NotifierProvider) to allow the UI to +// interact with our Todos class. @riverpod class Todos extends _$Todos { @override @@ -27,37 +29,39 @@ class Todos extends _$Todos { return []; } - // 让我们添加UI添加待办清单 + // Let's allow the UI to add todos. void addTodo(Todo todo) { - // 由于状态是不可变的,因此不允许执行 `state.add(todo)`。 - // 相反,我们应该创建一个包含以前的项目和新的项目的待办清单列表。 - // 在这里使用Dart的扩展运算符很有用! + // Since our state is immutable, we are not allowed to do `state.add(todo)`. + // Instead, we should create a new list of todos which contains the previous + // items and the new one. + // Using Dart's spread operator here is helpful! state = [...state, todo]; - // 不需要调用“notifyListeners”或其他类似的方法。 - // 直接 “state =” 就能自动在需要时重新构建UI。 + // No need to call "notifyListeners" or anything similar. Calling "state =" + // will automatically rebuild the UI when necessary. } - // 让我们允许删除待办清单 + // Let's allow removing todos void removeTodo(String todoId) { - // 同样我们的状态是不可变的。 - // 所以我们创建了一个新的列表,而不是改变现存的列表。 + // Again, our state is immutable. So we're making a new list instead of + // changing the existing list. state = [ for (final todo in state) if (todo.id != todoId) todo, ]; } - // 让我们把待办清单标记为已完成 + // Let's mark a todo as completed void toggle(String todoId) { state = [ for (final todo in state) - // 我们只标记完成的待办清单 + // we're marking only the matching todo as completed if (todo.id == todoId) - // 再一次因为我们的状态是不可变的,所以我们需要创建待办清单的副本, - // 我们使用之前实现的copyWith方法来实现。 + // Once more, since our state is immutable, we need to make a copy + // of the todo. We're using our `copyWith` method implemented before + // to help with that. todo.copyWith(completed: !todo.completed) else - // 其他未修改的待办清单 + // other todos are not modified todo, ]; } diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/codegen.freezed.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/codegen.freezed.dart index a04416833..0b73d3548 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/codegen.freezed.dart +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/codegen.freezed.dart @@ -12,7 +12,7 @@ part of 'codegen.dart'; T _$identity(T value) => value; final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); /// @nodoc mixin _$Todo { @@ -126,7 +126,7 @@ class _$TodoImpl implements _Todo { } @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && other is _$TodoImpl && diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/raw.dart index 05a3c7174..ec3e50308 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/raw.dart +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/raw.dart @@ -3,8 +3,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; /* SNIPPET START */ -// 最好使用不可变状态。 -// 我们还可以使用像 freezed 这样的package来帮助实现不可变。 +// An immutable state is preferred. +// We could also use packages like Freezed to help with the implementation. @immutable class Todo { const Todo({ @@ -13,12 +13,13 @@ class Todo { required this.completed, }); - // 在我们的类中所有的属性都应该是 `final` 的。 + // All properties should be `final` on our class. final String id; final String description; final bool completed; - // 由于Todo是不可变的,我们实现了一种方法允许克隆内容略有不同的Todo。 + // Since Todo is immutable, we implement a method that allows cloning the + // Todo with slightly different content. Todo copyWith({String? id, String? description, bool? completed}) { return Todo( id: id ?? this.id, @@ -28,53 +29,57 @@ class Todo { } } -// Notifier类将会被传递给我们的NotifierProvider。 -// 这个类不应该在其“state”属性之外暴露状态,也就是说没有公共的获取属性的方法! -// 这个类上的公共方法将允许UI修改它的状态。 +// The Notifier class that will be passed to our NotifierProvider. +// This class should not expose state outside of its "state" property, which means +// no public getters/properties! +// The public methods on this class will be what allow the UI to modify the state. class TodosNotifier extends Notifier> { - // 我们将待办清单的列表初始化 + // We initialize the list of todos to an empty list @override List build() { return []; } - // 让我们添加UI添加待办清单 + // Let's allow the UI to add todos. void addTodo(Todo todo) { - // 由于状态是不可变的,因此不允许执行 `state.add(todo)`。 - // 相反,我们应该创建一个包含以前的项目和新的项目的待办清单列表。 - // 在这里使用Dart的扩展运算符很有用! + // Since our state is immutable, we are not allowed to do `state.add(todo)`. + // Instead, we should create a new list of todos which contains the previous + // items and the new one. + // Using Dart's spread operator here is helpful! state = [...state, todo]; - // 不需要调用“notifyListeners”或其他类似的方法。 - // 直接 “state =” 就能自动在需要时重新构建UI。 + // No need to call "notifyListeners" or anything similar. Calling "state =" + // will automatically rebuild the UI when necessary. } - // 让我们允许删除待办清单 + // Let's allow removing todos void removeTodo(String todoId) { - // 同样我们的状态是不可变的。 - // 所以我们创建了一个新的列表,而不是改变现存的列表。 + // Again, our state is immutable. So we're making a new list instead of + // changing the existing list. state = [ for (final todo in state) if (todo.id != todoId) todo, ]; } - // 让我们把待办清单标记为已完成 + // Let's mark a todo as completed void toggle(String todoId) { state = [ for (final todo in state) - // 我们只标记完成的待办清单 + // we're marking only the matching todo as completed if (todo.id == todoId) - // 再一次因为我们的状态是不可变的,所以我们需要创建待办清单的副本, - // 我们使用之前实现的copyWith方法来实现。 + // Once more, since our state is immutable, we need to make a copy + // of the todo. We're using our `copyWith` method implemented before + // to help with that. todo.copyWith(completed: !todo.completed) else - // 其他未修改的待办清单 + // other todos are not modified todo, ]; } } -// 最后,我们使用NotifierProvider来允许UI与我们的TodosNotifier类交互。 +// Finally, we are using NotifierProvider to allow the UI to interact with +// our TodosNotifier class. final todosProvider = NotifierProvider>(() { return TodosNotifier(); }); diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/todos_consumer.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/todos_consumer.dart index 23430ced6..192cb9f66 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/todos_consumer.dart +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/notifier_provider/todos/todos_consumer.dart @@ -12,16 +12,16 @@ class TodoListView extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - // 当待办事项列表变更时重新构建widget + // rebuild the widget when the todo list changes List todos = ref.watch(todosProvider); - // 让我们在一个可滚动的列表视图中呈现待办清单 + // Let's render the todos in a scrollable list view return ListView( children: [ for (final todo in todos) CheckboxListTile( value: todo.completed, - // 当在清单上点击时更改它的完成状态。 + // When tapping on the todo, change its completed status onChanged: (value) => ref.read(todosProvider.notifier).toggle(todo.id), title: Text(todo.description), diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/provider.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/provider.mdx index a0e1f05f7..d0ed99725 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/provider.mdx +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/provider.mdx @@ -2,86 +2,108 @@ title: Provider --- -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; import CodeBlock from "@theme/CodeBlock"; import todo from "./provider/todo"; import completedTodos from "./provider/completed_todos"; import todosConsumer from "!!raw-loader!/docs/providers/provider/todos_consumer.dart"; import unoptimizedPreviousButton from "./provider/unoptimized_previous_button"; import optimizedPreviousButton from "./provider/optimized_previous_button"; -import { trimSnippet, AutoSnippet } from "../../../../../src/components/CodeSnippet"; +import { trimSnippet, AutoSnippet } from "@site/src/components/CodeSnippet"; -在所有的provider中 `Provider` 是最基础的。它创造了一个值……差不多就是这样。 +:::caution + +本页内容可能已经过时。 +今后会进行更新,但目前您可能需要参考侧边栏顶部的内容(介绍/要点/应用案例/......)。 +::: -`Provider` 一般用在: +`Provider` is the most basic of all providers. It creates a value... And that's about it. -- 缓存计算。 -- 向其他provider(比如`Repository`/`HttpClient`)暴露一个值。 -- 为测试或widget提供重写值的方法。 -- 减少 provider/widget 的重新构建,不必使用 `select`。 +`Provider` is typically used for: -## 使用 `Provider` 缓存计算 +- caching computations +- exposing a value to other providers (such as a `Repository`/`HttpClient`). +- offering a way for tests or widgets to override a value. +- reducing rebuilds of providers/widgets without having to use `select`. -`Provider` 是与 [ref.watch] 结合使用时用于缓存同步操作的强大的工具。 +## Using `Provider` to cache computations -例如筛选待办清单。 -由于筛选列表的性能开销可能略高,所以理想情况下,当应用重新渲染时我们不希望再筛选一遍待办列表。 -在这种情况,我们可以使用 `Provider` 为我们进行筛选。 +`Provider` is a powerful tool for caching synchronous operations when combined +with [ref.watch]. -为此,假设我们的应用程序有一个 [StateNotifierProvider] ,它操作待办清单的列表: +An example would be filtering a list of todos. +Since filtering a list could be slightly expensive, we ideally do not want to +filter our list of todos whenever our application re-renders. +In this situation, we could use `Provider` to do the filtering for us. + +For that, assume that our application has an existing [NotifierProvider] +which manipulates a list of todos: -接着,我们可以使用 `Provider` 暴露经过筛选的待办清单列表,只显示完成的待办事项: +From there, we can use `Provider` to expose the filtered list of todos, showing +only the completed todos: -使用这段代码,我们的UI现在可以通过监听 `completedTodosProvider` 来显示完成的待办清单列表: +With this code, our UI is now able to show the list of the completed todos +by listening to `completedTodosProvider`: {trimSnippet(todosConsumer)} -有趣的是,这个筛选列表现在被缓存起来了。 +The interesting part is, the list filtering is now cached. -这也就意味着不管我们阅读多少次已经完成的待办清单,只要在添加/删除/更新待办清单之前, -这个筛选的列表也不会被重新计算。 +Meaning that the list of completed todos will not be recomputed until +todos are added/removed/updated, even if we are reading the list of completed +todos multiple times. -注意,当待办清单列表发生更改时我们不需要手动使缓存失效。多亏了 [ref.watch] , -`Provider`能够自动知道什么时候应该重新计算结果。 +Note how we do not need to manually invalidate the cache when the list of todos +changes. `Provider` is automatically able to know when the result must be recomputed +thanks to [ref.watch]. -## 使用`Provider` 减少provider/widget的重新构建 +## Reducing provider/widget rebuilds by using `Provider` -`Provider` 独特的地方在于,就算在重新计算 `Provider` 时(通常在使用[ref.watch]时), -它也不会更新监听它的widget/provider,除非当中的值发生了变化。 +A unique aspect of `Provider` is that even when `Provider` is recomputed +(typically when using [ref.watch]), it will not update the widgets/providers +that listen to it unless the value changed. -一个真实的例子是启用/禁用分页视图中的上一个/下一个按钮: +A real world example would be for enabling/disabling previous/next buttons +of a paginated view: ![stepper example](https://user-images.githubusercontent.com/134939/47580830-31263a00-d950-11e8-9b61-0eaddab2709e.png) -在这个例子中,我们特别关注在这个“previous”按钮。 -这个按钮将用widget实现,它获取当前页面的索引,如果索引为0时,我们将禁用这个按钮。 +In our case, we will focus specifically on the "previous" button. +A naive implementation of such button would be a widget which obtains the +current page index, and if that index is equal to 0, we would disable the button. -这段代码可以是: +This code could be: -这段代码的问题是,每当我们更改当前页面时,“previous”按钮将重新构建。 -在理想情况下,我们希望按钮仅在激活和停用之间更改时重新构建。 +The issue with this code is that whenever we change the current page, the "previous" +button will rebuild. +In the ideal world, we would want the button to rebuild only when changing between +activated and deactivated. -但问题的根源在于我们在build函数内计算是否允许用户在“previous”按钮内直接转到上一页。 +The root of the issue here is that we are computing whether the user is +allowed to go to the previous page directly within the "previous" button. -为了解决这个问题,我们将这个逻辑从widget中提取出来放到provider里面: +A way to solve this is to extract this logic outside of the widget and into a `Provider`: -通过这样的小重构,多亏了 `Provider` 我们的 `PreviousButton` widget 当页面索引变化时将不再重新构建。 +By doing this small refactoring, our `PreviousButton` widget will no longer +rebuild when the page index changes thanks to `Provider`. -从现在开始,当页面索引改变时,我们的 `canGoToPreviousPageProvider` 将被重新计算。 -但是如果provider暴露的值没有改变,那么 `PreviousButton` 将不会重新构建。 +From now on when the page index changes, our `canGoToPreviousPageProvider` provider +will be recomputed. But if the value exposed by the provider does not change, +then `PreviousButton` will not rebuild. This change both improved the performance of our button and had the interesting benefit of extracting the logic outside of our widget. -这一更改提高了按钮的性能,还有一个有趣的好处,就是将逻辑抽离到widget之外。 [ref.watch]: ../concepts/reading#using-refwatch-to-observe-a-provider -[statenotifierprovider]: ./state_notifier_provider +[notifierprovider]: ./notifier_provider diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/provider/completed_todos/completed_todos.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/provider/completed_todos/completed_todos.dart index 3529f403e..1a29d96f5 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/provider/completed_todos/completed_todos.dart +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/provider/completed_todos/completed_todos.dart @@ -10,6 +10,6 @@ part 'completed_todos.g.dart'; List completedTodos(CompletedTodosRef ref) { final todos = ref.watch(todosProvider); - // 我们只返回完成的待办事项 + // we return only the completed todos return todos.where((todo) => todo.isCompleted).toList(); } diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/provider/completed_todos/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/provider/completed_todos/raw.dart index 568f13865..e24c48bd5 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/provider/completed_todos/raw.dart +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/provider/completed_todos/raw.dart @@ -5,9 +5,9 @@ import '../todo/raw.dart'; /* SNIPPET START */ final completedTodosProvider = Provider>((ref) { - // 我们从todosProvider获取所有待办清单 + // We obtain the list of all todos from the todosProvider final todos = ref.watch(todosProvider); - // // 我们只返回完成的待办事项 + // we return only the completed todos return todos.where((todo) => todo.isCompleted).toList(); }); diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/provider/optimized_previous_button/optimized_previous_button.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/provider/optimized_previous_button/optimized_previous_button.dart index 4c4d99d7f..7d3b8a332 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/provider/optimized_previous_button/optimized_previous_button.dart +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/provider/optimized_previous_button/optimized_previous_button.dart @@ -19,7 +19,7 @@ class PageIndex extends _$PageIndex { } } -// 一个计算是否允许用户跳转到上一页的provider +// A provider which computes whether the user is allowed to go to the previous page @riverpod /* highlight-start */ bool canGoToPreviousPage(CanGoToPreviousPageRef ref) { @@ -32,8 +32,8 @@ class PreviousButton extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - // 现在我们观察我们新的provider, - // 当我们跳转到前一页时我们的widget不再需要计算。 + // We are now watching our new Provider + // Our widget is no longer calculating whether we can go to the previous page. /* highlight-start */ final canGoToPreviousPage = ref.watch(canGoToPreviousPageProvider); /* highlight-end */ diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/provider/optimized_previous_button/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/provider/optimized_previous_button/raw.dart index 01727215d..30b1cc673 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/provider/optimized_previous_button/raw.dart +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/provider/optimized_previous_button/raw.dart @@ -6,7 +6,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; final pageIndexProvider = StateProvider((ref) => 0); -// 一个计算是否允许用户跳转到上一页的provider +// A provider which computes whether the user is allowed to go to the previous page /* highlight-start */ final canGoToPreviousPageProvider = Provider((ref) { /* highlight-end */ @@ -18,8 +18,8 @@ class PreviousButton extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - // 现在我们观察我们新的provider, - // 当我们跳转到前一页时我们的widget不再需要计算。 + // We are now watching our new Provider + // Our widget is no longer calculating whether we can go to the previous page. /* highlight-start */ final canGoToPreviousPage = ref.watch(canGoToPreviousPageProvider); /* highlight-end */ diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/provider/todo/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/provider/todo/raw.dart index f8722a998..11e62bc3a 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/provider/todo/raw.dart +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/provider/todo/raw.dart @@ -10,15 +10,18 @@ class Todo { final String description; } -class TodosNotifier extends StateNotifier> { - TodosNotifier() : super([]); +class TodosNotifier extends Notifier> { + @override + List build() { + return []; + } void addTodo(Todo todo) { state = [...state, todo]; } - // TODO 添加其他方法,比如 “删除待办” …… + // TODO add other methods, such as "removeTodo", ... } -final todosProvider = StateNotifierProvider>((ref) { +final todosProvider = NotifierProvider>(() { return TodosNotifier(); }); diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/provider/todo/todo.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/provider/todo/todo.dart index 34c3d432c..7089bc962 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/provider/todo/todo.dart +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/provider/todo/todo.dart @@ -21,4 +21,5 @@ class Todos extends _$Todos { void addTodo(Todo todo) { state = [...state, todo]; } + // TODO add other methods, such as "removeTodo", ... } diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/provider/todos_consumer.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/provider/todos_consumer.dart index aa99c4541..9b93e9185 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/provider/todos_consumer.dart +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/provider/todos_consumer.dart @@ -10,7 +10,7 @@ Widget build() { /* SNIPPET START */ Consumer(builder: (context, ref, child) { final completedTodos = ref.watch(completedTodosProvider); - // TODO 使用ListView/GridView/……展示待办清单列表 /* SKIP */ + // TODO show the todos using a ListView/GridView/.../* SKIP */ return Container(); /* SKIP END */ }); diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/provider/unoptimized_previous_button/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/provider/unoptimized_previous_button/raw.dart index 9948a72fd..828d382d5 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/provider/unoptimized_previous_button/raw.dart +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/provider/unoptimized_previous_button/raw.dart @@ -11,7 +11,7 @@ class PreviousButton extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - // 如果不是第一页,那么前一页按钮可用 + // if not on first page, the previous button is active final canGoToPreviousPage = ref.watch(pageIndexProvider) != 0; void goToPreviousPage() { diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/provider/unoptimized_previous_button/unoptimized_previous_button.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/provider/unoptimized_previous_button/unoptimized_previous_button.dart index 350d12563..f77de56c3 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/provider/unoptimized_previous_button/unoptimized_previous_button.dart +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/provider/unoptimized_previous_button/unoptimized_previous_button.dart @@ -24,7 +24,7 @@ class PreviousButton extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - // 如果不是第一页,那么前一页按钮可用 + // if not on first page, the previous button is active final canGoToPreviousPage = ref.watch(pageIndexProvider) != 0; void goToPreviousPage() { diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/state_notifier_provider.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/state_notifier_provider.mdx index 9d0e1f475..1c237a279 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/state_notifier_provider.mdx +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/state_notifier_provider.mdx @@ -2,30 +2,43 @@ title: StateNotifierProvider --- -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; import CodeBlock from "@theme/CodeBlock"; import todos from "!!raw-loader!/docs/providers/state_notifier_provider/todos.dart"; import todosConsumer from "!!raw-loader!/docs/providers/state_notifier_provider/todos_consumer.dart"; -import { trimSnippet } from "../../../../../src/components/CodeSnippet"; +import { trimSnippet } from "@site/src/components/CodeSnippet"; + +:::caution + +本页内容可能已经过时。 +今后会进行更新,但目前您可能需要参考侧边栏顶部的内容(介绍/要点/应用案例/......)。 +::: -`StateNotifierProvider` 是一个用于监听和公开StateNotifier的provider(来自 [state_notifier] package,由Riverpod重新分发)。 +`StateNotifierProvider` is a provider that is used to listen to and expose a +[StateNotifier] (from the package [state_notifier], which Riverpod re-exports). -它一般用于: +It is typically used for: -- 暴露一个 **不可变** 的状态,在对自定义事件做出反应后可以随时间改变。 -- 修改某些状态的逻辑(又名“业务逻辑”)集中在一个地方,随着时间的推移也能提高可维护性。 +- exposing an **immutable** state which can change over time after reacting to + custom events. +- centralizing the logic for modifying some state (aka "business logic") in a + single place, improving maintainability over time. :::info -首选 [NotifierProvider] 。 +Prefer using [NotifierProvider] instead. ::: -我们可以使用 `StateNotifierProvider` 来实现一个待办清单列表。 -这样做将允许我们公开 `addTodo` 等方法,让UI在用户交互时修改todo列表,下面是示例: +As a usage example, we could use `StateNotifierProvider` to implement a todo-list. +Doing so would allow us to expose methods such as `addTodo` to let the UI +modify the list of todos on user interactions: {trimSnippet(todos)} -现在我们已经定义了 `StateNotifierProvider` ,我们可以使用它来与UI中的待办清单列表交互: +Now that we have defined a `StateNotifierProvider`, we can use it to interact +with the list of todos in our UI: {trimSnippet(todosConsumer)} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/state_provider.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/state_provider.mdx index 5aa4aa926..c65a571dd 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/state_provider.mdx +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/state_provider.mdx @@ -2,8 +2,6 @@ title: StateProvider --- -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; import CodeBlock from "@theme/CodeBlock"; import product from "!!raw-loader!/docs/providers/state_provider/product.dart"; import productListView from "!!raw-loader!/docs/providers/state_provider/product_list_view.dart"; @@ -13,105 +11,125 @@ import connectedDropdown from "!!raw-loader!/docs/providers/state_provider/conne import sortedProductProvider from "!!raw-loader!/docs/providers/state_provider/sorted_product_provider.dart"; import updateReadTwice from "!!raw-loader!/docs/providers/state_provider/update_read_twice.dart"; import updateReadOnce from "!!raw-loader!/docs/providers/state_provider/update_read_once.dart"; -import { trimSnippet } from "../../../../../src/components/CodeSnippet"; +import { trimSnippet } from "@site/src/components/CodeSnippet"; -StateProvider是一个公开了一种修改其状态的方法的provider。 -它是 [StateNotifierProvider] 的简化版,旨在避免为非常简单的用例编写 [StateNotifier] 类。 +:::caution + +本页内容可能已经过时。 +今后会进行更新,但目前您可能需要参考侧边栏顶部的内容(介绍/要点/应用案例/......)。 +::: -`StateProvider` 的存在主要是为了允许用户界面对**简单**的变量进行修改。 -所以`StateProvider` 的状态通常为: +`StateProvider` is a provider that exposes a way to modify its state. +It is a simplification of [NotifierProvider], designed to avoid +having to write a [Notifier] class for very simple use-cases. -- 枚举类型,例如筛选器类型 -- 一段字符串(String),通常是输入框的原始内容 -- 用于复选框的布尔类型 -- 用于分页或年龄表单字段的数字 +`StateProvider` exists primarily to allow the modification of +**simple** variables by the User Interface. +The state of a `StateProvider` is typically one of: -你不应该使用 `StateProvider` 如果: +- an enum, such as a filter type +- a String, typically the raw content of a text field +- a boolean, for checkboxes +- a number, for pagination or age form fields -- 你的状态需要验证逻辑 -- 你的状态是一个复杂的对象 (比如自定义的类, 集合……) -- 修改状态的逻辑比简单的 `count++` 更复杂 +You should not use `StateProvider` if: -对于更复杂的情况,可以考虑使用 [StateNotifierProvider] ,并创建一个 [StateNotifier] 类。 -虽然最初的样板文件会有点大, -但有一个自定义的 [StateNotifier] 类对于项目的长期可维护性是至关重要的, -因为它将状态的业务逻辑集中在了一个地方。 +- your state needs validation logic +- your state is a complex object (such as a custom class, a list/map, ...) +- the logic for modifying your state is more advanced than a simple `count++`. -## 使用示例:使用下拉菜单更改筛选类型 +For more advanced cases, consider using [NotifierProvider] instead and +create a [Notifier] class. +While the initial boilerplate will be a bit larger, having a custom +[Notifier] class is critical for the long-term maintainability of your +project – as it centralizes the business logic of your state in a single place. -`StateProvider` 的一个真实的用例是管理简单表单组件的状态,比如下拉菜单/输入框/复选框。 -特别来说,我们将看到如何使用 `StateProvider` 实现一个下拉菜单, -该下拉菜单允许更改产品列表的排序方式。 +## Usage example: Changing the filter type using a dropdown -为了简单起见,我们将在应用中直接构建将获得的产品列表,如下所示: +A real-world use-case of `StateProvider` would be to manage the state of +simple form components like dropdowns/text fields/checkboxes. +In particular, we will see how to use `StateProvider` to implement a dropdown +that allows changing how a list of products is sorted. + +For the sake of simplicity, the list of products that we will obtain +will be built directly in the application and will be as follows: {trimSnippet(product)} -在真实的应用程序中,我们通常会使用 [FutureProvider] 通过网络请求来获得该列表。 +In a real-world application, this list would typically be obtained using +[FutureProvider] by making a network request. -然后,可以在用户界面上通过下面的操作来显示产品列表: +The User Interface could then show the list of products by doing: {trimSnippet(productListView)} -现在我们已经完成了基础,我们可以添加一个下拉菜单, -它将按价格或名称过滤我们的产品。 -为此,我们将使用[DropDownButton](https://api.flutter.dev/flutter/material/DropdownButton-class.html)。 +Now that we're done with the base, we can add a dropdown, which will +allow filtering our products either by price or by name. +For that, we will use [DropDownButton](https://api.flutter.dev/flutter/material/DropdownButton-class.html). {trimSnippet(dropdown)} -现在我们有了一个下拉列表, -让我们创建一个 `StateProvider` 并将下拉菜单的状态与我们的provider同步。 +Now that we have a dropdown, let's create a `StateProvider` and +synchronize the state of the dropdown with our provider. -首先,让我们创建一个 `StateProvider`: +First, let's create the `StateProvider`: {trimSnippet(sortProvider)} -然后,我们可以通过下面的操作将这个provider与我们的下拉菜单连接起来: +Then, we can connect this provider with our dropdown by doing: {trimSnippet(connectedDropdown)} -有了这些,我们现在应该能够更改筛选的类型。 -不过它对产品列表没有影响! -现在是最后一部分:更新我们的 `productsProvider` 以对产品列表进行排序。 +With this, we should now be able to change the sort type. +It has no impact on the list of products yet though! It's now time for the +final part: Updating our `productsProvider` to sort the list of products. -实现这一点的关键是使用 [ref.watch], -让我们的 `productsProvider` 获得排序类型, -并在排序类型更改时重新计算产品列表。 +A key component of implementing this is to use [ref.watch], to have +our `productsProvider` obtain the sort type and recompute the list of +products whenever the sort type changes. -代码实现会是: +The implementation would be: {trimSnippet(sortedProductProvider)} -就是这样!这个变改足以让用户界面在排序类型更改时自动重绘产品列表。 +That's all! This change is enough for the User Interface to automatically +re-render the list of products when the sort type changes. -下面是在Dartpad上完整的例子: +Here is the complete example on Dartpad: -## 如何在不读取provider两次的情况下根据之前的值更新状态 +## How to update the state based on the previous value without reading the provider twice -有时你希望根据之前的值更新 `StateProvider` 的状态。 -自然而然,你可能会这样写: +Sometimes, you want to update the state of a `StateProvider` based on the previous value. +Naturally, you may end-up writing: {trimSnippet(updateReadTwice)} -虽然这段代码没有什么特别的错误,但是语法上着实有点不太方便。 +While there's nothing particularly wrong with this snippet, the syntax is a bit inconvenient. -为了更方便地使用,我们可以使用 `update` 函数。 -这个函数将接受一个回调函数,该回调函数将接收当前状态并返回新状态。 -我们可以使用它来重构之前的代码: +To make the syntax a bit better, we can use the `update` function. +This function will take a callback that will receive the current state and is expected +to return the new state. +We can use it to refactor our previous code to: {trimSnippet(updateReadOnce)} -这样就实现了相同的效果,而且使语法更好一些。 +This change achieves the same effect while making the syntax a bit better. [ref.watch]: ../concepts/reading#using-refwatch-to-observe-a-provider [ref.read]: ../concepts/reading#using-refread-to-obtain-the-state-of-a-provider-once [statenotifierprovider]: ./state_notifier_provider +[notifierprovider]: ./notifier_provider [futureprovider]: ./future_provider +[notifier]: https://pub.dev/documentation/riverpod/latest/riverpod/Notifier-class.html [statenotifier]: https://pub.dev/documentation/state_notifier/latest/state_notifier/StateNotifier-class.html [provider]: ./provider [asyncvalue]: https://pub.dev/documentation/riverpod/latest/riverpod/AsyncValue-class.html diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/stream_provider.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/stream_provider.mdx index 5f6ff9be1..752b64aff 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/stream_provider.mdx +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/stream_provider.mdx @@ -2,33 +2,54 @@ title: StreamProvider --- -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; import CodeBlock from "@theme/CodeBlock"; -import { trimSnippet } from "../../../../../src/components/CodeSnippet"; -import streamProvider from "!!raw-loader!/docs/providers/stream_provider/live_stream_chat_provider.dart"; +import { trimSnippet,AutoSnippet } from "@site/src/components/CodeSnippet"; +import streamProvider from "./stream_provider/live_stream_chat_provider"; import streamConsumer from "!!raw-loader!/docs/providers/stream_provider/live_stream_chat_consumer.dart"; +:::caution + +本页内容可能已经过时。 +今后会进行更新,但目前您可能需要参考侧边栏顶部的内容(介绍/要点/应用案例/......)。 +::: + `StreamProvider` is similar to [FutureProvider] but for [Stream]s instead of [Future]s. -`FutureProvider` 与 [Provider] 类似,但用在 [Stream] 中而不是 [Future]。 -`StreamProvider` 一般用于: +`StreamProvider` is usually used for: + +- listening to Firebase or web-sockets +- rebuilding another provider every few seconds + +Since [Stream]s naturally expose a way for listening to updates, some may think +that using `StreamProvider` has a low value. In particular, you may believe that +Flutter's [StreamBuilder] would work just as well for listening to a [Stream], but +this is a mistake. + +Using `StreamProvider` over [StreamBuilder] has numerous benefits: -- 监听Firebase或web-sockets -- 每隔几秒重建另一个provider +- it allows other providers to listen to the stream using [ref.watch]. +- it ensures that loading and error cases are properly handled, thanks to [AsyncValue]. +- it removes the need for having to differentiate broadcast streams vs normal streams. +- it caches the latest value emitted by the stream, ensuring that if a + listener is added after an event is emitted, the listener will still have + immediate access to the most up-to-date event. +- it allows easily mocking the stream during tests by overriding the `StreamProvider`. -由于 [Stream] 自然地公开了一种监听更新的方式,一些人可能认为使用 `StreamProvider` 没什么用。 -特别是,你可能认为Flutter的 [StreamBuilder] 可以很好地用于监听流,但这是错误的。 +## Usage example: live chat using sockets -在 [StreamBuilder] 上使用 `StreamProvider` 有很多好处: +`StreamProvider` is used in when we handle stream of asynchronous data +such as Video Streaming, Weather broadcasting Apis or Live chat as follows: -- 它允许其他provider使用 [ref.watch] 监听流。 -- 多亏了 [AsyncValue] ,它能确保加载和错误情况得到正确处理。 -- 它消除了必须区分广播流和普通流的需要。 -- 它缓存由流发出的最新值,确保如果在事件发出后添加监听器, - 监听器仍然可以立即访问最新的事件。 -- 它允许在测试期间通过覆盖 `StreamProvider` 轻松地模拟流。 + + +Then, the UI can listen to live streaming chats like so: + +{trimSnippet(streamConsumer)} [ref.watch]: ../concepts/reading#using-refwatch-to-observe-a-provider [statenotifierprovider]: ./state_notifier_provider @@ -39,14 +60,4 @@ import streamConsumer from "!!raw-loader!/docs/providers/stream_provider/live_st [stream]: https://api.dart.dev/dart-async/Stream-class.html [stream.periodic]: https://api.dart.dev/stable/2.15.1/dart-async/Stream/Stream.periodic.html [family]: ../concepts/modifiers/family -[streambuilder]: https://api.flutter.dev/flutter/widgets/StreamBuilder-class.html - -## 用法示例:使用套接字的实时聊天 - -`StreamProvider` 用于处理异步数据流,如视频流、天气广播Api。 - -{trimSnippet(streamProvider)} - -然后,UI就可以像这样聊天了: - -{trimSnippet(streamConsumer)} +[streambuilder]: https://api.flutter.dev/flutter/widgets/StreamBuilder-class.html \ No newline at end of file diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_consumer.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_consumer.dart index 79c186f7a..383def216 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_consumer.dart +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_consumer.dart @@ -6,21 +6,20 @@ import 'live_stream_chat_provider.dart'; /* SNIPPET START */ Widget build(BuildContext context, WidgetRef ref) { final liveChats = ref.watch(chatProvider); + // Like FutureProvider, it is possible to handle loading/error states using AsyncValue.when - return liveChats.when( - loading: () => const CircularProgressIndicator(), - error: (error, stackTrace) => Text(error.toString()), - data: (messages) { - // Display all the messages in a scrollable list view. - return ListView.builder( + return switch (liveChats) { + // Display all the messages in a scrollable list view. + AsyncData(:final value) => ListView.builder( // Show messages from bottom to top reverse: true, - itemCount: messages.length, + itemCount: value.length, itemBuilder: (context, index) { - final message = messages[index]; + final message = value[index]; return Text(message); }, - ); - }, - ); + ), + AsyncError(:final error) => Text(error.toString()), + _ => const CircularProgressIndicator(), + }; } diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_provider/codegen.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_provider/codegen.dart new file mode 100644 index 000000000..e2e34878c --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_provider/codegen.dart @@ -0,0 +1,23 @@ +// ignore_for_file: avoid_unused_constructor_parameters + +import 'dart:convert'; +import 'dart:io'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'codegen.g.dart'; + +/* SNIPPET START */ + +@riverpod +Stream> chat(ChatRef ref) async* { + // Connect to an API using sockets, and decode the output + final socket = await Socket.connect('my-api', 4242); + ref.onDispose(socket.close); + + var allMessages = const []; + await for (final message in socket.map(utf8.decode)) { + // A new message has been received. Let's add it to the list of all messages. + allMessages = [...allMessages, message]; + yield allMessages; + } +} diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_provider/codegen.g.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_provider/codegen.g.dart new file mode 100644 index 000000000..213c08a68 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_provider/codegen.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: non_constant_identifier_names + +part of 'codegen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$chatHash() => r'db1302132f90e854fe2f5da9d97d89c9a3c8b858'; + +/// See also [chat]. +@ProviderFor(chat) +final chatProvider = AutoDisposeStreamProvider>.internal( + chat, + name: r'chatProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$chatHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ChatRef = AutoDisposeStreamProviderRef>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_provider/index.tsx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_provider/index.tsx new file mode 100644 index 000000000..339b89a56 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_provider/index.tsx @@ -0,0 +1,9 @@ +import raw from "!!raw-loader!./raw.dart"; +import codegen from "!!raw-loader!./codegen.dart"; + +export default { + raw, + hooks: raw, + codegen, + hooksCodegen: codegen, +}; diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_provider/raw.dart b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_provider/raw.dart new file mode 100644 index 000000000..beb2bcd05 --- /dev/null +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/providers/stream_provider/live_stream_chat_provider/raw.dart @@ -0,0 +1,18 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +/* SNIPPET START */ +final chatProvider = StreamProvider>((ref) async* { + // Connect to an API using sockets, and decode the output + final socket = await Socket.connect('my-api', 4242); + ref.onDispose(socket.close); + + var allMessages = const []; + await for (final message in socket.map(utf8.decode)) { + // A new message has been received. Let's add it to the list of all messages. + allMessages = [...allMessages, message]; + yield allMessages; + } +}); diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/riverpod_for_provider_users.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/riverpod_for_provider_users.mdx deleted file mode 100644 index 3277c77da..000000000 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/riverpod_for_provider_users.mdx +++ /dev/null @@ -1,367 +0,0 @@ ---- -title: 给Provider开发者的Riverpod指南 ---- - -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; -import CodeBlock from "@theme/CodeBlock"; -import pubspec from "./getting_started/pubspec"; -import dartHelloWorld from "./getting_started/dart_hello_world"; -import helloWorld from "./getting_started/hello_world"; -import dartPubspec from "./getting_started/dart_pubspec"; -import { - trimSnippet, - AutoSnippet, - When, -} from "../../../../src/components/CodeSnippet"; - -本文是为熟悉 [Provider] 并希望了解Riverpod的开发者所设计的。 - -## Riverpod 和 [Provider] 之间的关系 - -Riverpod可以说是Provider的“精神”继承者。而且“Riverpod”这个名字就是“Provider”的变位词。 - -Riverpod是在寻找解决Provider所面临的技术限制的方案中诞生的。 -一开始Riverpod被认为是解决这一问题的Provider的下一个大版本。 -但最后并没有这么做,因为这样会是一个相当大的突破性变更, -而Provider是现如今最常用的Flutter package 之一。 - -当然了,Riverpod和Provider从概念上来说还是差不多的。 -两个package都扮演着类似的角色。两者都试图: - -- 缓存和小会一些有状态的对象 -- 在测试时提供一个模拟这些对象的方法 -- 在flutter widget中提供了一个简单的方式来监听这些对象的变化 - -与此同时,如果Riverpod继续成熟下去,它也可能会成为Provider。 - -Riverpod修复了Provider的许多基本问题,例如但不限于: - -- 显著简化了与“provider”的组合。 - Riverpod提供了简单强大的工具比如 [ref.watch] 和 [ref.listen] , - 而不是冗长且容易出错的 `ProxyProvider`。 -- 允许多个“Provider”公开相同类型的值。 - 当你暴露一个int类型或String类型的provider时不需要自定义一个类也能完美地使用。 -- 不需要在测试中重新定义provider。 - 在Riverpod中默认情况下provider可以直接使用。 -- 通过提供一种替代的方法来销毁对象([autoDispose]),减少对“作用域”的过度依赖。 - 虽然很强大,但确定一个provider的作用域相当复杂而且很难做对。 - -还有更多…… - -Riverpod唯一的缺点就是他需要变更Widget继承的类型才能使用: - -- 在Riverpod中你应该扩展 `ConsumerWidget` 类,而不是 `StatelessWidget` 类。 -- 在Riverpod中你应该扩展 `ConsumerStatefulWidget` 类,而不是 `StatefulWidget` 类。 - -但从大局上来看这种不便利的影响还是比较小的。而且这个限制在未来可能被移除。 - -所以要回答这个问题,你可能会问自己: -**我要使用Provider 还是 Riverpod ?** - -你大概率会选择 Riverpod。 -Riverpod设计得更好还能急剧简化你的逻辑。 - -## Provider和Riverpod的差异 - -### 定义 providers - -两个package最主要的区别在于如何定于“provider”。 - -对 [Provider] 来说,provider是widget,因此被放在widget树中,一般位于 `MultiProvider` 中: - -```dart -class Counter extends ChangeNotifier { - ... -} - -void main() { - runApp( - MultiProvider( - providers: [ - ChangeNotifierProvider(create: (context) => Counter()), - ], - child: MyApp(), - ) - ); -} -``` - -对于Riverpod来说, provider **不是** widget 而是普通的Dart对象。 -类似地,provider定义在widget树之外,并声明为全局final变量。 - -另外,为了让Riverpod工作,应当在整个应用上方添加一个 `ProviderScope` widget。 -下面是Riverpod的provider示例: - -```dart -// Providers 现在是顶级变量 -final counterProvider = ChangeNotifierProvider((ref) => Counter()); - -void main() { - runApp( - // 这个 widget 可以让Riverpod在整个Flutter 项目中使用。 - ProviderScope( - child: MyApp(), - ), - ); -} -``` - -provider的定义只是向上移动了几行。 - -:::info -由于Riverpod的provider是普通的Dart对象,因此可以同时在Dart和Flutter使用Riverpod。 -例如Riverpod也可以编写命令行应用。 -::: - -### 读取provider: BuildContext - -当使用Provider时,读取provider的一种方法是使用widget的 `BuildContext`. - -例如,如果provider被定义为: - -```dart -Provider(...); -``` - -然后使用 [Provider] 读取它: - -```dart -class Example extends StatelessWidget { - @override - Widget build(BuildContext context) { - Model model = context.watch(); - - } -} -``` - -Riverpod 的情况也一样: - -```dart -final modelProvider = Provider(...); - -class Example extends ConsumerWidget { - @override - Widget build(BuildContext context, WidgetRef ref) { - Model model = ref.watch(modelProvider); - - } -} -``` - -注意: - -- Riverpod的代码段展示了其扩展自 `ConsumerWidget` 类而不是 `StatelessWidget` 类。 - 它在build函数中添加了一个额外的参数: `WidgetRef`。 - -- 不同于 `BuildContext.watch` ,我们在Riverpod中使用来自`ConsumerWidget` 的 `WidgetRef` 来调用 `WidgetRef.watch`。 - -- Riverpod不依赖泛型类型。相反,它依赖于使用的provider定义创建的变量。 - -它们的使用关键词很相似。Provider和Riverpod都适用关键字“watch”来描述“当值发生变化时,这个widget应该重新构建”。 - -:::info -Riverpod使用和Provider相同的术语来读取provider。 - -- `BuildContext.watch` -> `WidgetRef.watch` -- `BuildContext.read` -> `WidgetRef.read` - -`context.watch` 和 `context.read` 的规则也适用于Riverpod: -在 `build` 方法中使用 “watch”。在点击或其他事件中使用 “read”。 -::: - -### 读取 provider: Consumer - -Provider附带了一个名为 `Consumer` (以及 `Consumer2` 之类的变体) 的widget用来读取provider。 - -`Consumer` 对性能优化很有帮助,它允许更细粒度的widget树重新构建,在状态变更时只更新相关的widget: - -因此,如果provider被定义为: - -```dart -Provider(...); -``` - -Provider允许你通过 `Consumer` 读取provider: - -```dart -Consumer( - builder: (BuildContext context, Model model, Widget? child) { - - } -) -``` - -Riverpod也有这样的原则。Riverpod也有一个名为 `Consumer` 的相同目的的widget。 - -如果我们定义provider为: - -```dart -final modelProvider = Provider(...); -``` - -然后使用 `Consumer` 我们可以这样: - -```dart -Consumer( - builder: (BuildContext context, WidgetRef ref, Widget? child) { - Model model = ref.watch(modelProvider); - - } -) -``` - -请注意这个 `Consumer` 是如何提供给我们 `WidgetRef` 对象的。 -这和我们在前面与 `ConsumerWidget` 相关的内容中看到的对象相同。 - -### 组合 provider: 带有无状态对象的 ProxyProvider - -当使用Provider,当组合不同的provider时官方的方法是使用 `ProxyProvider` widget (或者使用比如 `ProxyProvider2` 的变体)。 - -比如说,我们定义了: - -```dart -class UserIdNotifier extends ChangeNotifier { - String? userId; -} - -// ... - -ChangeNotifierProvider(create: (context) => UserIdNotifier()), -``` - -接下来我们有两个选择: -我们可能结合 `UserIdNotifier` 来创建一个新的 “无状态(stateless)” 的provider(通常是一个重载了==方法的不可变的值), 比如: - -```dart -ProxyProvider( - update: (context, userIdNotifier, _) { - return 'The user ID of the the user is ${userIdNotifier.userId}'; - } -) -``` - -每当 `UserIdNotifier.userId` 出现变化时provider会自动返回一个新的 `String` 的值。 - -我们可以在Riverpod中做类似的事,但是语法不同。 -首先,在Riverpod中 `UserIdNotifier` 的定义是: - -```dart -class UserIdNotifier extends ChangeNotifier { - String? userId; -} - -// ... - -final userIdNotifierProvider = ChangeNotifierProvider( - (ref) => UserIdNotifier(), -), -``` - -然后我们就可以根据 `userId` 生成我们的 `String` : - -```dart -final labelProvider = Provider((ref) { - UserIdNotifier userIdNotifier = ref.watch(userIdNotifierProvider); - return 'The user ID of the the user is ${userIdNotifier.userId}'; -}); -``` - -注意 `ref.watch(userIdNotifierProvider)` 这一行。 - - -这行代码告诉Riverpod获取 `userIdNotifierProvider` 的内容, -并当值发生变化时, `labelProvider` 也将被重新计算。 -因此,当 `userId` 发生变化时,我们的 `labelProvider` 将自动更新 `String`。 - -`ref.watch` 这一行你应该感觉类似。 -这种模式在前面的[如何在widget中读取provider](#读取provider-buildcontext)已经解释过了。 -实际上,provider现在可以像widget一样监听其他provider。 - -### 组合provider: 带有有状态对象的ProxyProvider - -在组合provider时,另一个可选的替代的用法是暴露一个有状态的对象,比如 `ChangeNotifier` 实例。 - -为此我们可以使用 `ChangeNotifierProxyProvider`(如 `ChangeNotifierProxyProvider2` 等变体)。 -比如我们可能定义: - -```dart -class UserIdNotifier extends ChangeNotifier { - String? userId; -} - -// ... - -ChangeNotifierProvider(create: (context) => UserIdNotifier()), -``` - -然后,我们可以定义一个基于`UserIdNotifier.userId`的新 `ChangeNotifier`。举个例子: - -```dart -class UserNotifier extends ChangeNotifier { - String? _userId; - - void setUserId(String? userId) { - if (userId != _userId) { - print('The user ID changed from $_userId to $userId'); - _userId = userId; - } - } -} - -// ... - -ChangeNotifierProxyProvider( - create: (context) => UserNotifier(), - update: (context, userIdNotifier, userNotifier) { - return userNotifier! - ..setUserId(userIdNotifier.userId); - }, -); -``` - -这个新的provider创建一个 `UserNotifier` 实例(并且永远不会重新构造),并在用户ID更改时打印一个字符串。 -在Riverpod中,这个功能的实现方式不同于provider。首先,我们的 `UserIdNotifier` 定义为: - -```dart -class UserIdNotifier extends ChangeNotifier { - String? userId; -} - -// ... - -final userIdNotifierProvider = ChangeNotifierProvider( - (ref) => UserIdNotifier(), -), -``` - -接着,与前面的 `ChangeNotifierProxyProvider` 等价的是: - -```dart -class UserNotifier extends ChangeNotifier {} - -final userNotfierProvider = ChangeNotifierProvider((ref) { - final userNotifier = UserNotifier(); - ref.listen( - userIdNotifierProvider, - (previous, next) { - if (previous?.userId != next.userId) { - print('The user ID changed from ${previous?.userId} to ${next.userId}'); - } - }, - ); - - return userNotifier; -}); -``` - -这段代码的核心是 `ref.listen` 这一行。 -`ref.listen` 方法是一个允许你监听其他provider和当provider变更时执行函数的工具。 - -该函数的 `previous` 和 `next` 参数对应provider 更改前的最后一个值和更改后的新值。 - -[provider]: https://pub.dev/packages/provider -[ref.watch]: /docs/concepts/reading#using-refwatch-to-observe-a-provider -[ref.listen]: /docs/concepts/reading#using-reflisten-to-react-to-a-provider-change -[autodispose]: /docs/concepts/modifiers/auto_dispose diff --git a/website/i18n/zh-Hans/docusaurus-theme-classic/footer.json b/website/i18n/zh-Hans/docusaurus-theme-classic/footer.json index 6560363ca..795de1dab 100644 --- a/website/i18n/zh-Hans/docusaurus-theme-classic/footer.json +++ b/website/i18n/zh-Hans/docusaurus-theme-classic/footer.json @@ -11,9 +11,21 @@ "message": "赞助", "description": "The title of the footer links column with title=Sponsors in the footer" }, + "link.item.label.Why Riverpod?": { + "message": "为什么选择 Riverpod?", + "description": "The label of footer link with label=Why Riverpod? linking to docs/introduction/why_riverpod" + }, "link.item.label.Getting started": { "message": "开始上手", - "description": "The label of footer link with label=Getting started linking to docs/getting_started" + "description": "The label of footer link with label=Getting started linking to docs/introduction/getting_started" + }, + "link.item.label.Discord": { + "message": "Discord", + "description": "The label of footer link with label=Discord linking to https://discord.gg/Bbumvej" + }, + "link.item.label.GitHub": { + "message": "GitHub", + "description": "The label of footer link with label=GitHub linking to https://github.com/rrousselgit/riverpod" }, "link.item.label.Stack Overflow": { "message": "Stack Overflow", @@ -23,16 +35,20 @@ "message": "Twitter", "description": "The label of footer link with label=Twitter linking to https://twitter.com/remi_rousselet" }, - "link.item.label.GitHub": { - "message": "GitHub", - "description": "The label of footer link with label=GitHub linking to https://github.com/rrousselgit/riverpod" - }, "link.item.label.Code of conduct": { "message": "行为准则", "description": "The label of footer link with label=Code of conduct linking to https://github.com/rrousselGit/riverpod/blob/master/CODE_OF_CONDUCT.md" }, + "link.item.label.Contributing guide": { + "message": "贡献指南", + "description": "The label of footer link with label=Contributing guide linking to https://github.com/rrousselGit/riverpod/blob/rework-flow/CONTRIBUTING.md" + }, "copyright": { - "message": "Copyright © 2023 Remi Rousselet.
使用Docusaurus构建。", + "message": "Copyright © 2023 Remi Rousselet.
使用 Docusaurus 构建。", "description": "The footer copyright" + }, + "logo.alt": { + "message": "Riverpod", + "description": "The alt text of footer logo" } -} \ No newline at end of file +} diff --git a/website/i18n/zh-Hans/docusaurus-theme-classic/navbar.json b/website/i18n/zh-Hans/docusaurus-theme-classic/navbar.json index 128196bc1..ade90e773 100644 --- a/website/i18n/zh-Hans/docusaurus-theme-classic/navbar.json +++ b/website/i18n/zh-Hans/docusaurus-theme-classic/navbar.json @@ -3,6 +3,10 @@ "message": "Riverpod", "description": "The title in the navbar" }, + "logo.alt": { + "message": "Riverpod", + "description": "The alt text of navbar logo" + }, "item.label.Docs": { "message": "文档", "description": "Navbar item with label Docs" diff --git a/website/src/documents_meta.js b/website/src/documents_meta.js index e3899daf9..272478fb4 100644 --- a/website/src/documents_meta.js +++ b/website/src/documents_meta.js @@ -32,9 +32,9 @@ export const documentTitles = { "concepts/why_immutability": "Why Immutability", "concepts/scopes": "Scopes", "concepts/reading": "Reading a Provider", - "concepts/providers": "Providers", "concepts/provider_observer": "ProviderObserver", "concepts/provider_lifecycles": "Provider Lifecycles", + "concepts/providers": "Providers", "concepts/combining_providers": "Combining Provider States", "concepts/about_hooks": "About hooks", "concepts/about_code_generation": "About code generation", @@ -45,11 +45,6 @@ export const documentTitles = { "advanced/select": "Optimizing performance", }, 'zh-Hans': { - "riverpod_for_provider_users": "给Provider开发者的Riverpod指南", - "introduction": "介绍", - "getting_started": "开始上手", - "about_hooks": "关于钩子", - "about_code_generation": "关于代码生成", "providers/stream_provider": "StreamProvider", "providers/state_provider": "StateProvider", "providers/state_notifier_provider": "StateNotifierProvider", @@ -57,181 +52,207 @@ export const documentTitles = { "providers/notifier_provider": "(Async)NotifierProvider", "providers/future_provider": "FutureProvider", "providers/change_notifier_provider": "ChangeNotifierProvider", - "cookbooks/testing": "测试", - "concepts/why_immutability": "为何需要不可变性", - "concepts/scopes": "作用域", - "concepts/reading": "读取 Provider", - "concepts/providers": "Providers", + "migration/from_state_notifier": "从 `StateNotifier` 迁移", + "migration/from_change_notifier": "从 `ChangeNotifier` 迁移", + "migration/0.14.0_to_1.0.0": "^0.14.0 to ^1.0.0", + "migration/0.13.0_to_0.14.0": "^0.13.0 to ^0.14.0", + "introduction/why_riverpod": "为什么选择 Riverpod?", + "introduction/getting_started": "入门指南", + "from_provider/quickstart": "快速开始", + "from_provider/provider_vs_riverpod": "Provider 对比 Riverpod", + "from_provider/motivation/motivation": "动机", + "essentials/websockets_sync": "Websocket 和同步执行", + "essentials/testing": "测试你的提供者程序", + "essentials/side_effects": "执行副作用", + "essentials/provider_observer": "日志和错误报告", + "essentials/passing_args": "将参数传递给您的请求", + "essentials/first_request": "开始你的第一次 provider/network 请求", + "essentials/faq": "FAQ 常见问题", + "essentials/eager_initialization": "急切的初始化提供者程序", + "essentials/do_dont": "最佳实践", + "essentials/combining_requests": "组合请求", + "essentials/auto_dispose": "清除缓存并对状态处置做出反应", + "cookbooks/testing": "Testing", + "cookbooks/search_as_we_type": "Search as we type", + "concepts/why_immutability": "Why Immutability", + "concepts/scopes": "Scopes", + "concepts/reading": "Reading a Provider", "concepts/provider_observer": "ProviderObserver", - "concepts/provider_lifecycles": "Provider生命周期", - "concepts/combining_providers": "组合 Provider 状态", + "concepts/provider_lifecycles": "Provider Lifecycles", + "concepts/providers": "Providers", + "concepts/combining_providers": "Combining Provider States", + "concepts/about_hooks": "关于 Hooks(钩子)", + "concepts/about_code_generation": "关于代码生成", "concepts/modifiers/family": ".family", "concepts/modifiers/auto_dispose": ".autoDispose", + "case_studies/pull_to_refresh": "下拉刷新", + "case_studies/cancel": "网络请求的去抖动或取消", + "advanced/select": "性能优化", }, - 'ko': { - "riverpod_for_provider_users": "Provider사용자를 위한 Riverpod 가이드", - "introduction": "소개", - "getting_started": "시작하기", - "about_hooks": "hooks 알아보기", - "about_code_generation": "Code Generation 알아보기", + 'ru': { "providers/stream_provider": "StreamProvider", "providers/state_provider": "StateProvider", "providers/state_notifier_provider": "StateNotifierProvider", "providers/provider": "Provider", "providers/future_provider": "FutureProvider", "providers/change_notifier_provider": "ChangeNotifierProvider", - "cookbooks/testing": "테스트", - "concepts/why_immutability": "불변성(Immutability)의 중요성", - "concepts/reading": "프로바이더 읽기", - "concepts/providers": "프로바이더란?", + "migration/0.14.0_to_1.0.0": "С ^0.14.0 на ^1.0.0", + "migration/0.13.0_to_0.14.0": "С ^0.13.0 на ^0.14.0", + "cookbooks/testing": "Тестирование", + "cookbooks/search_as_we_type": "Поиск во мере ввода", + "concepts/reading": "Чтение провайдера", "concepts/provider_observer": "ProviderObserver", - "concepts/combining_providers": "프로바이더 결합하기", + "concepts/providers": "Провайдеры", + "concepts/combining_providers": "Объединение состояний провайдеров", "concepts/modifiers/family": ".family", "concepts/modifiers/auto_dispose": ".autoDispose", - "migration/0.14.0_to_1.0.0": "^0.14.0 to ^1.0.0", - "migration/0.13.0_to_0.14.0": "^0.13.0 to ^0.14.0", - "cookbooks/search_as_we_type": "Search as we type", + "getting_started": "Введение", "cookbooks/refresh": "Pull-to-refresh / Retry-on-error", }, - 'fr': { - "riverpod_for_provider_users": "Riverpod pour les utilisateurs de Provider", - "introduction": "Introduction", - "getting_started": "Débuter", - "about_hooks": "À propos des hooks", - "about_code_generation": "À propos de la génération de code", + 'ko': { "providers/stream_provider": "StreamProvider", "providers/state_provider": "StateProvider", "providers/state_notifier_provider": "StateNotifierProvider", "providers/provider": "Provider", - "providers/notifier_provider": "(Async)NotifierProvider", "providers/future_provider": "FutureProvider", "providers/change_notifier_provider": "ChangeNotifierProvider", - "cookbooks/testing": "Tests", - "concepts/why_immutability": "Pourquoi l'Immuabilité", - "concepts/scopes": "Scopes", - "concepts/reading": "Lire un Provider", - "concepts/providers": "Providers", + "migration/0.14.0_to_1.0.0": "^0.14.0 to ^1.0.0", + "migration/0.13.0_to_0.14.0": "^0.13.0 to ^0.14.0", + "cookbooks/testing": "테스트", + "cookbooks/search_as_we_type": "Search as we type", + "concepts/why_immutability": "불변성(Immutability)의 중요성", + "concepts/reading": "프로바이더 읽기", "concepts/provider_observer": "ProviderObserver", - "concepts/provider_lifecycles": "Cycles de vie des Provider", - "concepts/combining_providers": "Combiner des providers", + "concepts/providers": "프로바이더란?", + "concepts/combining_providers": "프로바이더 결합하기", "concepts/modifiers/family": ".family", "concepts/modifiers/auto_dispose": ".autoDispose", - "migration/0.14.0_to_1.0.0": "^0.14.0 vers ^1.0.0", - "migration/0.13.0_to_0.14.0": "^0.13.0 vers ^0.14.0", + "getting_started": "시작하기", + "cookbooks/refresh": "Pull-to-refresh / Retry-on-error", + "riverpod_for_provider_users": "Provider사용자를 위한 Riverpod 가이드", + "introduction": "소개", + "about_hooks": "hooks 알아보기", + "about_code_generation": "Code Generation 알아보기", }, 'ja': { - "introduction": "イントロダクション", - "getting_started": "はじめに", - "about_code_generation": "コードジェネレーション", "providers/stream_provider": "StreamProvider", "providers/state_provider": "StateProvider", "providers/state_notifier_provider": "StateNotifierProvider", "providers/provider": "Provider", "providers/future_provider": "FutureProvider", "providers/change_notifier_provider": "ChangeNotifierProvider", + "migration/0.14.0_to_1.0.0": "^0.14.0 → ^1.0.0", + "migration/0.13.0_to_0.14.0": "^0.13.0 → ^0.14.0", "cookbooks/testing": "テスト", "concepts/reading": "プロバイダの利用方法", - "concepts/providers": "プロバイダとは", "concepts/provider_observer": "ProviderObserver", + "concepts/providers": "プロバイダとは", "concepts/combining_providers": "プロバイダのステートを組み合わせる", "concepts/modifiers/family": ".family", "concepts/modifiers/auto_dispose": ".autoDispose", - "migration/0.14.0_to_1.0.0": "^0.14.0 → ^1.0.0", - "migration/0.13.0_to_0.14.0": "^0.13.0 → ^0.14.0", + "getting_started": "はじめに", + "introduction": "イントロダクション", + "about_code_generation": "コードジェネレーション", }, - 'ru': { - "getting_started": "Введение", + 'it': { "providers/stream_provider": "StreamProvider", "providers/state_provider": "StateProvider", "providers/state_notifier_provider": "StateNotifierProvider", "providers/provider": "Provider", "providers/future_provider": "FutureProvider", - "providers/change_notifier_provider": "ChangeNotifierProvider", - "cookbooks/testing": "Тестирование", - "concepts/reading": "Чтение провайдера", - "concepts/providers": "Провайдеры", + "migration/0.14.0_to_1.0.0": "da ^0.14.0 a ^1.0.0", + "migration/0.13.0_to_0.14.0": "da ^0.13.0 a ^0.14.0", + "cookbooks/testing": "Testing", + "cookbooks/search_as_we_type": "Search as we type", + "concepts/reading": "Leggere un provider", "concepts/provider_observer": "ProviderObserver", - "concepts/combining_providers": "Объединение состояний провайдеров", + "concepts/providers": "I Provider", + "concepts/combining_providers": "Combinare stati di provider", "concepts/modifiers/family": ".family", "concepts/modifiers/auto_dispose": ".autoDispose", - "migration/0.14.0_to_1.0.0": "С ^0.14.0 на ^1.0.0", - "migration/0.13.0_to_0.14.0": "С ^0.13.0 на ^0.14.0", - "cookbooks/search_as_we_type": "Поиск во мере ввода", - "cookbooks/refresh": "Pull-to-refresh / Retry-on-error", - }, - 'it': { "getting_started": "Introduzione", + }, + 'fr': { "providers/stream_provider": "StreamProvider", "providers/state_provider": "StateProvider", "providers/state_notifier_provider": "StateNotifierProvider", "providers/provider": "Provider", + "providers/notifier_provider": "(Async)NotifierProvider", "providers/future_provider": "FutureProvider", - "cookbooks/testing": "Testing", - "concepts/reading": "Leggere un provider", - "concepts/providers": "I Provider", + "providers/change_notifier_provider": "ChangeNotifierProvider", + "migration/0.14.0_to_1.0.0": "^0.14.0 vers ^1.0.0", + "migration/0.13.0_to_0.14.0": "^0.13.0 vers ^0.14.0", + "cookbooks/testing": "Tests", + "concepts/why_immutability": "Pourquoi l'Immuabilité", + "concepts/scopes": "Scopes", + "concepts/reading": "Lire un Provider", "concepts/provider_observer": "ProviderObserver", - "concepts/combining_providers": "Combinare stati di provider", + "concepts/provider_lifecycles": "Cycles de vie des Provider", + "concepts/providers": "Providers", + "concepts/combining_providers": "Combiner des providers", "concepts/modifiers/family": ".family", "concepts/modifiers/auto_dispose": ".autoDispose", - "migration/0.14.0_to_1.0.0": "da ^0.14.0 a ^1.0.0", - "migration/0.13.0_to_0.14.0": "da ^0.13.0 a ^0.14.0", - "cookbooks/search_as_we_type": "Search as we type", + "getting_started": "Débuter", + "riverpod_for_provider_users": "Riverpod pour les utilisateurs de Provider", + "introduction": "Introduction", + "about_hooks": "À propos des hooks", + "about_code_generation": "À propos de la génération de code", }, 'es': { - "getting_started": "Empezando", "providers/stream_provider": "StreamProvider", "providers/state_provider": "StateProvider", "providers/state_notifier_provider": "StateNotifierProvider", "providers/provider": "Provider", "providers/future_provider": "FutureProvider", + "migration/0.14.0_to_1.0.0": "^0.14.0 a ^1.0.0", + "migration/0.13.0_to_0.14.0": "^0.13.0 a ^0.14.0", "cookbooks/testing": "Testing", "concepts/reading": "Leyendo un Provider", - "concepts/providers": "Providers", "concepts/provider_observer": "ProviderObserver", + "concepts/providers": "Providers", "concepts/combining_providers": "Combinando providers", "concepts/modifiers/family": ".family", "concepts/modifiers/auto_dispose": ".autoDispose", - "migration/0.14.0_to_1.0.0": "^0.14.0 a ^1.0.0", - "migration/0.13.0_to_0.14.0": "^0.13.0 a ^0.14.0", + "getting_started": "Empezando", }, - 'de': { - "getting_started": "Getting started", + 'bn': { + "providers/stream_provider": "StreamProvider", "providers/state_provider": "StateProvider", "providers/state_notifier_provider": "StateNotifierProvider", "providers/provider": "Provider", "providers/future_provider": "FutureProvider", - "cookbooks/testing": "Testing", - "concepts/reading": "Reading a provider", - "concepts/providers": "Providers", - "concepts/provider_observer": "ProviderObserver", - "concepts/combining_providers": "Combining providers", - "concepts/modifiers/family": ".family", - "concepts/modifiers/auto_dispose": ".autoDispose", "migration/0.14.0_to_1.0.0": "^0.14.0 to ^1.0.0", "migration/0.13.0_to_0.14.0": "^0.13.0 to ^0.14.0", + "cookbooks/testing": "টেস্টিং", "cookbooks/search_as_we_type": "Search as we type", + "concepts/reading": "প্রভাইডার রিড করা", + "concepts/provider_observer": "প্রভাইডার অবসার্বার", + "concepts/providers": "প্রভাইডাররা", + "concepts/combining_providers": "প্রভাইডার যুক্ত করা", + "concepts/modifiers/family": ".family", + "concepts/modifiers/auto_dispose": ".autoDispose", + "getting_started": "Getting started", "cookbooks/refresh": "Pull-to-refresh / Retry-on-error", }, - 'bn': { - "getting_started": "Getting started", - "providers/stream_provider": "StreamProvider", + 'de': { "providers/state_provider": "StateProvider", "providers/state_notifier_provider": "StateNotifierProvider", "providers/provider": "Provider", "providers/future_provider": "FutureProvider", - "cookbooks/testing": "টেস্টিং", - "concepts/reading": "প্রভাইডার রিড করা", - "concepts/providers": "প্রভাইডাররা", - "concepts/provider_observer": "প্রভাইডার অবসার্বার", - "concepts/combining_providers": "প্রভাইডার যুক্ত করা", - "concepts/modifiers/family": ".family", - "concepts/modifiers/auto_dispose": ".autoDispose", "migration/0.14.0_to_1.0.0": "^0.14.0 to ^1.0.0", "migration/0.13.0_to_0.14.0": "^0.13.0 to ^0.14.0", + "cookbooks/testing": "Testing", "cookbooks/search_as_we_type": "Search as we type", + "concepts/reading": "Reading a provider", + "concepts/provider_observer": "ProviderObserver", + "concepts/providers": "Providers", + "concepts/combining_providers": "Combining providers", + "concepts/modifiers/family": ".family", + "concepts/modifiers/auto_dispose": ".autoDispose", + "getting_started": "Getting started", "cookbooks/refresh": "Pull-to-refresh / Retry-on-error", }, }; export const outdatedTranslations = [ -{"countryCode":"ru","id":"providers/future_provider","englishPath":"/docs/providers/future_provider"}, +{"countryCode":"ru","id":"providers/future_provider","englishPath":"/docs\\providers/future_provider"}, ]; diff --git a/website/static/snippets/combine.dart b/website/static/snippets/combine.dart index 051941774..48797b3ad 100644 --- a/website/static/snippets/combine.dart +++ b/website/static/snippets/combine.dart @@ -22,7 +22,7 @@ final filterProvider = StateProvider((ref) => Filter.all); @riverpod List filteredTodos(FilteredTodosRef ref) { - // Providers can consumer other providers using the "ref" object. + // Providers can consume other providers using the "ref" object. // With ref.watch, providers will automatically update if the watched values changes. final List todos = ref.watch(todosProvider); final Filter filter = ref.watch(filterProvider); diff --git a/website/yarn.lock b/website/yarn.lock index 88e64c06c..6d1d69816 100644 --- a/website/yarn.lock +++ b/website/yarn.lock @@ -4022,9 +4022,9 @@ flux@^4.0.1: fbjs "^3.0.1" follow-redirects@^1.0.0, follow-redirects@^1.14.7: - version "1.15.3" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.3.tgz#fe2f3ef2690afce7e82ed0b44db08165b207123a" - integrity sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q== + version "1.15.4" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.4.tgz#cdc7d308bf6493126b17ea2191ea0ccf3e535adf" + integrity sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw== foreground-child@^3.1.0: version "3.1.1"